mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b96087ec61 | ||
|
|
dfe0ff753c | ||
|
|
d810f34cc5 | ||
|
|
95208e96e0 | ||
|
|
4f21c36825 | ||
|
|
1903f59db5 | ||
|
|
f1619e428e | ||
|
|
80bb2b6389 | ||
|
|
8c321ed750 | ||
|
|
601a6ff788 | ||
|
|
7eadf35840 | ||
|
|
fd637ff4f8 | ||
|
|
d3810c2eba | ||
|
|
bbcc20fd60 | ||
|
|
c475a14408 | ||
|
|
8a925177e1 | ||
|
|
50f1e8eb77 | ||
|
|
4fe0ff6ebf | ||
|
|
a5fb38b0b2 | ||
|
|
b605638415 | ||
|
|
5398b6f667 | ||
|
|
2e0940c996 | ||
|
|
f336cc3435 | ||
|
|
f03fa6bcf9 | ||
|
|
a6f441248b | ||
|
|
6d5f6b4c94 | ||
|
|
7c809f5627 | ||
|
|
c57f0d329b | ||
|
|
beeb097e55 | ||
|
|
61ae2eb91b | ||
|
|
71d92aed8d | ||
|
|
0b86a86209 | ||
|
|
6be15331a6 | ||
|
|
516d789491 | ||
|
|
9eec0f1b78 | ||
|
|
1b5c045706 | ||
|
|
82e24cb18d | ||
|
|
cdc6eee295 | ||
|
|
7222bb6af9 | ||
|
|
f91034ab8c | ||
|
|
1c62ab6f49 | ||
|
|
3e888249cc | ||
|
|
527662f336 | ||
|
|
0dc7152681 | ||
|
|
bb06376a32 | ||
|
|
26943de778 | ||
|
|
2afaf00d1f | ||
|
|
b5b718196b | ||
|
|
5341b8248f | ||
|
|
9816285205 | ||
|
|
378e70afdc | ||
|
|
76ce9e1a5c | ||
|
|
815b7c6e05 | ||
|
|
af6c44eca4 | ||
|
|
08dbafe49a | ||
|
|
8c1124e3c4 | ||
|
|
73b0f9a339 | ||
|
|
050dd19e80 | ||
|
|
ed72e58ea6 | ||
|
|
bf74b7445e | ||
|
|
60a9a4d79c | ||
|
|
71ddeed2e7 | ||
|
|
72dbc01712 | ||
|
|
04a196695a | ||
|
|
e471bb960d | ||
|
|
f57646d397 | ||
|
|
cd350ee1d1 | ||
|
|
180ec0090d | ||
|
|
8918ac1598 | ||
|
|
d35de8a12f | ||
|
|
fdf085f814 | ||
|
|
a160bc33bf | ||
|
|
7ea0b95d7b | ||
|
|
0e05313a6a | ||
|
|
79e8123ffe | ||
|
|
1761ab9046 | ||
|
|
9900a956e6 | ||
|
|
3a7b0c4c75 | ||
|
|
d33fb677be | ||
|
|
2c4031092e | ||
|
|
3d63545ab7 | ||
|
|
60d9327a78 | ||
|
|
8c94c1f238 | ||
|
|
911d48b420 | ||
|
|
28ca19b91c | ||
|
|
c379c51cd6 | ||
|
|
5c77d2b757 | ||
|
|
1c9b77975e | ||
|
|
87592b64a3 | ||
|
|
55b32f1229 | ||
|
|
2a215319ee | ||
|
|
7e17e4be22 | ||
|
|
9020997c96 | ||
|
|
e08f4f8023 | ||
|
|
5bc79cc3db | ||
|
|
7ffb3b65e0 | ||
|
|
19098a46ac | ||
|
|
34a11971af | ||
|
|
fa6defc172 | ||
|
|
ad7fc48c97 | ||
|
|
b178d5ca6a | ||
|
|
9903ea2631 | ||
|
|
2f99ec5289 | ||
|
|
5b5c71d78c | ||
|
|
387b888168 | ||
|
|
ef3dfa8ad1 | ||
|
|
7c295a34f3 | ||
|
|
523bd7f6a4 | ||
|
|
3c15252cb1 | ||
|
|
e2b0829f6e | ||
|
|
8f8a4209e3 | ||
|
|
6385deeb6e | ||
|
|
366e12193e | ||
|
|
ad26b469cb | ||
|
|
9bfe005524 | ||
|
|
8513c39fe3 | ||
|
|
791e26939c | ||
|
|
4c30037d1e | ||
|
|
9dd4228674 | ||
|
|
1a3fb8a75f | ||
|
|
2ce5c6c508 | ||
|
|
940c2b221c | ||
|
|
fe2e95622f | ||
|
|
922251a398 | ||
|
|
559096f1c2 | ||
|
|
fcdf2d7947 | ||
|
|
947c6beede | ||
|
|
09d0fb4daa | ||
|
|
2a45ee02fb | ||
|
|
466a049bd0 | ||
|
|
1e2037753a | ||
|
|
4e48609c49 | ||
|
|
5204e5a111 | ||
|
|
7590ee9799 | ||
|
|
8b667cf048 | ||
|
|
089b809aed | ||
|
|
aa68cd84d0 | ||
|
|
72da264c22 | ||
|
|
8709daf698 | ||
|
|
54ba9a0f00 | ||
|
|
df4ceb5a45 | ||
|
|
af82dd7bdd | ||
|
|
ea63def0e1 | ||
|
|
2b6ec893d1 | ||
|
|
cdc45caa80 | ||
|
|
1123913ad4 | ||
|
|
cb2b60bd90 | ||
|
|
5d8b1535ac | ||
|
|
d13bdbcea6 | ||
|
|
7a080478b5 | ||
|
|
a58653ada8 | ||
|
|
d06d020c13 | ||
|
|
b86a07e632 | ||
|
|
38756421d1 | ||
|
|
c016cf7960 | ||
|
|
3341688e18 | ||
|
|
476c945d7e | ||
|
|
33399b3f11 | ||
|
|
5d06c9a6f7 | ||
|
|
35524b8df9 | ||
|
|
0846ba148c | ||
|
|
d54e705b7c | ||
|
|
7fc0d45b89 | ||
|
|
266d6328a3 | ||
|
|
e370b9377a | ||
|
|
cc6d474dcb | ||
|
|
9f226a254a | ||
|
|
c5a1a98f03 | ||
|
|
94e1bb6ec3 | ||
|
|
b804245cbe | ||
|
|
f7716f2f87 | ||
|
|
cae858e16a | ||
|
|
fe61f724d6 | ||
|
|
091ecfbbf8 | ||
|
|
e18b9a5e56 | ||
|
|
e9b8752c3f | ||
|
|
46408ee5e8 | ||
|
|
2c524604df | ||
|
|
2da2a27373 | ||
|
|
dc7d3f149f | ||
|
|
07ff4112d4 | ||
|
|
47a86be7ba | ||
|
|
d84f56bb0e | ||
|
|
afa74d655d | ||
|
|
893f1d376e | ||
|
|
2f161c25cd | ||
|
|
5a8a9472e9 | ||
|
|
3bb1796e12 | ||
|
|
908270aba1 | ||
|
|
63b7146a3c | ||
|
|
0ce4eeae72 | ||
|
|
b9c936f594 | ||
|
|
90c81c1750 | ||
|
|
2cc21bf1f2 | ||
|
|
b4d915bbbb | ||
|
|
f15360cb12 | ||
|
|
31c9728b3a | ||
|
|
4c996baf72 | ||
|
|
8a543bfda4 | ||
|
|
87da62bca2 | ||
|
|
81ecf2fd74 | ||
|
|
9232fcc601 | ||
|
|
dabdf3b8a5 | ||
|
|
cc3f210bf6 | ||
|
|
f325224ffb | ||
|
|
1e6b48a754 | ||
|
|
87b0093873 | ||
|
|
7fd2a91503 | ||
|
|
fdf08c4f09 | ||
|
|
e5f34c3393 | ||
|
|
e0a25d9030 | ||
|
|
005fd78275 | ||
|
|
d4f4667008 | ||
|
|
1b0d20d53c | ||
|
|
8f0e7f3bcc | ||
|
|
9594c4b59f | ||
|
|
057fc40c1d | ||
|
|
fa97df5f4e | ||
|
|
2e2ee06279 | ||
|
|
99a1b13e23 | ||
|
|
748a9bf52d | ||
|
|
022b33e83c | ||
|
|
db994ed473 | ||
|
|
43c41925a2 | ||
|
|
9c56d5c7e9 | ||
|
|
b919c36db0 | ||
|
|
c70e015751 | ||
|
|
3608b19b3a | ||
|
|
7122ca05c4 | ||
|
|
7e130c5421 | ||
|
|
f7bcfcea24 | ||
|
|
a1e1cef6d2 | ||
|
|
3e003ee9f4 | ||
|
|
828ae7e708 | ||
|
|
2998b2a1ca | ||
|
|
1f109de7a9 | ||
|
|
c0b8de7a76 | ||
|
|
0bad5f40e4 | ||
|
|
dc9434645b | ||
|
|
266db27c72 | ||
|
|
6ffc41345f | ||
|
|
943134812e | ||
|
|
019cf0a986 | ||
|
|
a3746cab00 | ||
|
|
c7eb7daeae | ||
|
|
e396612254 | ||
|
|
cf00dfa1c3 | ||
|
|
b208fbf6a9 | ||
|
|
d779144da1 | ||
|
|
3227b0f4ae | ||
|
|
bfe60049b3 | ||
|
|
5174afd300 | ||
|
|
44189802e9 | ||
|
|
214aa2e450 | ||
|
|
c3eb256139 | ||
|
|
dd1c0fe2e8 | ||
|
|
f137f4dd96 | ||
|
|
8df75a730a | ||
|
|
a3c8629fd9 | ||
|
|
730f5c5c3d | ||
|
|
edf5b513dc | ||
|
|
11976b8d24 | ||
|
|
03cd314495 | ||
|
|
9d064890d7 | ||
|
|
7bf868e751 | ||
|
|
b2ec5c10ce | ||
|
|
4e07d16148 | ||
|
|
f2c332ea6a | ||
|
|
d64a23b2f7 | ||
|
|
3c39e3232f | ||
|
|
6376aea320 | ||
|
|
a27371b6a4 | ||
|
|
9f2415420b | ||
|
|
7eddc4c658 | ||
|
|
4cd12e9049 | ||
|
|
adf0633e7b | ||
|
|
4d9083bdfa | ||
|
|
a75f129542 | ||
|
|
2c51011018 | ||
|
|
d84566ac28 | ||
|
|
bf033becbd | ||
|
|
8622e5c378 | ||
|
|
bcda96be51 | ||
|
|
01e8e5d827 | ||
|
|
8d2514571c | ||
|
|
3b96ccb03c | ||
|
|
a452488d61 | ||
|
|
378f8d28e1 | ||
|
|
f798a60475 | ||
|
|
92eb2656e0 | ||
|
|
2266976296 | ||
|
|
ad506280b2 | ||
|
|
1aecd18a72 | ||
|
|
56b0786379 | ||
|
|
6eaefd95ae | ||
|
|
1e7cc7ba00 | ||
|
|
e0db6eb2ea | ||
|
|
8d284cd967 | ||
|
|
86e9351cd0 | ||
|
|
c5a5f57451 | ||
|
|
ff33c9939c | ||
|
|
1420df7d7a | ||
|
|
ede262c109 | ||
|
|
0dec4931a0 | ||
|
|
b343de0c70 | ||
|
|
95831a5b22 | ||
|
|
493fc99bcb | ||
|
|
c69130e0e0 | ||
|
|
7a6dc3ff29 | ||
|
|
b246c4ed90 | ||
|
|
9710c755c5 | ||
|
|
7d0d4cea33 | ||
|
|
95870f4ecc | ||
|
|
852cd6a179 | ||
|
|
2a2130f85a | ||
|
|
416c190993 | ||
|
|
8bfccf1d4e | ||
|
|
99943ff52b | ||
|
|
bc05d71824 | ||
|
|
dc76a3ad41 | ||
|
|
226234e6bc | ||
|
|
2be5a784e4 | ||
|
|
7bb94c8560 | ||
|
|
61d8516eb3 | ||
|
|
4d07f0274a | ||
|
|
db0ac480af | ||
|
|
5b21a056d8 | ||
|
|
8d60726a15 | ||
|
|
400ff4a750 | ||
|
|
e9f8c616dd | ||
|
|
44b26e1993 | ||
|
|
1973dee59b | ||
|
|
5471b6a083 | ||
|
|
038d7bd727 | ||
|
|
7e48b7a1f1 | ||
|
|
78eb3df235 | ||
|
|
bdb497c9a3 | ||
|
|
22c39ed6c8 | ||
|
|
06550e3830 | ||
|
|
bfec804ee1 | ||
|
|
55d367ec03 | ||
|
|
6a93806c62 | ||
|
|
1cbe8bd394 | ||
|
|
03604b1bc6 | ||
|
|
bb47caad56 | ||
|
|
f84f245903 | ||
|
|
098053da35 | ||
|
|
355f9f6b52 | ||
|
|
3851641b5a | ||
|
|
30aa1ded1d | ||
|
|
be251811eb | ||
|
|
68913d9a20 | ||
|
|
6525b39e83 | ||
|
|
3ebdb63268 | ||
|
|
b0b3cfa39a | ||
|
|
32de338247 | ||
|
|
46ac7e7f11 | ||
|
|
9f2fc88f0f | ||
|
|
eb877545de | ||
|
|
cc0d76966c | ||
|
|
2d7b9923ef | ||
|
|
d11e93c36c | ||
|
|
d04a7b5a77 | ||
|
|
ce7ff2ca44 | ||
|
|
de92ce5c49 | ||
|
|
66f594342e | ||
|
|
3a6505a32d | ||
|
|
acf205cd7f | ||
|
|
99698928af | ||
|
|
a9aff79cc6 | ||
|
|
df109c0023 | ||
|
|
1d8bcb22bd | ||
|
|
ab32dbc9a2 | ||
|
|
631b1684e2 | ||
|
|
7b3da8b744 | ||
|
|
c762ee4b43 | ||
|
|
315d3bfee8 | ||
|
|
980c2cda3a | ||
|
|
3ea632fa1b | ||
|
|
f08ca7b9cc | ||
|
|
4b8dc9409f | ||
|
|
d478e001b5 | ||
|
|
8c419ec52a | ||
|
|
9470dfc88a | ||
|
|
13626290c0 | ||
|
|
cbd31d513e | ||
|
|
3e414a8c60 | ||
|
|
38482cf0fe | ||
|
|
8c044ab85b | ||
|
|
208f92160d | ||
|
|
e28770e744 | ||
|
|
a4b592a355 | ||
|
|
f0626508d3 | ||
|
|
bc12b1496a | ||
|
|
80a9b70253 | ||
|
|
5d9d7601a9 | ||
|
|
021a12f648 | ||
|
|
6cb00d513b | ||
|
|
7984bc2ae3 | ||
|
|
6659b11962 | ||
|
|
284d04a211 | ||
|
|
e1de2763c3 | ||
|
|
e04fd9e7fe | ||
|
|
463e52b063 | ||
|
|
29bf0132f9 | ||
|
|
ec06b70b8a | ||
|
|
42f946dbc6 | ||
|
|
3254e425e2 | ||
|
|
18d9811eaa | ||
|
|
216579bb01 | ||
|
|
d356ef5633 | ||
|
|
daab3781a2 | ||
|
|
961abda4eb | ||
|
|
de79b70cbc | ||
|
|
aa9580e59c | ||
|
|
c692f075d1 | ||
|
|
4b7037ac9a | ||
|
|
318a784e20 | ||
|
|
7c81290252 | ||
|
|
17bd5c3adb | ||
|
|
07220b351c | ||
|
|
0685a0e7fb | ||
|
|
5fba636517 | ||
|
|
f9e108c4ee | ||
|
|
a0de2bcb6b | ||
|
|
b3b4461fe2 | ||
|
|
6ebce1832b | ||
|
|
152ff41cf8 | ||
|
|
34b51f7289 | ||
|
|
317682d7f6 | ||
|
|
8dff708613 | ||
|
|
635f54bd08 | ||
|
|
2ce3c9c378 | ||
|
|
645ddaf661 | ||
|
|
5373cb7998 | ||
|
|
2f0e5819f7 | ||
|
|
f1bc55bf89 | ||
|
|
7fe4030453 | ||
|
|
7a01fa0a67 | ||
|
|
937bdf9058 | ||
|
|
22fe390be6 | ||
|
|
135a993d32 | ||
|
|
827e008497 | ||
|
|
9f56b537fb | ||
|
|
45ff23bde2 | ||
|
|
4b5fb84678 | ||
|
|
1c024c1ba2 | ||
|
|
db90157191 | ||
|
|
045d941aef | ||
|
|
93a0017b25 | ||
|
|
4dc4a3a56d | ||
|
|
979af97b01 | ||
|
|
f928ad702f | ||
|
|
91f121eb62 | ||
|
|
5177d5d0af | ||
|
|
2613c353d1 | ||
|
|
1a6e19ae94 | ||
|
|
0b37af5e13 | ||
|
|
037db69f6a | ||
|
|
d18d9c5964 | ||
|
|
826d65770a | ||
|
|
576779b69b | ||
|
|
a3d6d2498c | ||
|
|
ec2993918c | ||
|
|
cfa22d0a62 | ||
|
|
617e8405ff | ||
|
|
fefecfeb9a | ||
|
|
462f5eb54b | ||
|
|
a42101531a | ||
|
|
d83a0b6808 | ||
|
|
189cd259ee | ||
|
|
c7357ed109 | ||
|
|
8c0a4430e0 | ||
|
|
36eddbafad | ||
|
|
bd2342ebfa | ||
|
|
5236039b1e | ||
|
|
b51f508d59 | ||
|
|
61720181b5 | ||
|
|
786ea115d7 | ||
|
|
40d2cab159 | ||
|
|
07164f55d3 | ||
|
|
da63feed99 | ||
|
|
ddb0fd81c7 | ||
|
|
1b1928fd64 | ||
|
|
41acaa0570 | ||
|
|
f771160147 | ||
|
|
e880941cef | ||
|
|
16607cb12a | ||
|
|
d6cd3da61d | ||
|
|
bf1f6024d6 | ||
|
|
9deaca3c07 | ||
|
|
d25d1f65dc | ||
|
|
b740db335b | ||
|
|
942b9b5b1a | ||
|
|
0571675fcd | ||
|
|
457d51a972 | ||
|
|
488b2ff231 | ||
|
|
e46830a57f | ||
|
|
bf9fdb41ed | ||
|
|
af45b4df74 | ||
|
|
74218cc36a | ||
|
|
48168feb55 | ||
|
|
5e15cc9d4c | ||
|
|
2765f3390d | ||
|
|
4802b1caec | ||
|
|
9e36a3cc85 | ||
|
|
8ba8a3393d | ||
|
|
b9673da07b | ||
|
|
f1b008388c | ||
|
|
35da17b32a | ||
|
|
2ae603574c | ||
|
|
5d91c1057f | ||
|
|
1aaead319e | ||
|
|
444f67d219 | ||
|
|
a021fef830 | ||
|
|
ff25ae254d | ||
|
|
a61ef7ee2f | ||
|
|
dd84c64d16 | ||
|
|
6a151ee135 | ||
|
|
0363728b83 | ||
|
|
a2c40367fa | ||
|
|
efb6c63a95 | ||
|
|
b22947d4ec | ||
|
|
ced6b76ef5 | ||
|
|
800959f458 | ||
|
|
8b571a3b54 | ||
|
|
c1e32d853f | ||
|
|
cad81f83e3 | ||
|
|
e07a442362 | ||
|
|
01323abbc8 | ||
|
|
3819fe8367 | ||
|
|
960431b92d | ||
|
|
ae1a8226d5 | ||
|
|
74740abead | ||
|
|
ed4a436860 | ||
|
|
e97df073df | ||
|
|
585938ca2c | ||
|
|
1e8ba742a8 | ||
|
|
3bcb5a0997 | ||
|
|
9e5d26ebd2 | ||
|
|
d2e3459206 | ||
|
|
5add9584a3 | ||
|
|
b332f4d4ae | ||
|
|
5cdbde244a | ||
|
|
3ace26da6f | ||
|
|
e15244c7b2 | ||
|
|
ade5a33c27 | ||
|
|
64388a31ad | ||
|
|
0e93306867 | ||
|
|
00252cbeff | ||
|
|
2ef437a2bc | ||
|
|
9c9c07fb26 | ||
|
|
1e29d0299b | ||
|
|
260c466dcb | ||
|
|
e06471603f | ||
|
|
cf3a872e67 | ||
|
|
1afc34eea3 | ||
|
|
0c2f1d583b | ||
|
|
3b40d2227d | ||
|
|
c5e8470e36 | ||
|
|
9bff44b446 | ||
|
|
f4422c491e | ||
|
|
82fb06f9c9 | ||
|
|
32d8c36ff5 | ||
|
|
fd862950e2 | ||
|
|
57da2def1d | ||
|
|
4dcbc4245b | ||
|
|
642e2ffe12 | ||
|
|
d2b04ddd92 | ||
|
|
33173c4d9b | ||
|
|
ff0c04ffa3 | ||
|
|
df274dcca9 | ||
|
|
a619119684 | ||
|
|
77ed10f57b | ||
|
|
a9ebc2b9af | ||
|
|
8fe3265c74 | ||
|
|
95550f4c77 | ||
|
|
fc47f9b84f | ||
|
|
c0f86ae1d1 | ||
|
|
ad244d6654 | ||
|
|
bfd0c4bf2f | ||
|
|
d36c7c7f90 | ||
|
|
10232673d9 | ||
|
|
c0e3672e32 | ||
|
|
2c357df33e | ||
|
|
3999de7e0b | ||
|
|
a3e5b20536 | ||
|
|
bb1641419f | ||
|
|
0b69700f18 | ||
|
|
ac2695e196 | ||
|
|
098d085621 | ||
|
|
a1f5f37af7 | ||
|
|
d48b30c734 | ||
|
|
3b5dff195f | ||
|
|
fad7b46c65 | ||
|
|
04588bccb9 | ||
|
|
b1017ac17c | ||
|
|
ebf65bfc5d | ||
|
|
22801d2d8f | ||
|
|
8380107e6b | ||
|
|
a77b0a238f | ||
|
|
6c81635188 | ||
|
|
fed40128a5 | ||
|
|
ee151de56a | ||
|
|
b0bcbd8731 | ||
|
|
06be78802e | ||
|
|
0fd8493929 | ||
|
|
710848938f | ||
|
|
1a5c05b6a8 | ||
|
|
306826fe0f | ||
|
|
a3230011eb | ||
|
|
6abf29dd97 | ||
|
|
c36ef99368 | ||
|
|
3acb3e7a9b | ||
|
|
d852cade9c | ||
|
|
c6bc81caef | ||
|
|
c7235829c3 |
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-flow-strip-types",
|
||||
"transform-object-rest-spread"
|
||||
"@babel/plugin-transform-flow-strip-types",
|
||||
"@babel/plugin-proposal-object-rest-spread"
|
||||
],
|
||||
"presets": [
|
||||
["env", {
|
||||
["@babel/preset-env", {
|
||||
"targets": {
|
||||
"node": "6.11.4"
|
||||
"node": "8"
|
||||
}
|
||||
}]
|
||||
]
|
||||
],
|
||||
"sourceMaps": "inline"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
lib
|
||||
coverage
|
||||
|
||||
out
|
||||
|
||||
+4
-2
@@ -14,7 +14,7 @@
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": ["error", 2],
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"no-trailing-spaces": 2,
|
||||
"eol-last": 2,
|
||||
@@ -22,6 +22,8 @@
|
||||
"no-multiple-empty-lines": 1,
|
||||
"prefer-const": "error",
|
||||
"space-infix-ops": "error",
|
||||
"no-useless-escape": "off"
|
||||
"no-useless-escape": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"require-atomic-updates": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<!--
|
||||
**Please use this template. If you don't use this template, your issue may be closed without comment.**
|
||||
We use GitHub Issues for bugs.
|
||||
|
||||
If you have a non-bug question, ask on Stack Overflow or Server Fault:
|
||||
- https://stackoverflow.com/questions/tagged/parse.com
|
||||
- https://serverfault.com/tags/parse
|
||||
If you have a non-bug question, ask on [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server)
|
||||
|
||||
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
|
||||
|
||||
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
-->
|
||||
|
||||
### Issue Description
|
||||
|
||||
Describe your issue in as much detail as possible.
|
||||
<!-- Describe your issue in as much detail as possible. -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable.
|
||||
<!-- Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable. -->
|
||||
|
||||
#### Expected Results
|
||||
|
||||
What you expected to happen.
|
||||
<!-- What you expected to happen. -->
|
||||
|
||||
#### Actual Outcome
|
||||
|
||||
What is happening instead.
|
||||
<!-- What is happening instead. -->
|
||||
|
||||
### Environment Setup
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: "\U0001F4A1 Feature request"
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
name: "🙋Getting Help"
|
||||
about: Join https://community.parseplatform.org
|
||||
|
||||
---
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: "\U0001F525 parse-server 3.0.0"
|
||||
about: Report an issue while migrating to parse-server 3.0.0
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
** Use this template only if you have an issue migrating to parse-server 3.0.0 **
|
||||
|
||||
If you have a question, ask on [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server)
|
||||
|
||||
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
|
||||
|
||||
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
--->
|
||||
|
||||
# Before opening the issue please ensure that you have:
|
||||
|
||||
- [ ] [Read the migration guide](https://github.com/parse-community/parse-server/blob/master/3.0.0.md) to parse-server 3.0.0
|
||||
- [ ] [Read the migration guide](https://github.com/parse-community/Parse-SDK-JS/blob/master/2.0.0.md) to Parse SDK JS 2.0.0
|
||||
|
||||
### Issue Description
|
||||
|
||||
<!--- Describe your issue in as much detail as possible. -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!--- Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable. --->
|
||||
|
||||
### Expected Results
|
||||
|
||||
<!--- What you expected to happen. --->
|
||||
|
||||
### Actual Outcome
|
||||
|
||||
<!--- What is happening instead. --->
|
||||
|
||||
### Environment Setup
|
||||
|
||||
- **Server**
|
||||
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
|
||||
- Operating System: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
- **Database**
|
||||
- MongoDB version: [FILL THIS OUT]
|
||||
- Storage engine: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
### Logs/Trace
|
||||
|
||||
<!--- Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment. --->
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: "\U0001F4F2 Push Notifications"
|
||||
about: Issues with setting up or delivering push notifications
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
|
||||
**We use GitHub Issues for reporting bugs with parse-server.**
|
||||
|
||||
If you have a *question*, you should join the [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server)
|
||||
|
||||
If you have a non-bug question, ask on Stack Overflow or Server Fault:
|
||||
- https://stackoverflow.com/questions/tagged/parse.com
|
||||
- https://serverfault.com/tags/parse
|
||||
|
||||
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
|
||||
|
||||
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
--->
|
||||
|
||||
### Issue Description
|
||||
|
||||
<!--- Describe your issue in as much detail as possible. -->
|
||||
|
||||
### Push Configuration
|
||||
|
||||
Please provide a copy of your `push` configuration here, obfuscating any sensitive part.
|
||||
|
||||
### Environment Setup
|
||||
|
||||
- **Server**
|
||||
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
|
||||
- Operating System: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
- **Database**
|
||||
- MongoDB version: [FILL THIS OUT]
|
||||
- Storage engine: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
### Logs/Trace
|
||||
|
||||
<!--- Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment. --->
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: "\U0001F41B Report an issue"
|
||||
about: Report an issue on parse-server
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
|
||||
**We use GitHub Issues for reporting bugs with parse-server.**
|
||||
|
||||
If you have a *question*, you should join the [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server).
|
||||
|
||||
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
|
||||
|
||||
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
--->
|
||||
### Issue Description
|
||||
|
||||
<!--- Describe your issue in as much detail as possible. -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!--- Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable. --->
|
||||
|
||||
### Expected Results
|
||||
|
||||
<!--- What you expected to happen. --->
|
||||
|
||||
### Actual Outcome
|
||||
|
||||
<!--- What is happening instead. --->
|
||||
|
||||
### Environment Setup
|
||||
|
||||
- **Server**
|
||||
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
|
||||
- Operating System: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
- **Database**
|
||||
- MongoDB version: [FILL THIS OUT]
|
||||
- Storage engine: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
### Logs/Trace
|
||||
|
||||
<!--- Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment. --->
|
||||
@@ -0,0 +1,24 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 45
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- good first issue
|
||||
- up-for-grabs
|
||||
- bug
|
||||
- enhancement
|
||||
- help wanted
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Limit to only `issues` not `pulls`
|
||||
only: issues
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
@@ -15,6 +15,10 @@ lib-cov
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# docs output
|
||||
out
|
||||
docs
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
"text-summary"
|
||||
],
|
||||
"exclude": [
|
||||
"**/spec/**",
|
||||
"lib/"
|
||||
"**/spec/**"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
semi: true
|
||||
trailingComma: "es5"
|
||||
singleQuote: true
|
||||
+45
-41
@@ -1,75 +1,79 @@
|
||||
language: node_js
|
||||
dist: trusty
|
||||
services:
|
||||
- mongodb
|
||||
- postgresql
|
||||
- redis-server
|
||||
- docker
|
||||
- mongodb
|
||||
- postgresql
|
||||
- redis-server
|
||||
- docker
|
||||
addons:
|
||||
postgresql: '9.5'
|
||||
apt_packages:
|
||||
- postgresql-9.5-postgis-2.3
|
||||
apt:
|
||||
packages:
|
||||
- postgresql-9.5-postgis-2.3
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^[0-9]+.[0-9]+.[0-9]+(-.*)?$/
|
||||
- "/^[0-9]+.[0-9]+.[0-9]+(-.*)?$/"
|
||||
- 3.x
|
||||
- /^greenkeeper/.*$/
|
||||
- "/^greenkeeper/.*$/"
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- "$HOME/.npm"
|
||||
- "$HOME/.mongodb/versions"
|
||||
|
||||
# Test stage
|
||||
stage: test
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/.bin/nyc'
|
||||
- NODE_VERSION=6.11.4
|
||||
matrix:
|
||||
- MONGODB_VERSION=3.2.13
|
||||
- MONGODB_VERSION=3.4.4
|
||||
- MONGODB_VERSION=4.0.4
|
||||
- MONGODB_VERSION=3.6.9
|
||||
- PARSE_SERVER_TEST_DB=postgres
|
||||
- PARSE_SERVER_TEST_CACHE=redis
|
||||
- NODE_VERSION=8.7
|
||||
- NODE_VERSION=11.4.0
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: NODE_VERSION=11.4.0
|
||||
before_install:
|
||||
- nvm install $NODE_VERSION
|
||||
- nvm use $NODE_VERSION
|
||||
- npm install -g greenkeeper-lockfile@1
|
||||
before_script:
|
||||
- node -e 'require("./lib/index.js")'
|
||||
- psql -c 'create database parse_server_postgres_adapter_test_database;' -U postgres
|
||||
- psql -c 'CREATE EXTENSION postgis;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
- psql -c 'CREATE EXTENSION postgis_topology;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
- silent=1 mongodb-runner --start
|
||||
- greenkeeper-lockfile-update
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run coverage
|
||||
after_script:
|
||||
- greenkeeper-lockfile-upload
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
jobs:
|
||||
include:
|
||||
# release on github latest branch
|
||||
- stage: release
|
||||
node_js: '6.11.4'
|
||||
env:
|
||||
before_script: skip
|
||||
after_script: skip
|
||||
script: skip
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: ./resources/npm-git.sh
|
||||
on:
|
||||
branch:
|
||||
- master
|
||||
- 3.x
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email:
|
||||
secure: "YweTGc22uqFWpzbfiUa5ptBLRAy6tt6d9TZLwEkPtmnsWzN9dguGyKWmXiw0qL+848FWQ5PWzUgBn5XdigV9tF3rJY6RGs8i38WulNjwSoGuRZa3AChsQHAb1KenANcJybzhnwgEj9gRsrGZPEsyI2whfake/xLDtG91kHjpJANsd4gseOh6fdS4FIYCbyXvSbC7S0yZzIJkgSkMqJO7RJ8r0HYQ7srYIw31dM3ZXSmUYu+GaMDUUu3RLAGYoKaROxhDRnzkjdeLLiwZH8hQ/6CaqwqX54iJ6OS+MOQU3fi5ZXalA7RZvtC4RmrcCkaTf3i0f+5xejYIFgzXdYGMVm8DUc82tDw1s4b6Pb19bgi1xXOQ0IKzRmZuGxnvkRN61dnYdnpnnNuG97HXgVjiOigZXVLZkWazUdnf9zXqmkC+KxPfa4Ldqg0TMjQ9J14n6TXxRti8Tt0xMa1Uzho7VdsxdJy35Bghy398O6X8VdX6parfzEkX7c/JzcA3TIMJ9+S1dy3J4Tb2URB5367W6h7cDeXtGmwLTFu75Q1CNqRJkUGbSrX2NyMqG5tc8oaTM+OWvLxcbKPRy9T6qN7x2JmCHHaapf8/8VR9wCy2PwE5j+KWhmctEHqqOgrtG5gsjC0eCPJsposxZWyM7M6aUpXe3w+olBfKq9apUGStUSU="
|
||||
api_key:
|
||||
secure: "QprkaqQ+WCvZQR4qIEs5iS6peMCbRd8Hgt0s6HfdmhavNXwDFY8Bkdf6zJwWHLiqs4pyClXDZ2f6QiOs7y9IvJZ+wOIbsf4N5V6s06zOxJ0NAOwhe0mgWS3Us0zgXIfmW4BpmGnU4ql/qGL+9vNfyQJ7wxEJxVK7hiYh9Epu49E2jmefDqTX+SNSrDCg4HkRcxerxYGnAJDCP50QaNlyLSciODD6wHaddrSYkdvmISLMnLHug61OkE4OBIOWXfYV+e31kDj+zgczAfVQgekDKtaimCQclHFrmaEPl0KIm6wsDQAw5HWkepA/WZfv4SbCrDaKJDZw+LBI7dR0ezmiOH/zmWWrRW7D4wjkDGiumWjA8etuf8I4GRyC/d1RS+hnlvPr0Bu+WljuVxLoK3nhZOdiK5t4QlVDoGankkRjLylwFQgo8tzu5N0dc26z3ClowTwcKsjStmFr53gjCD7l3qoFjyPot1JlW3LPhG9Nch7rK33/7ONqVai3zxb1xB9ynd8TSzKi4/66LeYEDcGVM5A9Vmkp+egCnOhkOAXdI8O4jid98NANir+U4xmUYZ2PAMNmSeHlpSpwH2pC1/BHpDKA9RZEuEdr3sgmtuXXwwOCp+xvuVwxZZ6+gVEjG4nGTqSNxUIq1fzjGih8ElJtbM1Uhh2dVE9uxW7EC/oCuuk="
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
repo: parse-community/parse-server
|
||||
- stage: release
|
||||
node_js: '10'
|
||||
env:
|
||||
before_script: skip
|
||||
after_script: skip
|
||||
script:
|
||||
- "./release_docs.sh"
|
||||
deploy:
|
||||
- provider: pages
|
||||
skip_cleanup: true
|
||||
github_token:
|
||||
secure: YU2lUqmW036AHBRu7xO/AwEeQ900Q/5O6FL96ZqWEfD7Gadaq4iapkNvhPR0HcZYRHqQV2/2LCHVnhd0dbj0ShkmVIHdQE7O+MF+j0v0GIVc8FPTPe1/d2Hy4apSWNe6FeCrYKVeliAu+ZqvNuFLhVmYvIeJdlJrMGOb6P76UZXRDv2srXhq4uBcCVUJuTajyyd5ttJfcNapymTP+xzEDYc7Hr4LJaubmv/wVD/xwPBbvfYFuBqysUpkPKi/ODlQbB0ybYh2fFnX71WUyFUGbtB5xI9ma951Zp4v3t73c31uUl27dJaHzO62EtVTdcUKAa804EtAtsvpeJWMVWKUgigm9UZcXdEwKa79fl5nLaq34lrSktAYOkexwPqYj+vbS6sn52JrluSxLE+4cEke7tYbnJ9X12SAQXKgcXY3n30+6gKf9RVqYdlNsYpEAqKVIDb6SlEkk86dP+uIg/XXb5RKEwxbDXnb6xdl9JRc+GTbeeY3/vg2h9QTdmFVblPtBhHrNenQYP/BS0n+EfUnAIBKqRmQgyhao3SiY5FMACM8higI2Lvvhpq46pDhXqsexYCz5F008C6YXGDh5gC93rJFec0pjh55DNdQu6uw3YMQ5jtf3QUXoPAoMFud3cTulMlnjC1WZG8QbqER8dzUZ4TcaQUdzribJI/mRriheBg=
|
||||
local_dir: docs/
|
||||
on:
|
||||
all_branches: true
|
||||
- provider: npm
|
||||
api_key:
|
||||
secure: Yrng6jsnMAtzrrln9DwRuY4xpcxl/WYS/1A5fckyQF6DsLmNlvqVtu3MWF+zdJgANF63G3jIee11tNBpYRecHl8FjPGwO0kc1vEgvIRVnveyR0bwIIDH6s/mR9dg4rZikflwG4XExsLyaQd7ko8aTIOIayfxJiv/u0yqwuBuBW2bFrL/41b1cKGK5+Iq2a+PdFENUPenKXISkACGaMnQF4Y/KVF98UwCiGLf957yFWc2sD6TFbjNDbAENSccvg1J3fBb+djbtzKzldl29ntp2mKVGSVASiKCRa6hSfgQulHiidFqFIKgpYJ1uATRr3UFr/NRVt1WbrgLnzY74OCX7y02c/xiYQMMEPRl/P8hHJu+KjQ3PBWsBvmsRN0QMUJv3PEjUPE3AY8sw49NoxiJZzJr7574vUBuM/dk0byYI6K/gwmUrPIhQjljzZqinS8JJJ4FjGjCdzXRhT5Q+PZvt5bF1rMOZXayNJZa+cICtmiJoU3vO2Yf6TXC3wY1veKvmcs24nws5lp4keJePTKAi1Ig7VoSxwAlgs3EGANIh7oBKx9zWnXQYMdmxbYsvLXAWcnnM+PcSCvooLmXWso3BQBeRuUUSS2oLaBpsFMsiniLNbA1cW4fwMkgUGz4oEDRi8wTD89E/1J2oNxzqtWRPawcVKpMpnBbDJHrWJPEHMw=
|
||||
email:
|
||||
secure: lomzDl71N995SzRczm7VE9OZE+PzMo4X8t3zU97agu/FMH5Qcj8BLwE+uVDTnA9Vblyj62ZsFKsNjP2Qp53Vcd+jHM4EJNWNRZYpEMIRO3LngX43r83qoFEHUvPu9s1oaa04a6FojcsJx0wl6B6Ke2AX74MXnJDLb9iZBy1mkpLUMVccVhSfhdoIzhkq3dhUw+6d8C024tNMHcgDW3VnRAsWFtiL7dCMpjLOdI+UxlkeGkQkxXXuRsZ0ZdjoSoM8NSkiYMc8x6EnekyRDoHTujX3OFxuU6+GAjrUmVzNmJWrBIqHVb0DXBQxjEaG3d/cNu5UsQyZYq5sxRRH0BaLs7F4oIQg95etasEtTtUkmsZ3pshVlsweiLU366UdbfuAf5hrJjqLrU12BKZyLjaAwyeKz031r8dA4sJtGIp5uVdXobQQTH6r958A88byJ20uaYSqhqjhZo3hkWXIQP0WQN2Ej/g57HbVNLB/nPKkMILfk/tpp7nBDLT0QrjbZxeo1dwCHqsBEV6z7ZyWyFf4xwpDsir4txL4t8ElzeGdlACjCqAJvIh5w9YzfrwijtoVMvvP6pWvn/iI640d4rsdIDe8egxgqZ1R/TMd/tdHYX+eI+ZfFmCVGj36/uXwdG7KIoIZVjRQ2tvWr9ZuydEPWPSRVGT4ycFeu6wbm3elM3I=
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
repo: parse-community/parse-server
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
# Upgrading Parse Server to version 3.0.0
|
||||
|
||||
parse-server 3.0.0 comes also with the JS SDK version 2.0 which requires a migration. Follow the migration guide [here](https://github.com/parse-community/Parse-SDK-JS/blob/master/2.0.0.md).
|
||||
|
||||
With the 3.0.0, parse-server has completely revamped it's cloud code interface. Gone are the backbone style calls, welcome promises and async/await.
|
||||
|
||||
In order to leverage those new nodejs features, you'll need to run at least node 8.10. We've dropped the support of node 6 a few months ago, so if you haven't made the change yet, now would be a really good time to take the plunge.
|
||||
|
||||
## Migrating Cloud Code Hooks
|
||||
|
||||
### Synchronous validations:
|
||||
|
||||
```js
|
||||
// before
|
||||
Parse.Cloud.beforeSave('MyClassName', function(request, response) {
|
||||
if (!passesValidation(request.object)) {
|
||||
response.error('Ooops something went wrong');
|
||||
} else {
|
||||
response.success();
|
||||
}
|
||||
});
|
||||
|
||||
// after
|
||||
Parse.Cloud.beforeSave('MyClassName', (request) => {
|
||||
if (!passesValidation(request.object)) {
|
||||
throw 'Ooops something went wrong';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
All methods are wrapped in promises, so you can freely `throw` `Error`, `Parse.Error` or `string` to mark an error.
|
||||
|
||||
### Asynchronous validations:
|
||||
|
||||
For asynchronous code, you can use promises or async / await.
|
||||
|
||||
Consider the following beforeSave call that would replace the contents of a fileURL with a proper copy of the image as a Parse.File.
|
||||
|
||||
```js
|
||||
// before
|
||||
Parse.Cloud.beforeSave('Post', function(request, response) {
|
||||
Parse.Cloud.httpRequest({
|
||||
url: request.object.get('fileURL'),
|
||||
success: function(contents) {
|
||||
const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') });
|
||||
file.save({
|
||||
success: function() {
|
||||
request.object.set('file', file);
|
||||
response.success();
|
||||
},
|
||||
error: response.error
|
||||
});
|
||||
},
|
||||
error: response.error
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
As we can see the current way, with backbone style callbacks is quite tough to read and maintain.
|
||||
It's also not really trivial to handle errors, as you need to pass the response.error to each error handlers.
|
||||
|
||||
Now it can be modernized with promises:
|
||||
|
||||
```js
|
||||
// after (with promises)
|
||||
Parse.Cloud.beforeSave('MyClassName', (request) => {
|
||||
return Parse.Cloud.httpRequest({
|
||||
url: request.object.get('fileURL')
|
||||
}).then((contents) => {
|
||||
const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') });
|
||||
request.object.set('file', file);
|
||||
return file.save();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
And you can even make it better with async/await.
|
||||
|
||||
```js
|
||||
// after with async/await
|
||||
Parse.Cloud.beforeSave('MyClassName', async (request) => {
|
||||
const contents = await Parse.Cloud.httpRequest({
|
||||
url: request.object.get('fileURL')
|
||||
});
|
||||
|
||||
const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') });
|
||||
request.object.set('file', file);
|
||||
await file.save();
|
||||
});
|
||||
```
|
||||
|
||||
## Aborting hooks and functions
|
||||
|
||||
In order to abort a `beforeSave` or any hook, you now need to throw an error:
|
||||
|
||||
```js
|
||||
// after with async/await
|
||||
Parse.Cloud.beforeSave('MyClassName', async (request) => {
|
||||
// valid, will result in a Parse.Error(SCRIPT_FAILED, 'Something went wrong')
|
||||
throw 'Something went wrong'
|
||||
// also valid, will fail with Parse.Error(SCRIPT_FAILED, 'Something else went wrong')
|
||||
throw new Error('Something else went wrong')
|
||||
// using a Parse.Error is also valid
|
||||
throw new Parse.Error(1001, 'My error')
|
||||
});
|
||||
```
|
||||
|
||||
## Migrating Functions
|
||||
|
||||
Cloud Functions can be migrated the same way as cloud code hooks.
|
||||
In functions, the response object is not passed anymore, as it is in cloud code hooks
|
||||
|
||||
Continuing with the image downloader example:
|
||||
|
||||
```js
|
||||
// before
|
||||
Parse.Cloud.define('downloadImage', function(request, response) {
|
||||
Parse.Cloud.httpRequest({
|
||||
url: request.params.url,
|
||||
success: function(contents) {
|
||||
const file = new Parse.File(request.params.name, { base64: contents.buffer.toString('base64') });
|
||||
file.save({
|
||||
success: function() {
|
||||
response.success(file);
|
||||
},
|
||||
error: response.error
|
||||
});
|
||||
},
|
||||
error: response.error
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
You would call this method with:
|
||||
|
||||
```js
|
||||
Parse.Cloud.run('downloadImage',{
|
||||
url: 'https://example.com/file',
|
||||
name: 'my-file'
|
||||
});
|
||||
```
|
||||
|
||||
To migrate this function you would follow the same practices as the ones before, and we'll jump right away to the async/await implementation:
|
||||
|
||||
```js
|
||||
// after with async/await
|
||||
Parse.Cloud.define('downloadImage', async (request) => {
|
||||
const {
|
||||
url, name
|
||||
} = request.params;
|
||||
const response = await Parse.Cloud.httpRequest({ url });
|
||||
|
||||
const file = new Parse.File(name, { base64: response.buffer.toString('base64') });
|
||||
await file.save();
|
||||
return file;
|
||||
});
|
||||
```
|
||||
|
||||
## Migrating jobs
|
||||
|
||||
As with hooks and functions, jobs don't have a status object anymore.
|
||||
The message method has been moved to the request object.
|
||||
|
||||
```js
|
||||
// before
|
||||
Parse.Cloud.job('downloadImageJob', function(request, status) {
|
||||
var query = new Parse.Query('ImagesToDownload');
|
||||
query.find({
|
||||
success: function(images) {
|
||||
var done = 0;
|
||||
for (var i = 0; i<images.length; i++) {
|
||||
var image = images[i];
|
||||
status.message('Doing '+image.get('url'));
|
||||
Parse.Cloud.httpRequest({
|
||||
url: image.get('url'),
|
||||
success: function(contents) {
|
||||
status.message('Got '+image.get('url'));
|
||||
const file = new Parse.File(image.get('name'), { base64: contents.buffer.toString('base64') });
|
||||
file.save({
|
||||
success: function() {
|
||||
status.message('Saved '+image.get('url'));
|
||||
done++;
|
||||
if (done == images.length) {
|
||||
status.success();
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
response.message('Error '+image.get('url'));
|
||||
done++;
|
||||
if (done == images.length) {
|
||||
status.error();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
error: status.error
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
Parse.Cloud.job('downloadImageJob', async function(request) {
|
||||
const query = new Parse.Query('ImagesToDownload');
|
||||
const images = await query.find();
|
||||
const promises = images.map(async (image) => {
|
||||
request.message('Doing ' + image.get('url'));
|
||||
const contents = await Parse.Cloud.httpRequest({
|
||||
url: image.get('url')
|
||||
});
|
||||
request.message('Got ' + image.get('url'));
|
||||
const file = new Parse.File(image.get('name'), { base64: contents.buffer.toString('base64') });
|
||||
await file.save();
|
||||
request.message('Saved ' + image.get('url'));
|
||||
});
|
||||
await Promise.all(promises);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
As you can see the new implementation is more concise, easier to read and maintain.
|
||||
|
||||
If you encounter a problem or discover an issue with this guide, or with any Parse Community project, feel free to [reach out on github](https://github.com/parse-community/parse-server/issues/new/choose)
|
||||
|
||||
+315
-3
@@ -1,7 +1,319 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### master
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.2...master)
|
||||
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.7.0...master)
|
||||
|
||||
### 3.7.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.6.0...3.7.0)
|
||||
|
||||
- FIX: Prevent linkWith sessionToken from generating new session [#5801](https://github.com/parse-community/parse-server/pull/5801), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
- GraphQL: Improve session token error messages [#5753](https://github.com/parse-community/parse-server/pull/5753), thanks to [Douglas Muraoka](https://github.com/douglasmuraoka)
|
||||
- NEW: GraphQL { functions { call } } generic mutation [#5818](https://github.com/parse-community/parse-server/pull/5818), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- NEW: GraphQL Custom Schema [#5821](https://github.com/parse-community/parse-server/pull/5821), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- NEW: GraphQL custom schema on CLI [#5828](https://github.com/parse-community/parse-server/pull/5828), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- NEW: GraphQL @mock directive [#5836](https://github.com/parse-community/parse-server/pull/5836), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- FIX: GraphQL _or operator not working [#5840](https://github.com/parse-community/parse-server/pull/5840), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- NEW: Add "count" to CLP initial value [#5841](https://github.com/parse-community/parse-server/pull/5841), thanks to [Douglas Muraoka](https://github.com/douglasmuraoka)
|
||||
- NEW: Add ability to alter the response from the after save trigger [#5814](https://github.com/parse-community/parse-server/pull/5814), thanks to [BrunoMaurice](https://github.com/brunoMaurice)
|
||||
- FIX: Cache apple public key for the case it fails to fetch again [#5848](https://github.com/parse-community/parse-server/pull/5848), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- NEW: GraphQL Configuration Options [#5782](https://github.com/parse-community/parse-server/pull/5782), thanks to [Omair Vaiyani](https://github.com/omairvaiyani)
|
||||
- NEW: Required fields and default values [#5835](https://github.com/parse-community/parse-server/pull/5835), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- FIX: Postgres safely escape strings in nested objects [#5855](https://github.com/parse-community/parse-server/pull/5855), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
- NEW: Support PhantAuth authentication [#5850](https://github.com/parse-community/parse-server/pull/5850), thanks to [Ivan SZKIBA](https://github.com/szkiba)
|
||||
- FIX: Remove uws package [#5860](https://github.com/parse-community/parse-server/pull/5860), thanks to [Zeal Murapa](https://github.com/GoGross)
|
||||
|
||||
### 3.6.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.5.0...3.6.0)
|
||||
|
||||
- SECURITY FIX: Address [Security Advisory](https://github.com/parse-community/parse-server/security/advisories/GHSA-8w3j-g983-8jh5) of a potential [Enumeration Attack](https://www.owasp.org/index.php/Testing_for_User_Enumeration_and_Guessable_User_Account_(OWASP-AT-002)#Description_of_the_Issue) [73b0f9a](https://github.com/parse-community/parse-server/commit/73b0f9a339b81f5d757725dc557955a7b670a3ec), big thanks to [Fabian Strachanski](https://github.com/fastrde) for identifying the problem, creating a fix and following the [vulnerability disclosure guidelines](https://github.com/parse-community/parse-server/blob/master/SECURITY.md#parse-community-vulnerability-disclosure-program)
|
||||
- NEW: Added rest option: excludeKeys [#5737](https://github.com/parse-community/parse-server/pull/5737), thanks to [Raschid J.F. Rafeally](https://github.com/RaschidJFR)
|
||||
- FIX: LiveQuery create event with fields [#5790](https://github.com/parse-community/parse-server/pull/5790), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
- FIX: Generate sessionToken with linkWith [#5799](https://github.com/parse-community/parse-server/pull/5799), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
|
||||
### 3.5.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.4...3.5.0)
|
||||
|
||||
- NEW: GraphQL Support [#5674](https://github.com/parse-community/parse-server/pull/5674), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
|
||||
[GraphQL Guide](https://github.com/parse-community/parse-server#graphql)
|
||||
|
||||
- NEW: Sign in with Apple [#5694](https://github.com/parse-community/parse-server/pull/5694), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
- NEW: AppSecret to Facebook Auth [#5695](https://github.com/parse-community/parse-server/pull/5695), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
- NEW: Postgres: Regex support foreign characters [#5598](https://github.com/parse-community/parse-server/pull/5598), thanks to [Jeff Gu Kang](https://github.com/JeffGuKang)
|
||||
- FIX: Winston Logger string interpolation [#5729](https://github.com/parse-community/parse-server/pull/5729), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
|
||||
### 3.4.4
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.3...3.4.4)
|
||||
|
||||
Fix: Commit changes
|
||||
|
||||
### 3.4.3
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.2...3.4.3)
|
||||
|
||||
Fix: Use changes in master to travis configuration to enable pushing to npm and gh_pages. See diff for details.
|
||||
|
||||
### 3.4.2
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.1...3.4.2)
|
||||
|
||||
Fix: In my haste to get a [Security Fix](https://github.com/parse-community/parse-server/security/advisories/GHSA-2479-qvv7-47qq) out, I added [8709daf](https://github.com/parse-community/parse-server/commit/8709daf698ea69b59268cb66f0f7cee75b52daa5) to master instead of to 3.4.1. This commit fixes that. [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
### 3.4.1
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.0...3.4.1)
|
||||
|
||||
Security Fix: see Advisory: [GHSA-2479-qvv7-47q](https://github.com/parse-community/parse-server/security/advisories/GHSA-2479-qvv7-47qq) for details [8709daf](https://github.com/parse-community/parse-server/commit/8709daf698ea69b59268cb66f0f7cee75b52daa5). Big thanks to: [Benjamin Simonsson](https://github.com/BenniPlejd) for identifying the issue and promptly bringing it to the Parse Community's attention and also big thanks to the indefatigable [Diamond Lewis](https://github.com/dplewis) for crafting a failing test and then a solution within an hour of the report.
|
||||
|
||||
### 3.4.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.3.0...3.4.0)
|
||||
- NEW: Aggregate supports group by date fields [#5538](https://github.com/parse-community/parse-server/pull/5538) thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- NEW: API for Read Preferences [#3963](https://github.com/parse-community/parse-server/pull/3963) thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- NEW: Add Redis options for LiveQuery [#5584](https://github.com/parse-community/parse-server/pull/5584) thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
- NEW: Add Direct Access option for Server Config [#5550](https://github.com/parse-community/parse-server/pull/5550) thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
- FIX: updating mixed array in Postgres [#5552](https://github.com/parse-community/parse-server/pull/5552) thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
- FIX: notEqualTo GeoPoint Query in Postgres [#5549](https://github.com/parse-community/parse-server/pull/5549), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
- FIX: put the timestamp back in logs that was lost after Winston upgrade [#5571](https://github.com/parse-community/parse-server/pull/5571), thanks to [Steven Rowe](https://github.com/mrowe009) and [Arthur Cinader](https://github.com/acinader)
|
||||
- FIX: Validates permission before calling beforeSave [#5546](https://github.com/parse-community/parse-server/pull/5546), thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo)
|
||||
- FIX: Remove userSensitiveFields default value. [#5588](https://github.com/parse-community/parse-server/pull/5588), thanks to [William George](https://github.com/awgeorge)
|
||||
- FIX: Decode Date JSON value in LiveQuery. [#5540](https://github.com/parse-community/parse-server/pull/5540), thanks to [ananfang](https://github.com/ananfang)
|
||||
|
||||
|
||||
### 3.3.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.2.3...3.3.0)
|
||||
- NEW: beforeLogin trigger with support for auth providers ([#5445](https://github.com/parse-community/parse-server/pull/5445)), thanks to [Omair Vaiyani](https://github.com/omairvaiyani)
|
||||
- NEW: RFC 7662 compliant OAuth2 auth adapter ([#4910](https://github.com/parse-community/parse-server/pull/4910)), thanks to [Müller Zsolt](https://github.com/zsmuller)
|
||||
- FIX: cannot change password when maxPasswordHistory is 1 ([#5191](https://github.com/parse-community/parse-server/pull/5191)), thanks to [Tulsi Sapkota](https://github.com/Tolsee)
|
||||
- FIX (Postgres): count being very slow on large Parse Classes' collections ([#5330](https://github.com/parse-community/parse-server/pull/5330)), thanks to [CoderickLamar](https://github.com/CoderickLamar)
|
||||
- FIX: using per-key basis queue ([#5420](https://github.com/parse-community/parse-server/pull/5420)), thanks to [Georges Jamous](https://github.com/georgesjamous)
|
||||
- FIX: issue on count with Geo constraints and mongo ([#5286](https://github.com/parse-community/parse-server/pull/5286)), thanks to [Julien Quéré](https://github.com/jlnquere)
|
||||
|
||||
### 3.2.3
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.2.2...3.2.3)
|
||||
- Correct previous release with patch that is fully merged
|
||||
|
||||
### 3.2.2
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.2.1...3.2.2)
|
||||
- Security fix to properly process userSensitiveFields when parse-server is started with
|
||||
../lib/cli/parse-server [#5463](https://github.com/parse-community/parse-server/pull/5463
|
||||
)
|
||||
|
||||
### 3.2.1
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.2.0...3.2.1)
|
||||
- Increment package.json version to match the deployment tag
|
||||
|
||||
### 3.2.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.3...3.2.0)
|
||||
- NEW: Support accessing sensitive fields with an explicit ACL. Not documented yet, see [tests](https://github.com/parse-community/parse-server/blob/f2c332ea6a984808ad5b2e3ce34864a20724f72b/spec/UserPII.spec.js#L526) for examples
|
||||
- Upgrade Parse SDK JS to 2.3.1 [#5457](https://github.com/parse-community/parse-server/pull/5457)
|
||||
- Hides token contents in logStartupOptions if they arrive as a buffer [#6a9380](https://github.com/parse-community/parse-server/commit/6a93806c62205a56a8f4e3b8765848c552510337)
|
||||
- Support custom message for password requirements [#5399](https://github.com/parse-community/parse-server/pull/5399)
|
||||
- Support for Ajax password reset [#5332](https://github.com/parse-community/parse-server/pull/5332)
|
||||
- Postgres: Refuse to build unsafe JSON lists for contains [#5337](https://github.com/parse-community/parse-server/pull/5337)
|
||||
- Properly handle return values in beforeSave [#5228](https://github.com/parse-community/parse-server/pull/5228)
|
||||
- Fixes issue when querying user roles [#5276](https://github.com/parse-community/parse-server/pull/5276)
|
||||
- Fixes issue affecting update with CLP [#5269](https://github.com/parse-community/parse-server/pull/5269)
|
||||
|
||||
### 3.1.3
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.2...3.1.3)
|
||||
|
||||
- Postgres: Fixes support for global configuration
|
||||
- Postgres: Fixes support for numeric arrays
|
||||
- Postgres: Fixes issue affecting queries on empty arrays
|
||||
- LiveQuery: Adds support for transmitting the original object
|
||||
- Queries: Use estimated count if query is empty
|
||||
- Docker: Reduces the size of the docker image to 154Mb
|
||||
|
||||
|
||||
### 3.1.2
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.1...3.1.2)
|
||||
|
||||
- Removes dev script, use TDD instead of server.
|
||||
- Removes nodemon and problematic dependencies.
|
||||
- Addressed event-stream security debacle.
|
||||
|
||||
### 3.1.1
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.0...3.1.1)
|
||||
|
||||
#### Improvements:
|
||||
* Fixes issue that would prevent users with large number of roles to resolve all of them [Antoine Cormouls](https://github.com/Moumouls) (#5131, #5132)
|
||||
* Fixes distinct query on special fields ([#5144](https://github.com/parse-community/parse-server/pull/5144))
|
||||
|
||||
|
||||
### 3.1.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.0.0...3.1.0)
|
||||
|
||||
#### Breaking Changes:
|
||||
* Return success on sendPasswordResetEmail even if email not found. (#7fe4030)
|
||||
#### Security Fix:
|
||||
* Expire password reset tokens on email change (#5104)
|
||||
#### Improvements:
|
||||
* Live Query CLPs (#4387)
|
||||
* Reduces number of calls to injectDefaultSchema (#5107)
|
||||
* Remove runtime dependency on request (#5076)
|
||||
#### Bug fixes:
|
||||
* Fixes issue with vkontatke authentication (#4977)
|
||||
* Use the correct function when validating google auth tokens (#5018)
|
||||
* fix unexpected 'delete' trigger issue on LiveQuery (#5031)
|
||||
* Improves performance for roles and ACL's in live query server (#5126)
|
||||
|
||||
|
||||
### 3.0.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.4...3.0.0)
|
||||
|
||||
`parse-server` 3.0.0 comes with brand new handlers for cloud code. It now fully supports promises and async / await.
|
||||
For more informations, visit the v3.0.0 [migration guide](https://github.com/parse-community/parse-server/blob/master/3.0.0.md).
|
||||
|
||||
#### Breaking changes:
|
||||
* Cloud Code handlers have a new interface based on promises.
|
||||
* response.success / response.error are removed in Cloud Code
|
||||
* Cloud Code runs with Parse-SDK 2.0
|
||||
* The aggregate now require aggregates to be passed in the form: `{"pipeline": [...]}` (REST Only)
|
||||
|
||||
#### Improvements:
|
||||
* Adds Pipeline Operator to Aggregate Router.
|
||||
* Adds documentations for parse-server's adapters, constructors and more.
|
||||
* Adds ability to pass a context object between `beforeSave` and `afterSave` affecting the same object.
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes issue that would crash the server when mongo objects had undefined values [#4966](https://github.com/parse-community/parse-server/issues/4966)
|
||||
* Fixes issue that prevented ACL's from being used with `select` (see [#571](https://github.com/parse-community/Parse-SDK-JS/issues/571))
|
||||
|
||||
#### Dependency updates:
|
||||
* [@parse/simple-mailgun-adapter@1.1.0](https://www.npmjs.com/package/@parse/simple-mailgun-adapter)
|
||||
* [mongodb@3.1.3](https://www.npmjs.com/package/mongodb)
|
||||
* [request@2.88.0](https://www.npmjs.com/package/request)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [@parse/minami@1.0.0](https://www.npmjs.com/package/@parse/minami)
|
||||
* [deep-diff@1.0.2](https://www.npmjs.com/package/deep-diff)
|
||||
* [flow-bin@0.79.0](https://www.npmjs.com/package/flow-bin)
|
||||
* [jsdoc@3.5.5](https://www.npmjs.com/package/jsdoc)
|
||||
* [jsdoc-babel@0.4.0](https://www.npmjs.com/package/jsdoc-babel)
|
||||
|
||||
### 2.8.4
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.3...2.8.4)
|
||||
|
||||
#### Improvements:
|
||||
* Adds ability to forward errors to express handler (#4697)
|
||||
* Adds ability to increment the push badge with an arbitrary value (#4889)
|
||||
* Adds ability to preserve the file names when uploading (#4915)
|
||||
* `_User` now follow regular ACL policy. Letting administrator lock user out. (#4860) and (#4898)
|
||||
* Ensure dates are properly handled in aggregates (#4743)
|
||||
* Aggregates: Improved support for stages sharing the same name
|
||||
* Add includeAll option
|
||||
* Added verify password to users router and tests. (#4747)
|
||||
* Ensure read preference is never overriden, so DB config prevails (#4833)
|
||||
* add support for geoWithin.centerSphere queries via withJSON (#4825)
|
||||
* Allow sorting an object field (#4806)
|
||||
* Postgres: Don't merge JSON fields after save() to keep same behaviour as MongoDB (#4808) (#4815)
|
||||
|
||||
#### Dependency updates
|
||||
* [commander@2.16.0](https://www.npmjs.com/package/commander)
|
||||
* [mongodb@3.1.1](https://www.npmjs.com/package/mongodb)
|
||||
* [pg-promise@8.4.5](https://www.npmjs.com/package/pg-promise)
|
||||
* [ws@6.0.0](https://www.npmjs.com/package/ws)
|
||||
* [bcrypt@3.0.0](https://www.npmjs.com/package/bcrypt)
|
||||
* [uws@10.148.1](https://www.npmjs.com/package/uws)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [cross-env@5.2.0](https://www.npmjs.com/package/cross-env)
|
||||
* [eslint@5.0.0](https://www.npmjs.com/package/eslint)
|
||||
* [flow-bin@0.76.0](https://www.npmjs.com/package/flow-bin)
|
||||
* [mongodb-runner@4.0.0](https://www.npmjs.com/package/mongodb-runner)
|
||||
* [nodemon@1.18.1](https://www.npmjs.com/package/nodemon)
|
||||
* [nyc@12.0.2](https://www.npmjs.com/package/nyc)
|
||||
* [request-promise@4.2.2](https://www.npmjs.com/package/request-promise)
|
||||
* [supports-color@5.4.0](https://www.npmjs.com/package/supports-color)
|
||||
|
||||
### 2.8.3
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.2...2.8.3)
|
||||
|
||||
#### Improvements:
|
||||
|
||||
* Adds support for JS SDK 2.0 job status header
|
||||
* Removes npm-git scripts as npm supports using git repositories that build, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
|
||||
### 2.8.2
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.1...2.8.2)
|
||||
|
||||
##### Bug Fixes:
|
||||
* Ensure legacy users without ACL's are not locked out, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Improvements:
|
||||
* Use common HTTP agent to increase webhooks performance, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Adds withinPolygon support for Polygon objects, thanks to [Mads Bjerre](https://github.com/madsb)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [ws@5.2.0](https://www.npmjs.com/package/ws)
|
||||
* [commander@2.15.1](https://www.npmjs.com/package/commander)
|
||||
* [nodemon@1.17.5](https://www.npmjs.com/package/nodemon)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [flow-bin@0.73.0](https://www.npmjs.com/package/flow-bin)
|
||||
* [cross-env@5.1.6](https://www.npmjs.com/package/cross-env)
|
||||
* [gaze@1.1.3](https://www.npmjs.com/package/gaze)
|
||||
* [deepcopy@1.0.0](https://www.npmjs.com/package/deepcopy)
|
||||
* [deep-diff@1.0.1](https://www.npmjs.com/package/deep-diff)
|
||||
|
||||
|
||||
### 2.8.1
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.1...2.8.0)
|
||||
|
||||
Ensure all the files are properly exported to the final package.
|
||||
|
||||
### 2.8.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.0...2.7.4)
|
||||
|
||||
#### New Features
|
||||
* Adding Mongodb element to add `arrayMatches` the #4762 (#4766), thanks to [Jérémy Piednoel](https://github.com/jeremypiednoel)
|
||||
* Adds ability to Lockout users (#4749), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug fixes:
|
||||
* Fixes issue when using afterFind with relations (#4752), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New query condition support to match all strings that starts with some other given strings (#3864), thanks to [Eduard Bosch Bertran](https://github.com/eduardbosch)
|
||||
* Allow creation of indices on default fields (#4738), thanks to [Claire Neveu](https://github.com/ClaireNeveu)
|
||||
* Purging empty class (#4676), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Postgres: Fixes issues comparing to zero or false (#4667), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Fix Aggregate Match Pointer (#4643), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
|
||||
#### Improvements:
|
||||
* Allow Parse.Error when returning from Cloud Code (#4695), thanks to [Saulo Tauil](https://github.com/saulogt)
|
||||
* Fix typo: "requrest" -> "request" (#4761), thanks to [Joseph Frazier](https://github.com/josephfrazier)
|
||||
* Send version for Vkontakte API (#4725), thanks to [oleg](https://github.com/alekoleg)
|
||||
* Ensure we respond with invalid password even if email is unverified (#4708), thanks to [dblythy](https://github.com/dblythy)
|
||||
* Add _password_history to default sensitive data (#4699), thanks to [Jong Eun Lee](https://github.com/yomybaby)
|
||||
* Check for node version in postinstall script (#4657), thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Remove FB Graph API version from URL to use the oldest non deprecated version, thanks to [SebC](https://github.com/SebC99)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [@parse/push-adapter@2.0.3](https://www.npmjs.com/package/@parse/push-adapter)
|
||||
* [@parse/simple-mailgun-adapter@1.0.2](https://www.npmjs.com/package/@parse/simple-mailgun-adapter)
|
||||
* [uws@10.148.0](https://www.npmjs.com/package/uws)
|
||||
* [body-parser@1.18.3](https://www.npmjs.com/package/body-parser)
|
||||
* [mime@2.3.1](https://www.npmjs.com/package/mime)
|
||||
* [request@2.85.0](https://www.npmjs.com/package/request)
|
||||
* [mongodb@3.0.7](https://www.npmjs.com/package/mongodb)
|
||||
* [bcrypt@2.0.1](https://www.npmjs.com/package/bcrypt)
|
||||
* [ws@5.1.1](https://www.npmjs.com/package/ws)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [cross-env@5.1.5](https://www.npmjs.com/package/cross-env)
|
||||
* [flow-bin@0.71.0](https://www.npmjs.com/package/flow-bin)
|
||||
* [deep-diff@1.0.0](https://www.npmjs.com/package/deep-diff)
|
||||
* [nodemon@1.17.3](https://www.npmjs.com/package/nodemon)
|
||||
|
||||
|
||||
### 2.7.4
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.4...2.7.3)
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes an issue affecting polygon queries, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [pg-promise@8.2.1](https://www.npmjs.com/package/pg-promise)
|
||||
|
||||
##### Development Dependencies Updates:
|
||||
* [nodemon@1.17.1](https://www.npmjs.com/package/nodemon)
|
||||
|
||||
### 2.7.3
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.3...2.7.2)
|
||||
@@ -43,7 +355,7 @@
|
||||
|
||||
#### Improvements:
|
||||
* Improved match aggregate
|
||||
* Do not mark the empty pushs as failed
|
||||
* Do not mark the empty push as failed
|
||||
* Support pointer in aggregate query
|
||||
* Introduces flow types for storage
|
||||
* Postgres: Refactoring of Postgres Storage Adapter
|
||||
@@ -1067,7 +1379,7 @@ Other fixes by [Mathias Rangel Wulff](https://github.com/mathiasrw)
|
||||
* Fix: Improve compatability of cloud code beforeSave hook for newly created object
|
||||
* Fix: ACL creation for master key only objects
|
||||
* Fix: Allow uploading files without Content-Type
|
||||
* Fix: Add features to http requrest to match Parse.com
|
||||
* Fix: Add features to http request to match Parse.com
|
||||
* Fix: Bugs in development script when running from locations other than project root
|
||||
* Fix: Can pass query constraints in URL
|
||||
* Fix: Objects with legacy "_tombstone" key now don't cause issues.
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at florent@flovilmart.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at codeofconduct@parseplatform.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
|
||||
+61
-12
@@ -1,33 +1,82 @@
|
||||
### Contributing to Parse Server
|
||||
# Contributing to Parse Server
|
||||
|
||||
#### Pull Requests Welcome!
|
||||
We really want Parse to be yours, to see it grow and thrive in the open source community.
|
||||
|
||||
We really want Parse to be yours, to see it grow and thrive in the open source community.
|
||||
If you are not familiar with Pull Requests and want to know more about them, you can visit the [Creating a pull request](https://help.github.com/articles/creating-a-pull-request/) article. It contains detailed informations about the process.
|
||||
|
||||
##### Please Do's
|
||||
## Setting up the project for debugging and contributing:
|
||||
|
||||
### Recommended setup:
|
||||
|
||||
* [vscode](https://code.visualstudio.com), the popular IDE.
|
||||
* [Jasmine Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-jasmine-test-adapter), a very practical test exploration plugin which let you run, debug and see the test results inline.
|
||||
|
||||
### Setting up you local machine:
|
||||
|
||||
* [Fork](https://github.com/parse-community/parse-server) this project and clone the fork on your local machine:
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/parse-community/parse-server
|
||||
$ cd parse-server # go into the clone directory
|
||||
$ npm install # install all the node dependencies
|
||||
$ code . # launch vscode
|
||||
$ npm run watch # run babel watching for local file changes
|
||||
```
|
||||
|
||||
> To launch VS Code from the terminal with the `code` command you first need to follow the [launching from the command line section](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line) in the VS Code setup documentation.
|
||||
|
||||
Once you have babel running in watch mode, you can start making changes to parse-server.
|
||||
|
||||
### Good to know:
|
||||
|
||||
* The lib/ folder is not commited, so never make changes in there.
|
||||
* Always make changes to files in the `src/` folder.
|
||||
* All the tests should point to sources in the `lib/` folder.
|
||||
|
||||
### Troubleshooting:
|
||||
|
||||
*Question*: I modify the code in the src folder but it doesn't seem to have any effect.<br/>
|
||||
*Answer*: Check that `npm run watch` is running
|
||||
|
||||
*Question*: How do I use breakpoints and debug step by step?<br/>
|
||||
*Answer*: The easiest way is to install [Jasmine Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer), it will let you run selectively tests and debug them.
|
||||
|
||||
*Question*: How do I deploy my forked version on my servers?<br/>
|
||||
*Answer*: In your `package.json`, update the `parse-server` dependency to `https://github.com/MY_USERNAME/parse-server#MY_FEATURE`. Run `npm install`, commit the changes and deploy to your servers.
|
||||
|
||||
|
||||
### Please Do's
|
||||
|
||||
* Begin by reading the [Development Guide](http://docs.parseplatform.org/parse-server/guide/#development-guide) to learn how to get started running the parse-server.
|
||||
* Take testing seriously! Aim to increase the test coverage with every pull request. To obtain the test coverage of the project, run:
|
||||
* **Windows**: `npm run coverage:win`
|
||||
* **Unix**: `npm run coverage`
|
||||
* Run the tests for the file you are working on with the following command:
|
||||
* **Windows**: `npm run test:win spec/MyFile.spec.js`
|
||||
* **Unix**: `npm test spec/MyFile.spec.js`
|
||||
* Take testing seriously! Aim to increase the test coverage with every pull request. To obtain the test coverage of the project, run: `npm run coverage`
|
||||
* Run the tests for the file you are working on with the following command: `npm test spec/MyFile.spec.js`
|
||||
* Run the tests for the whole project to make sure the code passes all tests. This can be done by running the test command for a single file but removing the test file argument. The results can be seen at *<PROJECT_ROOT>/coverage/lcov-report/index.html*.
|
||||
* Lint your code by running `npm run lint` to make sure the code is not going to be rejected by the CI.
|
||||
* **Do not** publish the *lib* folder.
|
||||
|
||||
##### Run your tests against Postgres (optional)
|
||||
### Run your tests against Postgres (optional)
|
||||
|
||||
If your pull request introduces a change that may affect the storage or retrieval of objects, you may want to make sure it plays nice with Postgres.
|
||||
|
||||
* Run the tests against the postgres database with `PARSE_SERVER_TEST_DB=postgres npm test`. You'll need to have postgres running on your machine and setup [appropriately](https://github.com/parse-community/parse-server/blob/master/.travis.yml#L37)
|
||||
* The Postgres adapter has a special debugger that traces all the sql commands. You can enable it with setting the environment variable `PARSE_SERVER_LOG_LEVEL=debug`
|
||||
* If your feature is intended to only work with MongoDB, you should disable PostgreSQL-specific tests with:
|
||||
|
||||
- `describe_only_db('mongo')` // will create a `describe` that runs only on mongoDB
|
||||
- `it_only_db('mongo')` // will make a test that only runs on mongo
|
||||
- `it_exclude_dbs(['postgres'])` // will make a test that runs against all DB's but postgres
|
||||
|
||||
##### Code of Conduct
|
||||
### Generate Parse Server Config Definition
|
||||
|
||||
If you want to make changes to [Parse Server Configuration][config] add the desired configuration to [src/Options/index.js][config-index] and run `npm run definitions`. This will output [src/Options/Definitions.js][config-def] and [src/Options/docs.js][config-docs].
|
||||
|
||||
To view docs run `npm run docs` and check the `/out` directory.
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project adheres to the [Contributor Covenant Code of Conduct](https://github.com/parse-community/parse-server/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to honor this code.
|
||||
|
||||
[config]: http://parseplatform.org/parse-server/api/master/ParseServerOptions.html
|
||||
[config-def]: https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js
|
||||
[config-docs]: https://github.com/parse-community/parse-server/blob/master/src/Options/docs.js
|
||||
[config-index]: https://github.com/parse-community/parse-server/blob/master/src/Options/index.js
|
||||
|
||||
+26
-11
@@ -1,21 +1,36 @@
|
||||
FROM node:boron
|
||||
# Build stage
|
||||
FROM node:lts-alpine as build
|
||||
|
||||
RUN mkdir -p /parse-server
|
||||
COPY ./ /parse-server/
|
||||
RUN apk update; \
|
||||
apk add git;
|
||||
WORKDIR /tmp
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
RUN mkdir -p /parse-server/config
|
||||
VOLUME /parse-server/config
|
||||
# Release stage
|
||||
FROM node:lts-alpine as release
|
||||
|
||||
RUN mkdir -p /parse-server/cloud
|
||||
VOLUME /parse-server/cloud
|
||||
RUN apk update; \
|
||||
apk add git;
|
||||
|
||||
VOLUME /parse-server/cloud /parse-server/config
|
||||
|
||||
WORKDIR /parse-server
|
||||
|
||||
RUN npm install && \
|
||||
npm run build
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm ci --production --ignore-scripts
|
||||
|
||||
COPY bin bin
|
||||
COPY public_html public_html
|
||||
COPY views views
|
||||
COPY --from=build /tmp/lib lib
|
||||
RUN mkdir -p logs && chown -R node: logs
|
||||
|
||||
ENV PORT=1337
|
||||
|
||||
USER node
|
||||
EXPOSE $PORT
|
||||
|
||||
ENTRYPOINT ["npm", "start", "--"]
|
||||
ENTRYPOINT ["node", "./bin/parse-server"]
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||

|
||||

|
||||
|
||||
[](#backers) [](#sponsors)
|
||||
[](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)
|
||||
[](https://community.parseplatform.org/c/parse-server)
|
||||
[](https://greenkeeper.io/)
|
||||
[](https://twitter.com/intent/follow?screen_name=ParsePlatform)
|
||||
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
Parse Server is an [open source version of the Parse backend](http://blog.parseplatform.org/announcements/introducing-parse-server-and-the-database-migration-tool/) that can be deployed to any infrastructure that can run Node.js.
|
||||
|
||||
Parse Server works with the Express web application framework. It can be added to existing web applications, or run by itself.
|
||||
|
||||
The full documentation for Parse Server is available in the [wiki](https://github.com/parse-community/parse-server/wiki). The [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/) is a good place to get started. An [API reference](http://parseplatform.org/parse-server/api/) is also available. If you're interested in developing for Parse Server, the [Development guide](http://docs.parseplatform.org/parse-server/guide/#development-guide) will help you get set up.
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Running Parse Server](#running-parse-server)
|
||||
- [Locally](#locally)
|
||||
@@ -20,27 +28,28 @@ Parse Server works with the Express web application framework. It can be added t
|
||||
- [Running elsewhere](#running-parse-server-elsewhere)
|
||||
- [Sample Application](#parse-server-sample-application)
|
||||
- [Parse Server + Express](#parse-server--express)
|
||||
- [Logging](#logging)
|
||||
- [Documentation](#documentation)
|
||||
- [Configuration](#configuration)
|
||||
- [Basic Options](#basic-options)
|
||||
- [Client Key Options](#client-key-options)
|
||||
- [Advanced Options](#advanced-options)
|
||||
- [Logging](#logging-1)
|
||||
- [Email Verification & Password Reset](#email-verification-and-password-reset)
|
||||
- [Email Verification & Password Reset](#email-verification-and-password-reset)
|
||||
- [Custom Pages](#custom-pages)
|
||||
- [Using Environment Variables](#using-environment-variables-to-configure-parse-server)
|
||||
- [Available Adapters](#available-adapters)
|
||||
- [Configuring File Adapters](#configuring-file-adapters)
|
||||
- [Logging](#logging)
|
||||
- [Live Queries](#live-queries)
|
||||
- [GraphQL](#graphql)
|
||||
- [Upgrading to 3.0.0](#upgrading-to-300)
|
||||
- [Support](#support)
|
||||
- [Ride the Bleeding Edge](#want-to-ride-the-bleeding-edge)
|
||||
- [Contributing](#contributing)
|
||||
- [Backers](#backers)
|
||||
- [Contributors](#contributors)
|
||||
- [Sponsors](#sponsors)
|
||||
- [Backers](#backers)
|
||||
|
||||
|
||||
# Getting Started
|
||||
|
||||
April 2016 - We created a series of video screencasts, please check them out here: [http://blog.parseplatform.org/learn/parse-server-video-series-april-2016/](http://blog.parseplatform.org/learn/parse-server-video-series-april-2016/)
|
||||
|
||||
The fastest and easiest way to get started is to run MongoDB and Parse Server locally.
|
||||
|
||||
## Running Parse Server
|
||||
@@ -52,14 +61,16 @@ $ mongodb-runner start
|
||||
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test
|
||||
```
|
||||
***Note:*** *If installation with* `-g` *fails due to permission problems* (`npm ERR! code 'EACCES'`), *please refer to [this link](https://docs.npmjs.com/getting-started/fixing-npm-permissions).*
|
||||
|
||||
|
||||
|
||||
### Inside a Docker container
|
||||
```
|
||||
$ git clone https://github.com/parse-community/parse-server
|
||||
$ cd parse-server
|
||||
$ docker build --tag parse-server .
|
||||
$ docker run --name my-mongo -d mongo
|
||||
$ docker run --name my-parse-server --link my-mongo:mongo -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test
|
||||
```
|
||||
```
|
||||
|
||||
You can use any arbitrary string as your application id and master key. These will be used by your clients to authenticate with the Parse Server.
|
||||
|
||||
@@ -135,7 +146,7 @@ To learn more about using saving and querying objects on Parse Server, check out
|
||||
|
||||
### Connect your app to Parse Server
|
||||
|
||||
Parse provides SDKs for all the major platforms. Refer to the Parse Server guide to [learn how to connect your app to Parse Server](https://github.com/parse-community/parse-server/wiki/Parse-Server-Guide#using-parse-sdks-with-parse-server).
|
||||
Parse provides SDKs for all the major platforms. Refer to the Parse Server guide to [learn how to connect your app to Parse Server](https://docs.parseplatform.org/parse-server/guide/#using-parse-sdks-with-parse-server).
|
||||
|
||||
## Running Parse Server elsewhere
|
||||
|
||||
@@ -153,7 +164,7 @@ We have provided a basic [Node.js application](https://github.com/parse-communit
|
||||
* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-run-parse-server-on-ubuntu-14-04)
|
||||
* [Pivotal Web Services](https://github.com/cf-platform-eng/pws-parse-server)
|
||||
* [Back4app](http://blog.back4app.com/2016/03/01/quick-wizard-migration/)
|
||||
* [Gomix](https://gomix.com/#!/project/parse-server)
|
||||
* [Glitch](https://glitch.com/edit/#!/parse-server)
|
||||
* [Flynn](https://flynn.io/blog/parse-apps-on-flynn)
|
||||
|
||||
### Parse Server + Express
|
||||
@@ -182,35 +193,15 @@ app.listen(1337, function() {
|
||||
});
|
||||
```
|
||||
|
||||
For a full list of available options, run `parse-server --help`.
|
||||
|
||||
## Logging
|
||||
|
||||
Parse Server will, by default, log:
|
||||
* to the console
|
||||
* daily rotating files as new line delimited JSON
|
||||
|
||||
Logs are also be viewable in Parse Dashboard.
|
||||
|
||||
**Want to log each request and response?** Set the `VERBOSE` environment variable when starting `parse-server`. Usage :- `VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
|
||||
|
||||
**Want logs to be in placed in other folder?** Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. Usage :- `PARSE_SERVER_LOGS_FOLDER='<path-to-logs-folder>' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
|
||||
|
||||
**Want to log specific levels?** Pass the `logLevel` parameter when starting `parse-server`. Usage :- `parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --logLevel LOG_LEVEL`
|
||||
|
||||
**Want new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc.)?** Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
|
||||
|
||||
# Documentation
|
||||
|
||||
The full documentation for Parse Server is available in the [wiki](https://github.com/parse-community/parse-server/wiki). The [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/) is a good place to get started. If you're interested in developing for Parse Server, the [Development guide](http://docs.parseplatform.org/parse-server/guide/#development-guide) will help you get set up.
|
||||
For a full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html).
|
||||
|
||||
## Configuration
|
||||
|
||||
Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options.
|
||||
|
||||
For the full list of available options, run `parse-server --help`.
|
||||
For the full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html).
|
||||
|
||||
#### Basic options
|
||||
### Basic options
|
||||
|
||||
* `appId` **(required)** - The application id to host with this server instance. You can use any arbitrary string. For migrated apps, this should match your hosted Parse app.
|
||||
* `masterKey` **(required)** - The master key to use for overriding ACL security. You can use any arbitrary string. Keep it secret! For migrated apps, this should match your hosted Parse app.
|
||||
@@ -220,7 +211,7 @@ For the full list of available options, run `parse-server --help`.
|
||||
* `cloud` - The absolute path to your cloud code `main.js` file.
|
||||
* `push` - Configuration options for APNS and GCM push. See the [Push Notifications quick start](http://docs.parseplatform.org/parse-server/guide/#push-notifications_push-notifications-quick-start).
|
||||
|
||||
#### Client key options
|
||||
### Client key options
|
||||
|
||||
The client keys used with Parse are no longer necessary with Parse Server. If you wish to still require them, perhaps to be able to refuse access to older clients, you can set the keys at initialization time. Setting any of these keys will require all requests to provide one of the configured keys.
|
||||
|
||||
@@ -229,40 +220,7 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
|
||||
* `restAPIKey`
|
||||
* `dotNetKey`
|
||||
|
||||
#### Advanced options
|
||||
|
||||
* `fileKey` - For migrated apps, this is necessary to provide access to files already hosted on Parse.
|
||||
* `allowClientClassCreation` - Set to false to disable client class creation. Defaults to true.
|
||||
* `enableAnonymousUsers` - Set to false to disable anonymous users. Defaults to true.
|
||||
* `auth` - Used to configure support for [3rd party authentication](http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication).
|
||||
* `facebookAppIds` - An array of valid Facebook application IDs that users may authenticate with.
|
||||
* `mountPath` - Mount path for the server. Defaults to `/parse`.
|
||||
* `filesAdapter` - The default behavior (GridStore) can be changed by creating an adapter class (see [`FilesAdapter.js`](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js)).
|
||||
* `maxUploadSize` - Max file size for uploads. Defaults to 20 MB.
|
||||
* `loggerAdapter` - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)).
|
||||
* `logLevel` - Set the specific level you want to log. Defaults to `info`. The default logger uses the npm log levels as defined by the underlying winston logger. Check [Winston logging levels](https://github.com/winstonjs/winston#logging-levels) for details on values to specify.
|
||||
* `sessionLength` - The length of time in seconds that a session should be valid for. Defaults to 31536000 seconds (1 year).
|
||||
* `maxLimit` - The maximum value supported for the limit option on queries. Defaults to unlimited.
|
||||
* `revokeSessionOnPasswordReset` - When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.
|
||||
* `accountLockout` - Lock account when a malicious user is attempting to determine an account password by trial and error.
|
||||
* `passwordPolicy` - Optional password policy rules to enforce.
|
||||
* `customPages` - A hash with urls to override email verification links, password reset links and specify frame url for masking user-facing pages. Available keys: `parseFrameURL`, `invalidLink`, `choosePassword`, `passwordResetSuccess`, `verifyEmailSuccess`.
|
||||
* `middleware` - (CLI only), a module name, function that is an express middleware. When using the CLI, the express app will load it just **before** mounting parse-server on the mount path. This option is useful for injecting a monitoring middleware.
|
||||
* `masterKeyIps` - The array of ip addresses where masterKey usage will be restricted to only these ips. (Default to [] which means allow all ips). If you're using this feature and have `useMasterKey: true` in cloudcode, make sure that you put your own ip in this list.
|
||||
* `readOnlyMasterKey` - A masterKey that has full read access to the data, but no write access. This key should be treated the same way as your masterKey, keeping it private.
|
||||
* `objectIdSize` - The string length of the newly generated object's ids.
|
||||
|
||||
##### Logging
|
||||
|
||||
Use the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server` to save your server logfiles to the specified folder.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
PARSE_SERVER_LOGS_FOLDER='<path-to-logs-folder>' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY
|
||||
```
|
||||
|
||||
##### Email verification and password reset
|
||||
### Email verification and password reset
|
||||
|
||||
Verifying user email addresses and enabling password reset via email requires an email adapter. As part of the `parse-server` package we provide an adapter for sending email through Mailgun. To use it, sign up for Mailgun, and add this to your initialization code:
|
||||
|
||||
@@ -314,14 +272,15 @@ var server = ParseServer({
|
||||
},
|
||||
// optional settings to enforce password policies
|
||||
passwordPolicy: {
|
||||
// Two optional settings to enforce strong passwords. Either one or both can be specified.
|
||||
// Two optional settings to enforce strong passwords. Either one or both can be specified.
|
||||
// If both are specified, both checks must pass to accept the password
|
||||
// 1. a RegExp object or a regex string representing the pattern to enforce
|
||||
// 1. a RegExp object or a regex string representing the pattern to enforce
|
||||
validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit
|
||||
// 2. a callback function to be invoked to validate the password
|
||||
validatorCallback: (password) => { return validatePassword(password) },
|
||||
// 2. a callback function to be invoked to validate the password
|
||||
validatorCallback: (password) => { return validatePassword(password) },
|
||||
validationError: 'Password must contain at least 1 digit.' // optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message.
|
||||
doNotAllowUsername: true, // optional setting to disallow username in passwords
|
||||
maxPasswordAge: 90, // optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset.
|
||||
maxPasswordAge: 90, // optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset.
|
||||
maxPasswordHistory: 5, // optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history.
|
||||
//optional setting to set a validity duration for password reset links (in seconds)
|
||||
resetTokenValidityDuration: 24*60*60, // expire after 24 hours
|
||||
@@ -330,6 +289,7 @@ var server = ParseServer({
|
||||
```
|
||||
|
||||
You can also use other email adapters contributed by the community such as:
|
||||
- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template)
|
||||
- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter)
|
||||
- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter)
|
||||
- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter)
|
||||
@@ -340,6 +300,27 @@ You can also use other email adapters contributed by the community such as:
|
||||
- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter)
|
||||
- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter)
|
||||
|
||||
### Custom Pages
|
||||
|
||||
It’s possible to change the default pages of the app and redirect the user to another path or domain.
|
||||
|
||||
```js
|
||||
var server = ParseServer({
|
||||
...otherOptions,
|
||||
|
||||
customPages {
|
||||
passwordResetSuccess: "http://yourapp.com/passwordResetSuccess",
|
||||
verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess",
|
||||
parseFrameURL: "http://yourapp.com/parseFrameURL",
|
||||
linkSendSuccess: "http://yourapp.com/linkSendSuccess",
|
||||
linkSendFail: "http://yourapp.com/linkSendFail",
|
||||
invalidLink: "http://yourapp.com/invalidLink",
|
||||
invalidVerificationLink: "http://yourapp.com/invalidVerificationLink",
|
||||
choosePassword: "http://yourapp.com/choosePassword"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Using environment variables to configure Parse Server
|
||||
|
||||
You may configure the Parse Server using environment variables:
|
||||
@@ -359,7 +340,7 @@ The default port is 1337, to use a different port set the PORT environment varia
|
||||
$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY
|
||||
```
|
||||
|
||||
For the full list of configurable environment variables, run `parse-server --help`.
|
||||
For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js).
|
||||
|
||||
### Available Adapters
|
||||
|
||||
@@ -373,15 +354,295 @@ You can also find more adapters maintained by the community by searching on [npm
|
||||
|
||||
Parse Server allows developers to choose from several options when hosting files:
|
||||
|
||||
* `GridStoreAdapter`, which is backed by MongoDB;
|
||||
* `GridFSBucketAdapter`, which is backed by MongoDB;
|
||||
* `S3Adapter`, which is backed by [Amazon S3](https://aws.amazon.com/s3/); or
|
||||
* `GCSAdapter`, which is backed by [Google Cloud Storage](https://cloud.google.com/storage/)
|
||||
|
||||
`GridStoreAdapter` is used by default and requires no setup, but if you're interested in using S3 or Google Cloud Storage, additional configuration information is available in the [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/#configuring-file-adapters).
|
||||
`GridFSBucketAdapter` is used by default and requires no setup, but if you're interested in using S3 or Google Cloud Storage, additional configuration information is available in the [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/#configuring-file-adapters).
|
||||
|
||||
### Logging
|
||||
|
||||
Parse Server will, by default, log:
|
||||
* to the console
|
||||
* daily rotating files as new line delimited JSON
|
||||
|
||||
Logs are also viewable in Parse Dashboard.
|
||||
|
||||
**Want to log each request and response?** Set the `VERBOSE` environment variable when starting `parse-server`. Usage :- `VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
|
||||
|
||||
**Want logs to be in placed in a different folder?** Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. Usage :- `PARSE_SERVER_LOGS_FOLDER='<path-to-logs-folder>' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
|
||||
|
||||
**Want to log specific levels?** Pass the `logLevel` parameter when starting `parse-server`. Usage :- `parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --logLevel LOG_LEVEL`
|
||||
|
||||
**Want new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc)?** Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
|
||||
|
||||
# Live Queries
|
||||
|
||||
Live queries are meant to be used in real-time reactive applications, where just using the traditional query paradigm could cause several problems, like increased response time and high network and server usage. Live queries should be used in cases where you need to continuously update a page with fresh data coming from the database, which often happens in (but is not limited to) online games, messaging clients and shared to-do lists.
|
||||
|
||||
Take a look at [Live Query Guide](https://docs.parseplatform.org/parse-server/guide/#live-queries), [Live Query Server Setup Guide](https://docs.parseplatform.org/parse-server/guide/#scalability) and [Live Query Protocol Specification](https://github.com/parse-community/parse-server/wiki/Parse-LiveQuery-Protocol-Specification). You can setup a standalone server or multiple instances for scalability (recommended).
|
||||
|
||||
# GraphQL
|
||||
|
||||
[GraphQL](https://graphql.org/), developed by Facebook, is an open-source data query and manipulation language for APIs. In addition to the traditional REST API, Parse Server automatically generates a GraphQL API based on your current application schema. Parse Server also allows you to define your custom GraphQL queries and mutations, whose resolvers can be bound to your cloud code functions.
|
||||
|
||||
## Running
|
||||
|
||||
### Using the CLI
|
||||
|
||||
The easiest way to run the Parse GraphQL API is through the CLI:
|
||||
|
||||
```
|
||||
$ npm install -g parse-server mongodb-runner
|
||||
$ mongodb-runner start
|
||||
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --mountGraphQL --mountPlayground
|
||||
```
|
||||
|
||||
After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.
|
||||
|
||||
***Note:*** Do ***NOT*** use --mountPlayground option in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps.
|
||||
|
||||
### Using Docker
|
||||
|
||||
You can also run the Parse GraphQL API inside a Docker container:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/parse-community/parse-server
|
||||
$ cd parse-server
|
||||
$ docker build --tag parse-server .
|
||||
$ docker run --name my-mongo -d mongo
|
||||
$ docker run --name my-parse-server --link my-mongo:mongo -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test --mountGraphQL --mountPlayground
|
||||
```
|
||||
|
||||
After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.
|
||||
|
||||
***Note:*** Do ***NOT*** use --mountPlayground option in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps.
|
||||
|
||||
### Using Express.js
|
||||
|
||||
You can also mount the GraphQL API in an Express.js application together with the REST API or solo:
|
||||
|
||||
```
|
||||
const express = require('express');
|
||||
const { default: ParseServer, ParseGraphQLServer } = require('parse-server');
|
||||
|
||||
const app = express();
|
||||
|
||||
const parseServer = new ParseServer({
|
||||
databaseURI: 'mongodb://localhost:27017/test',
|
||||
appId: 'APPLICATION_ID',
|
||||
masterKey: 'MASTER_KEY',
|
||||
serverURL: 'http://localhost:1337/parse'
|
||||
});
|
||||
|
||||
const parseGraphQLServer = new ParseGraphQLServer(
|
||||
parseServer,
|
||||
{
|
||||
graphQLPath: '/graphql',
|
||||
playgroundPath: '/playground'
|
||||
}
|
||||
);
|
||||
|
||||
app.use('/parse', parseServer.app); // (Optional) Mounts the REST API
|
||||
parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API
|
||||
parseGraphQLServer.applyPlayground(app); // (Optional) Mounts the GraphQL Playground - do NOT use in Production
|
||||
|
||||
app.listen(1337, function() {
|
||||
console.log('REST API running on http://localhost:1337/parse');
|
||||
console.log('GraphQL API running on http://localhost:1337/graphql');
|
||||
console.log('GraphQL Playground running on http://localhost:1337/playground');
|
||||
});
|
||||
```
|
||||
|
||||
After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.
|
||||
|
||||
***Note:*** Do ***NOT*** mount the GraphQL Playground in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps.
|
||||
|
||||
## Checking the API health
|
||||
|
||||
Run the following:
|
||||
|
||||
```graphql
|
||||
query Health {
|
||||
health
|
||||
}
|
||||
```
|
||||
|
||||
You should receive the following response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"health": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Creating your first object
|
||||
|
||||
Since your application does not have a schema yet, you can use the generic `create` mutation to create your first object. Run the following:
|
||||
|
||||
```graphql
|
||||
mutation CreateObject {
|
||||
objects {
|
||||
create(className: "GameScore" fields: { score: 1337 playerName: "Sean Plott" cheatMode: false }) {
|
||||
objectId
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You should receive a response similar to this:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"objects": {
|
||||
"create": {
|
||||
"objectId": "7jfBmbGgyF",
|
||||
"createdAt": "2019-06-20T23:50:50.825Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using automatically generated operations
|
||||
|
||||
Parse Server learned from the first object that you created and now you have the `GameScore` class in your schema. You can now start using the automatically generated operations!
|
||||
|
||||
Run the following to create a second object:
|
||||
|
||||
```graphql
|
||||
mutation CreateGameScore {
|
||||
objects {
|
||||
createGameScore(fields: { score: 2558 playerName: "Luke Skywalker" cheatMode: false }) {
|
||||
objectId
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You should receive a response similar to this:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"objects": {
|
||||
"createGameScore": {
|
||||
"objectId": "gySYolb2CL",
|
||||
"createdAt": "2019-06-20T23:56:37.114Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also run a query to this new class:
|
||||
|
||||
```graphql
|
||||
query FindGameScore {
|
||||
objects {
|
||||
findGameScore {
|
||||
results {
|
||||
playerName
|
||||
score
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You should receive a response similar to this:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"objects": {
|
||||
"findGameScore": {
|
||||
"results": [
|
||||
{
|
||||
"playerName": "Sean Plott",
|
||||
"score": 1337
|
||||
},
|
||||
{
|
||||
"playerName": "Luke Skywalker",
|
||||
"score": 2558
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Customizing your GraphQL Schema
|
||||
|
||||
Parse GraphQL Server allows you to create a custom GraphQL schema with own queries and mutations to be merged with the auto-generated ones. You can resolve these operations using your regular cloud code functions.
|
||||
|
||||
To start creating your custom schema, you need to code a `schema.graphql` file and initialize Parse Server with `--graphQLSchema` and `--cloud` options:
|
||||
|
||||
```bash
|
||||
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --mountGraphQL --mountPlayground --graphQLSchema ./schema.graphql --cloud ./main.js
|
||||
```
|
||||
|
||||
### Creating your first custom query
|
||||
|
||||
Use the code below for your `schema.graphql` and `main.js` files. Then restart your Parse Server.
|
||||
|
||||
```graphql
|
||||
# schema.graphql
|
||||
extend type Query {
|
||||
hello: String! @resolve
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// main.js
|
||||
Parse.Cloud.define('hello', async () => {
|
||||
return 'Hello world!';
|
||||
});
|
||||
```
|
||||
|
||||
You can now run your custom query using GraphQL Playground:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
hello
|
||||
}
|
||||
```
|
||||
|
||||
You should receive the response below:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"hello": "Hello world!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Learning more
|
||||
|
||||
The [Parse GraphQL Guide](http://docs.parseplatform.org/graphql/guide/) is a very good source for learning how to use the Parse GraphQL API.
|
||||
|
||||
You also have a very powerful tool inside your GraphQL Playground. Please look at the right side of your GraphQL Playground. You will see the `DOCS` and `SCHEMA` menus. They are automatically generated by analyzing your application schema. Please refer to them and learn more about everything that you can do with your Parse GraphQL API.
|
||||
|
||||
Additionally, the [GraphQL Learn Section](https://graphql.org/learn/) is a very good source to learn more about the power of the GraphQL language.
|
||||
|
||||
# Upgrading to 3.0.0
|
||||
|
||||
Starting 3.0.0, parse-server uses the JS SDK version 2.0.
|
||||
In short, parse SDK v2.0 removes the backbone style callbacks as well as the Parse.Promise object in favor of native promises.
|
||||
All the Cloud Code interfaces also have been updated to reflect those changes, and all backbone style response objects are removed and replaced by Promise style resolution.
|
||||
|
||||
We have written up a [migration guide](3.0.0.md), hoping this will help you transition to the next major release.
|
||||
|
||||
# Support
|
||||
|
||||
For implementation related questions or technical support, please refer to the [Stack Overflow](http://stackoverflow.com/questions/tagged/parse.com) and [Server Fault](https://serverfault.com/tags/parse) communities.
|
||||
Please take a look at our [support document](https://github.com/parse-community/.github/blob/master/SUPPORT.md).
|
||||
|
||||
If you believe you've found an issue with Parse Server, make sure these boxes are checked before [reporting an issue](https://github.com/parse-community/parse-server/issues):
|
||||
|
||||
@@ -393,64 +654,37 @@ If you believe you've found an issue with Parse Server, make sure these boxes ar
|
||||
|
||||
# Want to ride the bleeding edge?
|
||||
|
||||
The `latest` branch in this repository is automatically maintained to be the last
|
||||
commit to `master` to pass all tests, in the same form found on npm. It is
|
||||
recommend to use builds deployed npm for many reasons, but if you want to use
|
||||
It is recommend to use builds deployed npm for many reasons, but if you want to use
|
||||
the latest not-yet-released version of parse-server, you can do so by depending
|
||||
directly on this branch:
|
||||
|
||||
```
|
||||
npm install parseplatform/parse-server.git#latest
|
||||
npm install parse-community/parse-server.git#master
|
||||
```
|
||||
|
||||
## Experimenting
|
||||
|
||||
You can also use your own forks, and work in progress branches by specifying them:
|
||||
|
||||
```
|
||||
npm install github:myUsername/parse-server#my-awesome-feature
|
||||
```
|
||||
|
||||
And don't forget, if you plan to deploy it remotely, you should run `npm install` with the `--save` option.
|
||||
|
||||
# Contributing
|
||||
|
||||
We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server guide](CONTRIBUTING.md).
|
||||
|
||||
-----
|
||||
# Contributors
|
||||
|
||||
As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.
|
||||
|
||||
|
||||
# Backers
|
||||
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/parse-server#backer)]
|
||||
|
||||
<a href="https://opencollective.com/parse-server/backer/0/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/1/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/2/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/3/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/4/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/5/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/6/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/7/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/8/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/9/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/10/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/11/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/12/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/13/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/14/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/15/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/16/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/17/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/18/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/19/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/20/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/21/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/22/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/23/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/24/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/25/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/26/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/27/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/28/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/29/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/29/avatar.svg"></a>
|
||||
This project exists thanks to all the people who contribute... we'd love to see your face on this list!
|
||||
|
||||
<a href="../../graphs/contributors"><img src="https://opencollective.com/parse-server/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
# Sponsors
|
||||
|
||||
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/parse-server#sponsor)]
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor!](https://opencollective.com/parse-server#sponsor)
|
||||
|
||||
<a href="https://opencollective.com/parse-server/sponsor/0/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/1/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/1/avatar.svg"></a>
|
||||
@@ -483,3 +717,12 @@ Become a sponsor and get your logo on our README on Github with a link to your s
|
||||
<a href="https://opencollective.com/parse-server/sponsor/28/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/29/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/29/avatar.svg"></a>
|
||||
|
||||
# Backers
|
||||
|
||||
Support us with a monthly donation and help us continue our activities. [Become a backer!](https://opencollective.com/parse-server#backer)
|
||||
|
||||
<a href="https://opencollective.com/parse-server#backers" target="_blank"><img src="https://opencollective.com/parse-server/backers.svg?width=890" /></a>
|
||||
|
||||
-----
|
||||
|
||||
As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
If you believe you have found a security vulnerability on one of parse-community maintained packages,
|
||||
we encourage you to let us know right away.
|
||||
We will investigate all legitimate reports and do our best to quickly fix the problem.
|
||||
Before reporting though, please review this page including and those things that should not be reported.
|
||||
Before making a report, please review this page to understand our disclosure policy and how to communicate with us.
|
||||
|
||||
# Responsible Disclosure Policy
|
||||
If you comply with the policies below when reporting a security issue to parse community,
|
||||
@@ -16,7 +16,7 @@ We ask that:
|
||||
|
||||
# Communicating with us
|
||||
|
||||
All vulnerability should be privately reported to either [Node Security](https://nodesecurity.io/report) or directly to us at the following address [security at parseplatform dot org](mailto:security@parseplatform.org)
|
||||
All vulnerabilities should be privately reported to either [Node Security](https://nodesecurity.io/report) or directly to us at the following address [security at parseplatform dot org](mailto:security@parseplatform.org)
|
||||
|
||||
You can use our PGP public key, which is also uploaded [here](hkp://pgp.mit.edu):
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var nodemon = require('nodemon');
|
||||
var babel = require("babel-core");
|
||||
var gaze = require('gaze');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// Watch the src and transpile when changed
|
||||
gaze('src/**/*', function(err, watcher) {
|
||||
if (err) throw err;
|
||||
watcher.on('changed', function(sourceFile) {
|
||||
console.log(sourceFile + " has changed");
|
||||
try {
|
||||
targetFile = path.relative(__dirname, sourceFile).replace(/\/src\//, '/lib/');
|
||||
targetFile = path.resolve(__dirname, targetFile);
|
||||
fs.writeFile(targetFile, babel.transformFileSync(sourceFile).code);
|
||||
} catch (e) {
|
||||
console.error(e.message, e.stack);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
// Run and watch dist
|
||||
nodemon({
|
||||
script: 'bin/parse-server',
|
||||
ext: 'js json',
|
||||
watch: 'lib'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e.message, e.stack);
|
||||
}
|
||||
|
||||
process.once('SIGINT', function() {
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"plugins": ["node_modules/jsdoc-babel", "plugins/markdown"],
|
||||
"babel": {
|
||||
"plugins": ["@babel/plugin-transform-flow-strip-types"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["./README.md", "./src/cloud-code", "./src/Options/docs.js", "./src/ParseServer.js", "./src/Adapters"],
|
||||
"excludePattern": "(^|\\/|\\\\)_"
|
||||
},
|
||||
"templates": {
|
||||
"default": {
|
||||
"outputSourceFiles": false,
|
||||
"showInheritedInNav": false,
|
||||
"useLongnameInNav": true
|
||||
},
|
||||
"cleverLinks": true,
|
||||
"monospaceLinks": false
|
||||
},
|
||||
"opts": {
|
||||
"template": "node_modules/@parse/minami",
|
||||
"recurse": true
|
||||
}
|
||||
}
|
||||
Generated
+11416
File diff suppressed because it is too large
Load Diff
+84
-47
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.7.3",
|
||||
"version": "3.7.0",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ParsePlatform/parse-server"
|
||||
"url": "https://github.com/parse-community/parse-server"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
@@ -14,80 +14,117 @@
|
||||
"views/",
|
||||
"LICENSE",
|
||||
"PATENTS",
|
||||
"postinstall.js",
|
||||
"README.md"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@apollographql/graphql-playground-html": "1.6.24",
|
||||
"@parse/fs-files-adapter": "1.0.1",
|
||||
"@parse/push-adapter": "2.0.2",
|
||||
"@parse/s3-files-adapter": "1.2.1",
|
||||
"@parse/simple-mailgun-adapter": "1.0.1",
|
||||
"@parse/push-adapter": "3.0.8",
|
||||
"@parse/s3-files-adapter": "1.2.3",
|
||||
"@parse/simple-mailgun-adapter": "1.1.0",
|
||||
"apollo-server-express": "2.7.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.18.2",
|
||||
"commander": "2.15.0",
|
||||
"deepcopy": "0.6.3",
|
||||
"express": "4.16.2",
|
||||
"body-parser": "1.19.0",
|
||||
"commander": "2.20.0",
|
||||
"cors": "2.8.5",
|
||||
"deepcopy": "2.0.0",
|
||||
"express": "4.17.1",
|
||||
"follow-redirects": "1.7.0",
|
||||
"graphql": "14.4.2",
|
||||
"graphql-list-fields": "2.0.2",
|
||||
"graphql-tools": "^4.0.5",
|
||||
"graphql-upload": "8.0.7",
|
||||
"intersect": "1.0.1",
|
||||
"lodash": "4.17.5",
|
||||
"lru-cache": "4.1.2",
|
||||
"mime": "2.2.0",
|
||||
"mongodb": "3.0.4",
|
||||
"multer": "1.3.0",
|
||||
"parse": "1.11.1",
|
||||
"pg-promise": "8.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"lodash": "4.17.15",
|
||||
"lru-cache": "5.1.1",
|
||||
"mime": "2.4.4",
|
||||
"mongodb": "3.2.7",
|
||||
"node-rsa": "1.0.5",
|
||||
"parse": "2.6.0",
|
||||
"pg-promise": "9.0.0",
|
||||
"redis": "2.8.0",
|
||||
"request": "2.83.0",
|
||||
"semver": "5.5.0",
|
||||
"semver": "6.3.0",
|
||||
"subscriptions-transport-ws": "0.9.16",
|
||||
"tv4": "1.3.0",
|
||||
"uuid": "^3.1.0",
|
||||
"winston": "2.4.1",
|
||||
"winston-daily-rotate-file": "1.7.2",
|
||||
"ws": "5.0.0"
|
||||
"uuid": "3.3.2",
|
||||
"winston": "3.2.1",
|
||||
"winston-daily-rotate-file": "3.10.0",
|
||||
"ws": "7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-core": "6.26.0",
|
||||
"babel-eslint": "^8.2.2",
|
||||
"babel-plugin-transform-flow-strip-types": "6.22.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-preset-env": "1.6.1",
|
||||
"@babel/cli": "7.5.5",
|
||||
"@babel/core": "7.5.5",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.5.5",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.4.4",
|
||||
"@babel/preset-env": "7.5.5",
|
||||
"@parse/minami": "1.0.0",
|
||||
"apollo-cache-inmemory": "1.6.2",
|
||||
"apollo-client": "2.6.3",
|
||||
"apollo-link": "1.2.12",
|
||||
"apollo-link-http": "1.5.15",
|
||||
"apollo-link-ws": "1.0.18",
|
||||
"apollo-upload-client": "11.0.0",
|
||||
"apollo-utilities": "1.3.2",
|
||||
"babel-eslint": "10.0.2",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"cross-env": "5.1.4",
|
||||
"deep-diff": "0.3.8",
|
||||
"eslint": "^4.9.0",
|
||||
"eslint-plugin-flowtype": "^2.39.1",
|
||||
"flow-bin": "^0.67.1",
|
||||
"gaze": "1.1.2",
|
||||
"jasmine": "3.1.0",
|
||||
"jasmine-spec-reporter": "^4.1.0",
|
||||
"mongodb-runner": "3.6.1",
|
||||
"nodemon": "1.15.0",
|
||||
"nyc": "^11.0.2",
|
||||
"request-promise": "4.2.2"
|
||||
"cross-env": "5.2.0",
|
||||
"deep-diff": "1.0.2",
|
||||
"eslint": "6.1.0",
|
||||
"eslint-plugin-flowtype": "3.12.2",
|
||||
"flow-bin": "0.104.0",
|
||||
"form-data": "2.5.0",
|
||||
"gaze": "1.1.3",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"husky": "3.0.1",
|
||||
"jasmine": "3.4.0",
|
||||
"jsdoc": "3.6.3",
|
||||
"jsdoc-babel": "0.5.0",
|
||||
"lint-staged": "9.2.1",
|
||||
"mongodb-runner": "4.3.2",
|
||||
"node-fetch": "2.6.0",
|
||||
"nyc": "14.1.1",
|
||||
"prettier": "1.18.2"
|
||||
},
|
||||
"scripts": {
|
||||
"definitions": "node ./resources/buildConfigDefinitions.js",
|
||||
"docs": "jsdoc -c ./jsdoc-conf.json",
|
||||
"dev": "npm run build && node bin/dev",
|
||||
"lint": "flow && eslint --cache ./",
|
||||
"build": "babel src/ -d lib/ --copy-files",
|
||||
"pretest": "npm run lint",
|
||||
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
|
||||
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 nyc jasmine",
|
||||
"watch": "babel --watch src/ -d lib/ --copy-files",
|
||||
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
|
||||
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 nyc jasmine",
|
||||
"start": "node ./bin/parse-server",
|
||||
"prepublish": "npm run build"
|
||||
"prepare": "npm run build",
|
||||
"postinstall": "node -p 'require(\"./postinstall.js\")()'"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.11.4"
|
||||
"node": ">= 8"
|
||||
},
|
||||
"bin": {
|
||||
"parse-server": "./bin/parse-server"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "1.0.3",
|
||||
"uws": "^9.14.0"
|
||||
"bcrypt": "3.0.6"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parse-server",
|
||||
"logo": "https://opencollective.com/parse-server/logo.txt?reverse=true&variant=binary"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"{src,spec}/**/*.js": [
|
||||
"prettier --write",
|
||||
"eslint --cache",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
const pkg = require('./package.json');
|
||||
|
||||
const version = parseFloat(process.version.substr(1));
|
||||
const minimum = parseFloat(pkg.engines.node.match(/\d+/g).join('.'));
|
||||
|
||||
module.exports = function () {
|
||||
const openCollective = `
|
||||
1111111111
|
||||
1111111111111111
|
||||
1111111111111111111111
|
||||
11111111111111111111111111
|
||||
111111111111111 11111111
|
||||
1111111111111 111111
|
||||
1111111111111 111111111 111111
|
||||
111111111111 11111111111 111111
|
||||
1111111111111 11111111111 111111
|
||||
1111111111111 1111111111 111111
|
||||
1111111111111111111111111 1111111
|
||||
11111111 11111111
|
||||
111111 1111111111111111111
|
||||
11111 11111 111111111111111111
|
||||
11111 11111111111111111
|
||||
111111 111111111111111111
|
||||
11111111111111111111111111
|
||||
1111111111111111111111
|
||||
111111111111111111
|
||||
11111111111
|
||||
|
||||
|
||||
Thanks for installing parse 🙏
|
||||
Please consider donating to our open collective
|
||||
to help us maintain this package.
|
||||
|
||||
👉 https://opencollective.com/parse-server
|
||||
|
||||
`;
|
||||
process.stdout.write(openCollective);
|
||||
if (version >= minimum) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const errorMessage = `
|
||||
⚠️ parse-server requires at least node@${minimum}!
|
||||
You have node@${version}
|
||||
|
||||
`;
|
||||
|
||||
process.stdout.write(errorMessage);
|
||||
process.exit(1);
|
||||
};
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh -e
|
||||
set -x
|
||||
if [ "${TRAVIS_REPO_SLUG}" = "" ];
|
||||
then
|
||||
echo "Cannot release docs without TRAVIS_REPO_SLUG set"
|
||||
exit 0;
|
||||
fi
|
||||
REPO="https://github.com/${TRAVIS_REPO_SLUG}"
|
||||
|
||||
rm -rf docs
|
||||
git clone -b gh-pages --single-branch $REPO ./docs
|
||||
cd docs
|
||||
git pull origin gh-pages
|
||||
cd ..
|
||||
|
||||
DEST="master"
|
||||
|
||||
if [ "${TRAVIS_TAG}" != "" ];
|
||||
then
|
||||
DEST="${TRAVIS_TAG}"
|
||||
# change the default page to the latest
|
||||
echo "<meta http-equiv='refresh' content='0; url=/parse-server/api/${DEST}'>" > "docs/api/index.html"
|
||||
fi
|
||||
|
||||
npm run definitions
|
||||
npm run docs
|
||||
|
||||
mkdir -p "docs/api/${DEST}"
|
||||
cp -R out/* "docs/api/${DEST}"
|
||||
@@ -56,23 +56,35 @@ function getENVPrefix(iface) {
|
||||
|
||||
function processProperty(property, iface) {
|
||||
const firstComment = getCommentValue(last(property.leadingComments || []));
|
||||
const lastComment = getCommentValue((property.trailingComments || [])[0]);
|
||||
const name = property.key.name;
|
||||
const prefix = getENVPrefix(iface);
|
||||
|
||||
if (!firstComment) {
|
||||
return;
|
||||
}
|
||||
const components = firstComment.split(':ENV:').map((elt) => {
|
||||
return elt.trim();
|
||||
const lines = firstComment.split('\n').map((line) => line.trim());
|
||||
let help = '';
|
||||
let envLine;
|
||||
let defaultLine;
|
||||
lines.forEach((line) => {
|
||||
if (line.indexOf(':ENV:') === 0) {
|
||||
envLine = line;
|
||||
} else if (line.indexOf(':DEFAULT:') === 0) {
|
||||
defaultLine = line;
|
||||
} else {
|
||||
help += line;
|
||||
}
|
||||
});
|
||||
let env;
|
||||
if (envLine) {
|
||||
env = envLine.split(' ')[1];
|
||||
} else {
|
||||
env = (prefix + toENV(name));
|
||||
}
|
||||
let defaultValue;
|
||||
if (lastComment && lastComment.indexOf('=') >= 0) {
|
||||
const slice = lastComment.slice(lastComment.indexOf('=') + 1, lastComment.length).trim();
|
||||
defaultValue = slice;
|
||||
if (defaultLine) {
|
||||
defaultValue = defaultLine.split(' ')[1];
|
||||
}
|
||||
const help = components[0];
|
||||
const env = components[1] || (prefix + toENV(name));
|
||||
let type = property.value.type;
|
||||
let isRequired = true;
|
||||
if (type == 'NullableTypeAnnotation') {
|
||||
@@ -94,6 +106,7 @@ function processProperty(property, iface) {
|
||||
|
||||
function doInterface(iface) {
|
||||
return iface.body.properties
|
||||
.sort((a, b) => a.key.name.localeCompare(b.key.name))
|
||||
.map((prop) => processProperty(prop, iface))
|
||||
.filter((e) => e !== undefined);
|
||||
}
|
||||
@@ -123,18 +136,18 @@ function mapperFor(elt, t) {
|
||||
}
|
||||
|
||||
function parseDefaultValue(elt, value, t) {
|
||||
let litteralValue;
|
||||
let literalValue;
|
||||
if (t.isStringTypeAnnotation(elt)) {
|
||||
if (value == '""' || value == "''") {
|
||||
litteralValue = t.stringLiteral('');
|
||||
literalValue = t.stringLiteral('');
|
||||
} else {
|
||||
litteralValue = t.stringLiteral(value);
|
||||
literalValue = t.stringLiteral(value);
|
||||
}
|
||||
} else if (t.isNumberTypeAnnotation(elt)) {
|
||||
litteralValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
|
||||
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
|
||||
} else if (t.isArrayTypeAnnotation(elt)) {
|
||||
const array = parsers.objectParser(value);
|
||||
litteralValue = t.arrayExpression(array.map((value) => {
|
||||
literalValue = t.arrayExpression(array.map((value) => {
|
||||
if (typeof value == 'string') {
|
||||
return t.stringLiteral(value);
|
||||
} else {
|
||||
@@ -142,27 +155,36 @@ function parseDefaultValue(elt, value, t) {
|
||||
}
|
||||
}));
|
||||
} else if (t.isAnyTypeAnnotation(elt)) {
|
||||
litteralValue = t.arrayExpression([]);
|
||||
literalValue = t.arrayExpression([]);
|
||||
} else if (t.isBooleanTypeAnnotation(elt)) {
|
||||
litteralValue = t.booleanLiteral(parsers.booleanParser(value));
|
||||
literalValue = t.booleanLiteral(parsers.booleanParser(value));
|
||||
} else if (t.isGenericTypeAnnotation(elt)) {
|
||||
const type = elt.typeAnnotation.id.name;
|
||||
if (type == 'NumberOrBoolean') {
|
||||
litteralValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
|
||||
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
|
||||
}
|
||||
if (type == 'CustomPagesOptions') {
|
||||
const object = parsers.objectParser(value);
|
||||
const props = Object.keys(object).map((key) => {
|
||||
return t.objectProperty(key, object[value]);
|
||||
});
|
||||
litteralValue = t.objectExpression(props);
|
||||
literalValue = t.objectExpression(props);
|
||||
}
|
||||
if (type == 'ProtectedFields') {
|
||||
const prop = t.objectProperty(
|
||||
t.stringLiteral('_User'), t.objectPattern([
|
||||
t.objectProperty(t.stringLiteral('*'), t.arrayExpression([t.stringLiteral('email')]))
|
||||
])
|
||||
);
|
||||
literalValue = t.objectExpression([prop]);
|
||||
}
|
||||
}
|
||||
return litteralValue;
|
||||
return literalValue;
|
||||
}
|
||||
|
||||
function inject(t, list) {
|
||||
return list.map((elt) => {
|
||||
let comments = '';
|
||||
const results = list.map((elt) => {
|
||||
if (!elt.name) {
|
||||
return;
|
||||
}
|
||||
@@ -186,23 +208,42 @@ function inject(t, list) {
|
||||
throw new Error(`Unable to parse value for ${elt.name} `);
|
||||
}
|
||||
}
|
||||
let type = elt.type.replace('TypeAnnotation', '');
|
||||
if (type === 'Generic') {
|
||||
type = elt.typeAnnotation.id.name;
|
||||
}
|
||||
if (type === 'Array') {
|
||||
type = `${elt.typeAnnotation.elementType.type.replace('TypeAnnotation', '')}[]`;
|
||||
}
|
||||
if (type === 'NumberOrBoolean') {
|
||||
type = 'Number|Boolean';
|
||||
}
|
||||
if (type === 'Adapter') {
|
||||
const adapterType = elt.typeAnnotation.typeParameters.params[0].id.name;
|
||||
type = `Adapter<${adapterType}>`;
|
||||
}
|
||||
comments += ` * @property {${type}} ${elt.name} ${elt.help}\n`;
|
||||
const obj = t.objectExpression(props);
|
||||
return t.objectProperty(t.stringLiteral(elt.name), obj);
|
||||
}).filter((elt) => {
|
||||
return elt != undefined;
|
||||
});
|
||||
return { results, comments };
|
||||
}
|
||||
|
||||
const makeRequire = function(variableName, module, t) {
|
||||
const decl = t.variableDeclarator(t.identifier(variableName), t.callExpression(t.identifier('require'), [t.stringLiteral(module)]));
|
||||
return t.variableDeclaration('var', [decl])
|
||||
}
|
||||
|
||||
let docs = ``;
|
||||
const plugin = function (babel) {
|
||||
const t = babel.types;
|
||||
const moduleExports = t.memberExpression(t.identifier('module'), t.identifier('exports'));
|
||||
return {
|
||||
visitor: {
|
||||
ImportDeclaration: function(path) {
|
||||
path.remove();
|
||||
},
|
||||
Program: function(path) {
|
||||
// Inject the parser's loader
|
||||
path.unshiftContainer('body', makeRequire('parsers', './parsers', t));
|
||||
@@ -210,11 +251,12 @@ const plugin = function (babel) {
|
||||
ExportDeclaration: function(path) {
|
||||
// Export declaration on an interface
|
||||
if (path.node && path.node.declaration && path.node.declaration.type == 'InterfaceDeclaration') {
|
||||
const l = inject(t, doInterface(path.node.declaration));
|
||||
const { results, comments } = inject(t, doInterface(path.node.declaration));
|
||||
const id = path.node.declaration.id.name;
|
||||
const exports = t.memberExpression(moduleExports, t.identifier(id));
|
||||
docs += `/**\n * @interface ${id}\n${comments} */\n\n`;
|
||||
path.replaceWith(
|
||||
t.assignmentExpression('=', exports, t.objectExpression(l))
|
||||
t.assignmentExpression('=', exports, t.objectExpression(results))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -228,6 +270,7 @@ This code has been generated by resources/buildConfigDefinitions.js
|
||||
Do not edit manually, but update Options/index.js
|
||||
`
|
||||
|
||||
const babel = require("babel-core");
|
||||
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [ plugin ], auxiliaryCommentBefore });
|
||||
const babel = require("@babel/core");
|
||||
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [ plugin, '@babel/transform-flow-strip-types' ], babelrc: false, auxiliaryCommentBefore, sourceMaps: false });
|
||||
require('fs').writeFileSync('./src/Options/Definitions.js', res.code + '\n');
|
||||
require('fs').writeFileSync('./src/Options/docs.js', docs);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
# This script maintains a git branch which mirrors master but in a form that
|
||||
# what will eventually be deployed to npm, allowing npm dependencies to use:
|
||||
#
|
||||
# "parse-server": "parseplatform/parse-server#latest"
|
||||
#
|
||||
|
||||
# From: https://github.com/graphql/graphql-js/blob/master/resources/npm-git.sh
|
||||
|
||||
BUILD_DIR=latest
|
||||
BRANCH="${TRAVIS_BRANCH}"
|
||||
TARGET="latest"
|
||||
if [ "$BRANCH" != "master" ];
|
||||
then
|
||||
TARGET="$BRANCH-preview"
|
||||
fi
|
||||
|
||||
npm run build
|
||||
|
||||
mkdir -p $BUILD_DIR
|
||||
|
||||
cp package.json $BUILD_DIR/
|
||||
cp README.md $BUILD_DIR/
|
||||
cp LICENSE $BUILD_DIR/
|
||||
cp PATENTS $BUILD_DIR/
|
||||
cp CHANGELOG.md $BUILD_DIR/
|
||||
cp -R lib $BUILD_DIR
|
||||
cp -R bin $BUILD_DIR
|
||||
cp -R public_html $BUILD_DIR
|
||||
cp -R views $BUILD_DIR
|
||||
|
||||
cd $BUILD_DIR
|
||||
git init
|
||||
git config user.name "Travis CI"
|
||||
git config user.email "github@fb.com"
|
||||
git add .
|
||||
git commit -m "Deploy $BRANCH to $TARGET branch"
|
||||
git push --force --quiet "https://${GH_TOKEN}@github.com/parse-community/parse-server.git" master:$TARGET
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-object-rest-spread"
|
||||
],
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": {
|
||||
"node": "8"
|
||||
}
|
||||
}]
|
||||
],
|
||||
"sourceMaps": "inline",
|
||||
"retainLines": true
|
||||
}
|
||||
+3
-3
@@ -13,6 +13,7 @@
|
||||
"Item": true,
|
||||
"Container": true,
|
||||
"equal": true,
|
||||
"expectAsync": true,
|
||||
"notEqual": true,
|
||||
"it_only_db": true,
|
||||
"it_exclude_dbs": true,
|
||||
@@ -20,12 +21,11 @@
|
||||
"describe_only": true,
|
||||
"on_db": true,
|
||||
"defaultConfiguration": true,
|
||||
"expectSuccess": true,
|
||||
"range": true,
|
||||
"expectError": true,
|
||||
"jequal": true,
|
||||
"create": true,
|
||||
"arrayContains": true
|
||||
"arrayContains": true,
|
||||
"expectAsync": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": [0],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const Config = require("../src/Config");
|
||||
const Config = require('../lib/Config');
|
||||
|
||||
const loginWithWrongCredentialsShouldFail = function(username, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -22,7 +22,12 @@ const isAccountLockoutError = function(username, password, duration, waitTime) {
|
||||
Parse.User.logIn(username, password)
|
||||
.then(() => reject('login should have failed'))
|
||||
.catch(err => {
|
||||
if (err.message === 'Your account is locked due to multiple failed login attempts. Please try again after ' + duration + ' minute(s)') {
|
||||
if (
|
||||
err.message ===
|
||||
'Your account is locked due to multiple failed login attempts. Please try again after ' +
|
||||
duration +
|
||||
' minute(s)'
|
||||
) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
@@ -32,8 +37,7 @@ const isAccountLockoutError = function(username, password, duration, waitTime) {
|
||||
});
|
||||
};
|
||||
|
||||
describe("Account Lockout Policy: ", () => {
|
||||
|
||||
describe('Account Lockout Policy: ', () => {
|
||||
it('account should not be locked even after failed login attempts if account lockout policy is not set', done => {
|
||||
reconfigureServer({
|
||||
appName: 'unlimited',
|
||||
@@ -46,17 +50,28 @@ describe("Account Lockout Policy: ", () => {
|
||||
return user.signUp(null);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 1');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username1',
|
||||
'incorrect password 1'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 2');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username1',
|
||||
'incorrect password 2'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 3');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username1',
|
||||
'incorrect password 3'
|
||||
);
|
||||
})
|
||||
.then(() => done())
|
||||
.catch(err => {
|
||||
fail('allow unlimited failed login attempts failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'allow unlimited failed login attempts failed: ' + JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -66,9 +81,9 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'duration',
|
||||
accountLockout: {
|
||||
duration: 'invalid value',
|
||||
threshold: 5
|
||||
threshold: 5,
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
publicServerURL: 'https://my.public.server.com/1',
|
||||
})
|
||||
.then(() => {
|
||||
Config.get('test');
|
||||
@@ -76,10 +91,17 @@ describe("Account Lockout Policy: ", () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
if (
|
||||
err &&
|
||||
err ===
|
||||
'Account lockout duration should be greater than 0 and less than 100000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('set duration to an invalid number test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'set duration to an invalid number test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -90,9 +112,9 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'threshold',
|
||||
accountLockout: {
|
||||
duration: 5,
|
||||
threshold: 'invalid number'
|
||||
threshold: 'invalid number',
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
publicServerURL: 'https://my.public.server.com/1',
|
||||
})
|
||||
.then(() => {
|
||||
Config.get('test');
|
||||
@@ -100,10 +122,17 @@ describe("Account Lockout Policy: ", () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
if (
|
||||
err &&
|
||||
err ===
|
||||
'Account lockout threshold should be an integer greater than 0 and less than 1000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('set threshold to an invalid number test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'set threshold to an invalid number test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -114,9 +143,9 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'threshold',
|
||||
accountLockout: {
|
||||
duration: 5,
|
||||
threshold: 0
|
||||
threshold: 0,
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
publicServerURL: 'https://my.public.server.com/1',
|
||||
})
|
||||
.then(() => {
|
||||
Config.get('test');
|
||||
@@ -124,10 +153,16 @@ describe("Account Lockout Policy: ", () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
if (
|
||||
err &&
|
||||
err ===
|
||||
'Account lockout threshold should be an integer greater than 0 and less than 1000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'threshold value < 1 is invalid test failed: ' + JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -138,9 +173,9 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'threshold',
|
||||
accountLockout: {
|
||||
duration: 5,
|
||||
threshold: 1000
|
||||
threshold: 1000,
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
publicServerURL: 'https://my.public.server.com/1',
|
||||
})
|
||||
.then(() => {
|
||||
Config.get('test');
|
||||
@@ -148,10 +183,17 @@ describe("Account Lockout Policy: ", () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
if (
|
||||
err &&
|
||||
err ===
|
||||
'Account lockout threshold should be an integer greater than 0 and less than 1000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value > 999 is invalid test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'threshold value > 999 is invalid test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -162,9 +204,9 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'duration',
|
||||
accountLockout: {
|
||||
duration: 0,
|
||||
threshold: 5
|
||||
threshold: 5,
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
publicServerURL: 'https://my.public.server.com/1',
|
||||
})
|
||||
.then(() => {
|
||||
Config.get('test');
|
||||
@@ -172,10 +214,16 @@ describe("Account Lockout Policy: ", () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
if (
|
||||
err &&
|
||||
err ===
|
||||
'Account lockout duration should be greater than 0 and less than 100000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('duration value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'duration value < 1 is invalid test failed: ' + JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -186,9 +234,9 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'duration',
|
||||
accountLockout: {
|
||||
duration: 100000,
|
||||
threshold: 5
|
||||
threshold: 5,
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
publicServerURL: 'https://my.public.server.com/1',
|
||||
})
|
||||
.then(() => {
|
||||
Config.get('test');
|
||||
@@ -196,10 +244,17 @@ describe("Account Lockout Policy: ", () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
if (
|
||||
err &&
|
||||
err ===
|
||||
'Account lockout duration should be greater than 0 and less than 100000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('duration value > 99999 is invalid test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'duration value > 99999 is invalid test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -210,21 +265,27 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'lockout threshold',
|
||||
accountLockout: {
|
||||
duration: 1,
|
||||
threshold: 2
|
||||
threshold: 2,
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
const user = new Parse.User();
|
||||
user.setUsername("username2");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
user.setUsername('username2');
|
||||
user.setPassword('failedLoginAttemptsThreshold');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username2',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username2',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username2', 'wrong password', 1, 1);
|
||||
@@ -233,7 +294,10 @@ describe("Account Lockout Policy: ", () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('lock account after failed login attempts test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'lock account after failed login attempts test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -243,34 +307,43 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'lockout threshold',
|
||||
accountLockout: {
|
||||
duration: 0.05, // 0.05*60 = 3 secs
|
||||
threshold: 2
|
||||
threshold: 2,
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
const user = new Parse.User();
|
||||
user.setUsername("username3");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
user.setUsername('username3');
|
||||
user.setPassword('failedLoginAttemptsThreshold');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username3',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username3',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username3', 'wrong password', 0.05, 1);
|
||||
})
|
||||
.then(() => {
|
||||
// account should still be locked even after 2 seconds.
|
||||
// account should still be locked even after 2 seconds.
|
||||
return isAccountLockoutError('username3', 'wrong password', 0.05, 2000);
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('account should be locked for duration mins test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'account should be locked for duration mins test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -280,24 +353,30 @@ describe("Account Lockout Policy: ", () => {
|
||||
appName: 'lockout threshold',
|
||||
accountLockout: {
|
||||
duration: 0.05, // 0.05*60 = 3 secs
|
||||
threshold: 2
|
||||
threshold: 2,
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
const user = new Parse.User();
|
||||
user.setUsername("username4");
|
||||
user.setPassword("correct password");
|
||||
user.setUsername('username4');
|
||||
user.setPassword('correct password');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username4',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username4',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
// allow locked user to login after 3 seconds with a valid userid and password
|
||||
// allow locked user to login after 3 seconds with a valid userid and password
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Parse.User.logIn('username4', 'correct password')
|
||||
@@ -310,9 +389,11 @@ describe("Account Lockout Policy: ", () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('allow login for locked account after accountPolicy.duration minutes test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'allow login for locked account after accountPolicy.duration minutes test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
|
||||
const AdaptableController = require("../src/Controllers/AdaptableController").AdaptableController;
|
||||
const FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
|
||||
const FilesController = require("../src/Controllers/FilesController").FilesController;
|
||||
const AdaptableController = require('../lib/Controllers/AdaptableController')
|
||||
.AdaptableController;
|
||||
const FilesAdapter = require('../lib/Adapters/Files/FilesAdapter').default;
|
||||
const FilesController = require('../lib/Controllers/FilesController')
|
||||
.FilesController;
|
||||
|
||||
const MockController = function(options) {
|
||||
AdaptableController.call(this, options);
|
||||
}
|
||||
};
|
||||
MockController.prototype = Object.create(AdaptableController.prototype);
|
||||
MockController.prototype.constructor = AdaptableController;
|
||||
|
||||
describe("AdaptableController", ()=>{
|
||||
it("should use the provided adapter", (done) => {
|
||||
describe('AdaptableController', () => {
|
||||
it('should use the provided adapter', done => {
|
||||
const adapter = new FilesAdapter();
|
||||
const controller = new FilesController(adapter);
|
||||
expect(controller.adapter).toBe(adapter);
|
||||
// make sure _adapter is private
|
||||
expect(controller._adapter).toBe(undefined);
|
||||
// Override _adapter is not doing anything
|
||||
controller._adapter = "Hello";
|
||||
controller._adapter = 'Hello';
|
||||
expect(controller.adapter).toBe(adapter);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should throw when creating a new mock controller", (done) => {
|
||||
it('should throw when creating a new mock controller', done => {
|
||||
const adapter = new FilesAdapter();
|
||||
expect(() => {
|
||||
new MockController(adapter);
|
||||
@@ -30,7 +31,7 @@ describe("AdaptableController", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail setting the wrong adapter to the controller", (done) => {
|
||||
it('should fail setting the wrong adapter to the controller', done => {
|
||||
function WrongAdapter() {}
|
||||
const adapter = new FilesAdapter();
|
||||
const controller = new FilesController(adapter);
|
||||
@@ -41,7 +42,7 @@ describe("AdaptableController", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail to instantiate a controller with wrong adapter", (done) => {
|
||||
it('should fail to instantiate a controller with wrong adapter', done => {
|
||||
function WrongAdapter() {}
|
||||
const adapter = new WrongAdapter();
|
||||
expect(() => {
|
||||
@@ -50,32 +51,32 @@ describe("AdaptableController", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail to instantiate a controller without an adapter", (done) => {
|
||||
it('should fail to instantiate a controller without an adapter', done => {
|
||||
expect(() => {
|
||||
new FilesController();
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it("should accept an object adapter", (done) => {
|
||||
it('should accept an object adapter', done => {
|
||||
const adapter = {
|
||||
createFile: function() { },
|
||||
deleteFile: function() { },
|
||||
getFileData: function() { },
|
||||
getFileLocation: function() { },
|
||||
}
|
||||
createFile: function() {},
|
||||
deleteFile: function() {},
|
||||
getFileData: function() {},
|
||||
getFileLocation: function() {},
|
||||
};
|
||||
expect(() => {
|
||||
new FilesController(adapter);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it("should accept an object adapter", (done) => {
|
||||
it('should accept an prototype based object adapter', done => {
|
||||
function AGoodAdapter() {}
|
||||
AGoodAdapter.prototype.createFile = function() { };
|
||||
AGoodAdapter.prototype.deleteFile = function() { };
|
||||
AGoodAdapter.prototype.getFileData = function() { };
|
||||
AGoodAdapter.prototype.getFileLocation = function() { };
|
||||
AGoodAdapter.prototype.createFile = function() {};
|
||||
AGoodAdapter.prototype.deleteFile = function() {};
|
||||
AGoodAdapter.prototype.getFileData = function() {};
|
||||
AGoodAdapter.prototype.getFileLocation = function() {};
|
||||
|
||||
const adapter = new AGoodAdapter();
|
||||
expect(() => {
|
||||
|
||||
+46
-46
@@ -1,41 +1,41 @@
|
||||
const loadAdapter = require('../lib/Adapters/AdapterLoader').loadAdapter;
|
||||
const FilesAdapter = require('@parse/fs-files-adapter').default;
|
||||
const S3Adapter = require('@parse/s3-files-adapter').default;
|
||||
const ParsePushAdapter = require('@parse/push-adapter').default;
|
||||
const Config = require('../lib/Config');
|
||||
|
||||
const loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
|
||||
const FilesAdapter = require("@parse/fs-files-adapter").default;
|
||||
const S3Adapter = require("@parse/s3-files-adapter").default;
|
||||
const ParsePushAdapter = require("@parse/push-adapter").default;
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe("AdapterLoader", ()=>{
|
||||
|
||||
it("should instantiate an adapter from string in object", (done) => {
|
||||
const adapterPath = require('path').resolve("./spec/MockAdapter");
|
||||
describe('AdapterLoader', () => {
|
||||
it('should instantiate an adapter from string in object', done => {
|
||||
const adapterPath = require('path').resolve('./spec/MockAdapter');
|
||||
|
||||
const adapter = loadAdapter({
|
||||
adapter: adapterPath,
|
||||
options: {
|
||||
key: "value",
|
||||
foo: "bar"
|
||||
}
|
||||
key: 'value',
|
||||
foo: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
expect(adapter instanceof Object).toBe(true);
|
||||
expect(adapter.options.key).toBe("value");
|
||||
expect(adapter.options.foo).toBe("bar");
|
||||
expect(adapter.options.key).toBe('value');
|
||||
expect(adapter.options.foo).toBe('bar');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate an adapter from string", (done) => {
|
||||
const adapterPath = require('path').resolve("./spec/MockAdapter");
|
||||
it('should instantiate an adapter from string', done => {
|
||||
const adapterPath = require('path').resolve('./spec/MockAdapter');
|
||||
const adapter = loadAdapter(adapterPath);
|
||||
|
||||
expect(adapter instanceof Object).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate an adapter from string that is module", (done) => {
|
||||
const adapterPath = require('path').resolve("./src/Adapters/Files/FilesAdapter");
|
||||
it('should instantiate an adapter from string that is module', done => {
|
||||
const adapterPath = require('path').resolve(
|
||||
'./lib/Adapters/Files/FilesAdapter'
|
||||
);
|
||||
const adapter = loadAdapter({
|
||||
adapter: adapterPath
|
||||
adapter: adapterPath,
|
||||
});
|
||||
|
||||
expect(typeof adapter).toBe('object');
|
||||
@@ -46,9 +46,9 @@ describe("AdapterLoader", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate an adapter from npm module", (done) => {
|
||||
it('should instantiate an adapter from npm module', done => {
|
||||
const adapter = loadAdapter({
|
||||
module: '@parse/fs-files-adapter'
|
||||
module: '@parse/fs-files-adapter',
|
||||
});
|
||||
|
||||
expect(typeof adapter).toBe('object');
|
||||
@@ -59,59 +59,59 @@ describe("AdapterLoader", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate an adapter from function/Class", (done) => {
|
||||
it('should instantiate an adapter from function/Class', done => {
|
||||
const adapter = loadAdapter({
|
||||
adapter: FilesAdapter
|
||||
adapter: FilesAdapter,
|
||||
});
|
||||
expect(adapter instanceof FilesAdapter).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate the default adapter from Class", (done) => {
|
||||
it('should instantiate the default adapter from Class', done => {
|
||||
const adapter = loadAdapter(null, FilesAdapter);
|
||||
expect(adapter instanceof FilesAdapter).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should use the default adapter", (done) => {
|
||||
it('should use the default adapter', done => {
|
||||
const defaultAdapter = new FilesAdapter();
|
||||
const adapter = loadAdapter(null, defaultAdapter);
|
||||
expect(adapter instanceof FilesAdapter).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should use the provided adapter", (done) => {
|
||||
it('should use the provided adapter', done => {
|
||||
const originalAdapter = new FilesAdapter();
|
||||
const adapter = loadAdapter(originalAdapter);
|
||||
expect(adapter).toBe(originalAdapter);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail loading an improperly configured adapter", (done) => {
|
||||
it('should fail loading an improperly configured adapter', done => {
|
||||
const Adapter = function(options) {
|
||||
if (!options.foo) {
|
||||
throw "foo is required for that adapter";
|
||||
throw 'foo is required for that adapter';
|
||||
}
|
||||
}
|
||||
};
|
||||
const adapterOptions = {
|
||||
param: "key",
|
||||
doSomething: function() {}
|
||||
param: 'key',
|
||||
doSomething: function() {},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
const adapter = loadAdapter(adapterOptions, Adapter);
|
||||
expect(adapter).toEqual(adapterOptions);
|
||||
}).not.toThrow("foo is required for that adapter");
|
||||
}).not.toThrow('foo is required for that adapter');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load push adapter from options", (done) => {
|
||||
it('should load push adapter from options', done => {
|
||||
const options = {
|
||||
android: {
|
||||
senderId: 'yolo',
|
||||
apiKey: 'yolo'
|
||||
}
|
||||
}
|
||||
apiKey: 'yolo',
|
||||
},
|
||||
};
|
||||
expect(() => {
|
||||
const adapter = loadAdapter(undefined, ParsePushAdapter, options);
|
||||
expect(adapter.constructor).toBe(ParsePushAdapter);
|
||||
@@ -120,16 +120,16 @@ describe("AdapterLoader", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load custom push adapter from string (#3544)", (done) => {
|
||||
const adapterPath = require('path').resolve("./spec/MockPushAdapter");
|
||||
it('should load custom push adapter from string (#3544)', done => {
|
||||
const adapterPath = require('path').resolve('./spec/MockPushAdapter');
|
||||
const options = {
|
||||
ios: {
|
||||
bundleId: 'bundle.id'
|
||||
}
|
||||
}
|
||||
bundleId: 'bundle.id',
|
||||
},
|
||||
};
|
||||
const pushAdapterOptions = {
|
||||
adapter: adapterPath,
|
||||
options
|
||||
options,
|
||||
};
|
||||
expect(() => {
|
||||
reconfigureServer({
|
||||
@@ -144,12 +144,12 @@ describe("AdapterLoader", ()=>{
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should load S3Adapter from direct passing", (done) => {
|
||||
const s3Adapter = new S3Adapter("key", "secret", "bucket")
|
||||
it('should load S3Adapter from direct passing', done => {
|
||||
const s3Adapter = new S3Adapter('key', 'secret', 'bucket');
|
||||
expect(() => {
|
||||
const adapter = loadAdapter(s3Adapter, FilesAdapter);
|
||||
expect(adapter).toBe(s3Adapter);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
const AggregateRouter = require('../lib/Routers/AggregateRouter')
|
||||
.AggregateRouter;
|
||||
|
||||
describe('AggregateRouter', () => {
|
||||
it('get pipeline from Array', () => {
|
||||
const body = [
|
||||
{
|
||||
group: { objectId: {} },
|
||||
},
|
||||
];
|
||||
const expected = [{ $group: { _id: {} } }];
|
||||
const result = AggregateRouter.getPipeline(body);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('get pipeline from Object', () => {
|
||||
const body = {
|
||||
group: { objectId: {} },
|
||||
};
|
||||
const expected = [{ $group: { _id: {} } }];
|
||||
const result = AggregateRouter.getPipeline(body);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('get pipeline from Pipeline Operator (Array)', () => {
|
||||
const body = {
|
||||
pipeline: [
|
||||
{
|
||||
group: { objectId: {} },
|
||||
},
|
||||
],
|
||||
};
|
||||
const expected = [{ $group: { _id: {} } }];
|
||||
const result = AggregateRouter.getPipeline(body);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('get pipeline from Pipeline Operator (Object)', () => {
|
||||
const body = {
|
||||
pipeline: {
|
||||
group: { objectId: {} },
|
||||
},
|
||||
};
|
||||
const expected = [{ $group: { _id: {} } }];
|
||||
const result = AggregateRouter.getPipeline(body);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('get pipeline fails multiple keys in Array stage ', () => {
|
||||
const body = [
|
||||
{
|
||||
group: { objectId: {} },
|
||||
match: { name: 'Test' },
|
||||
},
|
||||
];
|
||||
try {
|
||||
AggregateRouter.getPipeline(body);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'Pipeline stages should only have one key found group, match'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('get pipeline fails multiple keys in Pipeline Operator Array stage ', () => {
|
||||
const body = {
|
||||
pipeline: [
|
||||
{
|
||||
group: { objectId: {} },
|
||||
match: { name: 'Test' },
|
||||
},
|
||||
],
|
||||
};
|
||||
try {
|
||||
AggregateRouter.getPipeline(body);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'Pipeline stages should only have one key found group, match'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
+57
-49
@@ -1,61 +1,69 @@
|
||||
const analyticsAdapter = {
|
||||
appOpened: function() {},
|
||||
trackEvent: function() {}
|
||||
}
|
||||
trackEvent: function() {},
|
||||
};
|
||||
|
||||
describe('AnalyticsController', () => {
|
||||
it('should track a simple event', (done) => {
|
||||
|
||||
it('should track a simple event', done => {
|
||||
spyOn(analyticsAdapter, 'trackEvent').and.callThrough();
|
||||
reconfigureServer({
|
||||
analyticsAdapter
|
||||
}).then(() => {
|
||||
return Parse.Analytics.track('MyEvent', {
|
||||
key: 'value',
|
||||
count: '0'
|
||||
})
|
||||
}).then(() => {
|
||||
expect(analyticsAdapter.trackEvent).toHaveBeenCalled();
|
||||
const lastCall = analyticsAdapter.trackEvent.calls.first();
|
||||
const args = lastCall.args;
|
||||
expect(args[0]).toEqual('MyEvent');
|
||||
expect(args[1]).toEqual({
|
||||
dimensions: {
|
||||
key: 'value',
|
||||
count: '0'
|
||||
}
|
||||
});
|
||||
done();
|
||||
}, (err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
analyticsAdapter,
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Analytics.track('MyEvent', {
|
||||
key: 'value',
|
||||
count: '0',
|
||||
});
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
expect(analyticsAdapter.trackEvent).toHaveBeenCalled();
|
||||
const lastCall = analyticsAdapter.trackEvent.calls.first();
|
||||
const args = lastCall.args;
|
||||
expect(args[0]).toEqual('MyEvent');
|
||||
expect(args[1]).toEqual({
|
||||
dimensions: {
|
||||
key: 'value',
|
||||
count: '0',
|
||||
},
|
||||
});
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should track a app opened event', (done) => {
|
||||
|
||||
it('should track a app opened event', done => {
|
||||
spyOn(analyticsAdapter, 'appOpened').and.callThrough();
|
||||
reconfigureServer({
|
||||
analyticsAdapter
|
||||
}).then(() => {
|
||||
return Parse.Analytics.track('AppOpened', {
|
||||
key: 'value',
|
||||
count: '0'
|
||||
})
|
||||
}).then(() => {
|
||||
expect(analyticsAdapter.appOpened).toHaveBeenCalled();
|
||||
const lastCall = analyticsAdapter.appOpened.calls.first();
|
||||
const args = lastCall.args;
|
||||
expect(args[0]).toEqual({
|
||||
dimensions: {
|
||||
key: 'value',
|
||||
count: '0'
|
||||
}
|
||||
});
|
||||
done();
|
||||
}, (err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
analyticsAdapter,
|
||||
})
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Analytics.track('AppOpened', {
|
||||
key: 'value',
|
||||
count: '0',
|
||||
});
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
expect(analyticsAdapter.appOpened).toHaveBeenCalled();
|
||||
const lastCall = analyticsAdapter.appOpened.calls.first();
|
||||
const args = lastCall.args;
|
||||
expect(args[0]).toEqual({
|
||||
dimensions: {
|
||||
key: 'value',
|
||||
count: '0',
|
||||
},
|
||||
});
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+304
-175
@@ -1,59 +1,67 @@
|
||||
const auth = require('../src/Auth');
|
||||
const Config = require('../src/Config');
|
||||
const rest = require('../src/rest');
|
||||
const AudiencesRouter = require('../src/Routers/AudiencesRouter').AudiencesRouter;
|
||||
const auth = require('../lib/Auth');
|
||||
const Config = require('../lib/Config');
|
||||
const rest = require('../lib/rest');
|
||||
const request = require('../lib/request');
|
||||
const AudiencesRouter = require('../lib/Routers/AudiencesRouter')
|
||||
.AudiencesRouter;
|
||||
|
||||
describe('AudiencesRouter', () => {
|
||||
it('uses find condition from request.body', (done) => {
|
||||
it('uses find condition from request.body', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
const iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {
|
||||
where: {
|
||||
query: '{ "test": "android" }'
|
||||
}
|
||||
query: '{ "test": "android" }',
|
||||
},
|
||||
},
|
||||
query: {},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
.then(res => {
|
||||
const results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses find condition from request.query', (done) => {
|
||||
it('uses find condition from request.query', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
const iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
@@ -61,96 +69,116 @@ describe('AudiencesRouter', () => {
|
||||
body: {},
|
||||
query: {
|
||||
where: {
|
||||
'query': '{ "test": "android" }'
|
||||
}
|
||||
query: '{ "test": "android" }',
|
||||
},
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
.then(res => {
|
||||
const results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with limit = 0', (done) => {
|
||||
it('query installations with limit = 0', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
const iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0
|
||||
limit: 0,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
Config.get('test');
|
||||
const router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with count = 1', done => {
|
||||
it_exclude_dbs(['postgres'])('query installations with count = 1', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
const iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
count: 1
|
||||
count: 1,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest))
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() =>
|
||||
rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
)
|
||||
)
|
||||
.then(() => router.handleFind(request))
|
||||
.then((res) => {
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(2);
|
||||
@@ -159,66 +187,110 @@ describe('AudiencesRouter', () => {
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('query installations with limit = 0 and count = 1', (done) => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
};
|
||||
const iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1
|
||||
},
|
||||
info: {}
|
||||
};
|
||||
|
||||
const router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should create, read, update and delete audiences throw api', (done) => {
|
||||
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
|
||||
.then(() => {
|
||||
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then((results) => {
|
||||
it_exclude_dbs(['postgres'])(
|
||||
'query installations with limit = 0 and count = 1',
|
||||
done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
const iosAudienceRequest = {
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1,
|
||||
},
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new AudiencesRouter();
|
||||
rest
|
||||
.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
androidAudienceRequest
|
||||
)
|
||||
.then(() => {
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('should create, read, update and delete audiences throw api', done => {
|
||||
Parse._request(
|
||||
'POST',
|
||||
'push_audiences',
|
||||
{ name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) },
|
||||
{ useMasterKey: true }
|
||||
).then(() => {
|
||||
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then(
|
||||
results => {
|
||||
expect(results.results.length).toEqual(1);
|
||||
expect(results.results[0].name).toEqual('My Audience');
|
||||
expect(results.results[0].query.deviceType).toEqual('ios');
|
||||
Parse._request('GET', `push_audiences/${results.results[0].objectId}`, {}, { useMasterKey: true }).then((results) => {
|
||||
Parse._request(
|
||||
'GET',
|
||||
`push_audiences/${results.results[0].objectId}`,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
).then(results => {
|
||||
expect(results.name).toEqual('My Audience');
|
||||
expect(results.query.deviceType).toEqual('ios');
|
||||
Parse._request('PUT', `push_audiences/${results.objectId}`, { name: 'My Audience 2' }, { useMasterKey: true }).then(() => {
|
||||
Parse._request('GET', `push_audiences/${results.objectId}`, {}, { useMasterKey: true }).then((results) => {
|
||||
Parse._request(
|
||||
'PUT',
|
||||
`push_audiences/${results.objectId}`,
|
||||
{ name: 'My Audience 2' },
|
||||
{ useMasterKey: true }
|
||||
).then(() => {
|
||||
Parse._request(
|
||||
'GET',
|
||||
`push_audiences/${results.objectId}`,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
).then(results => {
|
||||
expect(results.name).toEqual('My Audience 2');
|
||||
expect(results.query.deviceType).toEqual('ios');
|
||||
Parse._request('DELETE', `push_audiences/${results.objectId}`, {}, { useMasterKey: true }).then(() => {
|
||||
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then((results) => {
|
||||
Parse._request(
|
||||
'DELETE',
|
||||
`push_audiences/${results.objectId}`,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
).then(() => {
|
||||
Parse._request(
|
||||
'GET',
|
||||
'push_audiences',
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
).then(results => {
|
||||
expect(results.results.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
@@ -226,112 +298,169 @@ describe('AudiencesRouter', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only create with master key', (done) => {
|
||||
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only find with master key', (done) => {
|
||||
Parse._request('GET', 'push_audiences', {})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
it('should only create with master key', done => {
|
||||
Parse._request('POST', 'push_audiences', {
|
||||
name: 'My Audience',
|
||||
query: JSON.stringify({ deviceType: 'ios' }),
|
||||
}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only get with master key', (done) => {
|
||||
Parse._request('GET', `push_audiences/someId`, {})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
it('should only find with master key', done => {
|
||||
Parse._request('GET', 'push_audiences', {}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only update with master key', (done) => {
|
||||
Parse._request('PUT', `push_audiences/someId`, { name: 'My Audience 2' })
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
it('should only get with master key', done => {
|
||||
Parse._request('GET', `push_audiences/someId`, {}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only delete with master key', (done) => {
|
||||
Parse._request('DELETE', `push_audiences/someId`, {})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
it('should only update with master key', done => {
|
||||
Parse._request('PUT', `push_audiences/someId`, {
|
||||
name: 'My Audience 2',
|
||||
}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])('should support legacy parse.com audience fields', (done) => {
|
||||
const database = (Config.get(Parse.applicationId)).database.adapter.database;
|
||||
const now = new Date();
|
||||
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
|
||||
.then((audience) => {
|
||||
it('should only delete with master key', done => {
|
||||
Parse._request('DELETE', `push_audiences/someId`, {}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])(
|
||||
'should support legacy parse.com audience fields',
|
||||
done => {
|
||||
const database = Config.get(Parse.applicationId).database.adapter
|
||||
.database;
|
||||
const now = new Date();
|
||||
Parse._request(
|
||||
'POST',
|
||||
'push_audiences',
|
||||
{ name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) },
|
||||
{ useMasterKey: true }
|
||||
).then(audience => {
|
||||
database.collection('test__Audience').updateOne(
|
||||
{ _id: audience.objectId },
|
||||
{
|
||||
$set: {
|
||||
times_used: 1,
|
||||
_last_used: now
|
||||
}
|
||||
_last_used: now,
|
||||
},
|
||||
},
|
||||
{},
|
||||
(error) => {
|
||||
expect(error).toEqual(null)
|
||||
database.collection('test__Audience').find({ _id: audience.objectId}).toArray(
|
||||
(error, rows) => {
|
||||
expect(error).toEqual(null)
|
||||
error => {
|
||||
expect(error).toEqual(null);
|
||||
database
|
||||
.collection('test__Audience')
|
||||
.find({ _id: audience.objectId })
|
||||
.toArray((error, rows) => {
|
||||
expect(error).toEqual(null);
|
||||
expect(rows[0]['times_used']).toEqual(1);
|
||||
expect(rows[0]['_last_used']).toEqual(now);
|
||||
Parse._request('GET', 'push_audiences/' + audience.objectId, {}, {useMasterKey: true})
|
||||
.then((audience) => {
|
||||
Parse._request(
|
||||
'GET',
|
||||
'push_audiences/' + audience.objectId,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
)
|
||||
.then(audience => {
|
||||
expect(audience.name).toEqual('My Audience');
|
||||
expect(audience.query.deviceType).toEqual('ios');
|
||||
expect(audience.timesUsed).toEqual(1);
|
||||
expect(audience.lastUsed).toEqual(now.toISOString());
|
||||
done();
|
||||
})
|
||||
.catch((error) => { done.fail(error); })
|
||||
.catch(error => {
|
||||
done.fail(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('should be able to search on audiences', done => {
|
||||
Parse._request(
|
||||
'POST',
|
||||
'push_audiences',
|
||||
{ name: 'neverUsed', query: JSON.stringify({ deviceType: 'ios' }) },
|
||||
{ useMasterKey: true }
|
||||
).then(() => {
|
||||
const query = {
|
||||
timesUsed: { $exists: false },
|
||||
lastUsed: { $exists: false },
|
||||
};
|
||||
Parse._request(
|
||||
'GET',
|
||||
'push_audiences?order=-createdAt&limit=1',
|
||||
{ where: query },
|
||||
{ useMasterKey: true }
|
||||
)
|
||||
.then(results => {
|
||||
expect(results.results.length).toEqual(1);
|
||||
const audience = results.results[0];
|
||||
expect(audience.name).toEqual('neverUsed');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
done.fail(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to search on audiences', (done) => {
|
||||
Parse._request('POST', 'push_audiences', { name: 'neverUsed', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
|
||||
.then(() => {
|
||||
const query = {"timesUsed": {"$exists": false}, "lastUsed": {"$exists": false}};
|
||||
Parse._request('GET', 'push_audiences?order=-createdAt&limit=1', {where: query}, {useMasterKey: true})
|
||||
.then((results) => {
|
||||
expect(results.results.length).toEqual(1);
|
||||
const audience = results.results[0];
|
||||
expect(audience.name).toEqual("neverUsed");
|
||||
done();
|
||||
})
|
||||
.catch((error) => { done.fail(error); })
|
||||
})
|
||||
it('should handle _Audience invalid fields via rest', async () => {
|
||||
await reconfigureServer({
|
||||
appId: 'test',
|
||||
restAPIKey: 'test',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
});
|
||||
try {
|
||||
await request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/_Audience',
|
||||
body: { lorem: 'ipsum', _method: 'POST' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
expect(true).toBeFalsy();
|
||||
} catch (e) {
|
||||
expect(e.data.code).toBe(107);
|
||||
expect(e.data.error).toBe('Could not add field lorem');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+183
-33
@@ -1,6 +1,6 @@
|
||||
describe('Auth', () => {
|
||||
const Auth = require('../src/Auth.js').Auth;
|
||||
|
||||
const { Auth, getAuthForSessionToken } = require('../lib/Auth.js');
|
||||
const Config = require('../lib/Config');
|
||||
describe('getUserRoles', () => {
|
||||
let auth;
|
||||
let config;
|
||||
@@ -14,45 +14,45 @@ describe('Auth', () => {
|
||||
cacheController: {
|
||||
role: {
|
||||
get: () => Promise.resolve(currentRoles),
|
||||
set: jasmine.createSpy('set')
|
||||
}
|
||||
}
|
||||
}
|
||||
set: jasmine.createSpy('set'),
|
||||
},
|
||||
},
|
||||
};
|
||||
spyOn(config.cacheController.role, 'get').and.callThrough();
|
||||
|
||||
auth = new Auth({
|
||||
config: config,
|
||||
isMaster: false,
|
||||
user: {
|
||||
id: currentUserId
|
||||
id: currentUserId,
|
||||
},
|
||||
installationId: 'installationId'
|
||||
installationId: 'installationId',
|
||||
});
|
||||
});
|
||||
|
||||
it('should get user roles from the cache', (done) => {
|
||||
auth.getUserRoles()
|
||||
.then((roles) => {
|
||||
const firstSet = config.cacheController.role.set.calls.first();
|
||||
expect(firstSet).toEqual(undefined);
|
||||
it('should get user roles from the cache', done => {
|
||||
auth.getUserRoles().then(roles => {
|
||||
const firstSet = config.cacheController.role.set.calls.first();
|
||||
expect(firstSet).toEqual(undefined);
|
||||
|
||||
const firstGet = config.cacheController.role.get.calls.first();
|
||||
expect(firstGet.args[0]).toEqual(currentUserId);
|
||||
expect(roles).toEqual(currentRoles);
|
||||
done();
|
||||
});
|
||||
const firstGet = config.cacheController.role.get.calls.first();
|
||||
expect(firstGet.args[0]).toEqual(currentUserId);
|
||||
expect(roles).toEqual(currentRoles);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only query the roles once', (done) => {
|
||||
it('should only query the roles once', done => {
|
||||
const loadRolesSpy = spyOn(auth, '_loadRoles').and.callThrough();
|
||||
auth.getUserRoles()
|
||||
.then((roles) => {
|
||||
auth
|
||||
.getUserRoles()
|
||||
.then(roles => {
|
||||
expect(roles).toEqual(currentRoles);
|
||||
return auth.getUserRoles()
|
||||
return auth.getUserRoles();
|
||||
})
|
||||
.then(() => auth.getUserRoles())
|
||||
.then(() => auth.getUserRoles())
|
||||
.then((roles) => {
|
||||
.then(roles => {
|
||||
// Should only call the cache adapter once.
|
||||
expect(config.cacheController.role.get.calls.count()).toEqual(1);
|
||||
expect(loadRolesSpy.calls.count()).toEqual(1);
|
||||
@@ -64,30 +64,180 @@ describe('Auth', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have any roles with no user', (done) => {
|
||||
auth.user = null
|
||||
auth.getUserRoles()
|
||||
.then((roles) => expect(roles).toEqual([]))
|
||||
it('should not have any roles with no user', done => {
|
||||
auth.user = null;
|
||||
auth
|
||||
.getUserRoles()
|
||||
.then(roles => expect(roles).toEqual([]))
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should not have any user roles with master', (done) => {
|
||||
auth.isMaster = true
|
||||
auth.getUserRoles()
|
||||
.then((roles) => expect(roles).toEqual([]))
|
||||
it('should not have any user roles with master', done => {
|
||||
auth.isMaster = true;
|
||||
auth
|
||||
.getUserRoles()
|
||||
.then(roles => expect(roles).toEqual([]))
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should properly handle bcrypt upgrade', (done) => {
|
||||
it('should properly handle bcrypt upgrade', done => {
|
||||
const bcryptOriginal = require('bcrypt-nodejs');
|
||||
const bcryptNew = require('bcryptjs');
|
||||
bcryptOriginal.hash('my1Long:password', null, null, function(err, res) {
|
||||
bcryptNew.compare('my1Long:password', res, function(err, res) {
|
||||
expect(res).toBeTruthy();
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should load auth without a config', async () => {
|
||||
const user = new Parse.User();
|
||||
await user.signUp({
|
||||
username: 'hello',
|
||||
password: 'password',
|
||||
});
|
||||
expect(user.getSessionToken()).not.toBeUndefined();
|
||||
const userAuth = await getAuthForSessionToken({
|
||||
sessionToken: user.getSessionToken(),
|
||||
});
|
||||
expect(userAuth.user instanceof Parse.User).toBe(true);
|
||||
expect(userAuth.user.id).toBe(user.id);
|
||||
});
|
||||
|
||||
it('should load auth with a config', async () => {
|
||||
const user = new Parse.User();
|
||||
await user.signUp({
|
||||
username: 'hello',
|
||||
password: 'password',
|
||||
});
|
||||
expect(user.getSessionToken()).not.toBeUndefined();
|
||||
const userAuth = await getAuthForSessionToken({
|
||||
sessionToken: user.getSessionToken(),
|
||||
config: Config.get('test'),
|
||||
});
|
||||
expect(userAuth.user instanceof Parse.User).toBe(true);
|
||||
expect(userAuth.user.id).toBe(user.id);
|
||||
});
|
||||
|
||||
it('should load auth without a config', async () => {
|
||||
const user = new Parse.User();
|
||||
await user.signUp({
|
||||
username: 'hello',
|
||||
password: 'password',
|
||||
});
|
||||
expect(user.getSessionToken()).not.toBeUndefined();
|
||||
const userAuth = await getAuthForSessionToken({
|
||||
sessionToken: user.getSessionToken(),
|
||||
});
|
||||
expect(userAuth.user instanceof Parse.User).toBe(true);
|
||||
expect(userAuth.user.id).toBe(user.id);
|
||||
});
|
||||
|
||||
it('should load auth with a config', async () => {
|
||||
const user = new Parse.User();
|
||||
await user.signUp({
|
||||
username: 'hello',
|
||||
password: 'password',
|
||||
});
|
||||
expect(user.getSessionToken()).not.toBeUndefined();
|
||||
const userAuth = await getAuthForSessionToken({
|
||||
sessionToken: user.getSessionToken(),
|
||||
config: Config.get('test'),
|
||||
});
|
||||
expect(userAuth.user instanceof Parse.User).toBe(true);
|
||||
expect(userAuth.user.id).toBe(user.id);
|
||||
});
|
||||
|
||||
describe('getRolesForUser', () => {
|
||||
const rolesNumber = 300;
|
||||
|
||||
it('should load all roles without config', async () => {
|
||||
const user = new Parse.User();
|
||||
await user.signUp({
|
||||
username: 'hello',
|
||||
password: 'password',
|
||||
});
|
||||
expect(user.getSessionToken()).not.toBeUndefined();
|
||||
const userAuth = await getAuthForSessionToken({
|
||||
sessionToken: user.getSessionToken(),
|
||||
});
|
||||
const roles = [];
|
||||
for (let i = 0; i < rolesNumber; i++) {
|
||||
const acl = new Parse.ACL();
|
||||
const role = new Parse.Role('roleloadtest' + i, acl);
|
||||
role.getUsers().add([user]);
|
||||
roles.push(role.save());
|
||||
}
|
||||
const savedRoles = await Promise.all(roles);
|
||||
expect(savedRoles.length).toBe(rolesNumber);
|
||||
const cloudRoles = await userAuth.getRolesForUser();
|
||||
expect(cloudRoles.length).toBe(rolesNumber);
|
||||
});
|
||||
|
||||
it('should load all roles with config', async () => {
|
||||
const user = new Parse.User();
|
||||
await user.signUp({
|
||||
username: 'hello',
|
||||
password: 'password',
|
||||
});
|
||||
expect(user.getSessionToken()).not.toBeUndefined();
|
||||
const userAuth = await getAuthForSessionToken({
|
||||
sessionToken: user.getSessionToken(),
|
||||
config: Config.get('test'),
|
||||
});
|
||||
const roles = [];
|
||||
for (let i = 0; i < rolesNumber; i++) {
|
||||
const acl = new Parse.ACL();
|
||||
const role = new Parse.Role('roleloadtest' + i, acl);
|
||||
role.getUsers().add([user]);
|
||||
roles.push(role.save());
|
||||
}
|
||||
const savedRoles = await Promise.all(roles);
|
||||
expect(savedRoles.length).toBe(rolesNumber);
|
||||
const cloudRoles = await userAuth.getRolesForUser();
|
||||
expect(cloudRoles.length).toBe(rolesNumber);
|
||||
});
|
||||
|
||||
it('should load all roles for different users with config', async () => {
|
||||
const rolesNumber = 100;
|
||||
const user = new Parse.User();
|
||||
await user.signUp({
|
||||
username: 'hello',
|
||||
password: 'password',
|
||||
});
|
||||
const user2 = new Parse.User();
|
||||
await user2.signUp({
|
||||
username: 'world',
|
||||
password: '1234',
|
||||
});
|
||||
expect(user.getSessionToken()).not.toBeUndefined();
|
||||
const userAuth = await getAuthForSessionToken({
|
||||
sessionToken: user.getSessionToken(),
|
||||
config: Config.get('test'),
|
||||
});
|
||||
const user2Auth = await getAuthForSessionToken({
|
||||
sessionToken: user2.getSessionToken(),
|
||||
config: Config.get('test'),
|
||||
});
|
||||
const roles = [];
|
||||
for (let i = 0; i < rolesNumber; i += 1) {
|
||||
const acl = new Parse.ACL();
|
||||
const acl2 = new Parse.ACL();
|
||||
const role = new Parse.Role('roleloadtest' + i, acl);
|
||||
const role2 = new Parse.Role('role2loadtest' + i, acl2);
|
||||
role.getUsers().add([user]);
|
||||
role2.getUsers().add([user2]);
|
||||
roles.push(role.save());
|
||||
roles.push(role2.save());
|
||||
}
|
||||
const savedRoles = await Promise.all(roles);
|
||||
expect(savedRoles.length).toBe(rolesNumber * 2);
|
||||
const cloudRoles = await userAuth.getRolesForUser();
|
||||
const cloudRoles2 = await user2Auth.getRolesForUser();
|
||||
expect(cloudRoles.length).toBe(rolesNumber);
|
||||
expect(cloudRoles2.length).toBe(rolesNumber);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+980
-205
File diff suppressed because it is too large
Load Diff
+181
-50
@@ -1,15 +1,18 @@
|
||||
'use strict';
|
||||
import commander from '../src/cli/utils/commander';
|
||||
import definitions from '../src/cli/definitions/parse-server';
|
||||
import liveQueryDefinitions from '../src/cli/definitions/parse-live-query-server';
|
||||
const commander = require('../lib/cli/utils/commander').default;
|
||||
const definitions = require('../lib/cli/definitions/parse-server').default;
|
||||
const liveQueryDefinitions = require('../lib/cli/definitions/parse-live-query-server')
|
||||
.default;
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
const testDefinitions = {
|
||||
'arg0': 'PROGRAM_ARG_0',
|
||||
'arg1': {
|
||||
arg0: 'PROGRAM_ARG_0',
|
||||
arg1: {
|
||||
env: 'PROGRAM_ARG_1',
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
'arg2': {
|
||||
arg2: {
|
||||
env: 'PROGRAM_ARG_2',
|
||||
action: function(value) {
|
||||
const intValue = parseInt(value);
|
||||
@@ -17,16 +20,16 @@ const testDefinitions = {
|
||||
throw 'arg2 is invalid';
|
||||
}
|
||||
return intValue;
|
||||
}
|
||||
},
|
||||
},
|
||||
arg3: {},
|
||||
arg4: {
|
||||
default: 'arg4Value',
|
||||
},
|
||||
'arg3': {},
|
||||
'arg4': {
|
||||
default: 'arg4Value'
|
||||
}
|
||||
};
|
||||
|
||||
describe('commander additions', () => {
|
||||
afterEach((done) => {
|
||||
afterEach(done => {
|
||||
commander.options = [];
|
||||
delete commander.arg0;
|
||||
delete commander.arg1;
|
||||
@@ -36,9 +39,20 @@ describe('commander additions', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should load properly definitions from args', (done) => {
|
||||
it('should load properly definitions from args', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', '--arg1', 'arg1Value', '--arg2', '2', '--arg3', 'some']);
|
||||
commander.parse([
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'--arg0',
|
||||
'arg0Value',
|
||||
'--arg1',
|
||||
'arg1Value',
|
||||
'--arg2',
|
||||
'2',
|
||||
'--arg3',
|
||||
'some',
|
||||
]);
|
||||
expect(commander.arg0).toEqual('arg0Value');
|
||||
expect(commander.arg1).toEqual('arg1Value');
|
||||
expect(commander.arg2).toEqual(2);
|
||||
@@ -47,12 +61,12 @@ describe('commander additions', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should load properly definitions from env', (done) => {
|
||||
it('should load properly definitions from env', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse([], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
'PROGRAM_ARG_2': '3',
|
||||
PROGRAM_ARG_0: 'arg0ENVValue',
|
||||
PROGRAM_ARG_1: 'arg1ENVValue',
|
||||
PROGRAM_ARG_2: '3',
|
||||
});
|
||||
expect(commander.arg0).toEqual('arg0ENVValue');
|
||||
expect(commander.arg1).toEqual('arg1ENVValue');
|
||||
@@ -61,14 +75,17 @@ describe('commander additions', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should load properly use args over env', (done) => {
|
||||
it('should load properly use args over env', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', '--arg4', ''], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
'PROGRAM_ARG_2': '4',
|
||||
'PROGRAM_ARG_4': 'arg4ENVValue'
|
||||
});
|
||||
commander.parse(
|
||||
['node', './CLI.spec.js', '--arg0', 'arg0Value', '--arg4', ''],
|
||||
{
|
||||
PROGRAM_ARG_0: 'arg0ENVValue',
|
||||
PROGRAM_ARG_1: 'arg1ENVValue',
|
||||
PROGRAM_ARG_2: '4',
|
||||
PROGRAM_ARG_4: 'arg4ENVValue',
|
||||
}
|
||||
);
|
||||
expect(commander.arg0).toEqual('arg0Value');
|
||||
expect(commander.arg1).toEqual('arg1ENVValue');
|
||||
expect(commander.arg2).toEqual(4);
|
||||
@@ -76,24 +93,33 @@ describe('commander additions', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should fail in action as port is invalid', (done) => {
|
||||
it('should fail in action as port is invalid', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(()=> {
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value'], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
'PROGRAM_ARG_2': 'hello',
|
||||
expect(() => {
|
||||
commander.parse(['node', './CLI.spec.js', '--arg0', 'arg0Value'], {
|
||||
PROGRAM_ARG_0: 'arg0ENVValue',
|
||||
PROGRAM_ARG_1: 'arg1ENVValue',
|
||||
PROGRAM_ARG_2: 'hello',
|
||||
});
|
||||
}).toThrow('arg2 is invalid');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not override config.json', (done) => {
|
||||
it('should not override config.json', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', './spec/configs/CLIConfig.json'], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
});
|
||||
commander.parse(
|
||||
[
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'--arg0',
|
||||
'arg0Value',
|
||||
'./spec/configs/CLIConfig.json',
|
||||
],
|
||||
{
|
||||
PROGRAM_ARG_0: 'arg0ENVValue',
|
||||
PROGRAM_ARG_1: 'arg1ENVValue',
|
||||
}
|
||||
);
|
||||
const options = commander.getOptions();
|
||||
expect(options.arg2).toBe(8888);
|
||||
expect(options.arg3).toBe('hello'); //config value
|
||||
@@ -101,28 +127,45 @@ describe('commander additions', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should fail with invalid values in JSON', (done) => {
|
||||
it('should fail with invalid values in JSON', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', './spec/configs/CLIConfigFail.json'], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
});
|
||||
commander.parse(
|
||||
[
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'--arg0',
|
||||
'arg0Value',
|
||||
'./spec/configs/CLIConfigFail.json',
|
||||
],
|
||||
{
|
||||
PROGRAM_ARG_0: 'arg0ENVValue',
|
||||
PROGRAM_ARG_1: 'arg1ENVValue',
|
||||
}
|
||||
);
|
||||
}).toThrow('arg2 is invalid');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should fail when too many apps are set', (done) => {
|
||||
it('should fail when too many apps are set', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(['node','./CLI.spec.js','./spec/configs/CLIConfigFailTooManyApps.json']);
|
||||
commander.parse([
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'./spec/configs/CLIConfigFailTooManyApps.json',
|
||||
]);
|
||||
}).toThrow('Multiple apps are not supported');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should load config from apps', (done) => {
|
||||
it('should load config from apps', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigApps.json']);
|
||||
commander.parse([
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'./spec/configs/CLIConfigApps.json',
|
||||
]);
|
||||
const options = commander.getOptions();
|
||||
expect(options.arg1).toBe('my_app');
|
||||
expect(options.arg2).toBe(8888);
|
||||
@@ -131,10 +174,14 @@ describe('commander additions', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should fail when passing an invalid arguement', (done) => {
|
||||
it('should fail when passing an invalid arguement', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigUnknownArg.json']);
|
||||
commander.parse([
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'./spec/configs/CLIConfigUnknownArg.json',
|
||||
]);
|
||||
}).toThrow('error: unknown option myArg');
|
||||
done();
|
||||
});
|
||||
@@ -160,7 +207,7 @@ describe('definitions', () => {
|
||||
|
||||
it('should throw when using deprecated facebookAppIds', () => {
|
||||
expect(() => {
|
||||
definitions.facebookAppIds.action()
|
||||
definitions.facebookAppIds.action();
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -173,7 +220,10 @@ describe('LiveQuery definitions', () => {
|
||||
if (typeof definition.env !== 'undefined') {
|
||||
expect(typeof definition.env).toBe('string');
|
||||
}
|
||||
expect(typeof definition.help).toBe('string');
|
||||
expect(typeof definition.help).toBe(
|
||||
'string',
|
||||
`help for ${key} should be a string`
|
||||
);
|
||||
if (typeof definition.required !== 'undefined') {
|
||||
expect(typeof definition.required).toBe('boolean');
|
||||
}
|
||||
@@ -183,3 +233,84 @@ describe('LiveQuery definitions', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('execution', () => {
|
||||
const binPath = path.resolve(__dirname, '../bin/parse-server');
|
||||
let childProcess;
|
||||
|
||||
afterEach(async () => {
|
||||
if (childProcess) {
|
||||
childProcess.kill();
|
||||
}
|
||||
});
|
||||
|
||||
it('shoud start Parse Server', done => {
|
||||
childProcess = spawn(binPath, [
|
||||
'--appId',
|
||||
'test',
|
||||
'--masterKey',
|
||||
'test',
|
||||
'--databaseURI',
|
||||
'mongodb://localhost/test',
|
||||
]);
|
||||
childProcess.stdout.on('data', data => {
|
||||
data = data.toString();
|
||||
if (data.includes('parse-server running on')) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
childProcess.stderr.on('data', data => {
|
||||
done.fail(data.toString());
|
||||
});
|
||||
});
|
||||
|
||||
it('shoud start Parse Server with GraphQL', done => {
|
||||
childProcess = spawn(binPath, [
|
||||
'--appId',
|
||||
'test',
|
||||
'--masterKey',
|
||||
'test',
|
||||
'--databaseURI',
|
||||
'mongodb://localhost/test',
|
||||
'--mountGraphQL',
|
||||
]);
|
||||
let output = '';
|
||||
childProcess.stdout.on('data', data => {
|
||||
data = data.toString();
|
||||
output += data;
|
||||
if (data.includes('GraphQL running on')) {
|
||||
expect(output).toMatch('parse-server running on');
|
||||
done();
|
||||
}
|
||||
});
|
||||
childProcess.stderr.on('data', data => {
|
||||
done.fail(data.toString());
|
||||
});
|
||||
});
|
||||
|
||||
it('shoud start Parse Server with GraphQL and Playground', done => {
|
||||
childProcess = spawn(binPath, [
|
||||
'--appId',
|
||||
'test',
|
||||
'--masterKey',
|
||||
'test',
|
||||
'--databaseURI',
|
||||
'mongodb://localhost/test',
|
||||
'--mountGraphQL',
|
||||
'--mountPlayground',
|
||||
]);
|
||||
let output = '';
|
||||
childProcess.stdout.on('data', data => {
|
||||
data = data.toString();
|
||||
output += data;
|
||||
if (data.includes('Playground running on')) {
|
||||
expect(output).toMatch('GraphQL running on');
|
||||
expect(output).toMatch('parse-server running on');
|
||||
done();
|
||||
}
|
||||
});
|
||||
childProcess.stderr.on('data', data => {
|
||||
done.fail(data.toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const CacheController = require('../src/Controllers/CacheController.js').default;
|
||||
const CacheController = require('../lib/Controllers/CacheController.js')
|
||||
.default;
|
||||
|
||||
describe('CacheController', function() {
|
||||
let FakeCacheAdapter;
|
||||
@@ -10,14 +11,13 @@ describe('CacheController', function() {
|
||||
get: () => Promise.resolve(null),
|
||||
put: jasmine.createSpy('put'),
|
||||
del: jasmine.createSpy('del'),
|
||||
clear: jasmine.createSpy('clear')
|
||||
}
|
||||
clear: jasmine.createSpy('clear'),
|
||||
};
|
||||
|
||||
spyOn(FakeCacheAdapter, 'get').and.callThrough();
|
||||
});
|
||||
|
||||
|
||||
it('should expose role and user caches', (done) => {
|
||||
it('should expose role and user caches', done => {
|
||||
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
|
||||
expect(cache.role).not.toEqual(null);
|
||||
@@ -28,8 +28,7 @@ describe('CacheController', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
['role', 'user'].forEach((cacheName) => {
|
||||
['role', 'user'].forEach(cacheName => {
|
||||
it('should prefix ' + cacheName + ' cache', () => {
|
||||
const cache = new CacheController(FakeCacheAdapter, FakeAppID)[cacheName];
|
||||
|
||||
@@ -60,8 +59,7 @@ describe('CacheController', function() {
|
||||
expect(FakeCacheAdapter.clear.calls.count()).toEqual(3);
|
||||
});
|
||||
|
||||
it('should handle cache rejections', (done) => {
|
||||
|
||||
it('should handle cache rejections', done => {
|
||||
FakeCacheAdapter.get = () => Promise.reject();
|
||||
|
||||
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
@@ -70,5 +68,4 @@ describe('CacheController', function() {
|
||||
fail('Promise should not be rejected.');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+45
-39
@@ -1,5 +1,6 @@
|
||||
const Client = require('../src/LiveQuery/Client').Client;
|
||||
const ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
|
||||
const Client = require('../lib/LiveQuery/Client').Client;
|
||||
const ParseWebSocket = require('../lib/LiveQuery/ParseWebSocketServer')
|
||||
.ParseWebSocket;
|
||||
|
||||
describe('Client', function() {
|
||||
it('can be initialized', function() {
|
||||
@@ -13,7 +14,7 @@ describe('Client', function() {
|
||||
|
||||
it('can push response', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
Client.pushResponse(parseWebSocket, 'message');
|
||||
|
||||
@@ -22,7 +23,7 @@ describe('Client', function() {
|
||||
|
||||
it('can push error', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
Client.pushError(parseWebSocket, 1, 'error', true);
|
||||
|
||||
@@ -37,10 +38,10 @@ describe('Client', function() {
|
||||
it('can add subscription information', function() {
|
||||
const subscription = {};
|
||||
const fields = ['test'];
|
||||
const subscriptionInfo = {
|
||||
const subscriptionInfo = {
|
||||
subscription: subscription,
|
||||
fields: fields
|
||||
}
|
||||
fields: fields,
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
client.addSubscriptionInfo(1, subscriptionInfo);
|
||||
|
||||
@@ -51,10 +52,10 @@ describe('Client', function() {
|
||||
it('can get subscription information', function() {
|
||||
const subscription = {};
|
||||
const fields = ['test'];
|
||||
const subscriptionInfo = {
|
||||
const subscriptionInfo = {
|
||||
subscription: subscription,
|
||||
fields: fields
|
||||
}
|
||||
fields: fields,
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
client.addSubscriptionInfo(1, subscriptionInfo);
|
||||
const subscriptionInfoAgain = client.getSubscriptionInfo(1);
|
||||
@@ -65,10 +66,10 @@ describe('Client', function() {
|
||||
it('can delete subscription information', function() {
|
||||
const subscription = {};
|
||||
const fields = ['test'];
|
||||
const subscriptionInfo = {
|
||||
const subscriptionInfo = {
|
||||
subscription: subscription,
|
||||
fields: fields
|
||||
}
|
||||
fields: fields,
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
client.addSubscriptionInfo(1, subscriptionInfo);
|
||||
client.deleteSubscriptionInfo(1);
|
||||
@@ -76,10 +77,9 @@ describe('Client', function() {
|
||||
expect(client.subscriptionInfos.size).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('can generate ParseObject JSON with null selected field', function() {
|
||||
const parseObjectJSON = {
|
||||
key : 'value',
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
@@ -88,12 +88,14 @@ describe('Client', function() {
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
|
||||
expect(client._toJSONWithFields(parseObjectJSON, null)).toBe(parseObjectJSON);
|
||||
expect(client._toJSONWithFields(parseObjectJSON, null)).toBe(
|
||||
parseObjectJSON
|
||||
);
|
||||
});
|
||||
|
||||
it('can generate ParseObject JSON with undefined selected field', function() {
|
||||
const parseObjectJSON = {
|
||||
key : 'value',
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
@@ -102,18 +104,20 @@ describe('Client', function() {
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
|
||||
expect(client._toJSONWithFields(parseObjectJSON, undefined)).toBe(parseObjectJSON);
|
||||
expect(client._toJSONWithFields(parseObjectJSON, undefined)).toBe(
|
||||
parseObjectJSON
|
||||
);
|
||||
});
|
||||
|
||||
it('can generate ParseObject JSON with selected fields', function() {
|
||||
const parseObjectJSON = {
|
||||
key : 'value',
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
createdAt: '2015-12-07T21:27:13.746Z',
|
||||
ACL: 'test',
|
||||
test: 'test'
|
||||
test: 'test',
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
|
||||
@@ -123,22 +127,24 @@ describe('Client', function() {
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
createdAt: '2015-12-07T21:27:13.746Z',
|
||||
ACL: 'test',
|
||||
test: 'test'
|
||||
test: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('can generate ParseObject JSON with nonexistent selected fields', function() {
|
||||
const parseObjectJSON = {
|
||||
key : 'value',
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
createdAt: '2015-12-07T21:27:13.746Z',
|
||||
ACL: 'test',
|
||||
test: 'test'
|
||||
test: 'test',
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
const limitedParseObject = client._toJSONWithFields(parseObjectJSON, ['name']);
|
||||
const limitedParseObject = client._toJSONWithFields(parseObjectJSON, [
|
||||
'name',
|
||||
]);
|
||||
|
||||
expect(limitedParseObject).toEqual({
|
||||
className: 'test',
|
||||
@@ -152,7 +158,7 @@ describe('Client', function() {
|
||||
|
||||
it('can push connect response', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushConnect();
|
||||
@@ -165,7 +171,7 @@ describe('Client', function() {
|
||||
|
||||
it('can push subscribe response', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushSubscribe(2);
|
||||
@@ -179,7 +185,7 @@ describe('Client', function() {
|
||||
|
||||
it('can push unsubscribe response', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushUnsubscribe(2);
|
||||
@@ -193,16 +199,16 @@ describe('Client', function() {
|
||||
|
||||
it('can push create response', function() {
|
||||
const parseObjectJSON = {
|
||||
key : 'value',
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
createdAt: '2015-12-07T21:27:13.746Z',
|
||||
ACL: 'test',
|
||||
test: 'test'
|
||||
test: 'test',
|
||||
};
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushCreate(2, parseObjectJSON);
|
||||
@@ -217,16 +223,16 @@ describe('Client', function() {
|
||||
|
||||
it('can push enter response', function() {
|
||||
const parseObjectJSON = {
|
||||
key : 'value',
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
createdAt: '2015-12-07T21:27:13.746Z',
|
||||
ACL: 'test',
|
||||
test: 'test'
|
||||
test: 'test',
|
||||
};
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushEnter(2, parseObjectJSON);
|
||||
@@ -241,16 +247,16 @@ describe('Client', function() {
|
||||
|
||||
it('can push update response', function() {
|
||||
const parseObjectJSON = {
|
||||
key : 'value',
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
createdAt: '2015-12-07T21:27:13.746Z',
|
||||
ACL: 'test',
|
||||
test: 'test'
|
||||
test: 'test',
|
||||
};
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushUpdate(2, parseObjectJSON);
|
||||
@@ -265,16 +271,16 @@ describe('Client', function() {
|
||||
|
||||
it('can push leave response', function() {
|
||||
const parseObjectJSON = {
|
||||
key : 'value',
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
createdAt: '2015-12-07T21:27:13.746Z',
|
||||
ACL: 'test',
|
||||
test: 'test'
|
||||
test: 'test',
|
||||
};
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushLeave(2, parseObjectJSON);
|
||||
|
||||
+27
-19
@@ -1,41 +1,49 @@
|
||||
const ClientSDK = require('../src/ClientSDK');
|
||||
const ClientSDK = require('../lib/ClientSDK');
|
||||
|
||||
describe('ClientSDK', () => {
|
||||
it('should properly parse the SDK versions', () => {
|
||||
const clientSDKFromVersion = ClientSDK.fromString;
|
||||
expect(clientSDKFromVersion('i1.1.1')).toEqual({
|
||||
sdk: 'i',
|
||||
version: '1.1.1'
|
||||
version: '1.1.1',
|
||||
});
|
||||
expect(clientSDKFromVersion('i1')).toEqual({
|
||||
sdk: 'i',
|
||||
version: '1'
|
||||
version: '1',
|
||||
});
|
||||
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
|
||||
sdk: 'apple-tv',
|
||||
version: '1.13.0'
|
||||
version: '1.13.0',
|
||||
});
|
||||
expect(clientSDKFromVersion('js1.9.0')).toEqual({
|
||||
sdk: 'js',
|
||||
version: '1.9.0'
|
||||
version: '1.9.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly sastisfy', () => {
|
||||
expect(ClientSDK.compatible({
|
||||
js: '>=1.9.0'
|
||||
})("js1.9.0")).toBe(true);
|
||||
expect(
|
||||
ClientSDK.compatible({
|
||||
js: '>=1.9.0',
|
||||
})('js1.9.0')
|
||||
).toBe(true);
|
||||
|
||||
expect(ClientSDK.compatible({
|
||||
js: '>=1.9.0'
|
||||
})("js2.0.0")).toBe(true);
|
||||
expect(
|
||||
ClientSDK.compatible({
|
||||
js: '>=1.9.0',
|
||||
})('js2.0.0')
|
||||
).toBe(true);
|
||||
|
||||
expect(ClientSDK.compatible({
|
||||
js: '>=1.9.0'
|
||||
})("js1.8.0")).toBe(false);
|
||||
expect(
|
||||
ClientSDK.compatible({
|
||||
js: '>=1.9.0',
|
||||
})('js1.8.0')
|
||||
).toBe(false);
|
||||
|
||||
expect(ClientSDK.compatible({
|
||||
js: '>=1.9.0'
|
||||
})(undefined)).toBe(true);
|
||||
})
|
||||
})
|
||||
expect(
|
||||
ClientSDK.compatible({
|
||||
js: '>=1.9.0',
|
||||
})(undefined)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
+1447
-881
File diff suppressed because it is too large
Load Diff
+157
-127
@@ -1,105 +1,119 @@
|
||||
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
const LoggerController = require('../lib/Controllers/LoggerController')
|
||||
.LoggerController;
|
||||
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
|
||||
.WinstonLoggerAdapter;
|
||||
const fs = require('fs');
|
||||
const Config = require('../lib/Config');
|
||||
|
||||
const loremFile = __dirname + '/support/lorem.txt';
|
||||
|
||||
describe("Cloud Code Logger", () => {
|
||||
describe('Cloud Code Logger', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(done => {
|
||||
let spy;
|
||||
beforeEach(async () => {
|
||||
Parse.User.enableUnsafeCurrentUser();
|
||||
return reconfigureServer({
|
||||
// useful to flip to false for fine tuning :).
|
||||
silent: true,
|
||||
}).then(() => {
|
||||
return Parse.User.signUp('tester', 'abc')
|
||||
.then(loggedInUser => user = loggedInUser)
|
||||
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
|
||||
.then(() => done())
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.User.signUp('tester', 'abc')
|
||||
.catch(() => {})
|
||||
.then(loggedInUser => (user = loggedInUser))
|
||||
.then(() => Parse.User.logIn(user.get('username'), 'abc'));
|
||||
})
|
||||
.then(() => {
|
||||
spy = spyOn(
|
||||
Config.get('test').loggerController.adapter,
|
||||
'log'
|
||||
).and.callThrough();
|
||||
});
|
||||
});
|
||||
|
||||
// Note that helpers takes care of logout.
|
||||
// see helpers.js:afterEach
|
||||
|
||||
it("should expose log to functions", done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define("loggerTest", (req, res) => {
|
||||
it('should expose log to functions', () => {
|
||||
const spy = spyOn(
|
||||
Config.get('test').loggerController,
|
||||
'log'
|
||||
).and.callThrough();
|
||||
Parse.Cloud.define('loggerTest', req => {
|
||||
req.log.info('logTest', 'info log', { info: 'some log' });
|
||||
req.log.error('logTest', 'error log', { error: 'there was an error' });
|
||||
res.success({});
|
||||
return {};
|
||||
});
|
||||
|
||||
Parse.Cloud.run('loggerTest').then(() => {
|
||||
return logController.getLogs({ from: Date.now() - 500, size: 1000 });
|
||||
}).then((res) => {
|
||||
expect(res.length).not.toBe(0);
|
||||
const lastLogs = res.slice(0, 3);
|
||||
const cloudFunctionMessage = lastLogs[0];
|
||||
const errorMessage = lastLogs[1];
|
||||
const infoMessage = lastLogs[2];
|
||||
expect(cloudFunctionMessage.level).toBe('info');
|
||||
expect(cloudFunctionMessage.params).toEqual({});
|
||||
expect(cloudFunctionMessage.message).toMatch(/Ran cloud function loggerTest for user [^ ]* with:\n {2}Input: {}\n {2}Result: {}/);
|
||||
expect(cloudFunctionMessage.functionName).toEqual('loggerTest');
|
||||
expect(errorMessage.level).toBe('error');
|
||||
expect(errorMessage.error).toBe('there was an error');
|
||||
expect(errorMessage.message).toBe('logTest error log');
|
||||
expect(infoMessage.level).toBe('info');
|
||||
expect(infoMessage.info).toBe('some log');
|
||||
expect(infoMessage.message).toBe('logTest info log');
|
||||
done();
|
||||
return Parse.Cloud.run('loggerTest').then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
const cloudFunctionMessage = spy.calls.all()[2];
|
||||
const errorMessage = spy.calls.all()[1];
|
||||
const infoMessage = spy.calls.all()[0];
|
||||
expect(cloudFunctionMessage.args[0]).toBe('info');
|
||||
expect(cloudFunctionMessage.args[1][1].params).toEqual({});
|
||||
expect(cloudFunctionMessage.args[1][0]).toMatch(
|
||||
/Ran cloud function loggerTest for user [^ ]* with:\n {2}Input: {}\n {2}Result: {}/
|
||||
);
|
||||
expect(cloudFunctionMessage.args[1][1].functionName).toEqual(
|
||||
'loggerTest'
|
||||
);
|
||||
expect(errorMessage.args[0]).toBe('error');
|
||||
expect(errorMessage.args[1][2].error).toBe('there was an error');
|
||||
expect(errorMessage.args[1][0]).toBe('logTest');
|
||||
expect(errorMessage.args[1][1]).toBe('error log');
|
||||
expect(infoMessage.args[0]).toBe('info');
|
||||
expect(infoMessage.args[1][2].info).toBe('some log');
|
||||
expect(infoMessage.args[1][0]).toBe('logTest');
|
||||
expect(infoMessage.args[1][1]).toBe('info log');
|
||||
});
|
||||
});
|
||||
|
||||
it('trigger should obfuscate password', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.beforeSave(Parse.User, (req, res) => {
|
||||
res.success(req.object);
|
||||
Parse.Cloud.beforeSave(Parse.User, req => {
|
||||
return req.object;
|
||||
});
|
||||
|
||||
Parse.User.signUp('tester123', 'abc')
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((res) => {
|
||||
const entry = res[0];
|
||||
expect(entry.message).not.toMatch(/password":"abc/);
|
||||
expect(entry.message).toMatch(/\*\*\*\*\*\*\*\*/);
|
||||
.then(() => {
|
||||
const entry = spy.calls.mostRecent().args;
|
||||
expect(entry[1]).not.toMatch(/password":"abc/);
|
||||
expect(entry[1]).toMatch(/\*\*\*\*\*\*\*\*/);
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
|
||||
it("should expose log to trigger", (done) => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
it('should expose log to trigger', done => {
|
||||
Parse.Cloud.beforeSave('MyObject', req => {
|
||||
req.log.info('beforeSave MyObject', 'info log', { info: 'some log' });
|
||||
req.log.error('beforeSave MyObject', 'error log', { error: 'there was an error' });
|
||||
res.success({});
|
||||
req.log.error('beforeSave MyObject', 'error log', {
|
||||
error: 'there was an error',
|
||||
});
|
||||
return {};
|
||||
});
|
||||
|
||||
const obj = new Parse.Object('MyObject');
|
||||
obj.save().then(() => {
|
||||
return logController.getLogs({ from: Date.now() - 500, size: 1000 })
|
||||
}).then((res) => {
|
||||
expect(res.length).not.toBe(0);
|
||||
const lastLogs = res.slice(0, 3);
|
||||
const cloudTriggerMessage = lastLogs[0];
|
||||
const errorMessage = lastLogs[1];
|
||||
const infoMessage = lastLogs[2];
|
||||
expect(cloudTriggerMessage.level).toBe('info');
|
||||
expect(cloudTriggerMessage.triggerType).toEqual('beforeSave');
|
||||
expect(cloudTriggerMessage.message).toMatch(/beforeSave triggered for MyObject for user [^ ]*\n {2}Input: {}\n {2}Result: {}/);
|
||||
expect(cloudTriggerMessage.user).toBe(user.id);
|
||||
expect(errorMessage.level).toBe('error');
|
||||
expect(errorMessage.error).toBe('there was an error');
|
||||
expect(errorMessage.message).toBe('beforeSave MyObject error log');
|
||||
expect(infoMessage.level).toBe('info');
|
||||
expect(infoMessage.info).toBe('some log');
|
||||
expect(infoMessage.message).toBe('beforeSave MyObject info log');
|
||||
const lastCalls = spy.calls.all().reverse();
|
||||
const cloudTriggerMessage = lastCalls[0].args;
|
||||
const errorMessage = lastCalls[1].args;
|
||||
const infoMessage = lastCalls[2].args;
|
||||
expect(cloudTriggerMessage[0]).toBe('info');
|
||||
expect(cloudTriggerMessage[2].triggerType).toEqual('beforeSave');
|
||||
expect(cloudTriggerMessage[1]).toMatch(
|
||||
/beforeSave triggered for MyObject for user [^ ]*\n {2}Input: {}\n {2}Result: {"object":{}}/
|
||||
);
|
||||
expect(cloudTriggerMessage[2].user).toBe(user.id);
|
||||
expect(errorMessage[0]).toBe('error');
|
||||
expect(errorMessage[3].error).toBe('there was an error');
|
||||
expect(errorMessage[1] + ' ' + errorMessage[2]).toBe(
|
||||
'beforeSave MyObject error log'
|
||||
);
|
||||
expect(infoMessage[0]).toBe('info');
|
||||
expect(infoMessage[3].info).toBe('some log');
|
||||
expect(infoMessage[1] + ' ' + infoMessage[2]).toBe(
|
||||
'beforeSave MyObject info log'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -112,43 +126,39 @@ describe("Cloud Code Logger", () => {
|
||||
});
|
||||
|
||||
it('should truncate input and result of long lines', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
const longString = fs.readFileSync(loremFile, 'utf8');
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.success(req.params);
|
||||
Parse.Cloud.define('aFunction', req => {
|
||||
return req.params;
|
||||
});
|
||||
|
||||
Parse.Cloud.run('aFunction', { longString })
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[0];
|
||||
expect(log.level).toEqual('info');
|
||||
expect(log.message).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {.*?\(truncated\)$/m);
|
||||
.then(() => {
|
||||
const log = spy.calls.mostRecent().args;
|
||||
expect(log[0]).toEqual('info');
|
||||
expect(log[1]).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {.*?\(truncated\)$/m
|
||||
);
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should log an afterSave', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
Parse.Cloud.afterSave("MyObject", () => { });
|
||||
Parse.Cloud.afterSave('MyObject', () => {});
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((logs) => {
|
||||
const log = logs[0];
|
||||
expect(log.triggerType).toEqual('afterSave');
|
||||
.then(() => {
|
||||
const log = spy.calls.mostRecent().args;
|
||||
expect(log[2].triggerType).toEqual('afterSave');
|
||||
done();
|
||||
})
|
||||
// catch errors - not that the error is actually useful :(
|
||||
// catch errors - not that the error is actually useful :(
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should log a denied beforeSave', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
res.error('uh oh!');
|
||||
Parse.Cloud.beforeSave('MyObject', () => {
|
||||
throw 'uh oh!';
|
||||
});
|
||||
|
||||
new Parse.Object('MyObject')
|
||||
@@ -157,58 +167,64 @@ describe("Cloud Code Logger", () => {
|
||||
() => done.fail('this is not supposed to succeed'),
|
||||
() => new Promise(resolve => setTimeout(resolve, 100))
|
||||
)
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[1]; // 0 is the 'uh oh!' from rejection...
|
||||
expect(log.level).toEqual('error');
|
||||
expect(log.error).toEqual({ code: 141, message: 'uh oh!' });
|
||||
done()
|
||||
.then(() => {
|
||||
const logs = spy.calls.all().reverse();
|
||||
const log = logs[1].args; // 0 is the 'uh oh!' from rejection...
|
||||
expect(log[0]).toEqual('error');
|
||||
const error = log[2].error;
|
||||
expect(error instanceof Parse.Error).toBeTruthy();
|
||||
expect(error.code).toBe(141);
|
||||
expect(error.message).toBe('uh oh!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should log cloud function success', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.success('it worked!');
|
||||
Parse.Cloud.define('aFunction', () => {
|
||||
return 'it worked!';
|
||||
});
|
||||
|
||||
Parse.Cloud.run('aFunction', { foo: 'bar' })
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[0];
|
||||
expect(log.level).toEqual('info');
|
||||
expect(log.message).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Result: "it worked!/);
|
||||
done();
|
||||
});
|
||||
Parse.Cloud.run('aFunction', { foo: 'bar' }).then(() => {
|
||||
const log = spy.calls.mostRecent().args;
|
||||
expect(log[0]).toEqual('info');
|
||||
expect(log[1]).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Result: "it worked!/
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should log cloud function failure', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.error('it failed!');
|
||||
Parse.Cloud.define('aFunction', () => {
|
||||
throw 'it failed!';
|
||||
});
|
||||
|
||||
Parse.Cloud.run('aFunction', { foo: 'bar' })
|
||||
.then(null, () => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[2];
|
||||
expect(log.level).toEqual('error');
|
||||
expect(log.message).toMatch(
|
||||
/Failed running cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Error: {"code":141,"message":"it failed!"}/);
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
const logs = spy.calls.all().reverse();
|
||||
expect(logs[0].args[1]).toBe('Parse error: ');
|
||||
expect(logs[0].args[2].message).toBe('it failed!');
|
||||
|
||||
const log = logs[1].args;
|
||||
expect(log[0]).toEqual('error');
|
||||
expect(log[1]).toMatch(
|
||||
/Failed running cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Error:/
|
||||
);
|
||||
const errorString = JSON.stringify(new Parse.Error(141, 'it failed!'));
|
||||
expect(log[1].indexOf(errorString)).toBeGreaterThan(0);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
xit('should log a changed beforeSave indicating a change', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
Parse.Cloud.beforeSave('MyObject', req => {
|
||||
const myObj = req.object;
|
||||
myObj.set('aChange', true);
|
||||
res.success(myObj);
|
||||
return myObj;
|
||||
});
|
||||
|
||||
new Parse.Object('MyObject')
|
||||
@@ -228,19 +244,33 @@ describe("Cloud Code Logger", () => {
|
||||
}).pend('needs more work.....');
|
||||
|
||||
it('cloud function should obfuscate password', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define('testFunction', (req, res) => {
|
||||
res.success(1002,'verify code success');
|
||||
Parse.Cloud.define('testFunction', () => {
|
||||
return 'verify code success';
|
||||
});
|
||||
|
||||
Parse.Cloud.run('testFunction', {username:'hawk',password:'123456'})
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((res) => {
|
||||
const entry = res[0];
|
||||
expect(entry.params.password).toMatch(/\*\*\*\*\*\*\*\*/);
|
||||
Parse.Cloud.run('testFunction', { username: 'hawk', password: '123456' })
|
||||
.then(() => {
|
||||
const entry = spy.calls.mostRecent().args;
|
||||
expect(entry[2].params.password).toMatch(/\*\*\*\*\*\*\*\*/);
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should only log once for object not found', async () => {
|
||||
const config = Config.get('test');
|
||||
const spy = spyOn(config.loggerController, 'error').and.callThrough();
|
||||
try {
|
||||
const object = new Parse.Object('Object');
|
||||
object.id = 'invalid';
|
||||
await object.fetch();
|
||||
} catch (e) {
|
||||
/**/
|
||||
}
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy.calls.count()).toBe(1);
|
||||
const { args } = spy.calls.mostRecent();
|
||||
expect(args[0]).toBe('Parse error: ');
|
||||
expect(args[1].message).toBe('Object not found.');
|
||||
});
|
||||
});
|
||||
|
||||
+108
-39
@@ -1,51 +1,120 @@
|
||||
const DatabaseController = require('../src/Controllers/DatabaseController.js');
|
||||
const DatabaseController = require('../lib/Controllers/DatabaseController.js');
|
||||
const validateQuery = DatabaseController._validateQuery;
|
||||
|
||||
describe('DatabaseController', function() {
|
||||
|
||||
describe('validateQuery', function() {
|
||||
describe('with skipMongoDBServer13732Workaround disabled (the default)', function() {
|
||||
it('should restructure simple cases of SERVER-13732', done => {
|
||||
const query = {
|
||||
$or: [{ a: 1 }, { a: 2 }],
|
||||
_rperm: { $in: ['a', 'b'] },
|
||||
foo: 3,
|
||||
};
|
||||
validateQuery(query, false);
|
||||
expect(query).toEqual({
|
||||
$or: [
|
||||
{ a: 1, _rperm: { $in: ['a', 'b'] }, foo: 3 },
|
||||
{ a: 2, _rperm: { $in: ['a', 'b'] }, foo: 3 },
|
||||
],
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('should restructure simple cases of SERVER-13732', (done) => {
|
||||
const query = {$or: [{a: 1}, {a: 2}], _rperm: {$in: ['a', 'b']}, foo: 3};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{a: 1, _rperm: {$in: ['a', 'b']}, foo: 3},
|
||||
{a: 2, _rperm: {$in: ['a', 'b']}, foo: 3}]});
|
||||
done();
|
||||
it('should not restructure SERVER-13732 queries with $nears', done => {
|
||||
let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } };
|
||||
validateQuery(query, false);
|
||||
expect(query).toEqual({
|
||||
$or: [{ a: 1 }, { b: 1 }],
|
||||
c: { $nearSphere: {} },
|
||||
});
|
||||
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
|
||||
validateQuery(query, false);
|
||||
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
|
||||
done();
|
||||
});
|
||||
|
||||
it('should push refactored keys down a tree for SERVER-13732', done => {
|
||||
const query = {
|
||||
a: 1,
|
||||
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
|
||||
};
|
||||
validateQuery(query, false);
|
||||
expect(query).toEqual({
|
||||
$or: [
|
||||
{ $or: [{ b: 1, a: 1 }, { b: 2, a: 1 }] },
|
||||
{ $or: [{ c: 1, a: 1 }, { c: 2, a: 1 }] },
|
||||
],
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('should reject invalid queries', done => {
|
||||
expect(() => validateQuery({ $or: { a: 1 } }, false)).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should accept valid queries', done => {
|
||||
expect(() =>
|
||||
validateQuery({ $or: [{ a: 1 }, { b: 2 }] }, false)
|
||||
).not.toThrow();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not restructure SERVER-13732 queries with $nears', (done) => {
|
||||
let query = {$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}});
|
||||
describe('with skipMongoDBServer13732Workaround enabled', function() {
|
||||
it('should not restructure simple cases of SERVER-13732', done => {
|
||||
const query = {
|
||||
$or: [{ a: 1 }, { a: 2 }],
|
||||
_rperm: { $in: ['a', 'b'] },
|
||||
foo: 3,
|
||||
};
|
||||
validateQuery(query, true);
|
||||
expect(query).toEqual({
|
||||
$or: [{ a: 1 }, { a: 2 }],
|
||||
_rperm: { $in: ['a', 'b'] },
|
||||
foo: 3,
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
query = {$or: [{a: 1}, {b: 1}], c: {$near: {}}};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$near: {}}});
|
||||
it('should not restructure SERVER-13732 queries with $nears', done => {
|
||||
let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } };
|
||||
validateQuery(query, true);
|
||||
expect(query).toEqual({
|
||||
$or: [{ a: 1 }, { b: 1 }],
|
||||
c: { $nearSphere: {} },
|
||||
});
|
||||
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
|
||||
validateQuery(query, true);
|
||||
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
|
||||
done();
|
||||
});
|
||||
|
||||
done();
|
||||
it('should not push refactored keys down a tree for SERVER-13732', done => {
|
||||
const query = {
|
||||
a: 1,
|
||||
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
|
||||
};
|
||||
validateQuery(query, true);
|
||||
expect(query).toEqual({
|
||||
a: 1,
|
||||
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should reject invalid queries', done => {
|
||||
expect(() => validateQuery({ $or: { a: 1 } }, true)).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should accept valid queries', done => {
|
||||
expect(() =>
|
||||
validateQuery({ $or: [{ a: 1 }, { b: 2 }] }, true)
|
||||
).not.toThrow();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should push refactored keys down a tree for SERVER-13732', (done) => {
|
||||
const query = {a: 1, $or: [{$or: [{b: 1}, {b: 2}]},
|
||||
{$or: [{c: 1}, {c: 2}]}]};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{$or: [{b: 1, a: 1}, {b: 2, a: 1}]},
|
||||
{$or: [{c: 1, a: 1}, {c: 2, a: 1}]}]});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should reject invalid queries', (done) => {
|
||||
expect(() => validateQuery({$or: {'a': 1}})).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should accept valid queries', (done) => {
|
||||
expect(() => validateQuery({$or: [{'a': 1}, {'b': 2}]})).not.toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+302
-247
@@ -1,11 +1,9 @@
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const request = require('request');
|
||||
const requestp = require('request-promise');
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe("Email Verification Token Expiration: ", () => {
|
||||
const Config = require('../lib/Config');
|
||||
const request = require('../lib/request');
|
||||
|
||||
describe('Email Verification Token Expiration: ', () => {
|
||||
it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => {
|
||||
const user = new Parse.User();
|
||||
let sendEmailOptions;
|
||||
@@ -14,34 +12,40 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('testEmailVerifyTokenValidity');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
// wait for 1 second - simulate user behavior to some extent
|
||||
})
|
||||
.then(() => {
|
||||
// wait for 1 second - simulate user behavior to some extent
|
||||
setTimeout(() => {
|
||||
expect(sendEmailOptions).not.toBeUndefined();
|
||||
console.log(sendEmailOptions.link);
|
||||
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test');
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
expect(response.text).toEqual(
|
||||
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test'
|
||||
);
|
||||
done();
|
||||
});
|
||||
}, 1000);
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
@@ -55,41 +59,45 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('testEmailVerifyTokenValidity');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
// wait for 1 second - simulate user behavior to some extent
|
||||
})
|
||||
.then(() => {
|
||||
// wait for 1 second - simulate user behavior to some extent
|
||||
setTimeout(() => {
|
||||
expect(sendEmailOptions).not.toBeUndefined();
|
||||
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
user.fetch()
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
user
|
||||
.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}, 1000);
|
||||
}).catch((error) => {
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
@@ -103,29 +111,34 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('testEmailVerifyTokenValidity');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
|
||||
})
|
||||
.then(() => {
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
expect(response.text).toEqual(
|
||||
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity'
|
||||
);
|
||||
done();
|
||||
});
|
||||
}).catch((error) => {
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
@@ -139,36 +152,40 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('testEmailVerifyTokenValidity');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
user.fetch()
|
||||
})
|
||||
.then(() => {
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
user
|
||||
.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(true);
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
@@ -182,37 +199,40 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('testEmailVerifyTokenValidity');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
Parse.User.logIn("testEmailVerifyTokenValidity", "expiringToken")
|
||||
})
|
||||
.then(() => {
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken')
|
||||
.then(user => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.get('emailVerified')).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
@@ -226,14 +246,14 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername('sets_email_verify_token_expires_at');
|
||||
@@ -243,7 +263,9 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
})
|
||||
.then(() => {
|
||||
const config = Config.get('test');
|
||||
return config.database.find('_User', {username: 'sets_email_verify_token_expires_at'});
|
||||
return config.database.find('_User', {
|
||||
username: 'sets_email_verify_token_expires_at',
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toBe(1);
|
||||
@@ -269,36 +291,43 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("unsets_email_verify_token_expires_at");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('unsets_email_verify_token_expires_at');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
const config = Config.get('test');
|
||||
return config.database.find('_User', {username: 'unsets_email_verify_token_expires_at'}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
return results[0];
|
||||
})
|
||||
return config.database
|
||||
.find('_User', {
|
||||
username: 'unsets_email_verify_token_expires_at',
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toBe(1);
|
||||
return results[0];
|
||||
})
|
||||
.then(user => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.emailVerified).toEqual(true);
|
||||
expect(typeof user._email_verify_token).toBe('undefined');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe('undefined');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe(
|
||||
'undefined'
|
||||
);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -321,31 +350,30 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
const serverConfig = {
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
};
|
||||
|
||||
// setup server WITHOUT enabling the expire email verify token flag
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('testEmailVerifyTokenValidity');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get(sendEmailOptions.link, { followRedirect: false, })
|
||||
.on('error', error => reject(error))
|
||||
.on('response', (response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
resolve(user.fetch());
|
||||
});
|
||||
return request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
return user.fetch();
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
@@ -355,15 +383,18 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
return reconfigureServer(serverConfig);
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
expect(response.text).toEqual(
|
||||
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity'
|
||||
);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
@@ -377,25 +408,25 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
const serverConfig = {
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
};
|
||||
|
||||
// setup server WITHOUT enabling the expire email verify token flag
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('testEmailVerifyTokenValidity');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
// just get the user again - DO NOT email verify the user
|
||||
// just get the user again - DO NOT email verify the user
|
||||
return user.fetch();
|
||||
})
|
||||
.then(() => {
|
||||
@@ -405,22 +436,24 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
return reconfigureServer(serverConfig);
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test');
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
expect(response.text).toEqual(
|
||||
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test'
|
||||
);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('setting the email on the user should set a new email verification token and new expiration date for the token when expire email verify token flag is set', done => {
|
||||
|
||||
const user = new Parse.User();
|
||||
let userBeforeEmailReset;
|
||||
|
||||
@@ -430,28 +463,30 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
sendMail: () => {},
|
||||
};
|
||||
const serverConfig = {
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
};
|
||||
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("newEmailVerifyTokenOnEmailReset");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('newEmailVerifyTokenOnEmailReset');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
const config = Config.get('test');
|
||||
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
|
||||
return results[0];
|
||||
});
|
||||
return config.database
|
||||
.find('_User', { username: 'newEmailVerifyTokenOnEmailReset' })
|
||||
.then(results => {
|
||||
return results[0];
|
||||
});
|
||||
})
|
||||
.then(userFromDb => {
|
||||
expect(typeof userFromDb).toBe('object');
|
||||
@@ -459,25 +494,31 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
|
||||
// trigger another token generation by setting the email
|
||||
user.set('email', 'user@parse.com');
|
||||
return new Promise((resolve) => {
|
||||
// wait for half a sec to get a new expiration time
|
||||
return new Promise(resolve => {
|
||||
// wait for half a sec to get a new expiration time
|
||||
setTimeout(() => resolve(user.save()), 500);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
const config = Config.get('test');
|
||||
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
|
||||
return results[0];
|
||||
});
|
||||
return config.database
|
||||
.find('_User', { username: 'newEmailVerifyTokenOnEmailReset' })
|
||||
.then(results => {
|
||||
return results[0];
|
||||
});
|
||||
})
|
||||
.then(userAfterEmailReset => {
|
||||
expect(typeof userAfterEmailReset).toBe('object');
|
||||
expect(userBeforeEmailReset._email_verify_token).not.toEqual(userAfterEmailReset._email_verify_token);
|
||||
expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(userAfterEmailReset.__email_verify_token_expires_at);
|
||||
expect(userBeforeEmailReset._email_verify_token).not.toEqual(
|
||||
userAfterEmailReset._email_verify_token
|
||||
);
|
||||
expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(
|
||||
userAfterEmailReset.__email_verify_token_expires_at
|
||||
);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
@@ -494,14 +535,14 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername('resends_verification_token');
|
||||
@@ -511,46 +552,53 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
})
|
||||
.then(() => {
|
||||
const config = Config.get('test');
|
||||
return config.database.find('_User', {username: 'resends_verification_token'}).then((results) => {
|
||||
return results[0];
|
||||
});
|
||||
return config.database
|
||||
.find('_User', { username: 'resends_verification_token' })
|
||||
.then(results => {
|
||||
return results[0];
|
||||
});
|
||||
})
|
||||
.then((newUser) => {
|
||||
.then(newUser => {
|
||||
// store this user before we make our email request
|
||||
userBeforeRequest = newUser;
|
||||
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
|
||||
return requestp.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
return request({
|
||||
url: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
method: 'POST',
|
||||
body: {
|
||||
email: 'user@parse.com'
|
||||
email: 'user@parse.com',
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false // this promise is only rejected if the call itself failed
|
||||
});
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.statusCode).toBe(200);
|
||||
.then(response => {
|
||||
expect(response.status).toBe(200);
|
||||
expect(sendVerificationEmailCallCount).toBe(2);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
|
||||
// query for this user again
|
||||
const config = Config.get('test');
|
||||
return config.database.find('_User', {username: 'resends_verification_token'}).then((results) => {
|
||||
return results[0];
|
||||
});
|
||||
return config.database
|
||||
.find('_User', { username: 'resends_verification_token' })
|
||||
.then(results => {
|
||||
return results[0];
|
||||
});
|
||||
})
|
||||
.then((userAfterRequest) => {
|
||||
.then(userAfterRequest => {
|
||||
// verify that our token & expiration has been changed for this new request
|
||||
expect(typeof userAfterRequest).toBe('object');
|
||||
expect(userBeforeRequest._email_verify_token).not.toEqual(userAfterRequest._email_verify_token);
|
||||
expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual(userAfterRequest.__email_verify_token_expires_at);
|
||||
expect(userBeforeRequest._email_verify_token).not.toEqual(
|
||||
userAfterRequest._email_verify_token
|
||||
);
|
||||
expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual(
|
||||
userAfterRequest.__email_verify_token_expires_at
|
||||
);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -569,14 +617,14 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername('no_new_verification_token_once_verified');
|
||||
@@ -585,34 +633,31 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return requestp.get({
|
||||
return request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirect: false,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
});
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
|
||||
return requestp.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
return request({
|
||||
url: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
method: 'POST',
|
||||
body: {
|
||||
email: 'user@parse.com'
|
||||
email: 'user@parse.com',
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false // this promise is only rejected if the call itself failed
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
.then(fail, res => res)
|
||||
.then(response => {
|
||||
expect(response.status).toBe(400);
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
done();
|
||||
});
|
||||
@@ -632,31 +677,32 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
return requestp.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
return request({
|
||||
url: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
method: 'POST',
|
||||
body: {
|
||||
email: 'user@parse.com'
|
||||
email: 'user@parse.com',
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
})
|
||||
.then(fail)
|
||||
.catch(response => response)
|
||||
.then(response => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.status).toBe(400);
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
@@ -677,34 +723,35 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
request.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
request({
|
||||
url: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
method: 'POST',
|
||||
body: {},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
}, (err, response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.body.code).toBe(Parse.Error.EMAIL_MISSING);
|
||||
expect(response.body.error).toBe('you must provide an email');
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.then(fail, response => response)
|
||||
.then(response => {
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.data.code).toBe(Parse.Error.EMAIL_MISSING);
|
||||
expect(response.data.error).toBe('you must provide an email');
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
@@ -721,34 +768,37 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
request.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {email: 3},
|
||||
request({
|
||||
url: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
method: 'POST',
|
||||
body: { email: 3 },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
}, (err, response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.body.code).toBe(Parse.Error.INVALID_EMAIL_ADDRESS);
|
||||
expect(response.body.error).toBe('you must provide a valid email string');
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.then(fail, res => res)
|
||||
.then(response => {
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.data.code).toBe(Parse.Error.INVALID_EMAIL_ADDRESS);
|
||||
expect(response.data.error).toBe(
|
||||
'you must provide a valid email string'
|
||||
);
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
@@ -764,27 +814,29 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('testEmailVerifyTokenValidity');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
user.fetch()
|
||||
user
|
||||
.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
|
||||
expect(typeof user.get('_email_verify_token_expires_at')).toBe(
|
||||
'undefined'
|
||||
);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
})
|
||||
@@ -792,8 +844,8 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
|
||||
}).catch((error) => {
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
@@ -807,26 +859,28 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => { }
|
||||
}
|
||||
sendMail: () => {},
|
||||
};
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.setUsername('testEmailVerifyTokenValidity');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
Parse.User.logIn("testEmailVerifyTokenValidity", "expiringToken")
|
||||
})
|
||||
.then(() => {
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken')
|
||||
.then(user => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.get('emailVerified')).toBe(true);
|
||||
@@ -840,22 +894,23 @@ describe("Email Verification Token Expiration: ", () => {
|
||||
expect(user.get('email')).toBe('newEmail@parse.com');
|
||||
expect(user.get('emailVerified')).toBe(false);
|
||||
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirects: false,
|
||||
}).then(response => {
|
||||
expect(response.status).toEqual(302);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
const ParseServer = require('../lib/index');
|
||||
const express = require('express');
|
||||
const request = require('../lib/request');
|
||||
|
||||
describe('Enable express error handler', () => {
|
||||
it('should call the default handler in case of error, like updating a non existing object', done => {
|
||||
const serverUrl = 'http://localhost:12667/parse';
|
||||
const appId = 'anOtherTestApp';
|
||||
const masterKey = 'anOtherTestMasterKey';
|
||||
let server;
|
||||
|
||||
let lastError;
|
||||
|
||||
const parseServer = ParseServer.ParseServer(
|
||||
Object.assign({}, defaultConfiguration, {
|
||||
appId: appId,
|
||||
masterKey: masterKey,
|
||||
serverURL: serverUrl,
|
||||
enableExpressErrorHandler: true,
|
||||
serverStartComplete: () => {
|
||||
expect(Parse.applicationId).toEqual('anOtherTestApp');
|
||||
const app = express();
|
||||
app.use('/parse', parseServer);
|
||||
|
||||
server = app.listen(12667);
|
||||
|
||||
app.use(function(err, req, res, next) {
|
||||
next;
|
||||
lastError = err;
|
||||
});
|
||||
|
||||
request({
|
||||
method: 'PUT',
|
||||
url: serverUrl + '/classes/AnyClass/nonExistingId',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: { someField: 'blablabla' },
|
||||
})
|
||||
.then(() => {
|
||||
fail('Should throw error');
|
||||
})
|
||||
.catch(response => {
|
||||
const reqError = response.data;
|
||||
expect(reqError).toBeDefined();
|
||||
expect(lastError).toBeDefined();
|
||||
|
||||
expect(lastError.code).toEqual(101);
|
||||
expect(lastError.message).toEqual('Object not found.');
|
||||
|
||||
expect(lastError.code).toEqual(reqError.code);
|
||||
expect(lastError.message).toEqual(reqError.error);
|
||||
})
|
||||
.then(() => {
|
||||
server.close(done);
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,46 +1,55 @@
|
||||
const auth = require('../src/Auth');
|
||||
const Config = require('../src/Config');
|
||||
const rest = require('../src/rest');
|
||||
const auth = require('../lib/Auth');
|
||||
const Config = require('../lib/Config');
|
||||
const rest = require('../lib/rest');
|
||||
|
||||
describe('Enable single schema cache', () => {
|
||||
beforeEach((done) => {
|
||||
beforeEach(done => {
|
||||
reconfigureServer({
|
||||
enableSingleSchemaCache: true,
|
||||
schemaCacheTTL: 30000
|
||||
schemaCacheTTL: 30000,
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can perform multiple create and query operations', (done) => {
|
||||
it('can perform multiple create and query operations', done => {
|
||||
let config = fakeRequestForConfig();
|
||||
let nobody = auth.nobody(config);
|
||||
rest.create(config, nobody, 'Foo', {type: 1}).then(() => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.create(config, nobody, 'Foo', {type: 2});
|
||||
}).then(() => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.create(config, nobody, 'Bar');
|
||||
}).then(() => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.find(config, nobody, 'Bar', {type: 1});
|
||||
}).then(() => {
|
||||
fail('Should throw error');
|
||||
done();
|
||||
}, (error) => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
expect(error).toBeDefined();
|
||||
return rest.find(config, nobody, 'Foo', {type: 1});
|
||||
}).then((response) => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
expect(response.results.length).toEqual(1);
|
||||
done();
|
||||
});
|
||||
rest
|
||||
.create(config, nobody, 'Foo', { type: 1 })
|
||||
.then(() => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.create(config, nobody, 'Foo', { type: 2 });
|
||||
})
|
||||
.then(() => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.create(config, nobody, 'Bar');
|
||||
})
|
||||
.then(() => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.find(config, nobody, 'Bar', { type: 1 });
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
fail('Should throw error');
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
expect(error).toBeDefined();
|
||||
return rest.find(config, nobody, 'Foo', { type: 1 });
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
expect(response.results.length).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
|
||||
describe('EventEmitterPubSub', function() {
|
||||
it('can publish and subscribe', function() {
|
||||
|
||||
@@ -1,39 +1,43 @@
|
||||
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
const GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
||||
const Config = require("../src/Config");
|
||||
const FilesController = require('../src/Controllers/FilesController').default;
|
||||
const LoggerController = require('../lib/Controllers/LoggerController')
|
||||
.LoggerController;
|
||||
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
|
||||
.WinstonLoggerAdapter;
|
||||
const GridFSBucketAdapter = require('../lib/Adapters/Files/GridFSBucketAdapter')
|
||||
.GridFSBucketAdapter;
|
||||
const Config = require('../lib/Config');
|
||||
const FilesController = require('../lib/Controllers/FilesController').default;
|
||||
|
||||
const mockAdapter = {
|
||||
createFile: () => {
|
||||
return Parse.Promise.reject(new Error('it failed'));
|
||||
return Promise.reject(new Error('it failed with xyz'));
|
||||
},
|
||||
deleteFile: () => { },
|
||||
getFileData: () => { },
|
||||
getFileLocation: () => 'xyz'
|
||||
}
|
||||
deleteFile: () => {},
|
||||
getFileData: () => {},
|
||||
getFileLocation: () => 'xyz',
|
||||
};
|
||||
|
||||
// Small additional tests to improve overall coverage
|
||||
describe("FilesController",() =>{
|
||||
it("should properly expand objects", (done) => {
|
||||
|
||||
describe('FilesController', () => {
|
||||
it('should properly expand objects', done => {
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const gridStoreAdapter = new GridStoreAdapter('mongodb://localhost:27017/parse');
|
||||
const filesController = new FilesController(gridStoreAdapter)
|
||||
const result = filesController.expandFilesInObject(config, function(){});
|
||||
const gridStoreAdapter = new GridFSBucketAdapter(
|
||||
'mongodb://localhost:27017/parse'
|
||||
);
|
||||
const filesController = new FilesController(gridStoreAdapter);
|
||||
const result = filesController.expandFilesInObject(config, function() {});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
const fullFile = {
|
||||
type: '__type',
|
||||
url: "http://an.url"
|
||||
}
|
||||
url: 'http://an.url',
|
||||
};
|
||||
|
||||
const anObject = {
|
||||
aFile: fullFile
|
||||
}
|
||||
aFile: fullFile,
|
||||
};
|
||||
filesController.expandFilesInObject(config, anObject);
|
||||
expect(anObject.aFile.url).toEqual("http://an.url");
|
||||
expect(anObject.aFile.url).toEqual('http://an.url');
|
||||
|
||||
done();
|
||||
});
|
||||
@@ -42,24 +46,76 @@ describe("FilesController",() =>{
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
reconfigureServer({ filesAdapter: mockAdapter })
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 1000)))
|
||||
.then(() => new Parse.File("yolo.txt", [1,2,3], "text/plain").save())
|
||||
.then(() => new Parse.File('yolo.txt', [1, 2, 3], 'text/plain').save())
|
||||
.then(
|
||||
() => done.fail('should not succeed'),
|
||||
() => setImmediate(() => Parse.Promise.as('done'))
|
||||
() => setImmediate(() => Promise.resolve('done'))
|
||||
)
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((logs) => {
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
|
||||
.then(() =>
|
||||
logController.getLogs({ from: Date.now() - 1000, size: 1000 })
|
||||
)
|
||||
.then(logs => {
|
||||
// we get two logs here: 1. the source of the failure to save the file
|
||||
// and 2 the message that will be sent back to the client.
|
||||
const log1 = logs.pop();
|
||||
|
||||
const log1 = logs.find(
|
||||
x => x.message === 'Error creating a file: it failed with xyz'
|
||||
);
|
||||
expect(log1.level).toBe('error');
|
||||
expect(log1.message).toBe('it failed');
|
||||
const log2 = logs.pop();
|
||||
|
||||
const log2 = logs.find(
|
||||
x => x.message === 'Parse error: Could not store file: yolo.txt.'
|
||||
);
|
||||
expect(log2.level).toBe('error');
|
||||
expect(log2.code).toBe(130);
|
||||
expect(log2.message).toBe('Could not store file.');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a unique hash to the file name when the preserveFileName option is false', done => {
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const gridStoreAdapter = new GridFSBucketAdapter(
|
||||
'mongodb://localhost:27017/parse'
|
||||
);
|
||||
spyOn(gridStoreAdapter, 'createFile');
|
||||
gridStoreAdapter.createFile.and.returnValue(Promise.resolve());
|
||||
const fileName = 'randomFileName.pdf';
|
||||
const regexEscapedFileName = fileName.replace(/\./g, '\\$&');
|
||||
const filesController = new FilesController(gridStoreAdapter, null, {
|
||||
preserveFileName: false,
|
||||
});
|
||||
|
||||
filesController.createFile(config, fileName);
|
||||
|
||||
expect(gridStoreAdapter.createFile).toHaveBeenCalledTimes(1);
|
||||
expect(gridStoreAdapter.createFile.calls.mostRecent().args[0]).toMatch(
|
||||
`^.{32}_${regexEscapedFileName}$`
|
||||
);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not add a unique hash to the file name when the preserveFileName option is true', done => {
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const gridStoreAdapter = new GridFSBucketAdapter(
|
||||
'mongodb://localhost:27017/parse'
|
||||
);
|
||||
spyOn(gridStoreAdapter, 'createFile');
|
||||
gridStoreAdapter.createFile.and.returnValue(Promise.resolve());
|
||||
const fileName = 'randomFileName.pdf';
|
||||
const filesController = new FilesController(gridStoreAdapter, null, {
|
||||
preserveFileName: true,
|
||||
});
|
||||
|
||||
filesController.createFile(config, fileName);
|
||||
|
||||
expect(gridStoreAdapter.createFile).toHaveBeenCalledTimes(1);
|
||||
expect(gridStoreAdapter.createFile.calls.mostRecent().args[0]).toEqual(
|
||||
fileName
|
||||
);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
const GridStoreAdapter = require('../lib/Adapters/Files/GridStoreAdapter')
|
||||
.GridStoreAdapter;
|
||||
const GridFSBucketAdapter = require('../lib/Adapters/Files/GridFSBucketAdapter')
|
||||
.GridFSBucketAdapter;
|
||||
const { randomString } = require('../lib/cryptoUtils');
|
||||
const databaseURI = 'mongodb://localhost:27017/parse';
|
||||
|
||||
async function expectMissingFile(gfsAdapter, name) {
|
||||
try {
|
||||
await gfsAdapter.getFileData(name);
|
||||
fail('should have thrown');
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('FileNotFound: file myFileName was not found');
|
||||
}
|
||||
}
|
||||
|
||||
describe('GridFSBucket and GridStore interop', () => {
|
||||
beforeEach(async () => {
|
||||
const gsAdapter = new GridStoreAdapter(databaseURI);
|
||||
const db = await gsAdapter._connect();
|
||||
db.dropDatabase();
|
||||
});
|
||||
|
||||
it('a file created in GridStore should be available in GridFS', async () => {
|
||||
const gsAdapter = new GridStoreAdapter(databaseURI);
|
||||
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
await expectMissingFile(gfsAdapter, 'myFileName');
|
||||
const originalString = 'abcdefghi';
|
||||
await gsAdapter.createFile('myFileName', originalString);
|
||||
const gsResult = await gsAdapter.getFileData('myFileName');
|
||||
expect(gsResult.toString('utf8')).toBe(originalString);
|
||||
const gfsResult = await gfsAdapter.getFileData('myFileName');
|
||||
expect(gfsResult.toString('utf8')).toBe(originalString);
|
||||
});
|
||||
|
||||
it('properly fetches a large file from GridFS', async () => {
|
||||
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
const twoMegabytesFile = randomString(2048 * 1024);
|
||||
await gfsAdapter.createFile('myFileName', twoMegabytesFile);
|
||||
const gfsResult = await gfsAdapter.getFileData('myFileName');
|
||||
expect(gfsResult.toString('utf8')).toBe(twoMegabytesFile);
|
||||
});
|
||||
|
||||
it('properly deletes a file from GridFS', async () => {
|
||||
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
await gfsAdapter.createFile('myFileName', 'a simple file');
|
||||
await gfsAdapter.deleteFile('myFileName');
|
||||
await expectMissingFile(gfsAdapter, 'myFileName');
|
||||
}, 1000000);
|
||||
|
||||
it('properly overrides files', async () => {
|
||||
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
await gfsAdapter.createFile('myFileName', 'a simple file');
|
||||
await gfsAdapter.createFile('myFileName', 'an overrided simple file');
|
||||
const data = await gfsAdapter.getFileData('myFileName');
|
||||
expect(data.toString('utf8')).toBe('an overrided simple file');
|
||||
const bucket = await gfsAdapter._getBucket();
|
||||
const documents = await bucket.find({ filename: 'myFileName' }).toArray();
|
||||
expect(documents.length).toBe(2);
|
||||
await gfsAdapter.deleteFile('myFileName');
|
||||
await expectMissingFile(gfsAdapter, 'myFileName');
|
||||
});
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
const MongoClient = require("mongodb").MongoClient;
|
||||
const GridStore = require("mongodb").GridStore;
|
||||
|
||||
const GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
||||
const Config = require("../src/Config");
|
||||
const FilesController = require('../src/Controllers/FilesController').default;
|
||||
|
||||
|
||||
// Small additional tests to improve overall coverage
|
||||
describe_only_db('mongo')("GridStoreAdapter",() =>{
|
||||
it("should properly instanciate the GridStore when deleting a file", (done) => {
|
||||
|
||||
const databaseURI = 'mongodb://localhost:27017/parse';
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const gridStoreAdapter = new GridStoreAdapter(databaseURI);
|
||||
const filesController = new FilesController(gridStoreAdapter);
|
||||
|
||||
// save original unlink before redefinition
|
||||
const originalUnlink = GridStore.prototype.unlink;
|
||||
|
||||
let gridStoreMode;
|
||||
|
||||
// new unlink method that will capture the mode in which GridStore was opened
|
||||
GridStore.prototype.unlink = function() {
|
||||
|
||||
// restore original unlink during first call
|
||||
GridStore.prototype.unlink = originalUnlink;
|
||||
|
||||
gridStoreMode = this.mode;
|
||||
|
||||
return originalUnlink.call(this);
|
||||
};
|
||||
|
||||
|
||||
filesController.createFile(config, 'myFilename.txt', 'my file content', 'text/plain')
|
||||
.then(myFile => {
|
||||
|
||||
return MongoClient.connect(databaseURI)
|
||||
.then(database => {
|
||||
|
||||
// Verify the existance of the fs.files document
|
||||
return database.collection('fs.files').count().then(count => {
|
||||
expect(count).toEqual(1);
|
||||
return database;
|
||||
});
|
||||
})
|
||||
.then(database => {
|
||||
|
||||
// Verify the existance of the fs.files document
|
||||
return database.collection('fs.chunks').count().then(count => {
|
||||
expect(count).toEqual(1);
|
||||
return database.close();
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return filesController.deleteFile(config, myFile.name);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return MongoClient.connect(databaseURI)
|
||||
.then(database => {
|
||||
|
||||
// Verify the existance of the fs.files document
|
||||
return database.collection('fs.files').count().then(count => {
|
||||
expect(count).toEqual(0);
|
||||
return database;
|
||||
});
|
||||
})
|
||||
.then(database => {
|
||||
|
||||
// Verify the existance of the fs.files document
|
||||
return database.collection('fs.chunks').count().then(count => {
|
||||
expect(count).toEqual(0);
|
||||
return database.close();
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// Verify that gridStore was opened in read only mode
|
||||
expect(gridStoreMode).toEqual('r');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(fail);
|
||||
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
const GridStore = require('mongodb').GridStore;
|
||||
|
||||
const GridStoreAdapter = require('../lib/Adapters/Files/GridStoreAdapter')
|
||||
.GridStoreAdapter;
|
||||
const Config = require('../lib/Config');
|
||||
const FilesController = require('../lib/Controllers/FilesController').default;
|
||||
|
||||
// Small additional tests to improve overall coverage
|
||||
describe_only_db('mongo')('GridStoreAdapter', () => {
|
||||
it('should properly instanciate the GridStore when deleting a file', async done => {
|
||||
const databaseURI = 'mongodb://localhost:27017/parse';
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const gridStoreAdapter = new GridStoreAdapter(databaseURI);
|
||||
const db = await gridStoreAdapter._connect();
|
||||
db.dropDatabase();
|
||||
const filesController = new FilesController(
|
||||
gridStoreAdapter,
|
||||
Parse.applicationId,
|
||||
{}
|
||||
);
|
||||
|
||||
// save original unlink before redefinition
|
||||
const originalUnlink = GridStore.prototype.unlink;
|
||||
|
||||
let gridStoreMode;
|
||||
|
||||
// new unlink method that will capture the mode in which GridStore was opened
|
||||
GridStore.prototype.unlink = function() {
|
||||
// restore original unlink during first call
|
||||
GridStore.prototype.unlink = originalUnlink;
|
||||
|
||||
gridStoreMode = this.mode;
|
||||
|
||||
return originalUnlink.call(this);
|
||||
};
|
||||
|
||||
filesController
|
||||
.createFile(config, 'myFilename.txt', 'my file content', 'text/plain')
|
||||
.then(myFile => {
|
||||
return MongoClient.connect(databaseURI)
|
||||
.then(client => {
|
||||
const database = client.db(client.s.options.dbName);
|
||||
// Verify the existance of the fs.files document
|
||||
return database
|
||||
.collection('fs.files')
|
||||
.count()
|
||||
.then(count => {
|
||||
expect(count).toEqual(1);
|
||||
return { database, client };
|
||||
});
|
||||
})
|
||||
.then(({ database, client }) => {
|
||||
// Verify the existance of the fs.files document
|
||||
return database
|
||||
.collection('fs.chunks')
|
||||
.count()
|
||||
.then(count => {
|
||||
expect(count).toEqual(1);
|
||||
return client.close();
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return filesController.deleteFile(config, myFile.name);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return MongoClient.connect(databaseURI)
|
||||
.then(client => {
|
||||
const database = client.db(client.s.options.dbName);
|
||||
// Verify the existance of the fs.files document
|
||||
return database
|
||||
.collection('fs.files')
|
||||
.count()
|
||||
.then(count => {
|
||||
expect(count).toEqual(0);
|
||||
return { database, client };
|
||||
});
|
||||
})
|
||||
.then(({ database, client }) => {
|
||||
// Verify the existance of the fs.files document
|
||||
return database
|
||||
.collection('fs.chunks')
|
||||
.count()
|
||||
.then(count => {
|
||||
expect(count).toEqual(0);
|
||||
return client.close();
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// Verify that gridStore was opened in read only mode
|
||||
expect(gridStoreMode).toEqual('r');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(fail);
|
||||
});
|
||||
});
|
||||
+146
-179
@@ -1,116 +1,97 @@
|
||||
'use strict';
|
||||
|
||||
const httpRequest = require("../src/cloud-code/httpRequest"),
|
||||
HTTPResponse = require('../src/cloud-code/HTTPResponse').default,
|
||||
const httpRequest = require('../lib/cloud-code/httpRequest'),
|
||||
HTTPResponse = require('../lib/cloud-code/HTTPResponse').default,
|
||||
bodyParser = require('body-parser'),
|
||||
express = require("express");
|
||||
express = require('express');
|
||||
|
||||
const port = 13371;
|
||||
const httpRequestServer = "http://localhost:" + port;
|
||||
const httpRequestServer = 'http://localhost:' + port;
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.json({ 'type': '*/*' }));
|
||||
app.get("/hello", function(req, res){
|
||||
res.json({response: "OK"});
|
||||
});
|
||||
function startServer(done) {
|
||||
const app = express();
|
||||
app.use(bodyParser.json({ type: '*/*' }));
|
||||
app.get('/hello', function(req, res) {
|
||||
res.json({ response: 'OK' });
|
||||
});
|
||||
|
||||
app.get("/404", function(req, res){
|
||||
res.status(404);
|
||||
res.send("NO");
|
||||
});
|
||||
app.get('/404', function(req, res) {
|
||||
res.status(404);
|
||||
res.send('NO');
|
||||
});
|
||||
|
||||
app.get("/301", function(req, res){
|
||||
res.status(301);
|
||||
res.location("/hello");
|
||||
res.send();
|
||||
});
|
||||
app.get('/301', function(req, res) {
|
||||
res.status(301);
|
||||
res.location('/hello');
|
||||
res.send();
|
||||
});
|
||||
|
||||
app.post('/echo', function(req, res){
|
||||
res.json(req.body);
|
||||
});
|
||||
app.post('/echo', function(req, res) {
|
||||
res.json(req.body);
|
||||
});
|
||||
|
||||
app.get('/qs', function(req, res){
|
||||
res.json(req.query);
|
||||
});
|
||||
app.get('/qs', function(req, res) {
|
||||
res.json(req.query);
|
||||
});
|
||||
|
||||
app.listen(13371);
|
||||
return app.listen(13371, undefined, done);
|
||||
}
|
||||
|
||||
describe('httpRequest', () => {
|
||||
let server;
|
||||
beforeAll(done => {
|
||||
server = startServer(done);
|
||||
});
|
||||
|
||||
describe("httpRequest", () => {
|
||||
it("should do /hello", (done) => {
|
||||
afterAll(done => {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
it('should do /hello', done => {
|
||||
httpRequest({
|
||||
url: httpRequestServer + "/hello"
|
||||
}).then(function(httpResponse){
|
||||
url: httpRequestServer + '/hello',
|
||||
}).then(function(httpResponse) {
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.buffer).toEqual(new Buffer('{"response":"OK"}'));
|
||||
expect(httpResponse.text).toEqual('{"response":"OK"}');
|
||||
expect(httpResponse.data.response).toEqual("OK");
|
||||
expect(httpResponse.data.response).toEqual('OK');
|
||||
done();
|
||||
}, function(){
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it("should do /hello with callback and promises", (done) => {
|
||||
let calls = 0;
|
||||
it('should do not follow redirects by default', done => {
|
||||
httpRequest({
|
||||
url: httpRequestServer + "/hello",
|
||||
success: function() { calls++; },
|
||||
error: function() { calls++; }
|
||||
}).then(function(httpResponse){
|
||||
expect(calls).toBe(1);
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.buffer).toEqual(new Buffer('{"response":"OK"}'));
|
||||
expect(httpResponse.text).toEqual('{"response":"OK"}');
|
||||
expect(httpResponse.data.response).toEqual("OK");
|
||||
done();
|
||||
}, function(){
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should do not follow redirects by default", (done) => {
|
||||
|
||||
httpRequest({
|
||||
url: httpRequestServer + "/301"
|
||||
}).then(function(httpResponse){
|
||||
url: httpRequestServer + '/301',
|
||||
}).then(function(httpResponse) {
|
||||
expect(httpResponse.status).toBe(301);
|
||||
done();
|
||||
}, function(){
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it("should follow redirects when set", (done) => {
|
||||
|
||||
it('should follow redirects when set', done => {
|
||||
httpRequest({
|
||||
url: httpRequestServer + "/301",
|
||||
followRedirects: true
|
||||
}).then(function(httpResponse){
|
||||
url: httpRequestServer + '/301',
|
||||
followRedirects: true,
|
||||
}).then(function(httpResponse) {
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.buffer).toEqual(new Buffer('{"response":"OK"}'));
|
||||
expect(httpResponse.text).toEqual('{"response":"OK"}');
|
||||
expect(httpResponse.data.response).toEqual("OK");
|
||||
expect(httpResponse.data.response).toEqual('OK');
|
||||
done();
|
||||
}, function(){
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it("should fail on 404", (done) => {
|
||||
it('should fail on 404', done => {
|
||||
let calls = 0;
|
||||
httpRequest({
|
||||
url: httpRequestServer + "/404",
|
||||
success: function() {
|
||||
url: httpRequestServer + '/404',
|
||||
}).then(
|
||||
function() {
|
||||
calls++;
|
||||
fail("should not succeed");
|
||||
fail('should not succeed');
|
||||
done();
|
||||
},
|
||||
error: function(httpResponse) {
|
||||
function(httpResponse) {
|
||||
calls++;
|
||||
expect(calls).toBe(1);
|
||||
expect(httpResponse.status).toBe(404);
|
||||
@@ -119,131 +100,116 @@ describe("httpRequest", () => {
|
||||
expect(httpResponse.data).toBe(undefined);
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it("should fail on 404", (done) => {
|
||||
httpRequest({
|
||||
url: httpRequestServer + "/404",
|
||||
}).then(function(){
|
||||
fail("should not succeed");
|
||||
done();
|
||||
}, function(httpResponse){
|
||||
expect(httpResponse.status).toBe(404);
|
||||
expect(httpResponse.buffer).toEqual(new Buffer('NO'));
|
||||
expect(httpResponse.text).toEqual('NO');
|
||||
expect(httpResponse.data).toBe(undefined);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it("should post on echo", (done) => {
|
||||
let calls = 0;
|
||||
httpRequest({
|
||||
method: "POST",
|
||||
url: httpRequestServer + "/echo",
|
||||
body: {
|
||||
foo: "bar"
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: function() { calls++; },
|
||||
error: function() { calls++; }
|
||||
}).then(function(httpResponse){
|
||||
expect(calls).toBe(1);
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.data).toEqual({foo: "bar"});
|
||||
done();
|
||||
}, function(){
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should encode a query string body by default", (done) => {
|
||||
it('should post on echo', done => {
|
||||
httpRequest({
|
||||
method: 'POST',
|
||||
url: httpRequestServer + '/echo',
|
||||
body: {
|
||||
foo: 'bar',
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(
|
||||
function(httpResponse) {
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.data).toEqual({ foo: 'bar' });
|
||||
done();
|
||||
},
|
||||
function() {
|
||||
fail('should not fail');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode a query string body by default', done => {
|
||||
const options = {
|
||||
body: {"foo": "bar"},
|
||||
}
|
||||
body: { foo: 'bar' },
|
||||
};
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual('foo=bar');
|
||||
expect(result.headers['Content-Type']).toEqual('application/x-www-form-urlencoded');
|
||||
expect(result.headers['Content-Type']).toEqual(
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
it("should encode a JSON body", (done) => {
|
||||
it('should encode a JSON body', done => {
|
||||
const options = {
|
||||
body: {"foo": "bar"},
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
}
|
||||
body: { foo: 'bar' },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual('{"foo":"bar"}');
|
||||
done();
|
||||
|
||||
})
|
||||
it("should encode a www-form body", (done) => {
|
||||
});
|
||||
it('should encode a www-form body', done => {
|
||||
const options = {
|
||||
body: {"foo": "bar", "bar": "baz"},
|
||||
headers: {'cOntent-tYpe': 'application/x-www-form-urlencoded'}
|
||||
}
|
||||
body: { foo: 'bar', bar: 'baz' },
|
||||
headers: { 'cOntent-tYpe': 'application/x-www-form-urlencoded' },
|
||||
};
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual("foo=bar&bar=baz");
|
||||
expect(result.body).toEqual('foo=bar&bar=baz');
|
||||
done();
|
||||
});
|
||||
it("should not encode a wrong content type", (done) => {
|
||||
it('should not encode a wrong content type', done => {
|
||||
const options = {
|
||||
body:{"foo": "bar", "bar": "baz"},
|
||||
headers: {'cOntent-tYpe': 'mime/jpeg'}
|
||||
}
|
||||
body: { foo: 'bar', bar: 'baz' },
|
||||
headers: { 'cOntent-tYpe': 'mime/jpeg' },
|
||||
};
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual({"foo": "bar", "bar": "baz"});
|
||||
expect(result.body).toEqual({ foo: 'bar', bar: 'baz' });
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail gracefully", (done) => {
|
||||
it('should fail gracefully', done => {
|
||||
httpRequest({
|
||||
url: "http://not a good url",
|
||||
success: function() {
|
||||
fail("should not succeed");
|
||||
done();
|
||||
},
|
||||
error: function(error) {
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error).not.toBeNull();
|
||||
done();
|
||||
}
|
||||
url: 'http://not a good url',
|
||||
}).then(done.fail, function(error) {
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should params object to query string", (done) => {
|
||||
it('should params object to query string', done => {
|
||||
httpRequest({
|
||||
url: httpRequestServer + "/qs",
|
||||
url: httpRequestServer + '/qs',
|
||||
params: {
|
||||
foo: "bar"
|
||||
foo: 'bar',
|
||||
},
|
||||
}).then(
|
||||
function(httpResponse) {
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.data).toEqual({ foo: 'bar' });
|
||||
done();
|
||||
},
|
||||
function() {
|
||||
fail('should not fail');
|
||||
done();
|
||||
}
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.data).toEqual({foo: "bar"});
|
||||
done();
|
||||
}, function(){
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should params string to query string", (done) => {
|
||||
it('should params string to query string', done => {
|
||||
httpRequest({
|
||||
url: httpRequestServer + "/qs",
|
||||
params: "foo=bar&foo2=bar2"
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.data).toEqual({foo: "bar", foo2: 'bar2'});
|
||||
done();
|
||||
}, function(){
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
url: httpRequestServer + '/qs',
|
||||
params: 'foo=bar&foo2=bar2',
|
||||
}).then(
|
||||
function(httpResponse) {
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.data).toEqual({ foo: 'bar', foo2: 'bar2' });
|
||||
done();
|
||||
},
|
||||
function() {
|
||||
fail('should not fail');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should not crash with undefined body', () => {
|
||||
@@ -268,17 +234,17 @@ describe("httpRequest", () => {
|
||||
});
|
||||
|
||||
it('serialized httpResponse correctly with body object', () => {
|
||||
const httpResponse = new HTTPResponse({}, {foo: "bar"});
|
||||
const httpResponse = new HTTPResponse({}, { foo: 'bar' });
|
||||
Parse._encode(httpResponse);
|
||||
const serialized = JSON.stringify(httpResponse);
|
||||
const result = JSON.parse(serialized);
|
||||
|
||||
expect(httpResponse.text).toEqual('{"foo":"bar"}');
|
||||
expect(httpResponse.data).toEqual({foo: 'bar'});
|
||||
expect(httpResponse.body).toEqual({foo: 'bar'});
|
||||
expect(httpResponse.data).toEqual({ foo: 'bar' });
|
||||
expect(httpResponse.body).toEqual({ foo: 'bar' });
|
||||
|
||||
expect(result.text).toEqual('{"foo":"bar"}');
|
||||
expect(result.data).toEqual({foo: 'bar'});
|
||||
expect(result.data).toEqual({ foo: 'bar' });
|
||||
expect(result.body).toEqual(undefined);
|
||||
});
|
||||
|
||||
@@ -299,15 +265,17 @@ describe("httpRequest", () => {
|
||||
const serialized = JSON.stringify(httpResponse);
|
||||
const result = JSON.parse(serialized);
|
||||
expect(result.text).toEqual('{"foo":"bar"}');
|
||||
expect(result.data).toEqual({foo: 'bar'});
|
||||
expect(result.data).toEqual({ foo: 'bar' });
|
||||
});
|
||||
|
||||
it('serialized httpResponse with Parse._encode should be allright', () => {
|
||||
const json = '{"foo":"bar"}';
|
||||
const httpResponse = new HTTPResponse({}, new Buffer(json));
|
||||
const encoded = Parse._encode(httpResponse);
|
||||
let foundData, foundText, foundBody = false;
|
||||
for(const key in encoded) {
|
||||
let foundData,
|
||||
foundText,
|
||||
foundBody = false;
|
||||
for (const key in encoded) {
|
||||
if (key == 'data') {
|
||||
foundData = true;
|
||||
}
|
||||
@@ -322,5 +290,4 @@ describe("httpRequest", () => {
|
||||
expect(foundText).toBe(true);
|
||||
expect(foundBody).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
const InMemoryCache = require('../src/Adapters/Cache/InMemoryCache').default;
|
||||
|
||||
const InMemoryCache = require('../lib/Adapters/Cache/InMemoryCache').default;
|
||||
|
||||
describe('InMemoryCache', function() {
|
||||
const BASE_TTL = {
|
||||
ttl: 100
|
||||
ttl: 100,
|
||||
};
|
||||
const NO_EXPIRE_TTL = {
|
||||
ttl: NaN
|
||||
ttl: NaN,
|
||||
};
|
||||
const KEY = 'hello';
|
||||
const KEY_2 = KEY + '_2';
|
||||
|
||||
const VALUE = 'world';
|
||||
|
||||
|
||||
function wait(sleep) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, sleep);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
it('should destroy a expire items in the cache', (done) => {
|
||||
it('should destroy a expire items in the cache', done => {
|
||||
const cache = new InMemoryCache(BASE_TTL);
|
||||
|
||||
cache.put(KEY, VALUE);
|
||||
@@ -28,14 +26,14 @@ describe('InMemoryCache', function() {
|
||||
let value = cache.get(KEY);
|
||||
expect(value).toEqual(VALUE);
|
||||
|
||||
wait(BASE_TTL.ttl * 2).then(() => {
|
||||
value = cache.get(KEY)
|
||||
wait(BASE_TTL.ttl * 10).then(() => {
|
||||
value = cache.get(KEY);
|
||||
expect(value).toEqual(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete items', (done) => {
|
||||
it('should delete items', done => {
|
||||
const cache = new InMemoryCache(NO_EXPIRE_TTL);
|
||||
cache.put(KEY, VALUE);
|
||||
cache.put(KEY_2, VALUE);
|
||||
@@ -52,7 +50,7 @@ describe('InMemoryCache', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should clear all items', (done) => {
|
||||
it('should clear all items', done => {
|
||||
const cache = new InMemoryCache(NO_EXPIRE_TTL);
|
||||
cache.put(KEY, VALUE);
|
||||
cache.put(KEY_2, VALUE);
|
||||
@@ -70,5 +68,4 @@ describe('InMemoryCache', function() {
|
||||
const cache = new InMemoryCache({});
|
||||
expect(cache.ttl).toEqual(5 * 1000);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').default;
|
||||
const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter')
|
||||
.default;
|
||||
|
||||
describe('InMemoryCacheAdapter', function() {
|
||||
const KEY = 'hello';
|
||||
@@ -7,12 +8,12 @@ describe('InMemoryCacheAdapter', function() {
|
||||
function wait(sleep) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, sleep);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
it('should expose promisifyed methods', (done) => {
|
||||
it('should expose promisifyed methods', done => {
|
||||
const cache = new InMemoryCacheAdapter({
|
||||
ttl: NaN
|
||||
ttl: NaN,
|
||||
});
|
||||
|
||||
// Verify all methods return promises.
|
||||
@@ -20,38 +21,39 @@ describe('InMemoryCacheAdapter', function() {
|
||||
cache.put(KEY, VALUE),
|
||||
cache.del(KEY),
|
||||
cache.get(KEY),
|
||||
cache.clear()
|
||||
cache.clear(),
|
||||
]).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get/set/clear', (done) => {
|
||||
it('should get/set/clear', done => {
|
||||
const cache = new InMemoryCacheAdapter({
|
||||
ttl: NaN
|
||||
ttl: NaN,
|
||||
});
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
cache
|
||||
.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(value => expect(value).toEqual(VALUE))
|
||||
.then(() => cache.clear())
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(value => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('should expire after ttl', (done) => {
|
||||
it('should expire after ttl', done => {
|
||||
const cache = new InMemoryCacheAdapter({
|
||||
ttl: 10
|
||||
ttl: 10,
|
||||
});
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
cache
|
||||
.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(value => expect(value).toEqual(VALUE))
|
||||
.then(wait.bind(null, 50))
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(value => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,56 +1,71 @@
|
||||
const auth = require('../src/Auth');
|
||||
const Config = require('../src/Config');
|
||||
const rest = require('../src/rest');
|
||||
const InstallationsRouter = require('../src/Routers/InstallationsRouter').InstallationsRouter;
|
||||
const auth = require('../lib/Auth');
|
||||
const Config = require('../lib/Config');
|
||||
const rest = require('../lib/rest');
|
||||
const InstallationsRouter = require('../lib/Routers/InstallationsRouter')
|
||||
.InstallationsRouter;
|
||||
|
||||
describe('InstallationsRouter', () => {
|
||||
it('uses find condition from request.body', (done) => {
|
||||
it('uses find condition from request.body', done => {
|
||||
const config = Config.get('test');
|
||||
const androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
};
|
||||
const iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {
|
||||
where: {
|
||||
deviceType: 'android'
|
||||
}
|
||||
deviceType: 'android',
|
||||
},
|
||||
},
|
||||
query: {},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
rest
|
||||
.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
androidDeviceRequest
|
||||
)
|
||||
.then(() => {
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
iosDeviceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
})
|
||||
.then(res => {
|
||||
const results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch(err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses find condition from request.query', (done) => {
|
||||
it('uses find condition from request.query', done => {
|
||||
const config = Config.get('test');
|
||||
const androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
};
|
||||
const iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
@@ -58,90 +73,131 @@ describe('InstallationsRouter', () => {
|
||||
body: {},
|
||||
query: {
|
||||
where: {
|
||||
deviceType: 'android'
|
||||
}
|
||||
deviceType: 'android',
|
||||
},
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
rest
|
||||
.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
androidDeviceRequest
|
||||
)
|
||||
.then(() => {
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
iosDeviceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
})
|
||||
.then(res => {
|
||||
const results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with limit = 0', (done) => {
|
||||
it('query installations with limit = 0', done => {
|
||||
const config = Config.get('test');
|
||||
const androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
};
|
||||
const iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0
|
||||
limit: 0,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
Config.get('test');
|
||||
const router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
rest
|
||||
.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
androidDeviceRequest
|
||||
)
|
||||
.then(() => {
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
iosDeviceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
})
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch(err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with count = 1', done => {
|
||||
it_exclude_dbs(['postgres'])('query installations with count = 1', done => {
|
||||
const config = Config.get('test');
|
||||
const androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
};
|
||||
const iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
count: 1
|
||||
count: 1,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest))
|
||||
rest
|
||||
.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
androidDeviceRequest
|
||||
)
|
||||
.then(() =>
|
||||
rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
iosDeviceRequest
|
||||
)
|
||||
)
|
||||
.then(() => router.handleFind(request))
|
||||
.then((res) => {
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(2);
|
||||
@@ -150,44 +206,108 @@ describe('InstallationsRouter', () => {
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with limit = 0 and count = 1', (done) => {
|
||||
it_only_db('postgres')('query installations with count = 1', async () => {
|
||||
const config = Config.get('test');
|
||||
const androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
};
|
||||
const iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1
|
||||
count: 1,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
await rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
androidDeviceRequest
|
||||
);
|
||||
await rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
iosDeviceRequest
|
||||
);
|
||||
let res = await router.handleFind(request);
|
||||
let response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(0); // estimate count is zero
|
||||
|
||||
const pgAdapter = config.database.adapter;
|
||||
await pgAdapter.updateEstimatedCount('_Installation');
|
||||
|
||||
res = await router.handleFind(request);
|
||||
response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(2);
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])(
|
||||
'query installations with limit = 0 and count = 1',
|
||||
done => {
|
||||
const config = Config.get('test');
|
||||
const androidDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
};
|
||||
const iosDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1,
|
||||
},
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new InstallationsRouter();
|
||||
rest
|
||||
.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
androidDeviceRequest
|
||||
)
|
||||
.then(() => {
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
iosDeviceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
+219
-131
@@ -1,198 +1,286 @@
|
||||
const rp = require('request-promise');
|
||||
const request = require('../lib/request');
|
||||
|
||||
const defaultHeaders = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Rest-API-Key': 'rest'
|
||||
}
|
||||
'X-Parse-Rest-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
const masterKeyHeaders = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Rest-API-Key': 'rest',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
}
|
||||
'X-Parse-Master-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
const defaultOptions = {
|
||||
headers: defaultHeaders,
|
||||
json: true
|
||||
}
|
||||
json: true,
|
||||
};
|
||||
const masterKeyOptions = {
|
||||
headers: masterKeyHeaders,
|
||||
json: true
|
||||
}
|
||||
json: true,
|
||||
};
|
||||
|
||||
describe('JobSchedule', () => {
|
||||
it('should create _JobSchedule with masterKey', (done) => {
|
||||
it('should create _JobSchedule with masterKey', done => {
|
||||
const jobSchedule = new Parse.Object('_JobSchedule');
|
||||
jobSchedule.set({
|
||||
'jobName': 'MY Cool Job'
|
||||
jobName: 'MY Cool Job',
|
||||
});
|
||||
jobSchedule.save(null, {useMasterKey: true}).then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should fail creating _JobSchedule without masterKey', (done) => {
|
||||
const jobSchedule = new Parse.Object('_JobSchedule');
|
||||
jobSchedule.set({
|
||||
'jobName': 'SomeJob'
|
||||
});
|
||||
jobSchedule.save(null).then(done.fail)
|
||||
.catch(() => done());
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (/jobs)', (done) => {
|
||||
rp.get(Parse.serverURL + '/cloud_code/jobs', defaultOptions).then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (/jobs/data)', (done) => {
|
||||
rp.get(Parse.serverURL + '/cloud_code/jobs/data', defaultOptions).then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (PUT /jobs/id)', (done) => {
|
||||
rp.put(Parse.serverURL + '/cloud_code/jobs/jobId', defaultOptions).then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (PUT /jobs/id)', (done) => {
|
||||
rp.del(Parse.serverURL + '/cloud_code/jobs/jobId', defaultOptions).then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('should allow access when using masterKey (/jobs)', (done) => {
|
||||
rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should create a job schedule', (done) => {
|
||||
Parse.Cloud.job('job', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job'
|
||||
}
|
||||
}
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
})
|
||||
jobSchedule
|
||||
.save(null, { useMasterKey: true })
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
|
||||
done();
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should fail creating a job with an invalid name', (done) => {
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job'
|
||||
}
|
||||
}
|
||||
it('should fail creating _JobSchedule without masterKey', done => {
|
||||
const jobSchedule = new Parse.Object('_JobSchedule');
|
||||
jobSchedule.set({
|
||||
jobName: 'SomeJob',
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options)
|
||||
jobSchedule
|
||||
.save(null)
|
||||
.then(done.fail)
|
||||
.catch(() => done());
|
||||
});
|
||||
|
||||
it('should update a job', (done) => {
|
||||
Parse.Cloud.job('job1', () => {});
|
||||
Parse.Cloud.job('job2', () => {});
|
||||
it('should reject access when not using masterKey (/jobs)', done => {
|
||||
request(
|
||||
Object.assign(
|
||||
{ url: Parse.serverURL + '/cloud_code/jobs' },
|
||||
defaultOptions
|
||||
)
|
||||
).then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (/jobs/data)', done => {
|
||||
request(
|
||||
Object.assign(
|
||||
{ url: Parse.serverURL + '/cloud_code/jobs/data' },
|
||||
defaultOptions
|
||||
)
|
||||
).then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (PUT /jobs/id)', done => {
|
||||
request(
|
||||
Object.assign(
|
||||
{ method: 'PUT', url: Parse.serverURL + '/cloud_code/jobs/jobId' },
|
||||
defaultOptions
|
||||
)
|
||||
).then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('should reject access when not using masterKey (DELETE /jobs/id)', done => {
|
||||
request(
|
||||
Object.assign(
|
||||
{ method: 'DELETE', url: Parse.serverURL + '/cloud_code/jobs/jobId' },
|
||||
defaultOptions
|
||||
)
|
||||
).then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('should allow access when using masterKey (GET /jobs)', done => {
|
||||
request(
|
||||
Object.assign(
|
||||
{ url: Parse.serverURL + '/cloud_code/jobs' },
|
||||
masterKeyOptions
|
||||
)
|
||||
).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should create a job schedule', done => {
|
||||
Parse.Cloud.job('job', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/cloud_code/jobs',
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job1'
|
||||
}
|
||||
}
|
||||
jobName: 'job',
|
||||
},
|
||||
},
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
return rp.put(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, Object.assign(options, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job2'
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
|
||||
request(options)
|
||||
.then(res => {
|
||||
expect(res.data.objectId).not.toBeUndefined();
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0].jobName).toBe('job2');
|
||||
.then(() => {
|
||||
return request(
|
||||
Object.assign(
|
||||
{ url: Parse.serverURL + '/cloud_code/jobs' },
|
||||
masterKeyOptions
|
||||
)
|
||||
);
|
||||
})
|
||||
.then(res => {
|
||||
expect(res.data.length).toBe(1);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should fail updating a job with an invalid name', (done) => {
|
||||
Parse.Cloud.job('job1', () => {});
|
||||
it('should fail creating a job with an invalid name', done => {
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
url: Parse.serverURL + '/cloud_code/jobs',
|
||||
method: 'POST',
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job1'
|
||||
}
|
||||
}
|
||||
jobName: 'job',
|
||||
},
|
||||
},
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
return rp.put(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, Object.assign(options, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job2'
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
request(options)
|
||||
.then(done.fail)
|
||||
.catch(() => done());
|
||||
});
|
||||
|
||||
it('should destroy a job', (done) => {
|
||||
Parse.Cloud.job('job', () => {});
|
||||
it('should update a job', done => {
|
||||
Parse.Cloud.job('job1', () => {});
|
||||
Parse.Cloud.job('job2', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/cloud_code/jobs',
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job'
|
||||
}
|
||||
}
|
||||
jobName: 'job1',
|
||||
},
|
||||
},
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
return rp.del(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, masterKeyOptions);
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
|
||||
request(options)
|
||||
.then(res => {
|
||||
expect(res.data.objectId).not.toBeUndefined();
|
||||
return request(
|
||||
Object.assign(options, {
|
||||
url: Parse.serverURL + '/cloud_code/jobs/' + res.data.objectId,
|
||||
method: 'PUT',
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job2',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.length).toBe(0);
|
||||
.then(() => {
|
||||
return request(
|
||||
Object.assign({}, masterKeyOptions, {
|
||||
url: Parse.serverURL + '/cloud_code/jobs',
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(res => {
|
||||
expect(res.data.length).toBe(1);
|
||||
expect(res.data[0].jobName).toBe('job2');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should properly return job data', (done) => {
|
||||
it('should fail updating a job with an invalid name', done => {
|
||||
Parse.Cloud.job('job1', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/cloud_code/jobs',
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job1',
|
||||
},
|
||||
},
|
||||
});
|
||||
request(options)
|
||||
.then(res => {
|
||||
expect(res.data.objectId).not.toBeUndefined();
|
||||
return request(
|
||||
Object.assign(options, {
|
||||
method: 'PUT',
|
||||
url: Parse.serverURL + '/cloud_code/jobs/' + res.data.objectId,
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job2',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(done.fail)
|
||||
.catch(() => done());
|
||||
});
|
||||
|
||||
it('should destroy a job', done => {
|
||||
Parse.Cloud.job('job', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/cloud_code/jobs',
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job',
|
||||
},
|
||||
},
|
||||
});
|
||||
request(options)
|
||||
.then(res => {
|
||||
expect(res.data.objectId).not.toBeUndefined();
|
||||
return request(
|
||||
Object.assign(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: Parse.serverURL + '/cloud_code/jobs/' + res.data.objectId,
|
||||
},
|
||||
masterKeyOptions
|
||||
)
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return request(
|
||||
Object.assign(
|
||||
{
|
||||
url: Parse.serverURL + '/cloud_code/jobs',
|
||||
},
|
||||
masterKeyOptions
|
||||
)
|
||||
);
|
||||
})
|
||||
.then(res => {
|
||||
expect(res.data.length).toBe(0);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should properly return job data', done => {
|
||||
Parse.Cloud.job('job1', () => {});
|
||||
Parse.Cloud.job('job2', () => {});
|
||||
const options = Object.assign({}, masterKeyOptions, {
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/cloud_code/jobs',
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job1'
|
||||
}
|
||||
}
|
||||
jobName: 'job1',
|
||||
},
|
||||
},
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs/data', masterKeyOptions);
|
||||
request(options)
|
||||
.then(response => {
|
||||
const res = response.data;
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
})
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
return request(
|
||||
Object.assign(
|
||||
{ url: Parse.serverURL + '/cloud_code/jobs/data' },
|
||||
masterKeyOptions
|
||||
)
|
||||
);
|
||||
})
|
||||
.then(response => {
|
||||
const res = response.data;
|
||||
expect(res.in_use).toEqual(['job1']);
|
||||
expect(res.jobs).toContain('job1');
|
||||
expect(res.jobs).toContain('job2');
|
||||
expect(res.jobs.length).toBe(2);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
.catch(e => done.fail(e.data));
|
||||
});
|
||||
});
|
||||
|
||||
+62
-30
@@ -1,63 +1,95 @@
|
||||
const logging = require('../src/Adapters/Logger/WinstonLogger');
|
||||
const winston = require('winston');
|
||||
const logging = require('../lib/Adapters/Logger/WinstonLogger');
|
||||
const Transport = require('winston-transport');
|
||||
|
||||
class TestTransport extends winston.Transport {
|
||||
log(level, msg, meta, callback) {
|
||||
class TestTransport extends Transport {
|
||||
log(info, callback) {
|
||||
callback(null, true);
|
||||
}
|
||||
}
|
||||
|
||||
describe('Logger', () => {
|
||||
describe('WinstonLogger', () => {
|
||||
it('should add transport', () => {
|
||||
const testTransport = new (TestTransport)({
|
||||
name: 'test'
|
||||
});
|
||||
const testTransport = new TestTransport();
|
||||
spyOn(testTransport, 'log');
|
||||
logging.addTransport(testTransport);
|
||||
expect(Object.keys(logging.logger.transports).length).toBe(4);
|
||||
expect(logging.logger.transports.length).toBe(4);
|
||||
logging.logger.info('hi');
|
||||
expect(testTransport.log).toHaveBeenCalled();
|
||||
logging.logger.error('error');
|
||||
expect(testTransport.log).toHaveBeenCalled();
|
||||
logging.removeTransport(testTransport);
|
||||
expect(Object.keys(logging.logger.transports).length).toBe(3);
|
||||
expect(logging.logger.transports.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should have files transports', (done) => {
|
||||
it('should have files transports', done => {
|
||||
reconfigureServer().then(() => {
|
||||
const transports = logging.logger.transports;
|
||||
const transportKeys = Object.keys(transports);
|
||||
expect(transportKeys.length).toBe(3);
|
||||
expect(transports.length).toBe(3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable files logs', (done) => {
|
||||
it('should disable files logs', done => {
|
||||
reconfigureServer({
|
||||
logsFolder: null
|
||||
logsFolder: null,
|
||||
}).then(() => {
|
||||
const transports = logging.logger.transports;
|
||||
const transportKeys = Object.keys(transports);
|
||||
expect(transportKeys.length).toBe(1);
|
||||
expect(transports.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should enable JSON logs', (done) => {
|
||||
it('should have a timestamp', done => {
|
||||
logging.logger.info('hi');
|
||||
logging.logger.query({ limit: 1 }, (err, results) => {
|
||||
if (err) {
|
||||
done.fail(err);
|
||||
}
|
||||
expect(results['parse-server'][0].timestamp).toBeDefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('console should not be json', done => {
|
||||
// Force console transport
|
||||
reconfigureServer({
|
||||
logsFolder: null,
|
||||
silent: false,
|
||||
})
|
||||
.then(() => {
|
||||
spyOn(process.stdout, 'write');
|
||||
logging.logger.info('hi', { key: 'value' });
|
||||
expect(process.stdout.write).toHaveBeenCalled();
|
||||
const firstLog = process.stdout.write.calls.first().args[0];
|
||||
expect(firstLog).toEqual('info: hi {"key":"value"}' + '\n');
|
||||
return reconfigureServer();
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should enable JSON logs', done => {
|
||||
// Force console transport
|
||||
reconfigureServer({
|
||||
logsFolder: null,
|
||||
jsonLogs: true,
|
||||
silent: false
|
||||
}).then(() => {
|
||||
spyOn(process.stdout, 'write');
|
||||
logging.logger.info('hi', {key: 'value'});
|
||||
expect(process.stdout.write).toHaveBeenCalled();
|
||||
const firstLog = process.stdout.write.calls.first().args[0];
|
||||
expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' }) + '\n');
|
||||
return reconfigureServer({
|
||||
jsonLogs: false
|
||||
silent: false,
|
||||
})
|
||||
.then(() => {
|
||||
spyOn(process.stdout, 'write');
|
||||
logging.logger.info('hi', { key: 'value' });
|
||||
expect(process.stdout.write).toHaveBeenCalled();
|
||||
const firstLog = process.stdout.write.calls.first().args[0];
|
||||
expect(firstLog).toEqual(
|
||||
JSON.stringify({ key: 'value', level: 'info', message: 'hi' }) + '\n'
|
||||
);
|
||||
return reconfigureServer({
|
||||
jsonLogs: false,
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
});
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,33 +1,40 @@
|
||||
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
const LoggerController = require('../lib/Controllers/LoggerController')
|
||||
.LoggerController;
|
||||
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
|
||||
.WinstonLoggerAdapter;
|
||||
|
||||
describe('LoggerController', () => {
|
||||
it('can check process a query without throwing', (done) => {
|
||||
it('can process an empty query without throwing', done => {
|
||||
// Make mock request
|
||||
const query = {};
|
||||
|
||||
const loggerController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
expect(() => {
|
||||
loggerController.getLogs(query).then(function(res) {
|
||||
expect(res.length).not.toBe(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
loggerController
|
||||
.getLogs(query)
|
||||
.then(function(res) {
|
||||
expect(res.length).not.toBe(0);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('properly validates dateTimes', (done) => {
|
||||
it('properly validates dateTimes', done => {
|
||||
expect(LoggerController.validDateTime()).toBe(null);
|
||||
expect(LoggerController.validDateTime("String")).toBe(null);
|
||||
expect(LoggerController.validDateTime('String')).toBe(null);
|
||||
expect(LoggerController.validDateTime(123456).getTime()).toBe(123456);
|
||||
expect(LoggerController.validDateTime("2016-01-01Z00:00:00").getTime()).toBe(1451606400000);
|
||||
expect(
|
||||
LoggerController.validDateTime('2016-01-01Z00:00:00').getTime()
|
||||
).toBe(1451606400000);
|
||||
done();
|
||||
});
|
||||
|
||||
it('can set the proper default values', (done) => {
|
||||
it('can set the proper default values', done => {
|
||||
// Make mock request
|
||||
const result = LoggerController.parseOptions();
|
||||
expect(result.size).toEqual(10);
|
||||
@@ -37,14 +44,14 @@ describe('LoggerController', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('can process a query without throwing', (done) => {
|
||||
it('can parse an ascending query without throwing', done => {
|
||||
// Make mock request
|
||||
const query = {
|
||||
from: "2016-01-01Z00:00:00",
|
||||
until: "2016-01-01Z00:00:00",
|
||||
from: '2016-01-01Z00:00:00',
|
||||
until: '2016-01-01Z00:00:00',
|
||||
size: 5,
|
||||
order: 'asc',
|
||||
level: 'error'
|
||||
level: 'error',
|
||||
};
|
||||
|
||||
const result = LoggerController.parseOptions(query);
|
||||
@@ -58,50 +65,95 @@ describe('LoggerController', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('can check process a query without throwing', (done) => {
|
||||
// Make mock request
|
||||
it('can process an ascending query without throwing', done => {
|
||||
const query = {
|
||||
from: "2016-01-01",
|
||||
until: "2016-01-30",
|
||||
size: 5,
|
||||
order: 'desc',
|
||||
level: 'error'
|
||||
order: 'asc',
|
||||
level: 'error',
|
||||
};
|
||||
|
||||
const loggerController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
expect(() => {
|
||||
loggerController.getLogs(query).then(function(res) {
|
||||
expect(res.length).toBe(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
loggerController
|
||||
.getLogs(query)
|
||||
.then(function(res) {
|
||||
expect(res.length).not.toBe(0);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
fail('should not fail');
|
||||
done();
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw without an adapter', (done) => {
|
||||
it('can parse a descending query without throwing', done => {
|
||||
// Make mock request
|
||||
const query = {
|
||||
from: '2016-01-01Z00:00:00',
|
||||
until: '2016-01-01Z00:00:00',
|
||||
size: 5,
|
||||
order: 'desc',
|
||||
level: 'error',
|
||||
};
|
||||
|
||||
const result = LoggerController.parseOptions(query);
|
||||
|
||||
expect(result.from.getTime()).toEqual(1451606400000);
|
||||
expect(result.until.getTime()).toEqual(1451606400000);
|
||||
expect(result.size).toEqual(5);
|
||||
expect(result.order).toEqual('desc');
|
||||
expect(result.level).toEqual('error');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('can process a descending query without throwing', done => {
|
||||
const query = {
|
||||
size: 5,
|
||||
order: 'desc',
|
||||
level: 'error',
|
||||
};
|
||||
|
||||
const loggerController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
expect(() => {
|
||||
loggerController
|
||||
.getLogs(query)
|
||||
.then(function(res) {
|
||||
expect(res.length).not.toBe(0);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
fail('should not fail');
|
||||
done();
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw without an adapter', done => {
|
||||
expect(() => {
|
||||
new LoggerController();
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should replace implementations with verbose', (done) => {
|
||||
it('should replace implementations with verbose', done => {
|
||||
const adapter = new WinstonLoggerAdapter();
|
||||
const logger = new LoggerController(adapter, null, {verbose: true });
|
||||
spyOn(adapter, "log");
|
||||
const logger = new LoggerController(adapter, null, { verbose: true });
|
||||
spyOn(adapter, 'log');
|
||||
logger.silly('yo!');
|
||||
expect(adapter.log).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should replace implementations with logLevel', (done) => {
|
||||
it('should replace implementations with logLevel', done => {
|
||||
const adapter = new WinstonLoggerAdapter();
|
||||
const logger = new LoggerController(adapter, null, { logLevel: 'error' });
|
||||
spyOn(adapter, "log");
|
||||
spyOn(adapter, 'log');
|
||||
logger.warn('yo!');
|
||||
logger.info('yo!');
|
||||
logger.debug('yo!');
|
||||
|
||||
+94
-71
@@ -1,23 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const request = require('request');
|
||||
const LogsRouter = require('../src/Routers/LogsRouter').LogsRouter;
|
||||
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
const request = require('../lib/request');
|
||||
const LogsRouter = require('../lib/Routers/LogsRouter').LogsRouter;
|
||||
const LoggerController = require('../lib/Controllers/LoggerController')
|
||||
.LoggerController;
|
||||
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
|
||||
.WinstonLoggerAdapter;
|
||||
|
||||
const loggerController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
describe('LogsRouter', () => {
|
||||
it('can check valid master key of request', (done) => {
|
||||
it('can check valid master key of request', done => {
|
||||
// Make mock request
|
||||
const request = {
|
||||
auth: {
|
||||
isMaster: true
|
||||
isMaster: true,
|
||||
},
|
||||
query: {},
|
||||
config: {
|
||||
loggerController: loggerController
|
||||
}
|
||||
loggerController: loggerController,
|
||||
},
|
||||
};
|
||||
|
||||
const router = new LogsRouter();
|
||||
@@ -28,16 +30,16 @@ describe('LogsRouter', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('can check invalid construction of controller', (done) => {
|
||||
it('can check invalid construction of controller', done => {
|
||||
// Make mock request
|
||||
const request = {
|
||||
auth: {
|
||||
isMaster: true
|
||||
isMaster: true,
|
||||
},
|
||||
query: {},
|
||||
config: {
|
||||
loggerController: undefined // missing controller
|
||||
}
|
||||
loggerController: undefined, // missing controller
|
||||
},
|
||||
};
|
||||
|
||||
const router = new LogsRouter();
|
||||
@@ -49,15 +51,15 @@ describe('LogsRouter', () => {
|
||||
});
|
||||
|
||||
it('can check invalid master key of request', done => {
|
||||
request.get({
|
||||
request({
|
||||
url: 'http://localhost:8378/1/scriptlog',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(403);
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
}).then(fail, response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(403);
|
||||
expect(body.error).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
});
|
||||
@@ -66,7 +68,7 @@ describe('LogsRouter', () => {
|
||||
const headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
'X-Parse-Master-Key': 'test',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -74,24 +76,31 @@ describe('LogsRouter', () => {
|
||||
*/
|
||||
it('does scrub simple passwords on GET login', done => {
|
||||
reconfigureServer({
|
||||
verbose: true
|
||||
verbose: true,
|
||||
}).then(function() {
|
||||
request.get({
|
||||
request({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/login?username=test&password=simplepass.com'
|
||||
}, () => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
|
||||
json: true,
|
||||
headers: headers
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
// 4th entry is our actual GET request
|
||||
expect(body[3].url).toEqual('/1/login?username=test&password=********');
|
||||
expect(body[3].message).toEqual('REQUEST for [GET] /1/login?username=test&password=********: {}');
|
||||
done();
|
||||
url:
|
||||
'http://localhost:8378/1/login?username=test&password=simplepass.com',
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
request({
|
||||
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
|
||||
headers: headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(200);
|
||||
// 4th entry is our actual GET request
|
||||
expect(body[2].url).toEqual(
|
||||
'/1/login?username=test&password=********'
|
||||
);
|
||||
expect(body[2].message).toEqual(
|
||||
'REQUEST for [GET] /1/login?username=test&password=********: {}'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,26 +109,35 @@ describe('LogsRouter', () => {
|
||||
*/
|
||||
it('does scrub complex passwords on GET login', done => {
|
||||
reconfigureServer({
|
||||
verbose: true
|
||||
}).then(function() {
|
||||
request.get({
|
||||
headers: headers,
|
||||
// using urlencoded password, 'simple @,/?:&=+$#pass.com'
|
||||
url: 'http://localhost:8378/1/login?username=test&password=simple%20%40%2C%2F%3F%3A%26%3D%2B%24%23pass.com'
|
||||
}, () => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
|
||||
json: true,
|
||||
headers: headers
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
// 4th entry is our actual GET request
|
||||
expect(body[3].url).toEqual('/1/login?username=test&password=********');
|
||||
expect(body[3].message).toEqual('REQUEST for [GET] /1/login?username=test&password=********: {}');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
verbose: true,
|
||||
})
|
||||
.then(function() {
|
||||
return request({
|
||||
headers: headers,
|
||||
// using urlencoded password, 'simple @,/?:&=+$#pass.com'
|
||||
url:
|
||||
'http://localhost:8378/1/login?username=test&password=simple%20%40%2C%2F%3F%3A%26%3D%2B%24%23pass.com',
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
return request({
|
||||
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
|
||||
headers: headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(200);
|
||||
// 4th entry is our actual GET request
|
||||
expect(body[2].url).toEqual(
|
||||
'/1/login?username=test&password=********'
|
||||
);
|
||||
expect(body[2].message).toEqual(
|
||||
'REQUEST for [GET] /1/login?username=test&password=********: {}'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -127,28 +145,33 @@ describe('LogsRouter', () => {
|
||||
*/
|
||||
it('does not have password field in POST login', done => {
|
||||
reconfigureServer({
|
||||
verbose: true
|
||||
verbose: true,
|
||||
}).then(function() {
|
||||
request.post({
|
||||
request({
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/login',
|
||||
data: {
|
||||
body: {
|
||||
username: 'test',
|
||||
password: 'simplepass.com'
|
||||
}
|
||||
}, () => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
|
||||
json: true,
|
||||
headers: headers
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
// 4th entry is our actual GET request
|
||||
expect(body[3].url).toEqual('/1/login');
|
||||
expect(body[3].message).toEqual('REQUEST for [POST] /1/login: {}');
|
||||
done();
|
||||
password: 'simplepass.com',
|
||||
},
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
request({
|
||||
url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose',
|
||||
headers: headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(200);
|
||||
// 4th entry is our actual GET request
|
||||
expect(body[2].url).toEqual('/1/login');
|
||||
expect(body[2].message).toEqual(
|
||||
'REQUEST for [POST] /1/login: {\n "username": "test",\n "password": "********"\n}'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+71
-57
@@ -1,8 +1,7 @@
|
||||
const middlewares = require('../src/middlewares');
|
||||
const AppCache = require('../src/cache').AppCache;
|
||||
const middlewares = require('../lib/middlewares');
|
||||
const AppCache = require('../lib/cache').AppCache;
|
||||
|
||||
describe('middlewares', () => {
|
||||
|
||||
let fakeReq, fakeRes;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -10,12 +9,12 @@ describe('middlewares', () => {
|
||||
originalUrl: 'http://example.com/parse/',
|
||||
url: 'http://example.com/',
|
||||
body: {
|
||||
_ApplicationId: 'FakeAppId'
|
||||
_ApplicationId: 'FakeAppId',
|
||||
},
|
||||
headers: {},
|
||||
get: (key) => {
|
||||
return fakeReq.headers[key.toLowerCase()]
|
||||
}
|
||||
get: key => {
|
||||
return fakeReq.headers[key.toLowerCase()];
|
||||
},
|
||||
};
|
||||
fakeRes = jasmine.createSpyObj('fakeRes', ['end', 'status']);
|
||||
AppCache.put(fakeReq.body._ApplicationId, {});
|
||||
@@ -25,21 +24,21 @@ describe('middlewares', () => {
|
||||
AppCache.del(fakeReq.body._ApplicationId);
|
||||
});
|
||||
|
||||
it('should use _ContentType if provided', (done) => {
|
||||
it('should use _ContentType if provided', done => {
|
||||
expect(fakeReq.headers['content-type']).toEqual(undefined);
|
||||
const contentType = 'image/jpeg';
|
||||
fakeReq.body._ContentType = contentType;
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeReq.headers['content-type']).toEqual(contentType);
|
||||
expect(fakeReq.body._ContentType).toEqual(undefined);
|
||||
done()
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but no key supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
restAPIKey: 'restAPIKey',
|
||||
});
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
@@ -48,7 +47,7 @@ describe('middlewares', () => {
|
||||
it('should give invalid response when keys are configured but supplied key is incorrect', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
restAPIKey: 'restAPIKey',
|
||||
});
|
||||
fakeReq.headers['x-parse-rest-api-key'] = 'wrongKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
@@ -58,19 +57,18 @@ describe('middlewares', () => {
|
||||
it('should give invalid response when keys are configured but different key is supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
restAPIKey: 'restAPIKey',
|
||||
});
|
||||
fakeReq.headers['x-parse-client-key'] = 'clientKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
|
||||
it('should succeed when any one of the configured keys supplied', (done) => {
|
||||
it('should succeed when any one of the configured keys supplied', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
clientKey: 'clientKey',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
restAPIKey: 'restAPIKey',
|
||||
});
|
||||
fakeReq.headers['x-parse-rest-api-key'] = 'restAPIKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
@@ -79,11 +77,11 @@ describe('middlewares', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when client key supplied but empty', (done) => {
|
||||
it('should succeed when client key supplied but empty', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
clientKey: '',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
restAPIKey: 'restAPIKey',
|
||||
});
|
||||
fakeReq.headers['x-parse-client-key'] = '';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
@@ -92,9 +90,9 @@ describe('middlewares', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when no keys are configured and none supplied', (done) => {
|
||||
it('should succeed when no keys are configured and none supplied', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey'
|
||||
masterKey: 'masterKey',
|
||||
});
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
@@ -107,25 +105,27 @@ describe('middlewares', () => {
|
||||
installationId: '_InstallationId',
|
||||
sessionToken: '_SessionToken',
|
||||
masterKey: '_MasterKey',
|
||||
javascriptKey: '_JavaScriptKey'
|
||||
javascriptKey: '_JavaScriptKey',
|
||||
};
|
||||
|
||||
const BodyKeys = Object.keys(BodyParams);
|
||||
|
||||
BodyKeys.forEach((infoKey) => {
|
||||
BodyKeys.forEach(infoKey => {
|
||||
const bodyKey = BodyParams[infoKey];
|
||||
const keyValue = 'Fake' + bodyKey;
|
||||
// javascriptKey is the only one that gets defaulted,
|
||||
const otherKeys = BodyKeys.filter((otherKey) => otherKey !== infoKey && otherKey !== 'javascriptKey');
|
||||
const otherKeys = BodyKeys.filter(
|
||||
otherKey => otherKey !== infoKey && otherKey !== 'javascriptKey'
|
||||
);
|
||||
|
||||
it(`it should pull ${bodyKey} into req.info`, (done) => {
|
||||
it(`it should pull ${bodyKey} into req.info`, done => {
|
||||
fakeReq.body[bodyKey] = keyValue;
|
||||
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeReq.body[bodyKey]).toEqual(undefined);
|
||||
expect(fakeReq.info[infoKey]).toEqual(keyValue);
|
||||
|
||||
otherKeys.forEach((otherKey) => {
|
||||
otherKeys.forEach(otherKey => {
|
||||
expect(fakeReq.info[otherKey]).toEqual(undefined);
|
||||
});
|
||||
|
||||
@@ -137,7 +137,7 @@ describe('middlewares', () => {
|
||||
it('should not succeed if the ip does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.ip = 'ip3';
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
@@ -145,14 +145,14 @@ describe('middlewares', () => {
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the ip does belong to masterKeyIps list', (done) => {
|
||||
it('should succeed if the ip does belong to masterKeyIps list', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.ip = 'ip1';
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
@@ -161,22 +161,22 @@ describe('middlewares', () => {
|
||||
it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.connection = {remoteAddress : 'ip3'};
|
||||
fakeReq.connection = { remoteAddress: 'ip3' };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', (done) => {
|
||||
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.connection = {remoteAddress : 'ip1'};
|
||||
fakeReq.connection = { remoteAddress: 'ip1' };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
@@ -185,22 +185,22 @@ describe('middlewares', () => {
|
||||
it('should not succeed if the socket.remoteAddress does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.socket = {remoteAddress : 'ip3'};
|
||||
fakeReq.socket = { remoteAddress: 'ip3' };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', (done) => {
|
||||
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.socket = {remoteAddress : 'ip1'};
|
||||
fakeReq.socket = { remoteAddress: 'ip1' };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
@@ -209,61 +209,61 @@ describe('middlewares', () => {
|
||||
it('should not succeed if the connection.socket.remoteAddress does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.connection = { socket : {remoteAddress : 'ip3'}};
|
||||
fakeReq.connection = { socket: { remoteAddress: 'ip3' } };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', (done) => {
|
||||
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1','ip2']
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.connection = { socket : {remoteAddress : 'ip1'}};
|
||||
fakeReq.connection = { socket: { remoteAddress: 'ip1' } };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow any ip to use masterKey if masterKeyIps is empty', (done) => {
|
||||
it('should allow any ip to use masterKey if masterKeyIps is empty', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: []
|
||||
masterKeyIps: [],
|
||||
});
|
||||
fakeReq.ip = 'ip1';
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if xff header does belong to masterKeyIps', (done) => {
|
||||
it('should succeed if xff header does belong to masterKeyIps', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1']
|
||||
masterKeyIps: ['ip1'],
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if xff header with one ip does belong to masterKeyIps', (done) => {
|
||||
it('should succeed if xff header with one ip does belong to masterKeyIps', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1']
|
||||
masterKeyIps: ['ip1'],
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = 'ip1';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes,() => {
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
@@ -272,7 +272,7 @@ describe('middlewares', () => {
|
||||
it('should not succeed if xff header does not belong to masterKeyIps', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip4']
|
||||
masterKeyIps: ['ip4'],
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
|
||||
@@ -283,11 +283,25 @@ describe('middlewares', () => {
|
||||
it('should not succeed if xff header is empty and masterKeyIps is set', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1']
|
||||
masterKeyIps: ['ip1'],
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = '';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should properly expose the headers', () => {
|
||||
const headers = {};
|
||||
const res = {
|
||||
header: (key, value) => {
|
||||
headers[key] = value;
|
||||
},
|
||||
};
|
||||
middlewares.allowCrossDomain({}, res, () => {});
|
||||
expect(Object.keys(headers).length).toBe(4);
|
||||
expect(headers['Access-Control-Expose-Headers']).toBe(
|
||||
'X-Parse-Job-Status-Id, X-Parse-Push-Status-Id'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = function(options) {
|
||||
return {
|
||||
options: options
|
||||
options: options,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => Promise.resolve()
|
||||
}
|
||||
sendMail: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
module.exports = options => {
|
||||
if (!options) {
|
||||
throw "Options were not provided"
|
||||
throw 'Options were not provided';
|
||||
}
|
||||
const adapter = {
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => Promise.resolve()
|
||||
sendMail: () => Promise.resolve(),
|
||||
};
|
||||
if (options.sendMail) {
|
||||
adapter.sendMail = options.sendMail
|
||||
adapter.sendMail = options.sendMail;
|
||||
}
|
||||
if (options.sendPasswordResetEmail) {
|
||||
adapter.sendPasswordResetEmail = options.sendPasswordResetEmail
|
||||
adapter.sendPasswordResetEmail = options.sendPasswordResetEmail;
|
||||
}
|
||||
if (options.sendVerificationEmail) {
|
||||
adapter.sendVerificationEmail = options.sendVerificationEmail;
|
||||
}
|
||||
|
||||
return adapter;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,6 @@ module.exports = function(options) {
|
||||
send: function() {},
|
||||
getValidPushTypes: function() {
|
||||
return Object.keys(options.options);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,46 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
const MongoSchemaCollection = require('../src/Adapters/Storage/Mongo/MongoSchemaCollection').default;
|
||||
const MongoSchemaCollection = require('../lib/Adapters/Storage/Mongo/MongoSchemaCollection')
|
||||
.default;
|
||||
|
||||
describe('MongoSchemaCollection', () => {
|
||||
it('can transform legacy _client_permissions keys to parse format', done => {
|
||||
expect(MongoSchemaCollection._TESTmongoSchemaToParseSchema({
|
||||
"_id":"_Installation",
|
||||
"_client_permissions":{
|
||||
"get":true,
|
||||
"find":true,
|
||||
"update":true,
|
||||
"create":true,
|
||||
"delete":true,
|
||||
},
|
||||
"_metadata":{
|
||||
"class_permissions":{
|
||||
"get":{"*":true},
|
||||
"find":{"*":true},
|
||||
"update":{"*":true},
|
||||
"create":{"*":true},
|
||||
"delete":{"*":true},
|
||||
"addField":{"*":true},
|
||||
expect(
|
||||
MongoSchemaCollection._TESTmongoSchemaToParseSchema({
|
||||
_id: '_Installation',
|
||||
_client_permissions: {
|
||||
get: true,
|
||||
find: true,
|
||||
count: true,
|
||||
update: true,
|
||||
create: true,
|
||||
delete: true,
|
||||
},
|
||||
"indexes": {
|
||||
"name1":{"deviceToken":1}
|
||||
}
|
||||
},
|
||||
"installationId":"string",
|
||||
"deviceToken":"string",
|
||||
"deviceType":"string",
|
||||
"channels":"array",
|
||||
"user":"*_User",
|
||||
"pushType":"string",
|
||||
"GCMSenderId":"string",
|
||||
"timeZone":"string",
|
||||
"localeIdentifier":"string",
|
||||
"badge":"number",
|
||||
"appVersion":"string",
|
||||
"appName":"string",
|
||||
"appIdentifier":"string",
|
||||
"parseVersion":"string",
|
||||
})).toEqual({
|
||||
_metadata: {
|
||||
class_permissions: {
|
||||
get: { '*': true },
|
||||
find: { '*': true },
|
||||
count: { '*': true },
|
||||
update: { '*': true },
|
||||
create: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
protectedFields: { '*': [] },
|
||||
},
|
||||
indexes: {
|
||||
name1: { deviceToken: 1 },
|
||||
},
|
||||
},
|
||||
installationId: 'string',
|
||||
deviceToken: 'string',
|
||||
deviceType: 'string',
|
||||
channels: 'array',
|
||||
user: '*_User',
|
||||
pushType: 'string',
|
||||
GCMSenderId: 'string',
|
||||
timeZone: 'string',
|
||||
localeIdentifier: 'string',
|
||||
badge: 'number',
|
||||
appVersion: 'string',
|
||||
appName: 'string',
|
||||
appIdentifier: 'string',
|
||||
parseVersion: 'string',
|
||||
})
|
||||
).toEqual({
|
||||
className: '_Installation',
|
||||
fields: {
|
||||
installationId: { type: 'String' },
|
||||
@@ -65,13 +71,15 @@ describe('MongoSchemaCollection', () => {
|
||||
classLevelPermissions: {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
count: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
protectedFields: { '*': [] },
|
||||
},
|
||||
indexes: {
|
||||
name1: {deviceToken: 1}
|
||||
name1: { deviceToken: 1 },
|
||||
},
|
||||
});
|
||||
done();
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||
const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter')
|
||||
.default;
|
||||
const { MongoClient } = require('mongodb');
|
||||
const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const databaseURI =
|
||||
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
|
||||
const fakeClient = {
|
||||
s: { options: { dbName: null } },
|
||||
db: () => null,
|
||||
};
|
||||
|
||||
// These tests are specific to the mongo storage adapter + mongo storage format
|
||||
// and will eventually be moved into their own repo
|
||||
@@ -14,9 +21,10 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
});
|
||||
|
||||
it('auto-escapes symbols in auth information', () => {
|
||||
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
|
||||
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(fakeClient));
|
||||
new MongoStorageAdapter({
|
||||
uri: 'mongodb://user!with@+ symbols:password!with@+ symbols@localhost:1234/parse'
|
||||
uri:
|
||||
'mongodb://user!with@+ symbols:password!with@+ symbols@localhost:1234/parse',
|
||||
}).connect();
|
||||
expect(MongoClient.connect).toHaveBeenCalledWith(
|
||||
'mongodb://user!with%40%2B%20symbols:password!with%40%2B%20symbols@localhost:1234/parse',
|
||||
@@ -25,9 +33,10 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
});
|
||||
|
||||
it("doesn't double escape already URI-encoded information", () => {
|
||||
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
|
||||
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(fakeClient));
|
||||
new MongoStorageAdapter({
|
||||
uri: 'mongodb://user!with%40%2B%20symbols:password!with%40%2B%20symbols@localhost:1234/parse'
|
||||
uri:
|
||||
'mongodb://user!with%40%2B%20symbols:password!with%40%2B%20symbols@localhost:1234/parse',
|
||||
}).connect();
|
||||
expect(MongoClient.connect).toHaveBeenCalledWith(
|
||||
'mongodb://user!with%40%2B%20symbols:password!with%40%2B%20symbols@localhost:1234/parse',
|
||||
@@ -37,9 +46,10 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
|
||||
// https://github.com/parse-community/parse-server/pull/148#issuecomment-180407057
|
||||
it('preserves replica sets', () => {
|
||||
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
|
||||
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(fakeClient));
|
||||
new MongoStorageAdapter({
|
||||
uri: 'mongodb://test:testpass@ds056315-a0.mongolab.com:59325,ds059315-a1.mongolab.com:59315/testDBname?replicaSet=rs-ds059415'
|
||||
uri:
|
||||
'mongodb://test:testpass@ds056315-a0.mongolab.com:59325,ds059315-a1.mongolab.com:59315/testDBname?replicaSet=rs-ds059415',
|
||||
}).connect();
|
||||
expect(MongoClient.connect).toHaveBeenCalledWith(
|
||||
'mongodb://test:testpass@ds056315-a0.mongolab.com:59325,ds059315-a1.mongolab.com:59315/testDBname?replicaSet=rs-ds059415',
|
||||
@@ -49,7 +59,8 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
|
||||
it('stores objectId in _id', done => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
adapter
|
||||
.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -60,57 +71,70 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('find succeeds when query is within maxTimeMS', (done) => {
|
||||
it('find succeeds when query is within maxTimeMS', done => {
|
||||
const maxTimeMS = 250;
|
||||
const adapter = new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
mongoOptions: { maxTimeMS },
|
||||
});
|
||||
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS / 2})` }))
|
||||
adapter
|
||||
.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() =>
|
||||
adapter._rawFind('Foo', { $where: `sleep(${maxTimeMS / 2})` })
|
||||
)
|
||||
.then(
|
||||
() => done(),
|
||||
(err) => {
|
||||
err => {
|
||||
done.fail(`maxTimeMS should not affect fast queries ${err}`);
|
||||
}
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
it('find fails when query exceeds maxTimeMS', (done) => {
|
||||
it('find fails when query exceeds maxTimeMS', done => {
|
||||
const maxTimeMS = 250;
|
||||
const adapter = new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
mongoOptions: { maxTimeMS },
|
||||
});
|
||||
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS * 2})` }))
|
||||
adapter
|
||||
.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() =>
|
||||
adapter._rawFind('Foo', { $where: `sleep(${maxTimeMS * 2})` })
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
done.fail('Find succeeded despite taking too long!');
|
||||
},
|
||||
(err) => {
|
||||
err => {
|
||||
expect(err.name).toEqual('MongoError');
|
||||
expect(err.code).toEqual(50);
|
||||
expect(err.message).toEqual('operation exceeded time limit');
|
||||
expect(err.message).toMatch('operation exceeded time limit');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('stores pointers with a _p_ prefix', (done) => {
|
||||
it('stores pointers with a _p_ prefix', done => {
|
||||
const obj = {
|
||||
objectId: 'bar',
|
||||
aPointer: {
|
||||
__type: 'Pointer',
|
||||
className: 'JustThePointer',
|
||||
objectId: 'qwerty'
|
||||
}
|
||||
objectId: 'qwerty',
|
||||
},
|
||||
};
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
adapter.createObject('APointerDarkly', { fields: {
|
||||
objectId: { type: 'String' },
|
||||
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
|
||||
}}, obj)
|
||||
adapter
|
||||
.createObject(
|
||||
'APointerDarkly',
|
||||
{
|
||||
fields: {
|
||||
objectId: { type: 'String' },
|
||||
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
|
||||
},
|
||||
},
|
||||
obj
|
||||
)
|
||||
.then(() => adapter._rawFind('APointerDarkly', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -125,9 +149,10 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
|
||||
it('handles object and subdocument', done => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
const schema = { fields : { subdoc: { type: 'Object' } } };
|
||||
const obj = { subdoc: {foo: 'bar', wu: 'tan'} };
|
||||
adapter.createObject('MyClass', schema, obj)
|
||||
const schema = { fields: { subdoc: { type: 'Object' } } };
|
||||
const obj = { subdoc: { foo: 'bar', wu: 'tan' } };
|
||||
adapter
|
||||
.createObject('MyClass', schema, obj)
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -149,22 +174,25 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('handles creating an array, object, date', (done) => {
|
||||
it('handles creating an array, object, date', done => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
const obj = {
|
||||
array: [1, 2, 3],
|
||||
object: {foo: 'bar'},
|
||||
object: { foo: 'bar' },
|
||||
date: {
|
||||
__type: 'Date',
|
||||
iso: '2016-05-26T20:55:01.154Z',
|
||||
},
|
||||
};
|
||||
const schema = { fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
} };
|
||||
adapter.createObject('MyClass', schema, obj)
|
||||
const schema = {
|
||||
fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
},
|
||||
};
|
||||
adapter
|
||||
.createObject('MyClass', schema, obj)
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -190,30 +218,32 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("handles updating a single object with array, object date", (done) => {
|
||||
it('handles updating a single object with array, object date', done => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
|
||||
const schema = { fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
} };
|
||||
const schema = {
|
||||
fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
adapter.createObject('MyClass', schema, {})
|
||||
adapter
|
||||
.createObject('MyClass', schema, {})
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const update = {
|
||||
array: [1, 2, 3],
|
||||
object: {foo: 'bar'},
|
||||
object: { foo: 'bar' },
|
||||
date: {
|
||||
__type: 'Date',
|
||||
iso: '2016-05-26T20:55:01.154Z',
|
||||
},
|
||||
};
|
||||
const query = {};
|
||||
return adapter.findOneAndUpdate('MyClass', schema, query, update)
|
||||
return adapter.findOneAndUpdate('MyClass', schema, query, update);
|
||||
})
|
||||
.then(results => {
|
||||
const mob = results;
|
||||
@@ -238,7 +268,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('handleShutdown, close connection', (done) => {
|
||||
it('handleShutdown, close connection', done => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
|
||||
const schema = {
|
||||
@@ -246,14 +276,37 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
adapter.createObject('MyClass', schema, {}).then(() => {
|
||||
expect(adapter.database.serverConfig.isConnected()).toEqual(true);
|
||||
adapter.handleShutdown()
|
||||
adapter.handleShutdown();
|
||||
expect(adapter.database.serverConfig.isConnected()).toEqual(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('getClass if exists', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
|
||||
const schema = {
|
||||
fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
},
|
||||
};
|
||||
|
||||
await adapter.createClass('MyClass', schema);
|
||||
const myClassSchema = await adapter.getClass('MyClass');
|
||||
expect(myClassSchema).toBeDefined();
|
||||
});
|
||||
|
||||
it('getClass if not exists', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
await expectAsync(adapter.getClass('UnknownClass')).toBeRejectedWith(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+277
-128
@@ -1,132 +1,184 @@
|
||||
// These tests are unit tests designed to only test transform.js.
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const transform = require('../src/Adapters/Storage/Mongo/MongoTransform');
|
||||
const transform = require('../lib/Adapters/Storage/Mongo/MongoTransform');
|
||||
const dd = require('deep-diff');
|
||||
const mongodb = require('mongodb');
|
||||
|
||||
describe('parseObjectToMongoObjectForCreate', () => {
|
||||
it('a basic number', (done) => {
|
||||
const input = {five: 5};
|
||||
it('a basic number', done => {
|
||||
const input = { five: 5 };
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {five: {type: 'Number'}}
|
||||
fields: { five: { type: 'Number' } },
|
||||
});
|
||||
jequal(input, output);
|
||||
done();
|
||||
});
|
||||
|
||||
it('an object with null values', (done) => {
|
||||
const input = {objectWithNullValues: {isNull: null, notNull: 3}};
|
||||
it('an object with null values', done => {
|
||||
const input = { objectWithNullValues: { isNull: null, notNull: 3 } };
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {objectWithNullValues: {type: 'object'}}
|
||||
fields: { objectWithNullValues: { type: 'object' } },
|
||||
});
|
||||
jequal(input, output);
|
||||
done();
|
||||
});
|
||||
|
||||
it('built-in timestamps', (done) => {
|
||||
it('built-in timestamps with date', done => {
|
||||
const input = {
|
||||
createdAt: "2015-10-06T21:24:50.332Z",
|
||||
updatedAt: "2015-10-06T21:24:50.332Z"
|
||||
createdAt: '2015-10-06T21:24:50.332Z',
|
||||
updatedAt: '2015-10-06T21:24:50.332Z',
|
||||
};
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {},
|
||||
});
|
||||
expect(output._created_at instanceof Date).toBe(true);
|
||||
expect(output._updated_at instanceof Date).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('array of pointers', (done) => {
|
||||
it('array of pointers', done => {
|
||||
const pointer = {
|
||||
__type: 'Pointer',
|
||||
objectId: 'myId',
|
||||
className: 'Blah',
|
||||
};
|
||||
const out = transform.parseObjectToMongoObjectForCreate(null, {pointers: [pointer]},{
|
||||
fields: {pointers: {type: 'Array'}}
|
||||
});
|
||||
const out = transform.parseObjectToMongoObjectForCreate(
|
||||
null,
|
||||
{ pointers: [pointer] },
|
||||
{
|
||||
fields: { pointers: { type: 'Array' } },
|
||||
}
|
||||
);
|
||||
jequal([pointer], out.pointers);
|
||||
done();
|
||||
});
|
||||
|
||||
//TODO: object creation requests shouldn't be seeing __op delete, it makes no sense to
|
||||
//have __op delete in a new object. Figure out what this should actually be testing.
|
||||
xit('a delete op', (done) => {
|
||||
const input = {deleteMe: {__op: 'Delete'}};
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
|
||||
xit('a delete op', done => {
|
||||
const input = { deleteMe: { __op: 'Delete' } };
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {},
|
||||
});
|
||||
jequal(output, {});
|
||||
done();
|
||||
});
|
||||
|
||||
it('Doesnt allow ACL, as Parse Server should tranform ACL to _wperm + _rperm', done => {
|
||||
const input = {ACL: {'0123': {'read': true, 'write': true}}};
|
||||
expect(() => transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} })).toThrow();
|
||||
const input = { ACL: { '0123': { read: true, write: true } } };
|
||||
expect(() =>
|
||||
transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} })
|
||||
).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('plain', (done) => {
|
||||
const geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
||||
const out = transform.parseObjectToMongoObjectForCreate(null, {location: geoPoint},{
|
||||
fields: {location: {type: 'GeoPoint'}}
|
||||
});
|
||||
expect(out.location).toEqual([180, -180]);
|
||||
it('parse geopoint to mongo', done => {
|
||||
const lat = -45;
|
||||
const lng = 45;
|
||||
const geoPoint = { __type: 'GeoPoint', latitude: lat, longitude: lng };
|
||||
const out = transform.parseObjectToMongoObjectForCreate(
|
||||
null,
|
||||
{ location: geoPoint },
|
||||
{
|
||||
fields: { location: { type: 'GeoPoint' } },
|
||||
}
|
||||
);
|
||||
expect(out.location).toEqual([lng, lat]);
|
||||
done();
|
||||
});
|
||||
|
||||
it('in array', (done) => {
|
||||
const geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
||||
const out = transform.parseObjectToMongoObjectForCreate(null, {locations: [geoPoint, geoPoint]},{
|
||||
fields: {locations: {type: 'Array'}}
|
||||
});
|
||||
it('parse polygon to mongo', done => {
|
||||
const lat1 = -45;
|
||||
const lng1 = 45;
|
||||
const lat2 = -55;
|
||||
const lng2 = 55;
|
||||
const lat3 = -65;
|
||||
const lng3 = 65;
|
||||
const polygon = {
|
||||
__type: 'Polygon',
|
||||
coordinates: [[lat1, lng1], [lat2, lng2], [lat3, lng3]],
|
||||
};
|
||||
const out = transform.parseObjectToMongoObjectForCreate(
|
||||
null,
|
||||
{ location: polygon },
|
||||
{
|
||||
fields: { location: { type: 'Polygon' } },
|
||||
}
|
||||
);
|
||||
expect(out.location.coordinates).toEqual([
|
||||
[[lng1, lat1], [lng2, lat2], [lng3, lat3], [lng1, lat1]],
|
||||
]);
|
||||
done();
|
||||
});
|
||||
|
||||
it('in array', done => {
|
||||
const geoPoint = { __type: 'GeoPoint', longitude: 180, latitude: -180 };
|
||||
const out = transform.parseObjectToMongoObjectForCreate(
|
||||
null,
|
||||
{ locations: [geoPoint, geoPoint] },
|
||||
{
|
||||
fields: { locations: { type: 'Array' } },
|
||||
}
|
||||
);
|
||||
expect(out.locations).toEqual([geoPoint, geoPoint]);
|
||||
done();
|
||||
});
|
||||
|
||||
it('in sub-object', (done) => {
|
||||
const geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
||||
const out = transform.parseObjectToMongoObjectForCreate(null, { locations: { start: geoPoint }},{
|
||||
fields: {locations: {type: 'Object'}}
|
||||
});
|
||||
it('in sub-object', done => {
|
||||
const geoPoint = { __type: 'GeoPoint', longitude: 180, latitude: -180 };
|
||||
const out = transform.parseObjectToMongoObjectForCreate(
|
||||
null,
|
||||
{ locations: { start: geoPoint } },
|
||||
{
|
||||
fields: { locations: { type: 'Object' } },
|
||||
}
|
||||
);
|
||||
expect(out).toEqual({ locations: { start: geoPoint } });
|
||||
done();
|
||||
});
|
||||
|
||||
it('objectId', (done) => {
|
||||
const out = transform.transformWhere(null, {objectId: 'foo'});
|
||||
it('objectId', done => {
|
||||
const out = transform.transformWhere(null, { objectId: 'foo' });
|
||||
expect(out._id).toEqual('foo');
|
||||
done();
|
||||
});
|
||||
|
||||
it('objectId in a list', (done) => {
|
||||
it('objectId in a list', done => {
|
||||
const input = {
|
||||
objectId: {'$in': ['one', 'two', 'three']},
|
||||
objectId: { $in: ['one', 'two', 'three'] },
|
||||
};
|
||||
const output = transform.transformWhere(null, input);
|
||||
jequal(input.objectId, output._id);
|
||||
done();
|
||||
});
|
||||
|
||||
it('built-in timestamps', (done) => {
|
||||
const input = {createdAt: new Date(), updatedAt: new Date()};
|
||||
const output = transform.mongoObjectToParseObject(null, input, { fields: {} });
|
||||
it('built-in timestamps', done => {
|
||||
const input = { createdAt: new Date(), updatedAt: new Date() };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: {},
|
||||
});
|
||||
expect(typeof output.createdAt).toEqual('string');
|
||||
expect(typeof output.updatedAt).toEqual('string');
|
||||
done();
|
||||
});
|
||||
|
||||
it('pointer', (done) => {
|
||||
const input = {_p_userPointer: '_User$123'};
|
||||
it('pointer', done => {
|
||||
const input = { _p_userPointer: '_User$123' };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { userPointer: { type: 'Pointer', targetClass: '_User' } },
|
||||
});
|
||||
expect(typeof output.userPointer).toEqual('object');
|
||||
expect(output.userPointer).toEqual(
|
||||
{__type: 'Pointer', className: '_User', objectId: '123'}
|
||||
);
|
||||
expect(output.userPointer).toEqual({
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: '123',
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('null pointer', (done) => {
|
||||
const input = {_p_userPointer: null};
|
||||
it('null pointer', done => {
|
||||
const input = { _p_userPointer: null };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { userPointer: { type: 'Pointer', targetClass: '_User' } },
|
||||
});
|
||||
@@ -134,112 +186,134 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('file', (done) => {
|
||||
const input = {picture: 'pic.jpg'};
|
||||
it('file', done => {
|
||||
const input = { picture: 'pic.jpg' };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { picture: { type: 'File' }},
|
||||
fields: { picture: { type: 'File' } },
|
||||
});
|
||||
expect(typeof output.picture).toEqual('object');
|
||||
expect(output.picture).toEqual({__type: 'File', name: 'pic.jpg'});
|
||||
expect(output.picture).toEqual({ __type: 'File', name: 'pic.jpg' });
|
||||
done();
|
||||
});
|
||||
|
||||
it('geopoint', (done) => {
|
||||
const input = {location: [45, -45]};
|
||||
it('mongo geopoint to parse', done => {
|
||||
const lat = -45;
|
||||
const lng = 45;
|
||||
const input = { location: [lng, lat] };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { location: { type: 'GeoPoint' }},
|
||||
fields: { location: { type: 'GeoPoint' } },
|
||||
});
|
||||
expect(typeof output.location).toEqual('object');
|
||||
expect(output.location).toEqual(
|
||||
{__type: 'GeoPoint', longitude: 45, latitude: -45}
|
||||
);
|
||||
expect(output.location).toEqual({
|
||||
__type: 'GeoPoint',
|
||||
latitude: lat,
|
||||
longitude: lng,
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('polygon', (done) => {
|
||||
const input = {location: { type: 'Polygon', coordinates: [[[45, -45],[45, -45]]]}};
|
||||
it('mongo polygon to parse', done => {
|
||||
const lat = -45;
|
||||
const lng = 45;
|
||||
// Mongo stores polygon in WGS84 lng/lat
|
||||
const input = {
|
||||
location: { type: 'Polygon', coordinates: [[[lat, lng], [lat, lng]]] },
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { location: { type: 'Polygon' }},
|
||||
fields: { location: { type: 'Polygon' } },
|
||||
});
|
||||
expect(typeof output.location).toEqual('object');
|
||||
expect(output.location).toEqual(
|
||||
{__type: 'Polygon', coordinates: [[45, -45],[45, -45]]}
|
||||
);
|
||||
expect(output.location).toEqual({
|
||||
__type: 'Polygon',
|
||||
coordinates: [[lng, lat], [lng, lat]],
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('bytes', (done) => {
|
||||
const input = {binaryData: "aGVsbG8gd29ybGQ="};
|
||||
it('bytes', done => {
|
||||
const input = { binaryData: 'aGVsbG8gd29ybGQ=' };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { binaryData: { type: 'Bytes' }},
|
||||
fields: { binaryData: { type: 'Bytes' } },
|
||||
});
|
||||
expect(typeof output.binaryData).toEqual('object');
|
||||
expect(output.binaryData).toEqual(
|
||||
{__type: 'Bytes', base64: "aGVsbG8gd29ybGQ="}
|
||||
);
|
||||
expect(output.binaryData).toEqual({
|
||||
__type: 'Bytes',
|
||||
base64: 'aGVsbG8gd29ybGQ=',
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('nested array', (done) => {
|
||||
const input = {arr: [{_testKey: 'testValue' }]};
|
||||
it('nested array', done => {
|
||||
const input = { arr: [{ _testKey: 'testValue' }] };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { arr: { type: 'Array' } },
|
||||
});
|
||||
expect(Array.isArray(output.arr)).toEqual(true);
|
||||
expect(output.arr).toEqual([{ _testKey: 'testValue'}]);
|
||||
expect(output.arr).toEqual([{ _testKey: 'testValue' }]);
|
||||
done();
|
||||
});
|
||||
|
||||
it('untransforms objects containing nested special keys', done => {
|
||||
const input = {array: [{
|
||||
_id: "Test ID",
|
||||
_hashed_password: "I Don't know why you would name a key this, but if you do it should work",
|
||||
_tombstone: {
|
||||
_updated_at: "I'm sure people will nest keys like this",
|
||||
_acl: 7,
|
||||
_id: { someString: "str", someNumber: 7},
|
||||
regularKey: { moreContents: [1, 2, 3] },
|
||||
},
|
||||
regularKey: "some data",
|
||||
}]}
|
||||
const input = {
|
||||
array: [
|
||||
{
|
||||
_id: 'Test ID',
|
||||
_hashed_password:
|
||||
"I Don't know why you would name a key this, but if you do it should work",
|
||||
_tombstone: {
|
||||
_updated_at: "I'm sure people will nest keys like this",
|
||||
_acl: 7,
|
||||
_id: { someString: 'str', someNumber: 7 },
|
||||
regularKey: { moreContents: [1, 2, 3] },
|
||||
},
|
||||
regularKey: 'some data',
|
||||
},
|
||||
],
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { array: { type: 'Array' }},
|
||||
fields: { array: { type: 'Array' } },
|
||||
});
|
||||
expect(dd(output, input)).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
|
||||
it('changes new pointer key', (done) => {
|
||||
it('changes new pointer key', done => {
|
||||
const input = {
|
||||
somePointer: {__type: 'Pointer', className: 'Micro', objectId: 'oft'}
|
||||
somePointer: { __type: 'Pointer', className: 'Micro', objectId: 'oft' },
|
||||
};
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {somePointer: {type: 'Pointer'}}
|
||||
fields: { somePointer: { type: 'Pointer' } },
|
||||
});
|
||||
expect(typeof output._p_somePointer).toEqual('string');
|
||||
expect(output._p_somePointer).toEqual('Micro$oft');
|
||||
done();
|
||||
});
|
||||
|
||||
it('changes existing pointer keys', (done) => {
|
||||
it('changes existing pointer keys', done => {
|
||||
const input = {
|
||||
userPointer: {__type: 'Pointer', className: '_User', objectId: 'qwerty'}
|
||||
userPointer: {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: 'qwerty',
|
||||
},
|
||||
};
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {userPointer: {type: 'Pointer'}}
|
||||
fields: { userPointer: { type: 'Pointer' } },
|
||||
});
|
||||
expect(typeof output._p_userPointer).toEqual('string');
|
||||
expect(output._p_userPointer).toEqual('_User$qwerty');
|
||||
done();
|
||||
});
|
||||
|
||||
it('writes the old ACL format in addition to rperm and wperm on create', (done) => {
|
||||
it('writes the old ACL format in addition to rperm and wperm on create', done => {
|
||||
const input = {
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin'],
|
||||
};
|
||||
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {},
|
||||
});
|
||||
expect(typeof output._acl).toEqual('object');
|
||||
expect(output._acl['Kevin'].w).toBeTruthy();
|
||||
expect(output._acl['Kevin'].r).toBeUndefined();
|
||||
@@ -248,7 +322,7 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('removes Relation types', (done) => {
|
||||
it('removes Relation types', done => {
|
||||
const input = {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
};
|
||||
@@ -261,10 +335,10 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('writes the old ACL format in addition to rperm and wperm on update', (done) => {
|
||||
it('writes the old ACL format in addition to rperm and wperm on update', done => {
|
||||
const input = {
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin']
|
||||
_wperm: ['Kevin'],
|
||||
};
|
||||
|
||||
const output = transform.transformUpdate(null, input, { fields: {} });
|
||||
@@ -278,23 +352,25 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('untransforms from _rperm and _wperm to ACL', (done) => {
|
||||
it('untransforms from _rperm and _wperm to ACL', done => {
|
||||
const input = {
|
||||
_rperm: ["*"],
|
||||
_wperm: ["Kevin"]
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin'],
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, { fields: {} });
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: {},
|
||||
});
|
||||
expect(output._rperm).toEqual(['*']);
|
||||
expect(output._wperm).toEqual(['Kevin']);
|
||||
expect(output.ACL).toBeUndefined()
|
||||
expect(output.ACL).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
|
||||
it('untransforms mongodb number types', (done) => {
|
||||
it('untransforms mongodb number types', done => {
|
||||
const input = {
|
||||
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
|
||||
double: new mongodb.Double(Number.MAX_VALUE)
|
||||
}
|
||||
double: new mongodb.Double(Number.MAX_VALUE),
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: {
|
||||
long: { type: 'Number' },
|
||||
@@ -306,35 +382,107 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('Date object where iso attribute is of type Date', (done) => {
|
||||
it('Date object where iso attribute is of type Date', done => {
|
||||
const input = {
|
||||
ts : { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') }
|
||||
}
|
||||
ts: { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') },
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields : {
|
||||
ts : { type : 'Date' }
|
||||
}
|
||||
fields: {
|
||||
ts: { type: 'Date' },
|
||||
},
|
||||
});
|
||||
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
|
||||
done();
|
||||
});
|
||||
|
||||
it('Date object where iso attribute is of type String', (done) => {
|
||||
it('Date object where iso attribute is of type String', done => {
|
||||
const input = {
|
||||
ts : { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' }
|
||||
}
|
||||
ts: { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' },
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields : {
|
||||
ts : { type : 'Date' }
|
||||
}
|
||||
fields: {
|
||||
ts: { type: 'Date' },
|
||||
},
|
||||
});
|
||||
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
|
||||
done();
|
||||
});
|
||||
|
||||
it('object with undefined nested values', () => {
|
||||
const input = {
|
||||
_id: 'vQHyinCW1l',
|
||||
urls: { firstUrl: 'https://', secondUrl: undefined },
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: {
|
||||
urls: { type: 'Object' },
|
||||
},
|
||||
});
|
||||
expect(output.urls).toEqual({
|
||||
firstUrl: 'https://',
|
||||
secondUrl: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('undefined objects', () => {
|
||||
const input = {
|
||||
_id: 'vQHyinCW1l',
|
||||
urls: undefined,
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: {
|
||||
urls: { type: 'Object' },
|
||||
},
|
||||
});
|
||||
expect(output.urls).toBeUndefined();
|
||||
});
|
||||
|
||||
it('$regex in $all list', done => {
|
||||
const input = {
|
||||
arrayField: {
|
||||
$all: [
|
||||
{ $regex: '^\\Qone\\E' },
|
||||
{ $regex: '^\\Qtwo\\E' },
|
||||
{ $regex: '^\\Qthree\\E' },
|
||||
],
|
||||
},
|
||||
};
|
||||
const outputValue = {
|
||||
arrayField: { $all: [/^\Qone\E/, /^\Qtwo\E/, /^\Qthree\E/] },
|
||||
};
|
||||
|
||||
const output = transform.transformWhere(null, input);
|
||||
jequal(outputValue.arrayField, output.arrayField);
|
||||
done();
|
||||
});
|
||||
|
||||
it('$regex in $all list must be { $regex: "string" }', done => {
|
||||
const input = {
|
||||
arrayField: { $all: [{ $regex: 1 }] },
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
transform.transformWhere(null, input);
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('all values in $all must be $regex (start with string) or non $regex (start with string)', done => {
|
||||
const input = {
|
||||
arrayField: {
|
||||
$all: [{ $regex: '^\\Qone\\E' }, { $unknown: '^\\Qtwo\\E' }],
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
transform.transformWhere(null, input);
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformUpdate', () => {
|
||||
it('removes Relation types', (done) => {
|
||||
it('removes Relation types', done => {
|
||||
const input = {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
};
|
||||
@@ -356,8 +504,8 @@ describe('transformConstraint', () => {
|
||||
$eq: {
|
||||
ttl: {
|
||||
$relativeTime: '12 days ago',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}).toThrow();
|
||||
|
||||
@@ -366,8 +514,8 @@ describe('transformConstraint', () => {
|
||||
$ne: {
|
||||
ttl: {
|
||||
$relativeTime: '12 days ago',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}).toThrow();
|
||||
|
||||
@@ -375,11 +523,11 @@ describe('transformConstraint', () => {
|
||||
transform.transformConstraint({
|
||||
$exists: {
|
||||
$relativeTime: '12 days ago',
|
||||
}
|
||||
},
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
describe('relativeTimeToDate', () => {
|
||||
@@ -417,7 +565,9 @@ describe('relativeTimeToDate', () => {
|
||||
|
||||
describe('Error cases', () => {
|
||||
it('should error if string is completely gibberish', () => {
|
||||
expect(transform.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')).toEqual({
|
||||
expect(
|
||||
transform.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')
|
||||
).toEqual({
|
||||
status: 'error',
|
||||
info: "Time should either start with 'in' or end with 'ago'",
|
||||
});
|
||||
@@ -471,4 +621,3 @@ describe('relativeTimeToDate', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
const NullCacheAdapter = require('../src/Adapters/Cache/NullCacheAdapter').default;
|
||||
const NullCacheAdapter = require('../lib/Adapters/Cache/NullCacheAdapter')
|
||||
.default;
|
||||
|
||||
describe('NullCacheAdapter', function() {
|
||||
const KEY = 'hello';
|
||||
const VALUE = 'world';
|
||||
|
||||
it('should expose promisifyed methods', (done) => {
|
||||
it('should expose promisifyed methods', done => {
|
||||
const cache = new NullCacheAdapter({
|
||||
ttl: NaN
|
||||
ttl: NaN,
|
||||
});
|
||||
|
||||
// Verify all methods return promises.
|
||||
@@ -14,24 +15,24 @@ describe('NullCacheAdapter', function() {
|
||||
cache.put(KEY, VALUE),
|
||||
cache.del(KEY),
|
||||
cache.get(KEY),
|
||||
cache.clear()
|
||||
cache.clear(),
|
||||
]).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get/set/clear', (done) => {
|
||||
it('should get/set/clear', done => {
|
||||
const cache = new NullCacheAdapter({
|
||||
ttl: NaN
|
||||
ttl: NaN,
|
||||
});
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
cache
|
||||
.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(value => expect(value).toEqual(null))
|
||||
.then(() => cache.clear())
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(value => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+82
-69
@@ -1,140 +1,153 @@
|
||||
const OAuth = require("../src/Adapters/Auth/OAuth1Client");
|
||||
const OAuth = require('../lib/Adapters/Auth/OAuth1Client');
|
||||
|
||||
describe('OAuth', function() {
|
||||
it("Nonce should have right length", (done) => {
|
||||
it('Nonce should have right length', done => {
|
||||
jequal(OAuth.nonce().length, 30);
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build parameter string", (done) => {
|
||||
const string = OAuth.buildParameterString({c:1, a:2, b:3})
|
||||
jequal(string, "a=2&b=3&c=1");
|
||||
it('Should properly build parameter string', done => {
|
||||
const string = OAuth.buildParameterString({ c: 1, a: 2, b: 3 });
|
||||
jequal(string, 'a=2&b=3&c=1');
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build empty parameter string", (done) => {
|
||||
const string = OAuth.buildParameterString()
|
||||
jequal(string, "");
|
||||
it('Should properly build empty parameter string', done => {
|
||||
const string = OAuth.buildParameterString();
|
||||
jequal(string, '');
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build signature string", (done) => {
|
||||
const string = OAuth.buildSignatureString("get", "http://dummy.com", "");
|
||||
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
|
||||
it('Should properly build signature string', done => {
|
||||
const string = OAuth.buildSignatureString('get', 'http://dummy.com', '');
|
||||
jequal(string, 'GET&http%3A%2F%2Fdummy.com&');
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly generate request signature", (done) => {
|
||||
it('Should properly generate request signature', done => {
|
||||
let request = {
|
||||
host: "dummy.com",
|
||||
path: "path"
|
||||
host: 'dummy.com',
|
||||
path: 'path',
|
||||
};
|
||||
|
||||
const oauth_params = {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA",
|
||||
oauth_consumer_key: "hello",
|
||||
oauth_token: "token"
|
||||
oauth_nonce: 'AAAAAAAAAAAAAAAAA',
|
||||
oauth_consumer_key: 'hello',
|
||||
oauth_token: 'token',
|
||||
};
|
||||
|
||||
const consumer_secret = "world";
|
||||
const auth_token_secret = "secret";
|
||||
request = OAuth.signRequest(request, oauth_params, consumer_secret, auth_token_secret);
|
||||
jequal(request.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"');
|
||||
const consumer_secret = 'world';
|
||||
const auth_token_secret = 'secret';
|
||||
request = OAuth.signRequest(
|
||||
request,
|
||||
oauth_params,
|
||||
consumer_secret,
|
||||
auth_token_secret
|
||||
);
|
||||
jequal(
|
||||
request.headers['Authorization'],
|
||||
'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"'
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build request", (done) => {
|
||||
it('Should properly build request', done => {
|
||||
const options = {
|
||||
host: "dummy.com",
|
||||
consumer_key: "hello",
|
||||
consumer_secret: "world",
|
||||
auth_token: "token",
|
||||
auth_token_secret: "secret",
|
||||
host: 'dummy.com',
|
||||
consumer_key: 'hello',
|
||||
consumer_secret: 'world',
|
||||
auth_token: 'token',
|
||||
auth_token_secret: 'secret',
|
||||
// Custom oauth params for tests
|
||||
oauth_params: {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA"
|
||||
}
|
||||
oauth_nonce: 'AAAAAAAAAAAAAAAAA',
|
||||
},
|
||||
};
|
||||
const path = "path";
|
||||
const method = "get";
|
||||
const path = 'path';
|
||||
const method = 'get';
|
||||
|
||||
const oauthClient = new OAuth(options);
|
||||
const req = oauthClient.buildRequest(method, path, {"query": "param"});
|
||||
const req = oauthClient.buildRequest(method, path, { query: 'param' });
|
||||
|
||||
jequal(req.host, options.host);
|
||||
jequal(req.path, "/" + path + "?query=param");
|
||||
jequal(req.method, "GET");
|
||||
jequal(req.path, '/' + path + '?query=param');
|
||||
jequal(req.method, 'GET');
|
||||
jequal(req.headers['Content-Type'], 'application/x-www-form-urlencoded');
|
||||
jequal(req.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="wNkyEkDE%2F0JZ2idmqyrgHdvC0rs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"')
|
||||
jequal(
|
||||
req.headers['Authorization'],
|
||||
'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="wNkyEkDE%2F0JZ2idmqyrgHdvC0rs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"'
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
function validateCannotAuthenticateError(data, done) {
|
||||
jequal(typeof data, "object");
|
||||
jequal(typeof data.errors, "object");
|
||||
jequal(typeof data, 'object');
|
||||
jequal(typeof data.errors, 'object');
|
||||
const errors = data.errors;
|
||||
jequal(typeof errors[0], "object");
|
||||
jequal(typeof errors[0], 'object');
|
||||
// Cannot authenticate error
|
||||
jequal(errors[0].code, 32);
|
||||
done();
|
||||
}
|
||||
|
||||
it("Should fail a GET request", (done) => {
|
||||
it('Should fail a GET request', done => {
|
||||
const options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
host: 'api.twitter.com',
|
||||
consumer_key: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
consumer_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
};
|
||||
const path = "/1.1/help/configuration.json";
|
||||
const params = {"lang": "en"};
|
||||
const path = '/1.1/help/configuration.json';
|
||||
const params = { lang: 'en' };
|
||||
const oauthClient = new OAuth(options);
|
||||
oauthClient.get(path, params).then(function(data){
|
||||
oauthClient.get(path, params).then(function(data) {
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it("Should fail a POST request", (done) => {
|
||||
it('Should fail a POST request', done => {
|
||||
const options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
host: 'api.twitter.com',
|
||||
consumer_key: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
consumer_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
};
|
||||
const body = {
|
||||
lang: "en"
|
||||
lang: 'en',
|
||||
};
|
||||
const path = "/1.1/account/settings.json";
|
||||
const path = '/1.1/account/settings.json';
|
||||
|
||||
const oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
oauthClient.post(path, null, body).then(function(data) {
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it("Should fail a request", (done) => {
|
||||
it('Should fail a request', done => {
|
||||
const options = {
|
||||
host: "localhost",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
host: 'localhost',
|
||||
consumer_key: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
consumer_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
};
|
||||
const body = {
|
||||
lang: "en"
|
||||
lang: 'en',
|
||||
};
|
||||
const path = "/";
|
||||
const path = '/';
|
||||
|
||||
const oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(){
|
||||
jequal(false, true);
|
||||
done();
|
||||
}).catch(function(){
|
||||
jequal(true, true);
|
||||
done();
|
||||
})
|
||||
oauthClient
|
||||
.post(path, null, body)
|
||||
.then(function() {
|
||||
jequal(false, true);
|
||||
done();
|
||||
})
|
||||
.catch(function() {
|
||||
jequal(true, true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("Should fail with missing options", (done) => {
|
||||
it('Should fail with missing options', done => {
|
||||
const options = undefined;
|
||||
try {
|
||||
new OAuth(options);
|
||||
|
||||
+330
-256
@@ -1,12 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const request = require('request');
|
||||
const request = require('../lib/request');
|
||||
|
||||
const delayPromise = (delay) => {
|
||||
return new Promise((resolve) => {
|
||||
const delayPromise = delay => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, delay);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
describe('Parse.Push', () => {
|
||||
const setup = function() {
|
||||
@@ -15,10 +15,10 @@ describe('Parse.Push', () => {
|
||||
const pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
const badge = body.data.badge;
|
||||
const promises = installations.map((installation) => {
|
||||
const promises = installations.map(installation => {
|
||||
sendToInstallationSpy(installation);
|
||||
|
||||
if (installation.deviceType == "ios") {
|
||||
if (installation.deviceType == 'ios') {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(installation.originalBadge + 1).toEqual(installation.badge);
|
||||
} else {
|
||||
@@ -27,33 +27,39 @@ describe('Parse.Push', () => {
|
||||
return Promise.resolve({
|
||||
err: null,
|
||||
device: installation,
|
||||
transmitted: true
|
||||
})
|
||||
transmitted: true,
|
||||
});
|
||||
});
|
||||
return Promise.all(promises);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios", "android"];
|
||||
}
|
||||
}
|
||||
return ['ios', 'android'];
|
||||
},
|
||||
};
|
||||
|
||||
return reconfigureServer({
|
||||
appId: Parse.applicationId,
|
||||
masterKey: Parse.masterKey,
|
||||
serverURL: Parse.serverURL,
|
||||
push: {
|
||||
adapter: pushAdapter
|
||||
}
|
||||
adapter: pushAdapter,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
const installations = [];
|
||||
while(installations.length != 10) {
|
||||
const installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
while (installations.length != 10) {
|
||||
const installation = new Parse.Object('_Installation');
|
||||
installation.set(
|
||||
'installationId',
|
||||
'installation_' + installations.length
|
||||
);
|
||||
installation.set(
|
||||
'deviceToken',
|
||||
'device_token_' + installations.length
|
||||
);
|
||||
installation.set('badge', installations.length);
|
||||
installation.set('originalBadge', installations.length);
|
||||
installation.set('deviceType', 'ios');
|
||||
installations.push(installation);
|
||||
}
|
||||
return Parse.Object.saveAll(installations);
|
||||
@@ -63,83 +69,102 @@ describe('Parse.Push', () => {
|
||||
sendToInstallationSpy,
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
it('should properly send push', (done) => {
|
||||
return setup().then(({ sendToInstallationSpy }) => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'Increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
.then(() => {
|
||||
return delayPromise(500);
|
||||
})
|
||||
.then(() => {
|
||||
expect(sendToInstallationSpy.calls.count()).toEqual(10);
|
||||
})
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
it('should properly send push', done => {
|
||||
return setup()
|
||||
.then(({ sendToInstallationSpy }) => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
where: {
|
||||
deviceType: 'ios',
|
||||
},
|
||||
data: {
|
||||
badge: 'Increment',
|
||||
alert: 'Hello world!',
|
||||
},
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
)
|
||||
.then(() => {
|
||||
return delayPromise(500);
|
||||
})
|
||||
.then(() => {
|
||||
expect(sendToInstallationSpy.calls.count()).toEqual(10);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly send push with lowercaseIncrement', (done) => {
|
||||
return setup().then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
return delayPromise(500);
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
it('should properly send push with lowercaseIncrement', done => {
|
||||
return setup()
|
||||
.then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
where: {
|
||||
deviceType: 'ios',
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!',
|
||||
},
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return delayPromise(500);
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow clients to query _PushStatus', done => {
|
||||
setup()
|
||||
.then(() => Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true}))
|
||||
.then(() =>
|
||||
Parse.Push.send(
|
||||
{
|
||||
where: {
|
||||
deviceType: 'ios',
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!',
|
||||
},
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
)
|
||||
)
|
||||
.then(() => delayPromise(500))
|
||||
.then(() => {
|
||||
request.get({
|
||||
request({
|
||||
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
expect(body.error).toEqual('unauthorized');
|
||||
}).then(fail, response => {
|
||||
expect(response.data.error).toEqual('unauthorized');
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
@@ -147,86 +172,106 @@ describe('Parse.Push', () => {
|
||||
|
||||
it('should allow master key to query _PushStatus', done => {
|
||||
setup()
|
||||
.then(() => Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true}))
|
||||
.then(() =>
|
||||
Parse.Push.send(
|
||||
{
|
||||
where: {
|
||||
deviceType: 'ios',
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!',
|
||||
},
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
)
|
||||
)
|
||||
.then(() => delayPromise(500)) // put a delay as we keep writing
|
||||
.then(() => {
|
||||
request.get({
|
||||
request({
|
||||
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
try {
|
||||
expect(body.results.length).toEqual(1);
|
||||
expect(body.results[0].query).toEqual('{"deviceType":"ios"}');
|
||||
expect(body.results[0].payload).toEqual('{"badge":"increment","alert":"Hello world!"}');
|
||||
} catch(e) {
|
||||
expect(body.results[0].payload).toEqual(
|
||||
'{"badge":"increment","alert":"Hello world!"}'
|
||||
);
|
||||
} catch (e) {
|
||||
jfail(e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error if missing push configuration', done => {
|
||||
reconfigureServer({push: null})
|
||||
reconfigureServer({ push: null })
|
||||
.then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
return Parse.Push.send(
|
||||
{
|
||||
where: {
|
||||
deviceType: 'ios',
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!',
|
||||
},
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
}, (err) => {
|
||||
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
fail('should not succeed');
|
||||
},
|
||||
err => {
|
||||
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
|
||||
done();
|
||||
}
|
||||
)
|
||||
.catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
const successfulAny = function(body, installations) {
|
||||
const promises = installations.map((device) => {
|
||||
const promises = installations.map(device => {
|
||||
return Promise.resolve({
|
||||
transmitted: true,
|
||||
device: device,
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
const provideInstallations = function(num) {
|
||||
if(!num) {
|
||||
if (!num) {
|
||||
num = 2;
|
||||
}
|
||||
|
||||
const installations = [];
|
||||
while(installations.length !== num) {
|
||||
while (installations.length !== num) {
|
||||
// add Android installations
|
||||
const installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length);
|
||||
installation.set("deviceType", "android");
|
||||
const installation = new Parse.Object('_Installation');
|
||||
installation.set(
|
||||
'installationId',
|
||||
'installation_' + installations.length
|
||||
);
|
||||
installation.set('deviceToken', 'device_token_' + installations.length);
|
||||
installation.set('deviceType', 'android');
|
||||
installations.push(installation);
|
||||
}
|
||||
|
||||
@@ -242,8 +287,8 @@ describe('Parse.Push', () => {
|
||||
return successfulAny(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["android"];
|
||||
}
|
||||
return ['android'];
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -251,39 +296,44 @@ describe('Parse.Push', () => {
|
||||
* Simulates a simple push where 1 installation is removed between _PushStatus
|
||||
* count being set and the pushes being sent
|
||||
*/
|
||||
it('does not get stuck with _PushStatus \'running\' on 1 installation lost', (done) => {
|
||||
it("does not get stuck with _PushStatus 'running' on 1 installation lost", done => {
|
||||
reconfigureServer({
|
||||
push: {adapter: losingAdapter}
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(provideInstallations());
|
||||
}).then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: {alert: "We fixed our status!"},
|
||||
where: {deviceType: 'android'}
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
}).then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
push: { adapter: losingAdapter },
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Object.saveAll(provideInstallations());
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: { alert: 'We fixed our status!' },
|
||||
where: { deviceType: 'android' },
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({ useMasterKey: true });
|
||||
})
|
||||
.then(results => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
expect(result.get('numSent')).toEqual(1);
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
}).then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
expect(result.get('numSent')).toEqual(1);
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -291,14 +341,17 @@ describe('Parse.Push', () => {
|
||||
* Simulates a simple push where 1 installation is added between _PushStatus
|
||||
* count being set and the pushes being sent
|
||||
*/
|
||||
it('does not get stuck with _PushStatus \'running\' on 1 installation added', (done) => {
|
||||
it("does not get stuck with _PushStatus 'running' on 1 installation added", done => {
|
||||
const installations = provideInstallations();
|
||||
|
||||
// add 1 iOS installation which we will omit & add later on
|
||||
const iOSInstallation = new Parse.Object("_Installation");
|
||||
iOSInstallation.set("installationId", "installation_" + installations.length);
|
||||
iOSInstallation.set("deviceToken","device_token_" + installations.length);
|
||||
iOSInstallation.set("deviceType", "ios");
|
||||
const iOSInstallation = new Parse.Object('_Installation');
|
||||
iOSInstallation.set(
|
||||
'installationId',
|
||||
'installation_' + installations.length
|
||||
);
|
||||
iOSInstallation.set('deviceToken', 'device_token_' + installations.length);
|
||||
iOSInstallation.set('deviceType', 'ios');
|
||||
installations.push(iOSInstallation);
|
||||
|
||||
reconfigureServer({
|
||||
@@ -312,40 +365,45 @@ describe('Parse.Push', () => {
|
||||
return successfulAny(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["android"];
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations);
|
||||
}).then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: {alert: "We fixed our status!"},
|
||||
where: {deviceType: {'$ne' : 'random'}}
|
||||
return ['android'];
|
||||
},
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
}).then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Object.saveAll(installations);
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: { alert: 'We fixed our status!' },
|
||||
where: { deviceType: { $ne: 'random' } },
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({ useMasterKey: true });
|
||||
})
|
||||
.then(results => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
expect(result.get('numSent')).toEqual(3);
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
}).then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
expect(result.get('numSent')).toEqual(3);
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -353,43 +411,48 @@ describe('Parse.Push', () => {
|
||||
* Simulates an extended push, where some installations may be removed,
|
||||
* resulting in a non-zero count
|
||||
*/
|
||||
it('does not get stuck with _PushStatus \'running\' on many installations removed', (done) => {
|
||||
it("does not get stuck with _PushStatus 'running' on many installations removed", done => {
|
||||
const devices = 1000;
|
||||
const installations = provideInstallations(devices);
|
||||
|
||||
reconfigureServer({
|
||||
push: {adapter: losingAdapter}
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations);
|
||||
}).then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: {alert: "We fixed our status!"},
|
||||
where: {deviceType: 'android'}
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
}).then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
push: { adapter: losingAdapter },
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Object.saveAll(installations);
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: { alert: 'We fixed our status!' },
|
||||
where: { deviceType: 'android' },
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({ useMasterKey: true });
|
||||
})
|
||||
.then(results => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
// expect # less than # of batches used, assuming each batch is 100 pushes
|
||||
expect(result.get('numSent')).toEqual(devices - devices / 100);
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
}).then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
// expect # less than # of batches used, assuming each batch is 100 pushes
|
||||
expect(result.get('numSent')).toEqual(devices - (devices / 100));
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -397,18 +460,24 @@ describe('Parse.Push', () => {
|
||||
* Simulates an extended push, where some installations may be added,
|
||||
* resulting in a non-zero count
|
||||
*/
|
||||
it('does not get stuck with _PushStatus \'running\' on many installations added', (done) => {
|
||||
it("does not get stuck with _PushStatus 'running' on many installations added", done => {
|
||||
const devices = 1000;
|
||||
const installations = provideInstallations(devices);
|
||||
|
||||
// add 1 iOS installation which we will omit & add later on
|
||||
const iOSInstallations = [];
|
||||
|
||||
while(iOSInstallations.length !== (devices / 100)) {
|
||||
const iOSInstallation = new Parse.Object("_Installation");
|
||||
iOSInstallation.set("installationId", "installation_" + installations.length);
|
||||
iOSInstallation.set("deviceToken", "device_token_" + installations.length);
|
||||
iOSInstallation.set("deviceType", "ios");
|
||||
while (iOSInstallations.length !== devices / 100) {
|
||||
const iOSInstallation = new Parse.Object('_Installation');
|
||||
iOSInstallation.set(
|
||||
'installationId',
|
||||
'installation_' + installations.length
|
||||
);
|
||||
iOSInstallation.set(
|
||||
'deviceToken',
|
||||
'device_token_' + installations.length
|
||||
);
|
||||
iOSInstallation.set('deviceType', 'ios');
|
||||
installations.push(iOSInstallation);
|
||||
iOSInstallations.push(iOSInstallation);
|
||||
}
|
||||
@@ -424,40 +493,45 @@ describe('Parse.Push', () => {
|
||||
return successfulAny(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["android"];
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations);
|
||||
}).then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: {alert: "We fixed our status!"},
|
||||
where: {deviceType: {'$ne' : 'random'}}
|
||||
return ['android'];
|
||||
},
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
}).then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Object.saveAll(installations);
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: { alert: 'We fixed our status!' },
|
||||
where: { deviceType: { $ne: 'random' } },
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({ useMasterKey: true });
|
||||
})
|
||||
.then(results => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
// expect # less than # of batches used, assuming each batch is 100 pushes
|
||||
expect(result.get('numSent')).toEqual(devices + devices / 100);
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
}).then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
// expect # less than # of batches used, assuming each batch is 100 pushes
|
||||
expect(result.get('numSent')).toEqual(devices + (devices / 100));
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+870
-1201
File diff suppressed because it is too large
Load Diff
+1039
-798
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
const ParseCloudCodePublisher = require('../src/LiveQuery/ParseCloudCodePublisher').ParseCloudCodePublisher;
|
||||
const ParseCloudCodePublisher = require('../lib/LiveQuery/ParseCloudCodePublisher')
|
||||
.ParseCloudCodePublisher;
|
||||
const Parse = require('parse/node');
|
||||
|
||||
describe('ParseCloudCodePublisher', function() {
|
||||
@@ -7,22 +8,26 @@ describe('ParseCloudCodePublisher', function() {
|
||||
const mockParsePubSub = {
|
||||
createPublisher: jasmine.createSpy('publish').and.returnValue({
|
||||
publish: jasmine.createSpy('publish'),
|
||||
on: jasmine.createSpy('on')
|
||||
on: jasmine.createSpy('on'),
|
||||
}),
|
||||
createSubscriber: jasmine.createSpy('publish').and.returnValue({
|
||||
subscribe: jasmine.createSpy('subscribe'),
|
||||
on: jasmine.createSpy('on')
|
||||
})
|
||||
on: jasmine.createSpy('on'),
|
||||
}),
|
||||
};
|
||||
jasmine.mockLibrary('../src/LiveQuery/ParsePubSub', 'ParsePubSub', mockParsePubSub);
|
||||
jasmine.mockLibrary(
|
||||
'../lib/LiveQuery/ParsePubSub',
|
||||
'ParsePubSub',
|
||||
mockParsePubSub
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('can initialize', function() {
|
||||
const config = {}
|
||||
const config = {};
|
||||
new ParseCloudCodePublisher(config);
|
||||
|
||||
const ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
const ParsePubSub = require('../lib/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
expect(ParsePubSub.createPublisher).toHaveBeenCalledWith(config);
|
||||
});
|
||||
|
||||
@@ -32,7 +37,10 @@ describe('ParseCloudCodePublisher', function() {
|
||||
const request = {};
|
||||
publisher.onCloudCodeAfterSave(request);
|
||||
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterSave', request);
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(
|
||||
Parse.applicationId + 'afterSave',
|
||||
request
|
||||
);
|
||||
});
|
||||
|
||||
it('can handle cloud code afterDelete request', function() {
|
||||
@@ -41,7 +49,10 @@ describe('ParseCloudCodePublisher', function() {
|
||||
const request = {};
|
||||
publisher.onCloudCodeAfterDelete(request);
|
||||
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterDelete', request);
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(
|
||||
Parse.applicationId + 'afterDelete',
|
||||
request
|
||||
);
|
||||
});
|
||||
|
||||
it('can handle cloud code request', function() {
|
||||
@@ -52,7 +63,7 @@ describe('ParseCloudCodePublisher', function() {
|
||||
originalParseObject.set('key', 'originalValue');
|
||||
const request = {
|
||||
object: currentParseObject,
|
||||
original: originalParseObject
|
||||
original: originalParseObject,
|
||||
};
|
||||
publisher._onCloudCodeMessage('afterSave', request);
|
||||
|
||||
@@ -63,7 +74,7 @@ describe('ParseCloudCodePublisher', function() {
|
||||
expect(message.originalParseObject).toEqual(request.original._toFullJSON());
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
jasmine.restoreLibrary('../src/LiveQuery/ParsePubSub', 'ParsePubSub');
|
||||
afterEach(function() {
|
||||
jasmine.restoreLibrary('../lib/LiveQuery/ParsePubSub', 'ParsePubSub');
|
||||
});
|
||||
});
|
||||
|
||||
+456
-469
File diff suppressed because it is too large
Load Diff
+660
-514
File diff suppressed because it is too large
Load Diff
+130
-114
@@ -1,74 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
const request = require('request');
|
||||
const Config = require('../src/Config');
|
||||
const request = require('../lib/request');
|
||||
const Config = require('../lib/Config');
|
||||
|
||||
describe('a GlobalConfig', () => {
|
||||
beforeEach(done => {
|
||||
const config = Config.get('test');
|
||||
const query = on_db('mongo', () => {
|
||||
// Legacy is with an int...
|
||||
return { objectId: 1 };
|
||||
}, () => {
|
||||
return { objectId: "1" }
|
||||
})
|
||||
config.database.adapter.upsertOneObject(
|
||||
'_GlobalConfig',
|
||||
{ fields: { objectId: { type: 'Number' }, params: {type: 'Object'}} },
|
||||
query,
|
||||
{ params: { companies: ['US', 'DK'] } }
|
||||
).then(done, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
const query = on_db(
|
||||
'mongo',
|
||||
() => {
|
||||
// Legacy is with an int...
|
||||
return { objectId: 1 };
|
||||
},
|
||||
() => {
|
||||
return { objectId: '1' };
|
||||
}
|
||||
);
|
||||
config.database.adapter
|
||||
.upsertOneObject(
|
||||
'_GlobalConfig',
|
||||
{
|
||||
fields: { objectId: { type: 'Number' }, params: { type: 'Object' } },
|
||||
},
|
||||
query,
|
||||
{ params: { companies: ['US', 'DK'] } }
|
||||
)
|
||||
.then(done, err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can be retrieved', (done) => {
|
||||
request.get({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
};
|
||||
|
||||
it('can be retrieved', done => {
|
||||
request({
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
try {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(body.params.companies).toEqual(['US', 'DK']);
|
||||
} catch(e) { jfail(e); }
|
||||
} catch (e) {
|
||||
jfail(e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can be updated when a master key exists', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { companies: ['US', 'DK', 'SE'] } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
it('can be updated when a master key exists', done => {
|
||||
request({
|
||||
method: 'PUT',
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
body: { params: { companies: ['US', 'DK', 'SE'] } },
|
||||
headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can add and retrive files', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { file: { __type: 'File', name: 'name', url: 'http://url' } } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
it('can add and retrive files', done => {
|
||||
request({
|
||||
method: 'PUT',
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
body: {
|
||||
params: { file: { __type: 'File', name: 'name', url: 'http://url' } },
|
||||
},
|
||||
headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
Parse.Config.get().then((res) => {
|
||||
Parse.Config.get().then(res => {
|
||||
const file = res.get('file');
|
||||
expect(file.name()).toBe('name');
|
||||
expect(file.url()).toBe('http://url');
|
||||
@@ -77,20 +91,19 @@ describe('a GlobalConfig', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('can add and retrive Geopoints', (done) => {
|
||||
const geopoint = new Parse.GeoPoint(10,-20);
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { point: geopoint.toJSON() } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
it('can add and retrive Geopoints', done => {
|
||||
const geopoint = new Parse.GeoPoint(10, -20);
|
||||
request({
|
||||
method: 'PUT',
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
body: { params: { point: geopoint.toJSON() } },
|
||||
headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
Parse.Config.get().then((res) => {
|
||||
Parse.Config.get().then(res => {
|
||||
const point = res.get('point');
|
||||
expect(point.latitude).toBe(10);
|
||||
expect(point.longitude).toBe(-20);
|
||||
@@ -99,75 +112,78 @@ describe('a GlobalConfig', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('properly handles delete op', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { companies: {__op: 'Delete'}, foo: 'bar' } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
it('properly handles delete op', done => {
|
||||
request({
|
||||
method: 'PUT',
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
body: { params: { companies: { __op: 'Delete' }, foo: 'bar' } },
|
||||
headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
request.get({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
request({
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
try {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(body.params.companies).toBeUndefined();
|
||||
expect(body.params.foo).toBe('bar');
|
||||
expect(Object.keys(body.params).length).toBe(1);
|
||||
} catch(e) { jfail(e); }
|
||||
} catch (e) {
|
||||
jfail(e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fail to update if master key is missing', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { companies: [] } },
|
||||
it('fail to update if master key is missing', done => {
|
||||
request({
|
||||
method: 'PUT',
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
body: { params: { companies: [] } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key' : 'rest'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(403);
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(fail, response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(403);
|
||||
expect(body.error).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('failed getting config when it is missing', (done) => {
|
||||
it('failed getting config when it is missing', done => {
|
||||
const config = Config.get('test');
|
||||
config.database.adapter.deleteObjectsByQuery(
|
||||
'_GlobalConfig',
|
||||
{ fields: { params: { __type: 'String' } } },
|
||||
{ objectId: "1" }
|
||||
).then(() => {
|
||||
request.get({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body.params).toEqual({});
|
||||
config.database.adapter
|
||||
.deleteObjectsByQuery(
|
||||
'_GlobalConfig',
|
||||
{ fields: { params: { __type: 'String' } } },
|
||||
{ objectId: '1' }
|
||||
)
|
||||
.then(() => {
|
||||
request({
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
headers,
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(200);
|
||||
expect(body.params).toEqual({});
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
jfail(e);
|
||||
done();
|
||||
});
|
||||
}).catch((e) => {
|
||||
jfail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,973 @@
|
||||
const {
|
||||
default: ParseGraphQLController,
|
||||
GraphQLConfigClassName,
|
||||
GraphQLConfigId,
|
||||
GraphQLConfigKey,
|
||||
} = require('../lib/Controllers/ParseGraphQLController');
|
||||
const { isEqual } = require('lodash');
|
||||
|
||||
describe('ParseGraphQLController', () => {
|
||||
let parseServer;
|
||||
let databaseController;
|
||||
let cacheController;
|
||||
let databaseUpdateArgs;
|
||||
|
||||
// Holds the graphQLConfig in memory instead of using the db
|
||||
let graphQLConfigRecord;
|
||||
|
||||
const setConfigOnDb = graphQLConfigData => {
|
||||
graphQLConfigRecord = {
|
||||
objectId: GraphQLConfigId,
|
||||
[GraphQLConfigKey]: graphQLConfigData,
|
||||
};
|
||||
};
|
||||
const removeConfigFromDb = () => {
|
||||
graphQLConfigRecord = null;
|
||||
};
|
||||
const getConfigFromDb = () => {
|
||||
return graphQLConfigRecord;
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
parseServer = await global.reconfigureServer({
|
||||
schemaCacheTTL: 100,
|
||||
});
|
||||
databaseController = parseServer.config.databaseController;
|
||||
cacheController = parseServer.config.cacheController;
|
||||
|
||||
const defaultFind = databaseController.find.bind(databaseController);
|
||||
databaseController.find = async (className, query, ...args) => {
|
||||
if (
|
||||
className === GraphQLConfigClassName &&
|
||||
isEqual(query, { objectId: GraphQLConfigId })
|
||||
) {
|
||||
const graphQLConfigRecord = getConfigFromDb();
|
||||
return graphQLConfigRecord ? [graphQLConfigRecord] : [];
|
||||
} else {
|
||||
return defaultFind(className, query, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
const defaultUpdate = databaseController.update.bind(databaseController);
|
||||
databaseController.update = async (
|
||||
className,
|
||||
query,
|
||||
update,
|
||||
fullQueryOptions
|
||||
) => {
|
||||
databaseUpdateArgs = [className, query, update, fullQueryOptions];
|
||||
if (
|
||||
className === GraphQLConfigClassName &&
|
||||
isEqual(query, { objectId: GraphQLConfigId }) &&
|
||||
update &&
|
||||
!!update[GraphQLConfigKey] &&
|
||||
fullQueryOptions &&
|
||||
isEqual(fullQueryOptions, { upsert: true })
|
||||
) {
|
||||
setConfigOnDb(update[GraphQLConfigKey]);
|
||||
} else {
|
||||
return defaultUpdate(...databaseUpdateArgs);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
databaseUpdateArgs = null;
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should require a databaseController', () => {
|
||||
expect(() => new ParseGraphQLController()).toThrow(
|
||||
'ParseGraphQLController requires a "databaseController" to be instantiated.'
|
||||
);
|
||||
expect(() => new ParseGraphQLController({ cacheController })).toThrow(
|
||||
'ParseGraphQLController requires a "databaseController" to be instantiated.'
|
||||
);
|
||||
expect(
|
||||
() =>
|
||||
new ParseGraphQLController({
|
||||
cacheController,
|
||||
mountGraphQL: false,
|
||||
})
|
||||
).toThrow(
|
||||
'ParseGraphQLController requires a "databaseController" to be instantiated.'
|
||||
);
|
||||
});
|
||||
it('should construct without a cacheController', () => {
|
||||
expect(
|
||||
() =>
|
||||
new ParseGraphQLController({
|
||||
databaseController,
|
||||
})
|
||||
).not.toThrow();
|
||||
expect(
|
||||
() =>
|
||||
new ParseGraphQLController({
|
||||
databaseController,
|
||||
mountGraphQL: true,
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
it('should set isMounted to true if config.mountGraphQL is true', () => {
|
||||
const mountedController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
mountGraphQL: true,
|
||||
});
|
||||
expect(mountedController.isMounted).toBe(true);
|
||||
const unmountedController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
mountGraphQL: false,
|
||||
});
|
||||
expect(unmountedController.isMounted).toBe(false);
|
||||
const unmountedController2 = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expect(unmountedController2.isMounted).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGraphQLConfig', () => {
|
||||
it('should return an empty graphQLConfig if collection has none', async () => {
|
||||
removeConfigFromDb();
|
||||
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
mountGraphQL: false,
|
||||
});
|
||||
|
||||
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
|
||||
expect(graphQLConfig).toEqual({});
|
||||
});
|
||||
it('should return an existing graphQLConfig', async () => {
|
||||
setConfigOnDb({ enabledForClasses: ['_User'] });
|
||||
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
mountGraphQL: false,
|
||||
});
|
||||
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
|
||||
expect(graphQLConfig).toEqual({ enabledForClasses: ['_User'] });
|
||||
});
|
||||
it('should use the cache if mounted, and return the stored graphQLConfig', async () => {
|
||||
removeConfigFromDb();
|
||||
cacheController.graphQL.clear();
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
cacheController,
|
||||
mountGraphQL: true,
|
||||
});
|
||||
cacheController.graphQL.put(parseGraphQLController.configCacheKey, {
|
||||
enabledForClasses: ['SuperCar'],
|
||||
});
|
||||
|
||||
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
|
||||
expect(graphQLConfig).toEqual({ enabledForClasses: ['SuperCar'] });
|
||||
});
|
||||
it('should use the database when mounted and cache is empty', async () => {
|
||||
setConfigOnDb({ disabledForClasses: ['SuperCar'] });
|
||||
cacheController.graphQL.clear();
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
cacheController,
|
||||
mountGraphQL: true,
|
||||
});
|
||||
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
|
||||
expect(graphQLConfig).toEqual({ disabledForClasses: ['SuperCar'] });
|
||||
});
|
||||
it('should store the graphQLConfig in cache if mounted', async () => {
|
||||
setConfigOnDb({ enabledForClasses: ['SuperCar'] });
|
||||
cacheController.graphQL.clear();
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
cacheController,
|
||||
mountGraphQL: true,
|
||||
});
|
||||
const cachedValueBefore = await cacheController.graphQL.get(
|
||||
parseGraphQLController.configCacheKey
|
||||
);
|
||||
expect(cachedValueBefore).toBeNull();
|
||||
await parseGraphQLController.getGraphQLConfig();
|
||||
const cachedValueAfter = await cacheController.graphQL.get(
|
||||
parseGraphQLController.configCacheKey
|
||||
);
|
||||
expect(cachedValueAfter).toEqual({ enabledForClasses: ['SuperCar'] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateGraphQLConfig', () => {
|
||||
const successfulUpdateResponse = { response: { result: true } };
|
||||
|
||||
it('should throw if graphQLConfig is not provided', async function() {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig()
|
||||
).toBeRejectedWith('You must provide a graphQLConfig!');
|
||||
});
|
||||
|
||||
it('should correct update the graphQLConfig object using the databaseController', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
const graphQLConfig = {
|
||||
enabledForClasses: ['ClassA', 'ClassB'],
|
||||
disabledForClasses: [],
|
||||
classConfigs: [
|
||||
{ className: 'ClassA', query: { get: false } },
|
||||
{ className: 'ClassB', mutation: { destroy: false }, type: {} },
|
||||
],
|
||||
};
|
||||
|
||||
await parseGraphQLController.updateGraphQLConfig(graphQLConfig);
|
||||
|
||||
expect(databaseUpdateArgs).toBeTruthy();
|
||||
const [className, query, update, op] = databaseUpdateArgs;
|
||||
expect(className).toBe(GraphQLConfigClassName);
|
||||
expect(query).toEqual({ objectId: GraphQLConfigId });
|
||||
expect(update).toEqual({
|
||||
[GraphQLConfigKey]: graphQLConfig,
|
||||
});
|
||||
expect(op).toEqual({ upsert: true });
|
||||
});
|
||||
|
||||
it('should throw if graphQLConfig is not an object', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig([])
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig(function() {})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig(Promise.resolve({}))
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig('')
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if graphQLConfig has an invalid root key', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({ invalidKey: true })
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if graphQLConfig has invalid class filters', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({ enabledForClasses: {} })
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
enabledForClasses: [undefined],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
disabledForClasses: [null],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
enabledForClasses: ['_User', null],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({ disabledForClasses: [''] })
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
enabledForClasses: [],
|
||||
disabledForClasses: ['_User'],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if classConfigs array is invalid', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({ classConfigs: {} })
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({ classConfigs: [null] })
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [undefined],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [{ className: 'ValidClass' }, null],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({ classConfigs: [] })
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if a classConfig has invalid type settings', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
invalidKey: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if a classConfig has invalid type.inputFields settings', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: 'SuperCar',
|
||||
type: {
|
||||
inputFields: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: 'SuperCar',
|
||||
type: {
|
||||
inputFields: {
|
||||
invalidKey: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: 'SuperCar',
|
||||
type: {
|
||||
inputFields: {
|
||||
create: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: 'SuperCar',
|
||||
type: {
|
||||
inputFields: {
|
||||
update: [null],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: 'SuperCar',
|
||||
type: {
|
||||
inputFields: {
|
||||
create: [],
|
||||
update: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: 'SuperCar',
|
||||
type: {
|
||||
inputFields: {
|
||||
create: ['make', 'model'],
|
||||
update: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if a classConfig has invalid type.outputFields settings', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
outputFields: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
outputFields: [null],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
outputFields: ['name', undefined],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
outputFields: [''],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
outputFields: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
outputFields: ['name'],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if a classConfig has invalid type.constraintFields settings', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
constraintFields: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
constraintFields: [null],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
constraintFields: ['name', undefined],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
constraintFields: [''],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
constraintFields: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
constraintFields: ['name'],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if a classConfig has invalid type.sortFields settings', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
sortFields: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
sortFields: [null],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
sortFields: [
|
||||
{
|
||||
field: undefined,
|
||||
asc: true,
|
||||
desc: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
sortFields: [
|
||||
{
|
||||
field: '',
|
||||
asc: true,
|
||||
desc: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
sortFields: [
|
||||
{
|
||||
field: 'name',
|
||||
asc: true,
|
||||
desc: 'false',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
sortFields: [
|
||||
{
|
||||
field: 'name',
|
||||
asc: true,
|
||||
desc: true,
|
||||
},
|
||||
null,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
sortFields: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
sortFields: [
|
||||
{
|
||||
field: 'name',
|
||||
asc: true,
|
||||
desc: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if a classConfig has invalid query params', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
query: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
query: {
|
||||
invalidKey: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
query: {
|
||||
get: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
query: {
|
||||
find: 'true',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
query: {
|
||||
get: false,
|
||||
find: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
query: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
it('should throw if a classConfig has invalid mutation params', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
mutation: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
mutation: {
|
||||
invalidKey: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
mutation: {
|
||||
destroy: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
mutation: {
|
||||
update: 'true',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
mutation: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
mutation: {
|
||||
create: true,
|
||||
update: true,
|
||||
destroy: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolvedTo(successfulUpdateResponse);
|
||||
});
|
||||
|
||||
it('should throw if _User create fields is missing username or password', async () => {
|
||||
const parseGraphQLController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
});
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
inputFields: {
|
||||
create: ['username', 'no-password'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeRejected();
|
||||
expectAsync(
|
||||
parseGraphQLController.updateGraphQLConfig({
|
||||
classConfigs: [
|
||||
{
|
||||
className: '_User',
|
||||
type: {
|
||||
inputFields: {
|
||||
create: ['username', 'password'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBeResolved(successfulUpdateResponse);
|
||||
});
|
||||
it('should update the cache if mounted', async () => {
|
||||
removeConfigFromDb();
|
||||
cacheController.graphQL.clear();
|
||||
const mountedController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
cacheController,
|
||||
mountGraphQL: true,
|
||||
});
|
||||
const unmountedController = new ParseGraphQLController({
|
||||
databaseController,
|
||||
cacheController,
|
||||
mountGraphQL: false,
|
||||
});
|
||||
|
||||
let cacheBeforeValue;
|
||||
let cacheAfterValue;
|
||||
|
||||
cacheBeforeValue = await cacheController.graphQL.get(
|
||||
mountedController.configCacheKey
|
||||
);
|
||||
expect(cacheBeforeValue).toBeNull();
|
||||
|
||||
await mountedController.updateGraphQLConfig({
|
||||
enabledForClasses: ['SuperCar'],
|
||||
});
|
||||
cacheAfterValue = await cacheController.graphQL.get(
|
||||
mountedController.configCacheKey
|
||||
);
|
||||
expect(cacheAfterValue).toEqual({ enabledForClasses: ['SuperCar'] });
|
||||
|
||||
// reset
|
||||
removeConfigFromDb();
|
||||
cacheController.graphQL.clear();
|
||||
|
||||
cacheBeforeValue = await cacheController.graphQL.get(
|
||||
unmountedController.configCacheKey
|
||||
);
|
||||
expect(cacheBeforeValue).toBeNull();
|
||||
|
||||
await unmountedController.updateGraphQLConfig({
|
||||
enabledForClasses: ['SuperCar'],
|
||||
});
|
||||
cacheAfterValue = await cacheController.graphQL.get(
|
||||
unmountedController.configCacheKey
|
||||
);
|
||||
expect(cacheAfterValue).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
const defaultLogger = require('../lib/logger').default;
|
||||
const { ParseGraphQLSchema } = require('../lib/GraphQL/ParseGraphQLSchema');
|
||||
|
||||
describe('ParseGraphQLSchema', () => {
|
||||
let parseServer;
|
||||
let databaseController;
|
||||
let parseGraphQLController;
|
||||
let parseGraphQLSchema;
|
||||
|
||||
beforeAll(async () => {
|
||||
parseServer = await global.reconfigureServer({
|
||||
schemaCacheTTL: 100,
|
||||
});
|
||||
databaseController = parseServer.config.databaseController;
|
||||
parseGraphQLController = parseServer.config.parseGraphQLController;
|
||||
parseGraphQLSchema = new ParseGraphQLSchema({
|
||||
databaseController,
|
||||
parseGraphQLController,
|
||||
log: defaultLogger,
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should require a parseGraphQLController, databaseController and a log instance', () => {
|
||||
expect(() => new ParseGraphQLSchema()).toThrow(
|
||||
'You must provide a parseGraphQLController instance!'
|
||||
);
|
||||
expect(
|
||||
() => new ParseGraphQLSchema({ parseGraphQLController: {} })
|
||||
).toThrow('You must provide a databaseController instance!');
|
||||
expect(
|
||||
() =>
|
||||
new ParseGraphQLSchema({
|
||||
parseGraphQLController: {},
|
||||
databaseController: {},
|
||||
})
|
||||
).toThrow('You must provide a log instance!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('load', () => {
|
||||
it('should cache schema', async () => {
|
||||
const graphQLSchema = await parseGraphQLSchema.load();
|
||||
const updatedGraphQLSchema = await parseGraphQLSchema.load();
|
||||
expect(graphQLSchema).toBe(updatedGraphQLSchema);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
expect(graphQLSchema).toBe(await parseGraphQLSchema.load());
|
||||
});
|
||||
|
||||
it('should load a brand new GraphQL Schema if Parse Schema changes', async () => {
|
||||
await parseGraphQLSchema.load();
|
||||
const parseClasses = parseGraphQLSchema.parseClasses;
|
||||
const parseClassesString = parseGraphQLSchema.parseClassesString;
|
||||
const parseClassTypes = parseGraphQLSchema.parseClassTypes;
|
||||
const graphQLSchema = parseGraphQLSchema.graphQLSchema;
|
||||
const graphQLTypes = parseGraphQLSchema.graphQLTypes;
|
||||
const graphQLQueries = parseGraphQLSchema.graphQLQueries;
|
||||
const graphQLMutations = parseGraphQLSchema.graphQLMutations;
|
||||
const graphQLSubscriptions = parseGraphQLSchema.graphQLSubscriptions;
|
||||
const newClassObject = new Parse.Object('NewClass');
|
||||
await newClassObject.save();
|
||||
await databaseController.schemaCache.clear();
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await parseGraphQLSchema.load();
|
||||
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
expect(parseClassesString).not.toBe(
|
||||
parseGraphQLSchema.parseClassesString
|
||||
);
|
||||
expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes);
|
||||
expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema);
|
||||
expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes);
|
||||
expect(graphQLQueries).not.toBe(parseGraphQLSchema.graphQLQueries);
|
||||
expect(graphQLMutations).not.toBe(parseGraphQLSchema.graphQLMutations);
|
||||
expect(graphQLSubscriptions).not.toBe(
|
||||
parseGraphQLSchema.graphQLSubscriptions
|
||||
);
|
||||
});
|
||||
|
||||
it('should load a brand new GraphQL Schema if graphQLConfig changes', async () => {
|
||||
const parseGraphQLController = {
|
||||
graphQLConfig: { enabledForClasses: [] },
|
||||
getGraphQLConfig() {
|
||||
return this.graphQLConfig;
|
||||
},
|
||||
};
|
||||
const parseGraphQLSchema = new ParseGraphQLSchema({
|
||||
databaseController,
|
||||
parseGraphQLController,
|
||||
log: defaultLogger,
|
||||
});
|
||||
await parseGraphQLSchema.load();
|
||||
const parseClasses = parseGraphQLSchema.parseClasses;
|
||||
const parseClassesString = parseGraphQLSchema.parseClassesString;
|
||||
const parseClassTypes = parseGraphQLSchema.parseClassTypes;
|
||||
const graphQLSchema = parseGraphQLSchema.graphQLSchema;
|
||||
const graphQLTypes = parseGraphQLSchema.graphQLTypes;
|
||||
const graphQLQueries = parseGraphQLSchema.graphQLQueries;
|
||||
const graphQLMutations = parseGraphQLSchema.graphQLMutations;
|
||||
const graphQLSubscriptions = parseGraphQLSchema.graphQLSubscriptions;
|
||||
|
||||
parseGraphQLController.graphQLConfig = {
|
||||
enabledForClasses: ['_User'],
|
||||
};
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await parseGraphQLSchema.load();
|
||||
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
expect(parseClassesString).not.toBe(
|
||||
parseGraphQLSchema.parseClassesString
|
||||
);
|
||||
expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes);
|
||||
expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema);
|
||||
expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes);
|
||||
expect(graphQLQueries).not.toBe(parseGraphQLSchema.graphQLQueries);
|
||||
expect(graphQLMutations).not.toBe(parseGraphQLSchema.graphQLMutations);
|
||||
expect(graphQLSubscriptions).not.toBe(
|
||||
parseGraphQLSchema.graphQLSubscriptions
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
+683
-413
File diff suppressed because it is too large
Load Diff
+838
-456
File diff suppressed because it is too large
Load Diff
+1148
-383
File diff suppressed because it is too large
Load Diff
+1675
-1572
File diff suppressed because it is too large
Load Diff
+358
-206
@@ -1,264 +1,416 @@
|
||||
const TestObject = Parse.Object.extend('TestObject');
|
||||
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const rp = require('request-promise');
|
||||
const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter')
|
||||
.default;
|
||||
const mongoURI =
|
||||
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const request = require('../lib/request');
|
||||
const defaultHeaders = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Rest-API-Key': 'rest'
|
||||
}
|
||||
'X-Parse-Rest-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
describe('Parse.Polygon testing', () => {
|
||||
it('polygon save open path', (done) => {
|
||||
const coords = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const closed = [[0,0],[0,1],[1,1],[1,0],[0,0]];
|
||||
beforeAll(() => require('../lib/TestUtils').destroyAllDataPermanently());
|
||||
|
||||
it('polygon save open path', done => {
|
||||
const coords = [[0, 0], [0, 1], [1, 1], [1, 0]];
|
||||
const closed = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', new Parse.Polygon(coords));
|
||||
return obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then((result) => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closed);
|
||||
done();
|
||||
}, done.fail);
|
||||
return obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
})
|
||||
.then(result => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closed);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon save closed path', (done) => {
|
||||
const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]];
|
||||
it('polygon save closed path', done => {
|
||||
const coords = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', new Parse.Polygon(coords));
|
||||
return obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then((result) => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, coords);
|
||||
done();
|
||||
}, done.fail);
|
||||
return obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
})
|
||||
.then(result => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, coords);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon equalTo (open/closed) path', (done) => {
|
||||
const openPoints = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const closedPoints = [[0,0],[0,1],[1,1],[1,0],[0,0]];
|
||||
it('polygon equalTo (open/closed) path', done => {
|
||||
const openPoints = [[0, 0], [0, 1], [1, 1], [1, 0]];
|
||||
const closedPoints = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
|
||||
const openPolygon = new Parse.Polygon(openPoints);
|
||||
const closedPolygon = new Parse.Polygon(closedPoints);
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', openPolygon);
|
||||
return obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('polygon', openPolygon);
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
const polygon = results[0].get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closedPoints);
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('polygon', closedPolygon);
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
const polygon = results[0].get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closedPoints);
|
||||
done();
|
||||
}, done.fail);
|
||||
return obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('polygon', openPolygon);
|
||||
return query.find();
|
||||
})
|
||||
.then(results => {
|
||||
const polygon = results[0].get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closedPoints);
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('polygon', closedPolygon);
|
||||
return query.find();
|
||||
})
|
||||
.then(results => {
|
||||
const polygon = results[0].get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closedPoints);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon update', (done) => {
|
||||
const oldCoords = [[0,0],[0,1],[1,1],[1,0]];
|
||||
it('polygon update', done => {
|
||||
const oldCoords = [[0, 0], [0, 1], [1, 1], [1, 0]];
|
||||
const oldPolygon = new Parse.Polygon(oldCoords);
|
||||
const newCoords = [[2,2],[2,3],[3,3],[3,2]];
|
||||
const newCoords = [[2, 2], [2, 3], [3, 3], [3, 2]];
|
||||
const newPolygon = new Parse.Polygon(newCoords);
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', oldPolygon);
|
||||
return obj.save().then(() => {
|
||||
obj.set('polygon', newPolygon);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then((result) => {
|
||||
const polygon = result.get('polygon');
|
||||
newCoords.push(newCoords[0]);
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, newCoords);
|
||||
done();
|
||||
}, done.fail);
|
||||
return obj
|
||||
.save()
|
||||
.then(() => {
|
||||
obj.set('polygon', newPolygon);
|
||||
return obj.save();
|
||||
})
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
})
|
||||
.then(result => {
|
||||
const polygon = result.get('polygon');
|
||||
newCoords.push(newCoords[0]);
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, newCoords);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon invalid value', (done) => {
|
||||
const coords = [['foo','bar'],[0,1],[1,0],[1,1],[0,0]];
|
||||
it('polygon invalid value', done => {
|
||||
const coords = [['foo', 'bar'], [0, 1], [1, 0], [1, 1], [0, 0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
|
||||
return obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then(done.fail, done);
|
||||
obj.set('polygon', { __type: 'Polygon', coordinates: coords });
|
||||
return obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
})
|
||||
.then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('polygon three points minimum', (done) => {
|
||||
const coords = [[0,0]];
|
||||
it('polygon three points minimum', done => {
|
||||
const coords = [[0, 0]];
|
||||
const obj = new TestObject();
|
||||
// use raw so we test the server validates properly
|
||||
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
|
||||
obj.save().then(done.fail, done);
|
||||
obj.set('polygon', { __type: 'Polygon', coordinates: coords });
|
||||
obj.save().then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('polygon three different points minimum', (done) => {
|
||||
const coords = [[0,0],[0,1],[0,0]];
|
||||
it('polygon three different points minimum', done => {
|
||||
const coords = [[0, 0], [0, 1], [0, 0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', new Parse.Polygon(coords));
|
||||
obj.save().then(done.fail, done);
|
||||
obj.save().then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('polygon counterclockwise', (done) => {
|
||||
const coords = [[1,1],[0,1],[0,0],[1,0]];
|
||||
const closed = [[1,1],[0,1],[0,0],[1,0],[1,1]];
|
||||
it('polygon counterclockwise', done => {
|
||||
const coords = [[1, 1], [0, 1], [0, 0], [1, 0]];
|
||||
const closed = [[1, 1], [0, 1], [0, 0], [1, 0], [1, 1]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', new Parse.Polygon(coords));
|
||||
obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
}).then((result) => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closed);
|
||||
done();
|
||||
}, done.fail);
|
||||
obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
})
|
||||
.then(result => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closed);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygonContain query', (done) => {
|
||||
const points1 = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const points2 = [[0,0],[0,2],[2,2],[2,0]];
|
||||
const points3 = [[10,10],[10,15],[15,15],[15,10],[10,10]];
|
||||
const polygon1 = new Parse.Polygon(points1);
|
||||
const polygon2 = new Parse.Polygon(points2);
|
||||
const polygon3 = new Parse.Polygon(points3);
|
||||
const obj1 = new TestObject({location: polygon1});
|
||||
const obj2 = new TestObject({location: polygon2});
|
||||
const obj3 = new TestObject({location: polygon3});
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 }
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
describe('with location', () => {
|
||||
beforeAll(() => require('../lib/TestUtils').destroyAllDataPermanently());
|
||||
|
||||
it('polygonContain invalid input', (done) => {
|
||||
const points = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const polygon = new Parse.Polygon(points);
|
||||
const obj = new TestObject({location: polygon});
|
||||
obj.save().then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: { __type: 'GeoPoint', latitude: 181, longitude: 181 }
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then(done.fail, () => done());
|
||||
});
|
||||
it('polygonContain query', done => {
|
||||
const points1 = [[0, 0], [0, 1], [1, 1], [1, 0]];
|
||||
const points2 = [[0, 0], [0, 2], [2, 2], [2, 0]];
|
||||
const points3 = [[10, 10], [10, 15], [15, 15], [15, 10], [10, 10]];
|
||||
const polygon1 = new Parse.Polygon(points1);
|
||||
const polygon2 = new Parse.Polygon(points2);
|
||||
const polygon3 = new Parse.Polygon(points3);
|
||||
const obj1 = new TestObject({ location: polygon1 });
|
||||
const obj2 = new TestObject({ location: polygon2 });
|
||||
const obj3 = new TestObject({ location: polygon3 });
|
||||
Parse.Object.saveAll([obj1, obj2, obj3])
|
||||
.then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 },
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
expect(resp.data.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygonContain invalid geoPoint', (done) => {
|
||||
const points = [[0,0],[0,1],[1,1],[1,0]];
|
||||
const polygon = new Parse.Polygon(points);
|
||||
const obj = new TestObject({location: polygon});
|
||||
obj.save().then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: []
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey
|
||||
}
|
||||
});
|
||||
}).then(done.fail, () => done());
|
||||
it('polygonContain query no reverse input (Regression test for #4608)', done => {
|
||||
const points1 = [[0.25, 0], [0.25, 1.25], [0.75, 1.25], [0.75, 0]];
|
||||
const points2 = [[0, 0], [0, 2], [2, 2], [2, 0]];
|
||||
const points3 = [[10, 10], [10, 15], [15, 15], [15, 10], [10, 10]];
|
||||
const polygon1 = new Parse.Polygon(points1);
|
||||
const polygon2 = new Parse.Polygon(points2);
|
||||
const polygon3 = new Parse.Polygon(points3);
|
||||
const obj1 = new TestObject({ location: polygon1 });
|
||||
const obj2 = new TestObject({ location: polygon2 });
|
||||
const obj3 = new TestObject({ location: polygon3 });
|
||||
Parse.Object.saveAll([obj1, obj2, obj3])
|
||||
.then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: { __type: 'GeoPoint', latitude: 0.5, longitude: 1.0 },
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
expect(resp.data.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygonContain query real data (Regression test for #4608)', done => {
|
||||
const detroit = [
|
||||
[42.631655189280224, -83.78406753121705],
|
||||
[42.633047793854814, -83.75333640366955],
|
||||
[42.61625254348911, -83.75149921669944],
|
||||
[42.61526926650296, -83.78161794858735],
|
||||
[42.631655189280224, -83.78406753121705],
|
||||
];
|
||||
const polygon = new Parse.Polygon(detroit);
|
||||
const obj = new TestObject({ location: polygon });
|
||||
obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: {
|
||||
__type: 'GeoPoint',
|
||||
latitude: 42.624599,
|
||||
longitude: -83.770162,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
expect(resp.data.results.length).toBe(1);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygonContain invalid input', done => {
|
||||
const points = [[0, 0], [0, 1], [1, 1], [1, 0]];
|
||||
const polygon = new Parse.Polygon(points);
|
||||
const obj = new TestObject({ location: polygon });
|
||||
obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: { __type: 'GeoPoint', latitude: 181, longitude: 181 },
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('polygonContain invalid geoPoint', done => {
|
||||
const points = [[0, 0], [0, 1], [1, 1], [1, 0]];
|
||||
const polygon = new Parse.Polygon(points);
|
||||
const obj = new TestObject({ location: polygon });
|
||||
obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const where = {
|
||||
location: {
|
||||
$geoIntersects: {
|
||||
$point: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: Parse.serverURL + '/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Javascript-Key': Parse.javaScriptKey,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(done.fail, () => done());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe_only_db('mongo')('Parse.Polygon testing', () => {
|
||||
it('support 2d and 2dsphere', (done) => {
|
||||
const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]];
|
||||
beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently());
|
||||
it('support 2d and 2dsphere', done => {
|
||||
const coords = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
|
||||
// testings against REST API, use raw formats
|
||||
const polygon = {__type: 'Polygon', coordinates: coords};
|
||||
const location = {__type: 'GeoPoint', latitude:10, longitude:10};
|
||||
const polygon = { __type: 'Polygon', coordinates: coords };
|
||||
const location = { __type: 'GeoPoint', latitude: 10, longitude: 10 };
|
||||
const databaseAdapter = new MongoStorageAdapter({ uri: mongoURI });
|
||||
return reconfigureServer({
|
||||
appId: 'test',
|
||||
restAPIKey: 'rest',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
databaseAdapter
|
||||
}).then(() => {
|
||||
return databaseAdapter.createIndex('TestObject', {location: '2d'});
|
||||
}).then(() => {
|
||||
return databaseAdapter.createIndex('TestObject', {polygon: '2dsphere'});
|
||||
}).then(() => {
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: {
|
||||
'_method': 'POST',
|
||||
location,
|
||||
polygon,
|
||||
polygon2: polygon
|
||||
},
|
||||
headers: defaultHeaders
|
||||
});
|
||||
}).then((resp) => {
|
||||
return rp.post({
|
||||
url: `http://localhost:8378/1/classes/TestObject/${resp.objectId}`,
|
||||
json: {'_method': 'GET'},
|
||||
headers: defaultHeaders
|
||||
});
|
||||
}).then((resp) => {
|
||||
equal(resp.location, location);
|
||||
equal(resp.polygon, polygon);
|
||||
equal(resp.polygon2, polygon);
|
||||
return databaseAdapter.getIndexes('TestObject');
|
||||
}).then((indexes) => {
|
||||
equal(indexes.length, 4);
|
||||
equal(indexes[0].key, {_id: 1});
|
||||
equal(indexes[1].key, {location: '2d'});
|
||||
equal(indexes[2].key, {polygon: '2dsphere'});
|
||||
equal(indexes[3].key, {polygon2: '2dsphere'});
|
||||
done();
|
||||
}, done.fail);
|
||||
databaseAdapter,
|
||||
})
|
||||
.then(() => {
|
||||
return databaseAdapter.createIndex('TestObject', { location: '2d' });
|
||||
})
|
||||
.then(() => {
|
||||
return databaseAdapter.createIndex('TestObject', {
|
||||
polygon: '2dsphere',
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: {
|
||||
_method: 'POST',
|
||||
location,
|
||||
polygon,
|
||||
polygon2: polygon,
|
||||
},
|
||||
headers: defaultHeaders,
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: `http://localhost:8378/1/classes/TestObject/${
|
||||
resp.data.objectId
|
||||
}`,
|
||||
body: { _method: 'GET' },
|
||||
headers: defaultHeaders,
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
equal(resp.data.location, location);
|
||||
equal(resp.data.polygon, polygon);
|
||||
equal(resp.data.polygon2, polygon);
|
||||
return databaseAdapter.getIndexes('TestObject');
|
||||
})
|
||||
.then(indexes => {
|
||||
equal(indexes.length, 4);
|
||||
equal(indexes[0].key, { _id: 1 });
|
||||
equal(indexes[1].key, { location: '2d' });
|
||||
equal(indexes[2].key, { polygon: '2dsphere' });
|
||||
equal(indexes[3].key, { polygon2: '2dsphere' });
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon loop is not valid', (done) => {
|
||||
const coords = [[0,0],[0,1],[1,0],[1,1]];
|
||||
it('polygon coordinates reverse input', done => {
|
||||
const Config = require('../lib/Config');
|
||||
const config = Config.get('test');
|
||||
|
||||
// When stored the first point should be the last point
|
||||
const input = [[12, 11], [14, 13], [16, 15], [18, 17]];
|
||||
const output = [[[11, 12], [13, 14], [15, 16], [17, 18], [11, 12]]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', new Parse.Polygon(input));
|
||||
obj
|
||||
.save()
|
||||
.then(() => {
|
||||
return config.database.adapter._rawFind('TestObject', { _id: obj.id });
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].polygon.coordinates).toEqual(output);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('polygon loop is not valid', done => {
|
||||
const coords = [[0, 0], [0, 1], [1, 0], [1, 1]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', new Parse.Polygon(coords));
|
||||
obj.save().then(done.fail, done);
|
||||
obj.save().then(done.fail, () => done());
|
||||
});
|
||||
});
|
||||
|
||||
+65
-35
@@ -1,80 +1,105 @@
|
||||
const ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
const ParsePubSub = require('../lib/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
|
||||
describe('ParsePubSub', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
// Mock RedisPubSub
|
||||
const mockRedisPubSub = {
|
||||
createPublisher: jasmine.createSpy('createPublisherRedis'),
|
||||
createSubscriber: jasmine.createSpy('createSubscriberRedis')
|
||||
createSubscriber: jasmine.createSpy('createSubscriberRedis'),
|
||||
};
|
||||
jasmine.mockLibrary('../src/Adapters/PubSub/RedisPubSub', 'RedisPubSub', mockRedisPubSub);
|
||||
jasmine.mockLibrary(
|
||||
'../lib/Adapters/PubSub/RedisPubSub',
|
||||
'RedisPubSub',
|
||||
mockRedisPubSub
|
||||
);
|
||||
// Mock EventEmitterPubSub
|
||||
const mockEventEmitterPubSub = {
|
||||
createPublisher: jasmine.createSpy('createPublisherEventEmitter'),
|
||||
createSubscriber: jasmine.createSpy('createSubscriberEventEmitter')
|
||||
createSubscriber: jasmine.createSpy('createSubscriberEventEmitter'),
|
||||
};
|
||||
jasmine.mockLibrary('../src/Adapters/PubSub/EventEmitterPubSub', 'EventEmitterPubSub', mockEventEmitterPubSub);
|
||||
jasmine.mockLibrary(
|
||||
'../lib/Adapters/PubSub/EventEmitterPubSub',
|
||||
'EventEmitterPubSub',
|
||||
mockEventEmitterPubSub
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('can create redis publisher', function() {
|
||||
ParsePubSub.createPublisher({
|
||||
redisURL: 'redisURL'
|
||||
redisURL: 'redisURL',
|
||||
redisOptions: { socket_keepalive: true },
|
||||
});
|
||||
|
||||
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
expect(RedisPubSub.createPublisher).toHaveBeenCalledWith({redisURL: 'redisURL'});
|
||||
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
|
||||
.RedisPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
expect(RedisPubSub.createPublisher).toHaveBeenCalledWith({
|
||||
redisURL: 'redisURL',
|
||||
redisOptions: { socket_keepalive: true },
|
||||
});
|
||||
expect(EventEmitterPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create event emitter publisher', function() {
|
||||
ParsePubSub.createPublisher({});
|
||||
|
||||
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
|
||||
.RedisPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createPublisher).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create redis subscriber', function() {
|
||||
ParsePubSub.createSubscriber({
|
||||
redisURL: 'redisURL'
|
||||
redisURL: 'redisURL',
|
||||
redisOptions: { socket_keepalive: true },
|
||||
});
|
||||
|
||||
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).toHaveBeenCalledWith({redisURL: 'redisURL'});
|
||||
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
|
||||
.RedisPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).toHaveBeenCalledWith({
|
||||
redisURL: 'redisURL',
|
||||
redisOptions: { socket_keepalive: true },
|
||||
});
|
||||
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create event emitter subscriber', function() {
|
||||
ParsePubSub.createSubscriber({});
|
||||
|
||||
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
|
||||
.RedisPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createSubscriber).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create publisher/sub with custom adapter', function() {
|
||||
const adapter = {
|
||||
const adapter = {
|
||||
createPublisher: jasmine.createSpy('createPublisher'),
|
||||
createSubscriber: jasmine.createSpy('createSubscriber')
|
||||
}
|
||||
createSubscriber: jasmine.createSpy('createSubscriber'),
|
||||
};
|
||||
ParsePubSub.createPublisher({
|
||||
pubSubAdapter: adapter
|
||||
pubSubAdapter: adapter,
|
||||
});
|
||||
expect(adapter.createPublisher).toHaveBeenCalled();
|
||||
|
||||
ParsePubSub.createSubscriber({
|
||||
pubSubAdapter: adapter
|
||||
pubSubAdapter: adapter,
|
||||
});
|
||||
expect(adapter.createSubscriber).toHaveBeenCalled();
|
||||
|
||||
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
|
||||
.RedisPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
@@ -82,34 +107,39 @@ describe('ParsePubSub', function() {
|
||||
});
|
||||
|
||||
it('can create publisher/sub with custom function adapter', function() {
|
||||
const adapter = {
|
||||
const adapter = {
|
||||
createPublisher: jasmine.createSpy('createPublisher'),
|
||||
createSubscriber: jasmine.createSpy('createSubscriber')
|
||||
}
|
||||
createSubscriber: jasmine.createSpy('createSubscriber'),
|
||||
};
|
||||
ParsePubSub.createPublisher({
|
||||
pubSubAdapter: function() {
|
||||
return adapter;
|
||||
}
|
||||
},
|
||||
});
|
||||
expect(adapter.createPublisher).toHaveBeenCalled();
|
||||
|
||||
ParsePubSub.createSubscriber({
|
||||
pubSubAdapter: function() {
|
||||
return adapter;
|
||||
}
|
||||
},
|
||||
});
|
||||
expect(adapter.createSubscriber).toHaveBeenCalled();
|
||||
|
||||
const RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
const EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
|
||||
.RedisPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
jasmine.restoreLibrary('../src/Adapters/PubSub/RedisPubSub', 'RedisPubSub');
|
||||
jasmine.restoreLibrary('../src/Adapters/PubSub/EventEmitterPubSub', 'EventEmitterPubSub');
|
||||
afterEach(function() {
|
||||
jasmine.restoreLibrary('../lib/Adapters/PubSub/RedisPubSub', 'RedisPubSub');
|
||||
jasmine.restoreLibrary(
|
||||
'../lib/Adapters/PubSub/EventEmitterPubSub',
|
||||
'EventEmitterPubSub'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+915
-354
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+3628
-2116
File diff suppressed because it is too large
Load Diff
+669
-599
File diff suppressed because it is too large
Load Diff
+515
-425
File diff suppressed because it is too large
Load Diff
+61
-26
@@ -1,43 +1,55 @@
|
||||
'use strict';
|
||||
/* Tests for ParseServer.js */
|
||||
const express = require('express');
|
||||
import MongoStorageAdapter from '../src/Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||
import PostgresStorageAdapter from '../src/Adapters/Storage/Postgres/PostgresStorageAdapter';
|
||||
import ParseServer from '../src/ParseServer';
|
||||
const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter')
|
||||
.default;
|
||||
const PostgresStorageAdapter = require('../lib/Adapters/Storage/Postgres/PostgresStorageAdapter')
|
||||
.default;
|
||||
const ParseServer = require('../lib/ParseServer').default;
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
describe('Server Url Checks', () => {
|
||||
|
||||
const app = express();
|
||||
app.get('/health', function(req, res){
|
||||
res.json({
|
||||
status: 'ok'
|
||||
let server;
|
||||
beforeAll(done => {
|
||||
const app = express();
|
||||
app.get('/health', function(req, res) {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
});
|
||||
});
|
||||
server = app.listen(13376, undefined, done);
|
||||
});
|
||||
app.listen(13376);
|
||||
|
||||
it('validate good server url', (done) => {
|
||||
afterAll(done => {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
it('validate good server url', done => {
|
||||
Parse.serverURL = 'http://localhost:13376';
|
||||
ParseServer.verifyServerUrl(function(result) {
|
||||
if(!result) {
|
||||
if (!result) {
|
||||
done.fail('Did not pass valid url');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('mark bad server url', (done) => {
|
||||
it('mark bad server url', done => {
|
||||
Parse.serverURL = 'notavalidurl';
|
||||
ParseServer.verifyServerUrl(function(result) {
|
||||
if(result) {
|
||||
if (result) {
|
||||
done.fail('Did not mark invalid url');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handleShutdown, close connection', (done) => {
|
||||
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||
it('handleShutdown, close connection', done => {
|
||||
const mongoURI =
|
||||
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const postgresURI =
|
||||
'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||
let databaseAdapter;
|
||||
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
|
||||
databaseAdapter = new PostgresStorageAdapter({
|
||||
@@ -50,17 +62,40 @@ describe('Server Url Checks', () => {
|
||||
collectionPrefix: 'test_',
|
||||
});
|
||||
}
|
||||
const newConfiguration = Object.assign({}, defaultConfiguration, { databaseAdapter });
|
||||
const parseServer = ParseServer.start(newConfiguration, () => {
|
||||
parseServer.handleShutdown();
|
||||
parseServer.server.close((err) => {
|
||||
if (err) {
|
||||
done.fail('Close Server Error')
|
||||
}
|
||||
reconfigureServer({}).then(() => {
|
||||
done();
|
||||
const newConfiguration = Object.assign({}, defaultConfiguration, {
|
||||
databaseAdapter,
|
||||
serverStartComplete: () => {
|
||||
parseServer.handleShutdown();
|
||||
parseServer.server.close(err => {
|
||||
if (err) {
|
||||
done.fail('Close Server Error');
|
||||
}
|
||||
reconfigureServer({}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
const parseServer = ParseServer.start(newConfiguration);
|
||||
});
|
||||
|
||||
it('does not have unhandled promise rejection in the case of load error', done => {
|
||||
const parseServerProcess = spawn(
|
||||
path.resolve(__dirname, './support/FailingServer.js')
|
||||
);
|
||||
let stdout;
|
||||
let stderr;
|
||||
parseServerProcess.stdout.on('data', data => {
|
||||
stdout = data.toString();
|
||||
});
|
||||
parseServerProcess.stderr.on('data', data => {
|
||||
stderr = data.toString();
|
||||
});
|
||||
parseServerProcess.on('close', code => {
|
||||
expect(code).toEqual(1);
|
||||
expect(stdout).toBeUndefined();
|
||||
expect(stderr).toContain('MongoNetworkError');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,155 +1,217 @@
|
||||
const ParseServerRESTController = require('../src/ParseServerRESTController').ParseServerRESTController;
|
||||
const ParseServer = require('../src/ParseServer').default;
|
||||
const ParseServerRESTController = require('../lib/ParseServerRESTController')
|
||||
.ParseServerRESTController;
|
||||
const ParseServer = require('../lib/ParseServer').default;
|
||||
const Parse = require('parse/node').Parse;
|
||||
|
||||
let RESTController;
|
||||
|
||||
describe('ParseServerRESTController', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
RESTController = ParseServerRESTController(Parse.applicationId, ParseServer.promiseRouter({appId: Parse.applicationId}));
|
||||
})
|
||||
|
||||
it('should handle a get request', (done) => {
|
||||
RESTController.request("GET", "/classes/MyObject").then((res) => {
|
||||
expect(res.results.length).toBe(0);
|
||||
done();
|
||||
}, (err) => {
|
||||
console.log(err);
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
RESTController = ParseServerRESTController(
|
||||
Parse.applicationId,
|
||||
ParseServer.promiseRouter({ appId: Parse.applicationId })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle a get request with full serverURL mount path', (done) => {
|
||||
RESTController.request("GET", "/1/classes/MyObject").then((res) => {
|
||||
expect(res.results.length).toBe(0);
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
it('should handle a get request', done => {
|
||||
RESTController.request('GET', '/classes/MyObject').then(
|
||||
res => {
|
||||
expect(res.results.length).toBe(0);
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
console.log(err);
|
||||
jfail(err);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle a POST batch', (done) => {
|
||||
RESTController.request("POST", "batch", {
|
||||
it('should handle a get request with full serverURL mount path', done => {
|
||||
RESTController.request('GET', '/1/classes/MyObject').then(
|
||||
res => {
|
||||
expect(res.results.length).toBe(0);
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
jfail(err);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle a POST batch', done => {
|
||||
RESTController.request('POST', 'batch', {
|
||||
requests: [
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/classes/MyObject'
|
||||
path: '/classes/MyObject',
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/classes/MyObject',
|
||||
body: {"key": "value"}
|
||||
body: { key: 'value' },
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/classes/MyObject'
|
||||
}
|
||||
]
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(3);
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
path: '/classes/MyObject',
|
||||
},
|
||||
],
|
||||
}).then(
|
||||
res => {
|
||||
expect(res.length).toBe(3);
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
jfail(err);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle a POST request', (done) => {
|
||||
RESTController.request("POST", "/classes/MyObject", {"key": "value"}).then(() => {
|
||||
return RESTController.request("GET", "/classes/MyObject");
|
||||
}).then((res) => {
|
||||
expect(res.results.length).toBe(1);
|
||||
expect(res.results[0].key).toEqual("value");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
console.log(err);
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures sessionTokens are properly handled', (done) => {
|
||||
let userId;
|
||||
Parse.User.signUp('user', 'pass').then((user) => {
|
||||
userId = user.id;
|
||||
const sessionToken = user.getSessionToken();
|
||||
return RESTController.request("GET", "/users/me", undefined, {sessionToken});
|
||||
}).then((res) => {
|
||||
// Result is in JSON format
|
||||
expect(res.objectId).toEqual(userId);
|
||||
done();
|
||||
}).fail((err) => {
|
||||
console.log(err);
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures masterKey is properly handled', (done) => {
|
||||
let userId;
|
||||
Parse.User.signUp('user', 'pass').then((user) => {
|
||||
userId = user.id;
|
||||
return Parse.User.logOut().then(() => {
|
||||
return RESTController.request("GET", "/classes/_User", undefined, {useMasterKey: true});
|
||||
it('should handle a POST request', done => {
|
||||
RESTController.request('POST', '/classes/MyObject', { key: 'value' })
|
||||
.then(() => {
|
||||
return RESTController.request('GET', '/classes/MyObject');
|
||||
})
|
||||
.then(res => {
|
||||
expect(res.results.length).toBe(1);
|
||||
expect(res.results[0].key).toEqual('value');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
}).then((res) => {
|
||||
expect(res.results.length).toBe(1);
|
||||
expect(res.results[0].objectId).toEqual(userId);
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures no user is created when passing an empty username', (done) => {
|
||||
RESTController.request("POST", "/classes/_User", {username: "", password: "world"}).then(() => {
|
||||
jfail(new Error('Success callback should not be called when passing an empty username.'));
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(Parse.Error.USERNAME_MISSING);
|
||||
expect(err.message).toBe('bad or missing username');
|
||||
done();
|
||||
});
|
||||
it('ensures sessionTokens are properly handled', done => {
|
||||
let userId;
|
||||
Parse.User.signUp('user', 'pass')
|
||||
.then(user => {
|
||||
userId = user.id;
|
||||
const sessionToken = user.getSessionToken();
|
||||
return RESTController.request('GET', '/users/me', undefined, {
|
||||
sessionToken,
|
||||
});
|
||||
})
|
||||
.then(res => {
|
||||
// Result is in JSON format
|
||||
expect(res.objectId).toEqual(userId);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures no user is created when passing an empty password', (done) => {
|
||||
RESTController.request("POST", "/classes/_User", {username: "hello", password: ""}).then(() => {
|
||||
jfail(new Error('Success callback should not be called when passing an empty password.'));
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(Parse.Error.PASSWORD_MISSING);
|
||||
expect(err.message).toBe('password is required');
|
||||
done();
|
||||
});
|
||||
it('ensures masterKey is properly handled', done => {
|
||||
let userId;
|
||||
Parse.User.signUp('user', 'pass')
|
||||
.then(user => {
|
||||
userId = user.id;
|
||||
return Parse.User.logOut().then(() => {
|
||||
return RESTController.request('GET', '/classes/_User', undefined, {
|
||||
useMasterKey: true,
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(
|
||||
res => {
|
||||
expect(res.results.length).toBe(1);
|
||||
expect(res.results[0].objectId).toEqual(userId);
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
jfail(err);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('ensures no session token is created on creating users', (done) => {
|
||||
RESTController.request("POST", "/classes/_User", {username: "hello", password: "world"}).then((user) => {
|
||||
expect(user.sessionToken).toBeUndefined();
|
||||
const query = new Parse.Query('_Session');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then(sessions => {
|
||||
expect(sessions.length).toBe(0);
|
||||
done();
|
||||
}, done.fail);
|
||||
it('ensures no user is created when passing an empty username', done => {
|
||||
RESTController.request('POST', '/classes/_User', {
|
||||
username: '',
|
||||
password: 'world',
|
||||
}).then(
|
||||
() => {
|
||||
jfail(
|
||||
new Error(
|
||||
'Success callback should not be called when passing an empty username.'
|
||||
)
|
||||
);
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
expect(err.code).toBe(Parse.Error.USERNAME_MISSING);
|
||||
expect(err.message).toBe('bad or missing username');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('ensures a session token is created when passing installationId != cloud', (done) => {
|
||||
RESTController.request("POST", "/classes/_User", {username: "hello", password: "world"}, {installationId: 'my-installation'}).then((user) => {
|
||||
expect(user.sessionToken).not.toBeUndefined();
|
||||
const query = new Parse.Query('_Session');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then(sessions => {
|
||||
expect(sessions.length).toBe(1);
|
||||
expect(sessions[0].get('installationId')).toBe('my-installation');
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
it('ensures no user is created when passing an empty password', done => {
|
||||
RESTController.request('POST', '/classes/_User', {
|
||||
username: 'hello',
|
||||
password: '',
|
||||
}).then(
|
||||
() => {
|
||||
jfail(
|
||||
new Error(
|
||||
'Success callback should not be called when passing an empty password.'
|
||||
)
|
||||
);
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
expect(err.code).toBe(Parse.Error.PASSWORD_MISSING);
|
||||
expect(err.message).toBe('password is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('ensures no session token is created on creating users', done => {
|
||||
RESTController.request('POST', '/classes/_User', {
|
||||
username: 'hello',
|
||||
password: 'world',
|
||||
})
|
||||
.then(user => {
|
||||
expect(user.sessionToken).toBeUndefined();
|
||||
const query = new Parse.Query('_Session');
|
||||
return query.find({ useMasterKey: true });
|
||||
})
|
||||
.then(sessions => {
|
||||
expect(sessions.length).toBe(0);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('ensures a session token is created when passing installationId != cloud', done => {
|
||||
RESTController.request(
|
||||
'POST',
|
||||
'/classes/_User',
|
||||
{ username: 'hello', password: 'world' },
|
||||
{ installationId: 'my-installation' }
|
||||
)
|
||||
.then(user => {
|
||||
expect(user.sessionToken).not.toBeUndefined();
|
||||
const query = new Parse.Query('_Session');
|
||||
return query.find({ useMasterKey: true });
|
||||
})
|
||||
.then(
|
||||
sessions => {
|
||||
expect(sessions.length).toBe(1);
|
||||
expect(sessions[0].get('installationId')).toBe('my-installation');
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
jfail(err);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+104
-92
@@ -2,125 +2,137 @@
|
||||
// Tests behavior of Parse Sessions
|
||||
//
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
function setupTestUsers() {
|
||||
const user1 = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
const user3 = new Parse.User();
|
||||
|
||||
user1.set("username", "testuser_1");
|
||||
user2.set("username", "testuser_2");
|
||||
user3.set("username", "testuser_3");
|
||||
user1.set('username', 'testuser_1');
|
||||
user2.set('username', 'testuser_2');
|
||||
user3.set('username', 'testuser_3');
|
||||
|
||||
user1.set("password", "password");
|
||||
user2.set("password", "password");
|
||||
user3.set("password", "password");
|
||||
user1.set('password', 'password');
|
||||
user2.set('password', 'password');
|
||||
user3.set('password', 'password');
|
||||
|
||||
return user1.signUp().then(() => {
|
||||
return user2.signUp();
|
||||
}).then(() => {
|
||||
return user3.signUp();
|
||||
})
|
||||
return user1
|
||||
.signUp()
|
||||
.then(() => {
|
||||
return user2.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return user3.signUp();
|
||||
});
|
||||
}
|
||||
|
||||
describe('Parse.Session', () => {
|
||||
|
||||
// multiple sessions with masterKey + sessionToken
|
||||
it('should retain original sessionTokens with masterKey & sessionToken set', (done) => {
|
||||
setupTestUsers().then((user) => {
|
||||
const query = new Parse.Query(Parse.Session);
|
||||
return query.find({
|
||||
useMasterKey: true,
|
||||
sessionToken: user.get('sessionToken')
|
||||
});
|
||||
}).then((results) => {
|
||||
const foundKeys = [];
|
||||
expect(results.length).toBe(3);
|
||||
for(const key in results) {
|
||||
const sessionToken = results[key].get('sessionToken');
|
||||
if(foundKeys[sessionToken]) {
|
||||
fail('Duplicate session token present in response');
|
||||
break;
|
||||
it('should retain original sessionTokens with masterKey & sessionToken set', done => {
|
||||
setupTestUsers()
|
||||
.then(user => {
|
||||
const query = new Parse.Query(Parse.Session);
|
||||
return query.find({
|
||||
useMasterKey: true,
|
||||
sessionToken: user.get('sessionToken'),
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
const foundKeys = [];
|
||||
expect(results.length).toBe(3);
|
||||
for (const key in results) {
|
||||
const sessionToken = results[key].get('sessionToken');
|
||||
if (foundKeys[sessionToken]) {
|
||||
fail('Duplicate session token present in response');
|
||||
break;
|
||||
}
|
||||
foundKeys[sessionToken] = 1;
|
||||
}
|
||||
foundKeys[sessionToken] = 1;
|
||||
}
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(err);
|
||||
});
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(err);
|
||||
});
|
||||
});
|
||||
|
||||
// single session returned, with just one sessionToken
|
||||
it('should retain original sessionTokens with just sessionToken set', (done) => {
|
||||
it('should retain original sessionTokens with just sessionToken set', done => {
|
||||
let knownSessionToken;
|
||||
setupTestUsers().then((user) => {
|
||||
knownSessionToken = user.get('sessionToken');
|
||||
const query = new Parse.Query(Parse.Session);
|
||||
return query.find({
|
||||
sessionToken: knownSessionToken
|
||||
setupTestUsers()
|
||||
.then(user => {
|
||||
knownSessionToken = user.get('sessionToken');
|
||||
const query = new Parse.Query(Parse.Session);
|
||||
return query.find({
|
||||
sessionToken: knownSessionToken,
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toBe(1);
|
||||
const sessionToken = results[0].get('sessionToken');
|
||||
expect(sessionToken).toBe(knownSessionToken);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(err);
|
||||
});
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
const sessionToken = results[0].get('sessionToken');
|
||||
expect(sessionToken).toBe(knownSessionToken);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(err);
|
||||
});
|
||||
});
|
||||
|
||||
// multiple users with masterKey + sessionToken
|
||||
it('token on users should retain original sessionTokens with masterKey & sessionToken set', (done) => {
|
||||
setupTestUsers().then((user) => {
|
||||
const query = new Parse.Query(Parse.User);
|
||||
return query.find({
|
||||
useMasterKey: true,
|
||||
sessionToken: user.get('sessionToken')
|
||||
});
|
||||
}).then((results) => {
|
||||
const foundKeys = [];
|
||||
expect(results.length).toBe(3);
|
||||
for(const key in results) {
|
||||
const sessionToken = results[key].get('sessionToken');
|
||||
if(foundKeys[sessionToken] && sessionToken !== undefined) {
|
||||
fail('Duplicate session token present in response');
|
||||
break;
|
||||
it('token on users should retain original sessionTokens with masterKey & sessionToken set', done => {
|
||||
setupTestUsers()
|
||||
.then(user => {
|
||||
const query = new Parse.Query(Parse.User);
|
||||
return query.find({
|
||||
useMasterKey: true,
|
||||
sessionToken: user.get('sessionToken'),
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
const foundKeys = [];
|
||||
expect(results.length).toBe(3);
|
||||
for (const key in results) {
|
||||
const sessionToken = results[key].get('sessionToken');
|
||||
if (foundKeys[sessionToken] && sessionToken !== undefined) {
|
||||
fail('Duplicate session token present in response');
|
||||
break;
|
||||
}
|
||||
foundKeys[sessionToken] = 1;
|
||||
}
|
||||
foundKeys[sessionToken] = 1;
|
||||
}
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(err);
|
||||
});
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(err);
|
||||
});
|
||||
});
|
||||
|
||||
// multiple users with just sessionToken
|
||||
it('token on users should retain original sessionTokens with just sessionToken set', (done) => {
|
||||
it('token on users should retain original sessionTokens with just sessionToken set', done => {
|
||||
let knownSessionToken;
|
||||
setupTestUsers().then((user) => {
|
||||
knownSessionToken = user.get('sessionToken');
|
||||
const query = new Parse.Query(Parse.User);
|
||||
return query.find({
|
||||
sessionToken: knownSessionToken
|
||||
});
|
||||
}).then((results) => {
|
||||
const foundKeys = [];
|
||||
expect(results.length).toBe(3);
|
||||
for(const key in results) {
|
||||
const sessionToken = results[key].get('sessionToken');
|
||||
if(foundKeys[sessionToken] && sessionToken !== undefined) {
|
||||
fail('Duplicate session token present in response');
|
||||
break;
|
||||
setupTestUsers()
|
||||
.then(user => {
|
||||
knownSessionToken = user.get('sessionToken');
|
||||
const query = new Parse.Query(Parse.User);
|
||||
return query.find({
|
||||
sessionToken: knownSessionToken,
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
const foundKeys = [];
|
||||
expect(results.length).toBe(3);
|
||||
for (const key in results) {
|
||||
const sessionToken = results[key].get('sessionToken');
|
||||
if (foundKeys[sessionToken] && sessionToken !== undefined) {
|
||||
fail('Duplicate session token present in response');
|
||||
break;
|
||||
}
|
||||
foundKeys[sessionToken] = 1;
|
||||
}
|
||||
foundKeys[sessionToken] = 1;
|
||||
}
|
||||
|
||||
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(err);
|
||||
});
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(err);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+3158
-2770
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
const ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
|
||||
const ParseWebSocket = require('../lib/LiveQuery/ParseWebSocketServer')
|
||||
.ParseWebSocket;
|
||||
|
||||
describe('ParseWebSocket', function() {
|
||||
|
||||
it('can be initialized', function() {
|
||||
const ws = {};
|
||||
const parseWebSocket = new ParseWebSocket(ws);
|
||||
@@ -11,7 +11,7 @@ describe('ParseWebSocket', function() {
|
||||
|
||||
it('can handle events defined in typeMap', function() {
|
||||
const ws = {
|
||||
on: jasmine.createSpy('on')
|
||||
on: jasmine.createSpy('on'),
|
||||
};
|
||||
const callback = {};
|
||||
const parseWebSocket = new ParseWebSocket(ws);
|
||||
@@ -22,7 +22,7 @@ describe('ParseWebSocket', function() {
|
||||
|
||||
it('can handle events which are not defined in typeMap', function() {
|
||||
const ws = {
|
||||
on: jasmine.createSpy('on')
|
||||
on: jasmine.createSpy('on'),
|
||||
};
|
||||
const callback = {};
|
||||
const parseWebSocket = new ParseWebSocket(ws);
|
||||
@@ -33,10 +33,10 @@ describe('ParseWebSocket', function() {
|
||||
|
||||
it('can send a message', function() {
|
||||
const ws = {
|
||||
send: jasmine.createSpy('send')
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
const parseWebSocket = new ParseWebSocket(ws);
|
||||
parseWebSocket.send('message')
|
||||
parseWebSocket.send('message');
|
||||
|
||||
expect(parseWebSocket.ws.send).toHaveBeenCalledWith('message');
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const ParseWebSocketServer = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocketServer;
|
||||
const ParseWebSocketServer = require('../lib/LiveQuery/ParseWebSocketServer')
|
||||
.ParseWebSocketServer;
|
||||
|
||||
describe('ParseWebSocketServer', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
// Mock ws server
|
||||
const EventEmitter = require('events');
|
||||
@@ -16,11 +16,15 @@ describe('ParseWebSocketServer', function() {
|
||||
const onConnectCallback = jasmine.createSpy('onConnectCallback');
|
||||
const http = require('http');
|
||||
const server = http.createServer();
|
||||
const parseWebSocketServer = new ParseWebSocketServer(server, onConnectCallback, 5).server;
|
||||
const parseWebSocketServer = new ParseWebSocketServer(
|
||||
server,
|
||||
onConnectCallback,
|
||||
5
|
||||
).server;
|
||||
const ws = {
|
||||
readyState: 0,
|
||||
OPEN: 0,
|
||||
ping: jasmine.createSpy('ping')
|
||||
ping: jasmine.createSpy('ping'),
|
||||
};
|
||||
parseWebSocketServer.emit('connection', ws);
|
||||
|
||||
@@ -31,10 +35,10 @@ describe('ParseWebSocketServer', function() {
|
||||
expect(ws.ping).toHaveBeenCalled();
|
||||
server.close();
|
||||
done();
|
||||
}, 10)
|
||||
}, 10);
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
afterEach(function() {
|
||||
jasmine.restoreLibrary('ws', 'Server');
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user