mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61ab8c90f7 | ||
|
|
c9619bc525 | ||
|
|
0dd30a5051 | ||
|
|
e53514aa0f | ||
|
|
f71db83c9c | ||
|
|
4a71e118bf | ||
|
|
7801b7d9ab | ||
|
|
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 | ||
|
|
0569ddd8d3 | ||
|
|
3f19540f1a | ||
|
|
c12d430c43 | ||
|
|
c737f794a0 | ||
|
|
d768211ab9 | ||
|
|
673039fc5a | ||
|
|
e8c8151277 | ||
|
|
38f57c3fcb | ||
|
|
d19ac077b6 | ||
|
|
f0674df424 | ||
|
|
f03e35ff76 | ||
|
|
887adea4dd | ||
|
|
d7a554e426 | ||
|
|
e785f07876 | ||
|
|
909ead487c | ||
|
|
7feafb9eea | ||
|
|
60eea59c76 | ||
|
|
9db63a4a50 | ||
|
|
d8f3fb16bd | ||
|
|
213801c4b1 | ||
|
|
55f4b0f7c5 | ||
|
|
647d8e5bff | ||
|
|
11c40dce97 | ||
|
|
b754d51e8e | ||
|
|
8ec7785d53 | ||
|
|
7ab5da171e | ||
|
|
ab669a21e7 | ||
|
|
143b0f01cf | ||
|
|
10eafe922e | ||
|
|
848a6cf7ae | ||
|
|
cac14bce09 | ||
|
|
3b5a4579d1 | ||
|
|
8be7491595 | ||
|
|
ae349f3888 | ||
|
|
c85c90a454 | ||
|
|
3cd77eeb7c | ||
|
|
c16e6bb5e0 | ||
|
|
90fd051ba4 | ||
|
|
83a0d7b685 | ||
|
|
db8594dd33 | ||
|
|
f2e21b0538 | ||
|
|
550b69e271 | ||
|
|
edba18fa9d | ||
|
|
401cc357e5 | ||
|
|
40407fea80 | ||
|
|
44086c96e6 | ||
|
|
0807845f57 | ||
|
|
bcca1b6497 | ||
|
|
4da27773fd | ||
|
|
db3777ec15 | ||
|
|
4eda0a2024 | ||
|
|
51e1cbad47 | ||
|
|
64e568d000 | ||
|
|
33890bbbfc | ||
|
|
75fa16aa7e | ||
|
|
433e169055 | ||
|
|
8ec07b83d0 | ||
|
|
cb8f038ca8 | ||
|
|
fc6a2fddc4 | ||
|
|
10631371e6 | ||
|
|
f0f1870aa3 | ||
|
|
6143988a82 | ||
|
|
7d773a5d8a | ||
|
|
e3a26540da | ||
|
|
c549663c96 | ||
|
|
58f32c4745 | ||
|
|
2ee68d3a8b | ||
|
|
7a9d4044af | ||
|
|
04f8673edd | ||
|
|
6ba939994d | ||
|
|
bb053301e5 | ||
|
|
7bb9c6da59 | ||
|
|
b4aca8afc9 | ||
|
|
5681b3f902 | ||
|
|
820ae2b894 | ||
|
|
a868beda93 | ||
|
|
34453bff46 | ||
|
|
415ee3a1fd | ||
|
|
a2b2f18927 | ||
|
|
e0a31d2aeb | ||
|
|
08ba738227 | ||
|
|
33de770709 | ||
|
|
bad217911c | ||
|
|
8fd5f31089 | ||
|
|
0bf69975de | ||
|
|
1f22ee36e7 | ||
|
|
ca542c3750 | ||
|
|
1876d3f3fd | ||
|
|
4ceff38811 | ||
|
|
741f869140 | ||
|
|
9d1d8515fc | ||
|
|
9eed81e636 | ||
|
|
e2d33678a9 | ||
|
|
6102648855 | ||
|
|
dd55bbe700 | ||
|
|
4bccf96ae7 | ||
|
|
6a1510729a | ||
|
|
ea57c523da | ||
|
|
219ad7224a | ||
|
|
2b9397a5a9 | ||
|
|
4f56ec6265 | ||
|
|
cc48e5412e | ||
|
|
c0215399a2 | ||
|
|
49d0a651b5 | ||
|
|
7944e2bd2d | ||
|
|
37ceae0812 | ||
|
|
8bf6abfee3 | ||
|
|
de73f3723c | ||
|
|
e1d04cd655 | ||
|
|
aa1e97fd06 | ||
|
|
09fee7d12b | ||
|
|
7223add446 | ||
|
|
4e207d32a7 | ||
|
|
72e20be06d | ||
|
|
932a474606 | ||
|
|
08ab1f453d | ||
|
|
bb8041ee45 | ||
|
|
a01159e22c | ||
|
|
d163269c47 | ||
|
|
0e16670b5a | ||
|
|
c1a7347143 | ||
|
|
842343a1a9 | ||
|
|
a33c08a751 | ||
|
|
07df71a79b | ||
|
|
cabcac221f | ||
|
|
c0a81a88b7 | ||
|
|
46af1b6955 | ||
|
|
84aadba23a | ||
|
|
fecbb2d7ff | ||
|
|
6f1fe89948 | ||
|
|
1dd58b7527 | ||
|
|
87b79cedfa | ||
|
|
c2fc0f556e | ||
|
|
8a23c00265 | ||
|
|
a185c97557 | ||
|
|
3bb9e0e65e | ||
|
|
7d2332e651 | ||
|
|
72888bc539 | ||
|
|
6685932b7f | ||
|
|
0db858b04c | ||
|
|
d93b0e1fd9 | ||
|
|
9de4b8b2a7 | ||
|
|
d29a4483d0 | ||
|
|
44b17ef0d2 | ||
|
|
484fdc2523 | ||
|
|
2b52c81d49 | ||
|
|
d0e613a632 | ||
|
|
e4eeadd4c6 | ||
|
|
31bd02ce5c | ||
|
|
bc37972394 | ||
|
|
70ad9e9ffd | ||
|
|
9376b4d04a | ||
|
|
9745caf9fb | ||
|
|
b3ea75b69a | ||
|
|
6963ff61b8 | ||
|
|
ac970a40c4 | ||
|
|
315d30b426 | ||
|
|
557a2b2827 | ||
|
|
9145c88562 | ||
|
|
f0b7ffcdd6 | ||
|
|
033437034d | ||
|
|
7fac070435 | ||
|
|
2a168936fc | ||
|
|
23bffc8883 | ||
|
|
976da4d715 | ||
|
|
f2fc3ceabc | ||
|
|
c872da493d | ||
|
|
7c07fff7cc | ||
|
|
c777a263ba | ||
|
|
61a3e3c7b6 | ||
|
|
770aba193c | ||
|
|
a3890572c4 | ||
|
|
c059d15f56 | ||
|
|
fdf19def48 | ||
|
|
6eed955319 | ||
|
|
b703ec8a79 | ||
|
|
70ca7bdd41 | ||
|
|
9371958a09 | ||
|
|
cf630ba462 | ||
|
|
406a21e967 | ||
|
|
19bcfd27dd | ||
|
|
d8fd670013 | ||
|
|
07ae85eef0 | ||
|
|
a5ce9fc175 | ||
|
|
a39d045c7d | ||
|
|
ba0a51ddc9 | ||
|
|
8d8a8b250e | ||
|
|
d0184f438d | ||
|
|
7ecb36e71e | ||
|
|
be9f27493d | ||
|
|
64df33157d | ||
|
|
d598d73f36 | ||
|
|
be2760f9dd | ||
|
|
c121b9787e | ||
|
|
3a17904ce8 | ||
|
|
a1554d04ab | ||
|
|
956f1df0f0 | ||
|
|
4dce3bd63c | ||
|
|
9fbb5e29e8 | ||
|
|
a660a0c25f | ||
|
|
839a117246 | ||
|
|
bc3cef2cd9 | ||
|
|
21f4411571 | ||
|
|
0fb16a7848 | ||
|
|
0f840b6bb8 | ||
|
|
019f9e55e1 | ||
|
|
121d151af9 | ||
|
|
52c4dd3704 | ||
|
|
952e64d94a | ||
|
|
1914083942 | ||
|
|
139b9e1cb3 | ||
|
|
3079270b3e | ||
|
|
17f4dcd176 | ||
|
|
5aafc93476 | ||
|
|
0bace67bb1 | ||
|
|
6df944704c | ||
|
|
540daa4c4a | ||
|
|
2e1ba66fd2 | ||
|
|
a103871cbc | ||
|
|
f8403943ab | ||
|
|
da2bfca618 | ||
|
|
1798c07874 | ||
|
|
e2d1fd3fe0 | ||
|
|
ea67d23ef4 | ||
|
|
cfd7bc0f3b | ||
|
|
c4d921e3eb | ||
|
|
8513b697e9 | ||
|
|
ff4ae55e34 | ||
|
|
5d359c3b63 | ||
|
|
6e23822f99 | ||
|
|
b45f308ff9 | ||
|
|
3ed6dd2685 | ||
|
|
2b4c7570ea | ||
|
|
c6546218f4 | ||
|
|
c2c059f625 | ||
|
|
008cb1dbfe | ||
|
|
2cec92007a | ||
|
|
47f00e606f | ||
|
|
92d51def6b | ||
|
|
7e54265f6d | ||
|
|
811d8b0c7a | ||
|
|
3c79cae1b2 | ||
|
|
e6cc8204b3 | ||
|
|
0571c6f95e | ||
|
|
58079f00f9 | ||
|
|
a26e4c7470 | ||
|
|
9d6b4f4cc4 | ||
|
|
d158023083 | ||
|
|
ad52ed64af | ||
|
|
5931aa8851 | ||
|
|
287810443b |
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-flow-strip-types"
|
||||
"@babel/plugin-transform-flow-strip-types",
|
||||
"@babel/plugin-proposal-object-rest-spread"
|
||||
],
|
||||
"presets": [
|
||||
"es2015",
|
||||
"stage-0"
|
||||
]
|
||||
["@babel/preset-env", {
|
||||
"targets": {
|
||||
"node": "8"
|
||||
}
|
||||
}]
|
||||
],
|
||||
"sourceMaps": "inline"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
lib
|
||||
coverage
|
||||
|
||||
out
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
@@ -7,3 +7,4 @@
|
||||
[libs]
|
||||
|
||||
[options]
|
||||
suppress_comment= \\(.\\|\n\\)*\\@flow-disable-next
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.js text
|
||||
*.html text
|
||||
*.less text
|
||||
*.json text
|
||||
*.css text
|
||||
*.xml text
|
||||
*.md text
|
||||
*.txt text
|
||||
*.yml text
|
||||
*.sql text
|
||||
*.sh text
|
||||
|
||||
*.png binary
|
||||
@@ -1,28 +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)
|
||||
|
||||
You may also search through existing issues before opening a new one: https://github.com/ParsePlatform/Parse-Server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
|
||||
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
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
|
||||
|
||||
-->
|
||||
|
||||
### 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
|
||||
+51
-40
@@ -1,68 +1,79 @@
|
||||
language: node_js
|
||||
dist: trusty
|
||||
node_js:
|
||||
- '6.10'
|
||||
- '7.10'
|
||||
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]+(-.*)?$/
|
||||
- /^greenkeeper/.*$/
|
||||
- "/^[0-9]+.[0-9]+.[0-9]+(-.*)?$/"
|
||||
- 3.x
|
||||
- "/^greenkeeper/.*$/"
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- "$HOME/.npm"
|
||||
- "$HOME/.mongodb/versions"
|
||||
|
||||
# Test stage
|
||||
stage: test
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/.bin/nyc'
|
||||
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=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.10'
|
||||
env:
|
||||
before_script: skip
|
||||
after_script: skip
|
||||
script: skip
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: ./resources/npm-git.sh
|
||||
on:
|
||||
branch: master
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email:
|
||||
secure: "YweTGc22uqFWpzbfiUa5ptBLRAy6tt6d9TZLwEkPtmnsWzN9dguGyKWmXiw0qL+848FWQ5PWzUgBn5XdigV9tF3rJY6RGs8i38WulNjwSoGuRZa3AChsQHAb1KenANcJybzhnwgEj9gRsrGZPEsyI2whfake/xLDtG91kHjpJANsd4gseOh6fdS4FIYCbyXvSbC7S0yZzIJkgSkMqJO7RJ8r0HYQ7srYIw31dM3ZXSmUYu+GaMDUUu3RLAGYoKaROxhDRnzkjdeLLiwZH8hQ/6CaqwqX54iJ6OS+MOQU3fi5ZXalA7RZvtC4RmrcCkaTf3i0f+5xejYIFgzXdYGMVm8DUc82tDw1s4b6Pb19bgi1xXOQ0IKzRmZuGxnvkRN61dnYdnpnnNuG97HXgVjiOigZXVLZkWazUdnf9zXqmkC+KxPfa4Ldqg0TMjQ9J14n6TXxRti8Tt0xMa1Uzho7VdsxdJy35Bghy398O6X8VdX6parfzEkX7c/JzcA3TIMJ9+S1dy3J4Tb2URB5367W6h7cDeXtGmwLTFu75Q1CNqRJkUGbSrX2NyMqG5tc8oaTM+OWvLxcbKPRy9T6qN7x2JmCHHaapf8/8VR9wCy2PwE5j+KWhmctEHqqOgrtG5gsjC0eCPJsposxZWyM7M6aUpXe3w+olBfKq9apUGStUSU="
|
||||
api_key:
|
||||
secure: "QprkaqQ+WCvZQR4qIEs5iS6peMCbRd8Hgt0s6HfdmhavNXwDFY8Bkdf6zJwWHLiqs4pyClXDZ2f6QiOs7y9IvJZ+wOIbsf4N5V6s06zOxJ0NAOwhe0mgWS3Us0zgXIfmW4BpmGnU4ql/qGL+9vNfyQJ7wxEJxVK7hiYh9Epu49E2jmefDqTX+SNSrDCg4HkRcxerxYGnAJDCP50QaNlyLSciODD6wHaddrSYkdvmISLMnLHug61OkE4OBIOWXfYV+e31kDj+zgczAfVQgekDKtaimCQclHFrmaEPl0KIm6wsDQAw5HWkepA/WZfv4SbCrDaKJDZw+LBI7dR0ezmiOH/zmWWrRW7D4wjkDGiumWjA8etuf8I4GRyC/d1RS+hnlvPr0Bu+WljuVxLoK3nhZOdiK5t4QlVDoGankkRjLylwFQgo8tzu5N0dc26z3ClowTwcKsjStmFr53gjCD7l3qoFjyPot1JlW3LPhG9Nch7rK33/7ONqVai3zxb1xB9ynd8TSzKi4/66LeYEDcGVM5A9Vmkp+egCnOhkOAXdI8O4jid98NANir+U4xmUYZ2PAMNmSeHlpSpwH2pC1/BHpDKA9RZEuEdr3sgmtuXXwwOCp+xvuVwxZZ6+gVEjG4nGTqSNxUIq1fzjGih8ElJtbM1Uhh2dVE9uxW7EC/oCuuk="
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
repo: parse-community/parse-server
|
||||
- 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)
|
||||
|
||||
+573
-2
@@ -1,5 +1,576 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### master
|
||||
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.4.4...master)
|
||||
|
||||
### 3.4.3
|
||||
[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 [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)
|
||||
|
||||
#### Improvements:
|
||||
* Improve documentation for LiveQuery options, thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Improve documentation for using cloud code with docker, thanks to [Stephen Tuso](https://github.com/stephentuso)
|
||||
* Adds support for Facebook's AccountKit, thanks to [6thfdwp](https://github.com/6thfdwp)
|
||||
* Disable afterFind routines when running aggregates, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Improve support for distinct aggregations of nulls, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Regenreate the email verification token when requesting a new email, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fix issue affecting readOnly masterKey and purge command, thanks to [AreyouHappy](https://github.com/AreyouHappy)
|
||||
* Fixes Issue unsetting in beforeSave doesn't allow object creation, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Fixes issue crashing server on invalid live query payload, thanks to [fridays](https://github.com/fridays)
|
||||
* Fixes issue affecting postgres storage adapter "undefined property '__op'", thanks to [Tyson Andre](https://github,com/TysonAndre)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [winston@2.4.1](https://www.npmjs.com/package/winston)
|
||||
* [pg-promise@8.2.0](https://www.npmjs.com/package/pg-promise)
|
||||
* [commander@2.15.0](https://www.npmjs.com/package/commander)
|
||||
* [lru-cache@4.1.2](https://www.npmjs.com/package/lru-cache)
|
||||
* [parse@1.11.1](https://www.npmjs.com/package/parse)
|
||||
* [ws@5.0.0](https://www.npmjs.com/package/ws)
|
||||
* [mongodb@3.0.4](https://www.npmjs.com/package/mongodb)
|
||||
* [lodash@4.17.5](https://www.npmjs.com/package/lodash)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [cross-env@5.1.4](https://www.npmjs.com/package/cross-env)
|
||||
* [flow-bin@0.67.1](https://www.npmjs.com/package/flow-bin)
|
||||
* [jasmine@3.1.0](https://www.npmjs.com/package/jasmine)
|
||||
* [parse@1.11.1](https://www.npmjs.com/package/parse)
|
||||
* [babel-eslint@8.2.2](https://www.npmjs.com/package/babel-eslint)
|
||||
* [nodemon@1.15.0](https://www.npmjs.com/package/nodemon)
|
||||
|
||||
### 2.7.2
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.2...2.7.1)
|
||||
|
||||
#### Improvements:
|
||||
* Improved match aggregate
|
||||
* Do not mark the empty push as failed
|
||||
* Support pointer in aggregate query
|
||||
* Introduces flow types for storage
|
||||
* Postgres: Refactoring of Postgres Storage Adapter
|
||||
* Postgres: Support for multiple projection in aggregate
|
||||
* Postgres: performance optimizations
|
||||
* Adds infos about vulnerability disclosures
|
||||
* Adds ability to login with email when provided as username
|
||||
|
||||
#### Bug Fixes
|
||||
* Scrub Passwords with URL Encoded Characters
|
||||
* Fixes issue affecting using sorting in beforeFind
|
||||
|
||||
#### Dependency Updates:
|
||||
* [commander@2.13.0](https://www.npmjs.com/package/commander)
|
||||
* [semver@5.5.0](https://www.npmjs.com/package/semver)
|
||||
* [pg-promise@7.4.0](https://www.npmjs.com/package/pg-promise)
|
||||
* [ws@4.0.0](https://www.npmjs.com/package/ws)
|
||||
* [mime@2.2.0](https://www.npmjs.com/package/mime)
|
||||
* [parse@1.11.0](https://www.npmjs.com/package/parse)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [nodemon@1.14.11](https://www.npmjs.com/package/nodemon)
|
||||
* [flow-bin@0.64.0](https://www.npmjs.com/package/flow-bin)
|
||||
* [jasmine@2.9.0](https://www.npmjs.com/package/jasmine)
|
||||
* [cross-env@5.1.3](https://www.npmjs.com/package/cross-env)
|
||||
|
||||
### 2.7.1
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.1...2.7.0)
|
||||
|
||||
:warning: Fixes a security issue affecting Class Level Permissions
|
||||
|
||||
* Adds support for dot notation when using matchesKeyInQuery, thanks to [Henrik](https://github.com/bohemima) and [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
### 2.7.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.7.0...2.6.5)
|
||||
|
||||
:warning: This version contains an issue affecting Class Level Permissions on mongoDB. Please upgrade to 2.7.1.
|
||||
|
||||
Starting parse-server 2.7.0, the minimun nodejs version is 6.11.4, please update your engines before updating parse-server
|
||||
|
||||
#### New Features:
|
||||
* Aggregation endpoints, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Adds indexation options onto Schema endpoints, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
|
||||
#### Bug fixes:
|
||||
* Fixes sessionTokens being overridden in 'find' (#4332), thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
* Proper `handleShutdown()` feature to close database connections (#4361), thanks to [CHANG, TZU-YEN](https://github.com/trylovetom)
|
||||
* Fixes issue affecting state of _PushStatus objects, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
* Fixes issue affecting calling password reset password pages with wrong appid, thanks to [Bryan de Leon](https://github.com/bryandel)
|
||||
* Fixes issue affecting duplicates _Sessions on successive logins, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Improvements:
|
||||
* Updates contributing guides, and improves windows support, thanks to [Addison Elliott](https://github.com/addisonelliott)
|
||||
* Uses new official scoped packaged, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Improves health checks responses, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
* Add password confirmation to choose_password, thanks to [Worathiti Manosroi](https://github.com/pungme)
|
||||
* Improve performance of relation queries, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [commander@2.12.1](https://www.npmjs.com/package/commander)
|
||||
* [ws@3.3.2](https://www.npmjs.com/package/ws)
|
||||
* [uws@9.14.0](https://www.npmjs.com/package/uws)
|
||||
* [pg-promise@7.3.2](https://www.npmjs.com/package/pg-promise)
|
||||
* [parse@1.10.2](https://www.npmjs.com/package/parse)
|
||||
* [pg-promise@7.3.1](https://www.npmjs.com/package/pg-promise)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [cross-env@5.1.1](https://www.npmjs.com/package/cross-env)
|
||||
|
||||
|
||||
|
||||
### 2.6.5
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.5...2.6.4)
|
||||
|
||||
#### New Features:
|
||||
* Adds support for read-only masterKey, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Adds support for relative time queries (mongodb only), thanks to [Marvel Mathew](https://github.com/marvelm)
|
||||
|
||||
#### Improvements:
|
||||
* Handle possible afterSave exception, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
* Add support for expiration interval in Push, thanks to [Marvel Mathew](https://github.com/marvelm)
|
||||
|
||||
#### Bug Fixes:
|
||||
* The REST API key was improperly inferred from environment when using the CLI, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.6.4
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.4...2.6.3)
|
||||
|
||||
#### Improvements:
|
||||
* Improves management of configurations and default values, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Adds ability to start ParseServer with `ParseServer.start(options)`, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Adds request original IP to cloud code hooks, thanks to [Gustav Ahlberg](https://github.com/Gyran)
|
||||
* Corrects some outdated links, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
* Adds serverURL validation on startup, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
* Adds ability to login with POST requests alongside GET, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
* Adds ability to login with email, instead of username, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes issue affecting beforeSaves and increments, thanks to [Benjamin Wilson Friedman](https://github.com/montymxb)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [parse-server-push-adapter@2.0.2](https://www.npmjs.com/package/parse-server-push-adapter)
|
||||
* [semver@5.4.1](https://www.npmjs.com/package/semver)
|
||||
* [pg-promise@7.0.3](https://www.npmjs.com/package/pg-promise)
|
||||
* [mongodb@2.2.33](https://www.npmjs.com/package/mongodb)
|
||||
* [parse@1.10.1](https://www.npmjs.com/package/parse)
|
||||
* [express@4.16.0](https://www.npmjs.com/package/express)
|
||||
* [mime@1.4.1](https://www.npmjs.com/package/mime)
|
||||
* [parse-server-simple-mailgun-adapter@1.0.1](https://www.npmjs.com/package/parse-server-simple-mailgun-adapter)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [babel-preset-env@1.6.1](https://www.npmjs.com/package/babel-preset-env)
|
||||
* [cross-env@5.1.0](https://www.npmjs.com/package/cross-env)
|
||||
* [mongodb-runner@3.6.1](https://www.npmjs.com/package/mongodb-runner)
|
||||
* [eslint-plugin-flowtype@2.39.1](https://www.npmjs.com/package/eslint-plugin-flowtype)
|
||||
* [eslint@4.9.0](https://www.npmjs.com/package/eslint)
|
||||
|
||||
### 2.6.3
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.2...2.6.3)
|
||||
|
||||
#### Improvements:
|
||||
* Queries on Pointer fields with `$in` and `$nin` now supports list of objectId's, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* LiveQueries on `$in` and `$nin` for pointer fields work as expected thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Also remove device token when APNS error is BadDeviceToken, thanks to [Mauricio Tollin](https://github.com/)
|
||||
* LRU cache is not available on the ParseServer object, thanks to [Tyler Brock](https://github.com/tbrock)
|
||||
* Error messages are more expressive, thanks to [Tyler Brock](https://github.com/tbrock)
|
||||
* Postgres: Properly handle undefined field values, thanks to [Diamond Lewis](https://github.com/dlewis)
|
||||
* Updating with two GeoPoints fails correctly, thanks to [Anthony Mosca](https://github.com/aontas)
|
||||
|
||||
#### New Features:
|
||||
* Adds ability to set a maxLimit on server configuration for queries, thanks to [Chris Norris](https://github.com/)
|
||||
|
||||
#### Bug fixes:
|
||||
* Fixes issue affecting reporting `_PushStatus` with misconfigured serverURL, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fixes issue affecting deletion of class that doesn't exist, thanks to [Diamond Lewis](https://github.com/dlewis)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [winston@2.4.0](https://www.npmjs.com/package/winston)
|
||||
* [pg-promise@6.10.2](https://www.npmjs.com/package/pg-promise)
|
||||
* [winston-daily-rotate-file@1.6.0](https://www.npmjs.com/package/winston-daily-rotate-file)
|
||||
* [request@2.83.0](https://www.npmjs.com/package/request)
|
||||
* [body-parser@1.18.2](https://www.npmjs.com/package/body-parser)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [request-promise@4.2.2](https://www.npmjs.com/package/request-promise)
|
||||
* [eslint@4.7.1](https://www.npmjs.com/package/eslint)
|
||||
|
||||
### 2.6.2
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.1...2.6.2)
|
||||
|
||||
#### Improvements:
|
||||
* PushWorker/PushQueue channels are properly prefixed with the Parse applicationId, thanks to [Marvel Mathew](https://github.com/marvelm)
|
||||
* You can use Parse.Cloud.afterSave hooks on _PushStatus
|
||||
* You can use Parse.Cloud.onLiveQueryEvent to track the number of clients and subscriptions
|
||||
* Adds support for more fields from the Audience class.
|
||||
|
||||
#### New Features:
|
||||
* Push: Adds ability to track sentPerUTC offset if your push scheduler supports it.
|
||||
* Push: Adds support for cleaning up invalid deviceTokens from _Installation (PARSE_SERVER_CLEANUP_INVALID_INSTALLATIONS=1).
|
||||
|
||||
#### Dependency Updates:
|
||||
* [ws@3.2.0](https://www.npmjs.com/package/ws)
|
||||
* [pg-promise@6.5.3](https://www.npmjs.com/package/pg-promise)
|
||||
* [winston-daily-rotate-file@1.5.0](https://www.npmjs.com/package/winston-daily-rotate-file)
|
||||
* [body-parser@1.18.1](https://www.npmjs.com/package/body-parser)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [nodemon@1.12.1](https://www.npmjs.com/package/nodemon)
|
||||
* [mongodb-runner@3.6.0](https://www.npmjs.com/package/mongodb-runner)
|
||||
* [babel-eslint@8.0.0](https://www.npmjs.com/package/babel-eslint)
|
||||
|
||||
### 2.6.1
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.6.0...2.6.1)
|
||||
|
||||
#### Improvements:
|
||||
* Improves overall performance of the server, more particularly with large query results.
|
||||
* Improves performance of InMemoryCacheAdapter by removing serialization.
|
||||
* Improves logging performance by skipping necessary log calls.
|
||||
* Refactors object routers to simplify logic.
|
||||
* Adds automatic indexing on $text indexes, thanks to [Diamon Lewis](https://github.com/dplewis)
|
||||
|
||||
#### New Features:
|
||||
* Push: Adds ability to send localized pushes according to the _Installation localeIdentifier
|
||||
* Push: proper support for scheduling push in user's locale time, thanks to [Marvel Mathew](https://github.com/marvelm)
|
||||
* LiveQuery: Adds ability to use LiveQuery with a masterKey, thanks to [Jeremy May](https://github.com/kenishi)
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes an issue that would duplicate Session objects per userId-installationId pair.
|
||||
* Fixes an issue affecting pointer permissions introduced in this release.
|
||||
* Fixes an issue that would prevent displaying audiences correctly in dashboard.
|
||||
* Fixes an issue affecting preventLoginWithUnverifiedEmail upon signups.
|
||||
|
||||
#### Dependency Updates:
|
||||
* [pg-promise@6.3.2](https://www.npmjs.com/package/pg-promise)
|
||||
* [body-parser@1.18.0](https://www.npmjs.com/package/body-parser)
|
||||
* [nodemon@1.11.1](https://www.npmjs.com/package/nodemon)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [babel-cli@6.26.0](https://www.npmjs.com/package/babel-cli)
|
||||
|
||||
### 2.6.0
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.3...2.6.0)
|
||||
|
||||
#### Breaking Changes:
|
||||
* [parse-server-s3-adapter@1.2.0](https://www.npmjs.com/package/parse-server-s3-adapter): A new deprecation notice is introduced with parse-server-s3-adapter's version 1.2.0. An upcoming release will remove passing key and password arguments. AWS credentials should be set using AWS best practices. See the [Deprecation Notice for AWS credentials]( https://github.com/parse-server-modules/parse-server-s3-adapter/blob/master/README.md#deprecation-notice----aws-credentials) section of the adapter's README.
|
||||
|
||||
#### New Features
|
||||
* Polygon is fully supported as a type, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Query supports PolygonContains, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
|
||||
#### Improvements
|
||||
* Postgres: Adds support nested contains and containedIn, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Postgres: Adds support for `null` in containsAll queries, thanks to [Diamond Lewis](https://github.com/dplewis)
|
||||
* Cloud Code: Request headers are passed to the cloud functions, thanks to [miguel-s](https://github.com/miguel-s)
|
||||
* Push: All push queries now filter only where deviceToken exists
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes issue affecting updates of _User objects when authData was passed.
|
||||
* Push: Pushing to an empty audience should now properly report a failed _PushStatus
|
||||
* Linking Users: Fixes issue affecting linking users with sessionToken only
|
||||
|
||||
#### Dependency Updates:
|
||||
* [ws@3.1.0](https://www.npmjs.com/package/ws)
|
||||
* [mime@1.4.0](https://www.npmjs.com/package/mime)
|
||||
* [semver@5.4.0](https://www.npmjs.com/package/semver)
|
||||
* [uws@8.14.1](https://www.npmjs.com/package/uws)
|
||||
* [bcrypt@1.0.3](https://www.npmjs.com/package/bcrypt)
|
||||
* [mongodb@2.2.31](https://www.npmjs.com/package/mongodb)
|
||||
* [redis@2.8.0](https://www.npmjs.com/package/redis)
|
||||
* [pg-promise@6.3.1](https://www.npmjs.com/package/pg-promise)
|
||||
* [commander@2.11.0](https://www.npmjs.com/package/commander)
|
||||
|
||||
##### Devevelopment Dependencies Updates:
|
||||
* [jasmine@2.8.0](https://www.npmjs.com/package/jasmine)
|
||||
* [babel-register@6.26.0](https://www.npmjs.com/package/babel-register)
|
||||
* [babel-core@6.26.0](https://www.npmjs.com/package/babel-core)
|
||||
* [cross-env@5.0.2](https://www.npmjs.com/package/cross-env)
|
||||
|
||||
### 2.5.3
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.2...2.5.3)
|
||||
|
||||
#### New Features:
|
||||
* badge property on android installations will now be set as on iOS (#3970), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug Fixes:
|
||||
* Fixes incorrect number parser for cache options
|
||||
|
||||
### 2.5.2
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.1...2.5.2)
|
||||
|
||||
#### Improvements:
|
||||
* Restores ability to run on node >= 4.6
|
||||
* Adds ability to configure cache from CLI
|
||||
* Removes runtime check for node >= 4.6
|
||||
|
||||
### 2.5.1
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.5.0...2.5.1)
|
||||
|
||||
@@ -112,7 +683,7 @@ If you experience issues with older versions, please [open a issue](https://gith
|
||||
* Fix issue affecting overloaded query constraints ([#3723](https://github.com/parse-community/parse-server/pull/3723), [#3678](https://github.com/parse-community/parse-server/pull/3678)), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Properly catch unhandled rejections in _Installation updates ([#3795](https://github.com/parse-community/parse-server/pull/3795)), thanks to [kahoona77](https://github.com/kahoona77)
|
||||
|
||||
#### Dependency Updates:
|
||||
#### Dependency Updates:
|
||||
|
||||
* [uws@0.14.5](https://www.npmjs.com/package/uws)
|
||||
* [mime@1.3.6](https://www.npmjs.com/package/mime)
|
||||
@@ -768,7 +1339,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.
|
||||
|
||||
|
||||
+69
-11
@@ -1,28 +1,86 @@
|
||||
### 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:
|
||||
|
||||
* Take testing seriously! Aim to increase the test coverage with every pull request.
|
||||
* Run the tests for the file you are working on with `npm test spec/MyFile.spec.js`
|
||||
* Run the tests for the whole project and look at the coverage report to make sure your tests are exhaustive by running `npm test` and looking at (project-root)/lcov-report/parse-server/FileUnderTest.js.html
|
||||
* Lint your code by running `npm run lint` to make sure all your code is not gonna be rejected by the CI.
|
||||
* Never publish the lib folder.
|
||||
### Recommended setup:
|
||||
|
||||
##### Run your tests against Postgres (optional)
|
||||
* [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`
|
||||
* Run the tests for the whole project to make sure the code passes all tests. This can be done by running the test command for a single file but removing the test file argument. The results can be seen at *<PROJECT_ROOT>/coverage/lcov-report/index.html*.
|
||||
* Lint your code by running `npm run lint` to make sure the code is not going to be rejected by the CI.
|
||||
* **Do not** publish the *lib* folder.
|
||||
|
||||
### Run your tests against Postgres (optional)
|
||||
|
||||
If your pull request introduces a change that may affect the storage or retrieval of objects, you may want to make sure it plays nice with Postgres.
|
||||
|
||||
* Run the tests against the postgres database with `PARSE_SERVER_TEST_DB=postgres npm test`. You'll need to have postgres running on your machine and setup [appropriately](https://github.com/parse-community/parse-server/blob/master/.travis.yml#L37)
|
||||
* 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
|
||||
|
||||
+22
-12
@@ -1,21 +1,31 @@
|
||||
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
|
||||
|
||||
RUN mkdir -p /parse-server/cloud
|
||||
VOLUME /parse-server/cloud
|
||||
# Release stage
|
||||
FROM node:lts-alpine as release
|
||||
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,22 +1,51 @@
|
||||

|
||||

|
||||
|
||||
[](#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://community.parseplatform.org/c/parse-server)
|
||||
[](https://greenkeeper.io/)
|
||||
[](https://twitter.com/intent/follow?screen_name=ParsePlatform)
|
||||
|
||||
[](https://gitter.im/ParsePlatform/Chat)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
Parse Server is an [open source version of the Parse backend](http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/) that can be deployed to any infrastructure that can run Node.js.
|
||||
Parse Server 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)
|
||||
- [Docker](#inside-a-docker-container)
|
||||
- [Saving an Object](#saving-your-first-object)
|
||||
- [Connect an SDK](#connect-your-app-to-parse-server)
|
||||
- [Running elsewhere](#running-parse-server-elsewhere)
|
||||
- [Sample Application](#parse-server-sample-application)
|
||||
- [Parse Server + Express](#parse-server--express)
|
||||
- [Configuration](#configuration)
|
||||
- [Basic Options](#basic-options)
|
||||
- [Client Key Options](#client-key-options)
|
||||
- [Email Verification & Password Reset](#email-verification-and-password-reset)
|
||||
- [Using Environment Variables](#using-environment-variables-to-configure-parse-server)
|
||||
- [Available Adapters](#available-adapters)
|
||||
- [Configuring File Adapters](#configuring-file-adapters)
|
||||
- [Logging](#logging)
|
||||
- [Live Queries](#live-queries)
|
||||
- [Upgrading to 3.0.0](#upgrading-to-300)
|
||||
- [Support](#support)
|
||||
- [Ride the Bleeding Edge](#want-to-ride-the-bleeding-edge)
|
||||
- [Contributing](#contributing)
|
||||
- [Backers](#backers)
|
||||
- [Sponsors](#sponsors)
|
||||
|
||||
# Getting Started
|
||||
|
||||
[](https://greenkeeper.io/)
|
||||
|
||||
|
||||
April 2016 - We created a series of video screencasts, please check them out here: [http://blog.parse.com/learn/parse-server-video-series-april-2016/](http://blog.parse.com/learn/parse-server-video-series-april-2016/)
|
||||
|
||||
The fastest and easiest way to get started is to run MongoDB and Parse Server locally.
|
||||
|
||||
## Running Parse Server
|
||||
@@ -28,14 +57,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.
|
||||
|
||||
@@ -111,7 +142,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
|
||||
|
||||
@@ -129,7 +160,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
|
||||
@@ -158,35 +189,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.
|
||||
@@ -196,7 +207,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.
|
||||
|
||||
@@ -205,36 +216,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).
|
||||
* `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.
|
||||
|
||||
##### 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:
|
||||
|
||||
@@ -267,7 +249,7 @@ var server = ParseServer({
|
||||
appName: 'Parse App',
|
||||
// The email adapter
|
||||
emailAdapter: {
|
||||
module: 'parse-server-simple-mailgun-adapter',
|
||||
module: '@parse/simple-mailgun-adapter',
|
||||
options: {
|
||||
// The address that your emails come from
|
||||
fromAddress: 'parse@example.com',
|
||||
@@ -286,14 +268,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
|
||||
@@ -307,8 +290,10 @@ You can also use other email adapters contributed by the community such as:
|
||||
- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter)
|
||||
- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter)
|
||||
- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template)
|
||||
- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter)
|
||||
- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter)
|
||||
- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter)
|
||||
- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter)
|
||||
|
||||
### Using environment variables to configure Parse Server
|
||||
|
||||
@@ -320,7 +305,7 @@ PARSE_SERVER_APPLICATION_ID
|
||||
PARSE_SERVER_MASTER_KEY
|
||||
PARSE_SERVER_DATABASE_URI
|
||||
PARSE_SERVER_URL
|
||||
PARSE_SERVER_CLOUD_CODE_MAIN
|
||||
PARSE_SERVER_CLOUD
|
||||
```
|
||||
|
||||
The default port is 1337, to use a different port set the PORT environment variable:
|
||||
@@ -329,24 +314,60 @@ 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
|
||||
[Parse Server Modules (Adapters)](https://github.com/parse-server-modules)
|
||||
|
||||
All official adapters are distributed as scoped pacakges on [npm (@parse)](https://www.npmjs.com/search?q=scope%3Aparse).
|
||||
|
||||
Some well maintained adapters are also available on the [Parse Server Modules](https://github.com/parse-server-modules) organization.
|
||||
|
||||
You can also find more adapters maintained by the community by searching on [npm](https://www.npmjs.com/search?q=parse-server%20adapter&page=1&ranking=optimal).
|
||||
|
||||
### Configuring File Adapters
|
||||
|
||||
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).
|
||||
|
||||
# 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.
|
||||
For implementation related questions or any other questions please refer to [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server).
|
||||
|
||||
|
||||
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):
|
||||
|
||||
@@ -358,20 +379,99 @@ 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).
|
||||
|
||||
# Backers
|
||||
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/parse-server#backer)]
|
||||
|
||||
<a href="https://opencollective.com/parse-server/backer/0/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/1/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/2/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/3/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/4/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/5/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/6/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/7/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/8/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/9/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/10/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/11/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/12/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/13/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/14/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/15/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/16/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/17/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/18/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/19/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/20/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/21/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/22/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/23/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/24/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/25/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/26/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/27/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/28/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/backer/29/website" target="_blank"><img src="https://opencollective.com/parse-server/backer/29/avatar.svg"></a>
|
||||
|
||||
|
||||
# Sponsors
|
||||
|
||||
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/parse-server#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/parse-server/sponsor/0/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/1/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/2/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/3/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/4/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/5/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/6/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/7/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/8/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/9/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/10/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/11/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/12/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/13/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/14/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/15/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/16/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/17/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/18/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/19/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/20/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/21/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/22/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/23/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/24/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/25/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/26/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/27/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/28/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parse-server/sponsor/29/website" target="_blank"><img src="https://opencollective.com/parse-server/sponsor/29/avatar.svg"></a>
|
||||
|
||||
-----
|
||||
|
||||
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.
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# Parse Community Vulnerability Disclosure Program
|
||||
If you believe you have found a security vulnerability on one of parse-community maintained packages,
|
||||
we encourage you to let us know right away.
|
||||
We will investigate all legitimate reports and do our best to quickly fix the problem.
|
||||
Before reporting though, please review this page including and those things that should not be reported.
|
||||
|
||||
# Responsible Disclosure Policy
|
||||
If you comply with the policies below when reporting a security issue to parse community,
|
||||
we will not initiate a lawsuit or law enforcement investigation against you in response to your report.
|
||||
We ask that:
|
||||
|
||||
- You give us reasonable time to investigate and mitigate an issue you report before making public any information about the report or sharing such information with others. This means we request _at least_ **7 days** to get back to you with an initial response and _at least_ **30 days** from initial contact (made by you) to apply a patch.
|
||||
- You do not interact with an individual account (which includes modifying or accessing data from the account) if the account owner has not consented to such actions.
|
||||
- You make a good faith effort to avoid privacy violations and disruptions to others, including (but not limited to) destruction of data and interruption or degradation of our services.
|
||||
- You do not exploit a security issue you discover for any reason. (This includes demonstrating additional risk, such as attempted compromise of sensitive company data or probing for additional issues). You do not violate any other applicable laws or regulations.
|
||||
|
||||
# Communicating with us
|
||||
|
||||
All vulnerability should be privately reported to either [Node Security](https://nodesecurity.io/report) or directly to us at the following address [security at parseplatform dot org](mailto:security@parseplatform.org)
|
||||
|
||||
You can use our PGP public key, which is also uploaded [here](hkp://pgp.mit.edu):
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBFoi200BEACnFHa4Atvw62TjpI5uDtyOF1Ab6gd6898ITXlzFVlAoiqTBE2o
|
||||
S3H9vCe6w32HkTqyKiymdk50mAaDJrEOyAZSqj0gc4r7vmCx2s7f3iO9A9PEGsHj
|
||||
UROnkJ5v2su1Dk95XQrbrR4JyNvFMLLqEbdK78Mhx/Xd5QqOD8pop0cS8pF1f1Mb
|
||||
3MiZb3bxFj+7n+KC80C2+CNyJt95alVnq2MuwbEuwTJQV5CEgRqzBCov9qnLgloK
|
||||
w7YP4YLkKZoMZQ45mWCUTmn8YIa9PabDLXUhlKv3MQInhnJIELb+jSKO96glHr6p
|
||||
DpFf0pwRfsuoIhy3jaO7K/ws5uZY3/Ae3gjrAlOB8jhae0POWSwEM+iWHg3wcjpt
|
||||
lRdu/OgPXqKIgMAXw6Kx9XrskEhOI9ZQfHZlK3HL4ArSdtGYIO5pVNeRssljJvJm
|
||||
G5HJuGBaPCQNYX7BWJkXFF0HYV6Ke2JDXGVSM4ubPJsZcA4Yx7SYyvuOsNgqHOFM
|
||||
9snfPfAPAhu/4zCYNCO2NNBc7HaH+qwIvveWX5tTGe6UpX/wOcD8xkoEn+UygQbO
|
||||
lwu+kjFn8H6RlDChPR65aJTU5Lu0kqRnej1gCjYzOS6AjFOjLuRBlRaey+myhQHO
|
||||
TbII9nkYI3abYhD8Di77Ve6XFMQI1grPGaqNp6ZLfej1u8PExpclzxTbtQARAQAB
|
||||
tDdQYXJzZVBsYXRmb3JtLm9yZyBTZWN1cml0eSA8c2VjdXJpdHlAcGFyc2VwbGF0
|
||||
Zm9ybS5vcmc+iQI9BBMBCgAnBQJaIttNAhsDBQkHhh+ABQsJCAcDBRUKCQgLBRYC
|
||||
AwEAAh4BAheAAAoJEOaNxtHMZ3/L13EP/2X4i0h1Jidr0GN6t7LUJFDBDHxnY+V+
|
||||
kPXuucWqtFSkiznHNWSKh8N1LY9N+5eYQj21fJw2RV+ePZKb07jcThG8G1qg+tIn
|
||||
bbLO2RxmuO/ISgqgvpmtZH9FHtjpGaGStCQqljtZDcBI0Y52l9SLItZjoiRlXePb
|
||||
C+embDhO6Wzgsi+zkzskm8ISw4mTbDY+HN/1TakCsfMMs8J7twQar+eW97WibzRW
|
||||
pvhIeJ6egzxs2dW0bXxb0OPvx9xm/FxxRgf+2vySYFnZ88j7Luur4VKdBvh1NtUT
|
||||
n+xLp/heCHQ/If1ou1Dd6rypEng3A75WANf5m1TAd8JrlQZEOLm4oW+brpDBamqc
|
||||
dP2z2/zzKykZzRoN1PSX9JRsyLhJhZSXIm/49gATtkaUFBBsFrrwqdWTV7tWFmQI
|
||||
Oe2wT9IExe+RmXsVqwIndyVkcROuXXGt3y92dcmEriNUGmYXJchs9pAdOCkpHqmf
|
||||
BkZWalXuWdWiwshNiEF3KWiNbw6BMEWlKPuuJ6kWevLj35MQMLIIlGLx722/kbBB
|
||||
KIJfp8NdWncIyx0ulq70GWdNaKvu+9dDElMCsPqXeVxbXVpGiBcIgX1S0JEU0YX7
|
||||
kShAAkZDwhyfUh4IYbyJbNVNzhMazDqI48kP8Kz8vqvZAf3SXyjSBL1XKWLAfKtb
|
||||
Bs4OuHR3FDkjuQINBFoi200BEADoh8xHW4SVuVnF+Y82Z2B25Ybhwd13mP3LUxwX
|
||||
cRYeyVge8V7kyftYiUbIybDoxa3H8ysiSgH51yHkoRsq8JO3WEnJZop1ZnjOKhjv
|
||||
g73vciKImAGrsTVtUR4ErwUsHaXgCFmlmyTI/RLPDfvHdZyYm9wji4XLZrFu5O/s
|
||||
JD1wU1/aMYCBRjrszI4uklyqGUx2kKLYEI7iITAVVQ8/xFlPPYhVX02jR7fCqTkI
|
||||
ualVAzK/6Lc4VGqjzFG5kElLCIVDTMLJvPETgXeSAuBigcC+osTw8IRhqY87qNbn
|
||||
ZpGDKJxV7ARXg5akg/98xI1TZiAMP1vHVKke7XiH5E6M/3IyPiY5ns4SaXu3ZonN
|
||||
EUR9Z60IeUHVkGpMUk0K0avkaLIAWb93D6jlZ+fJoNBuCoWB31d6+/W8LnrocTQb
|
||||
mL9ls8HiabrPDsuzI9y3ptOjgGn8pmWoJHM2fiQ5eElexHFB+1QTqq1yiTnuohi/
|
||||
p+BbKK6V/FZbrP4Rd3hOmPkOWag3KvwGFF8vW6gmx3lft+B90TtBSMv46lqDI5TX
|
||||
VUr3Un9YHIF+xQMNAgZuJkTIVLrYgexRHnJGrwhRXSZgpVkvj/ygIhR2EvY8zySh
|
||||
/EFw9ZTmhOrR34q/qlChEuUriP1cc1ygTnjfcw+ZgsTPh50VZk8s5cZAa8tVFINR
|
||||
cZKBSwARAQABiQIlBBgBCgAPBQJaIttNAhsMBQkHhh+AAAoJEOaNxtHMZ3/LgbUQ
|
||||
AJHyhtH3bkQsWH0Z+PonbcNxZNq1ZlOfosQIWRzX8BioJpQ5qttkKt5PwhBDrr9h
|
||||
4ySYzsbTg3fSkIkjfUfaRr+62xay5efaIwG5PcHdXOR/fKCJ6NrTvMEzPUZ8521e
|
||||
yk0rjm++bNqh+Knez0+xnk99n1XlPrMAXrEb63oM5IBnpJC1PcztzMhdvsNw+B/l
|
||||
2YcVhXH2peOp/GbAd58fB7JMggwumCktFnXYsYZlMFch3mwKDb66Qi2gbCgK2KHU
|
||||
sj/mPag7vpy1E4lOlBnDeZpYnjfE/8VJkT4ck5OAwywZK/NUqLfh35RaIwjeXDLM
|
||||
nlLff3HQJPXYzavCzLZ5dMZJfn0968NIHitjoW9VLs9UhrY7EWI7T6GAMX3wHcq6
|
||||
ssGOkS0Y1OW8s7jFuoe00PByjiCHcFjBG2NF3n08Nu2c5hewGPs0FdhTadQtHpI2
|
||||
TTeSIxQ2Ui21UfcX4wMbqELh871ZeQcbVp5LWWibVbfy4mx4Tq/Hvgp7DeBh8DLF
|
||||
/7MDDwZ+RIBoy98CYz4xsFMdS/9L64uBk/0C+U4OwFJI1FDDxFp6cDqjxdykWi48
|
||||
wsCczfashguiuJeJ1Ug8URRLY/DKQmjSJaCwy3McK/MOb+JVMazMUyrU9XaGuP4Y
|
||||
Co6fHPyjrvmE5DtU5Vp8O68ZpOYrkM6X22dIQpPi6atm
|
||||
=o7Nx
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
@@ -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
+10359
File diff suppressed because it is too large
Load Diff
+75
-52
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.5.1",
|
||||
"version": "3.4.4",
|
||||
"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,77 +14,100 @@
|
||||
"views/",
|
||||
"LICENSE",
|
||||
"PATENTS",
|
||||
"postinstall.js",
|
||||
"README.md"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@parse/fs-files-adapter": "1.0.1",
|
||||
"@parse/push-adapter": "3.0.0",
|
||||
"@parse/s3-files-adapter": "1.2.1",
|
||||
"@parse/simple-mailgun-adapter": "1.1.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.17.2",
|
||||
"commander": "2.10.0",
|
||||
"deepcopy": "0.6.3",
|
||||
"express": "4.15.3",
|
||||
"body-parser": "1.19.0",
|
||||
"commander": "2.20.0",
|
||||
"deepcopy": "2.0.0",
|
||||
"express": "4.17.0",
|
||||
"follow-redirects": "1.7.0",
|
||||
"intersect": "1.0.1",
|
||||
"lodash": "4.17.4",
|
||||
"lru-cache": "4.1.1",
|
||||
"mime": "1.3.6",
|
||||
"mongodb": "2.2.28",
|
||||
"multer": "1.3.0",
|
||||
"parse": "1.10.0",
|
||||
"parse-server-fs-adapter": "1.0.1",
|
||||
"parse-server-push-adapter": "2.0.0",
|
||||
"parse-server-s3-adapter": "1.1.0",
|
||||
"parse-server-simple-mailgun-adapter": "1.0.0",
|
||||
"pg-promise": "6.3.0",
|
||||
"redis": "2.7.1",
|
||||
"request": "2.81.0",
|
||||
"semver": "5.3.0",
|
||||
"lodash": "4.17.11",
|
||||
"lru-cache": "5.1.1",
|
||||
"mime": "2.4.3",
|
||||
"mongodb": "3.2.5",
|
||||
"parse": "2.4.0",
|
||||
"pg-promise": "8.7.2",
|
||||
"redis": "2.8.0",
|
||||
"semver": "6.0.0",
|
||||
"tv4": "1.3.0",
|
||||
"winston": "2.3.1",
|
||||
"winston-daily-rotate-file": "1.4.6",
|
||||
"ws": "3.0.0"
|
||||
"uuid": "3.3.2",
|
||||
"winston": "3.2.1",
|
||||
"winston-daily-rotate-file": "3.9.0",
|
||||
"ws": "7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "6.24.1",
|
||||
"babel-core": "6.25.0",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-plugin-syntax-flow": "6.18.0",
|
||||
"babel-plugin-transform-flow-strip-types": "6.22.0",
|
||||
"babel-preset-es2015": "6.24.1",
|
||||
"babel-preset-stage-0": "6.24.1",
|
||||
"babel-register": "6.24.1",
|
||||
"@babel/cli": "7.4.4",
|
||||
"@babel/core": "7.4.5",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.4.4",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.4.4",
|
||||
"@babel/preset-env": "7.4.5",
|
||||
"@parse/minami": "1.0.0",
|
||||
"babel-eslint": "10.0.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"cross-env": "5.0.1",
|
||||
"deep-diff": "0.3.8",
|
||||
"eslint": "^4.0.0",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
"gaze": "1.1.2",
|
||||
"jasmine": "2.6.0",
|
||||
"jasmine-spec-reporter": "^4.1.0",
|
||||
"mongodb-runner": "3.5.0",
|
||||
"nodemon": "1.11.0",
|
||||
"nyc": "^11.0.2",
|
||||
"request-promise": "4.2.1"
|
||||
"cross-env": "5.2.0",
|
||||
"deep-diff": "1.0.2",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-plugin-flowtype": "3.9.0",
|
||||
"flow-bin": "0.98.1",
|
||||
"gaze": "1.1.3",
|
||||
"husky": "2.3.0",
|
||||
"jasmine": "3.4.0",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"jsdoc": "3.6.2",
|
||||
"jsdoc-babel": "0.5.0",
|
||||
"lint-staged": "8.1.7",
|
||||
"mongodb-runner": "4.3.2",
|
||||
"nyc": "14.1.1",
|
||||
"prettier": "1.17.1",
|
||||
"supports-color": "6.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"definitions": "node ./resources/buildConfigDefinitions.js",
|
||||
"docs": "jsdoc -c ./jsdoc-conf.json",
|
||||
"dev": "npm run build && node bin/dev",
|
||||
"lint": "eslint --cache ./",
|
||||
"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 $COVERAGE_OPTION jasmine",
|
||||
"test:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
|
||||
"coverage": "cross-env COVERAGE_OPTION='./node_modules/.bin/nyc' npm test",
|
||||
"coverage:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 node ./node_modules/.bin/nyc ./node_modules/jasmine/bin/jasmine.js",
|
||||
"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": ">=4.6"
|
||||
"node": ">= 8"
|
||||
},
|
||||
"bin": {
|
||||
"parse-server": "./bin/parse-server"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "1.0.2",
|
||||
"uws": "^0.14.5"
|
||||
"bcrypt": "3.0.6",
|
||||
"uws": "10.148.1"
|
||||
},
|
||||
"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}"
|
||||
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* Parse Server Configuration Builder
|
||||
*
|
||||
* This module builds the definitions file (src/Options/Definitions.js)
|
||||
* from the src/Options/index.js options interfaces.
|
||||
* The Definitions.js module is responsible for the default values as well
|
||||
* as the mappings for the CLI.
|
||||
*
|
||||
* To rebuild the definitions file, run
|
||||
* `$ node resources/buildConfigDefinitions.js`
|
||||
*/
|
||||
const parsers = require('../src/Options/parsers');
|
||||
|
||||
function last(array) {
|
||||
return array[array.length - 1];
|
||||
}
|
||||
|
||||
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
function toENV(key) {
|
||||
let str = '';
|
||||
let previousIsUpper = false;
|
||||
for(let i = 0; i < key.length; i++) {
|
||||
const char = key[i];
|
||||
if (letters.indexOf(char) >= 0) {
|
||||
if (!previousIsUpper) {
|
||||
str += '_';
|
||||
previousIsUpper = true;
|
||||
}
|
||||
} else {
|
||||
previousIsUpper = false;
|
||||
}
|
||||
str += char;
|
||||
}
|
||||
return str.toUpperCase();
|
||||
}
|
||||
|
||||
function getCommentValue(comment) {
|
||||
if (!comment) { return }
|
||||
return comment.value.trim();
|
||||
}
|
||||
|
||||
function getENVPrefix(iface) {
|
||||
if (iface.id.name === 'ParseServerOptions') {
|
||||
return 'PARSE_SERVER_';
|
||||
}
|
||||
if (iface.id.name === 'CustomPagesOptions') {
|
||||
return 'PARSE_SERVER_CUSTOM_PAGES_';
|
||||
}
|
||||
if (iface.id.name === 'LiveQueryServerOptions') {
|
||||
return 'PARSE_LIVE_QUERY_SERVER_';
|
||||
}
|
||||
if (iface.id.name === 'LiveQueryOptions') {
|
||||
return 'PARSE_SERVER_LIVEQUERY_';
|
||||
}
|
||||
}
|
||||
|
||||
function processProperty(property, iface) {
|
||||
const firstComment = getCommentValue(last(property.leadingComments || []));
|
||||
const name = property.key.name;
|
||||
const prefix = getENVPrefix(iface);
|
||||
|
||||
if (!firstComment) {
|
||||
return;
|
||||
}
|
||||
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 (defaultLine) {
|
||||
defaultValue = defaultLine.split(' ')[1];
|
||||
}
|
||||
let type = property.value.type;
|
||||
let isRequired = true;
|
||||
if (type == 'NullableTypeAnnotation') {
|
||||
isRequired = false;
|
||||
type = property.value.typeAnnotation.type;
|
||||
}
|
||||
return {
|
||||
name,
|
||||
env,
|
||||
help,
|
||||
type,
|
||||
defaultValue,
|
||||
types: property.value.types,
|
||||
typeAnnotation: property.value.typeAnnotation,
|
||||
required: isRequired
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function doInterface(iface) {
|
||||
return iface.body.properties
|
||||
.sort((a, b) => a.key.name.localeCompare(b.key.name))
|
||||
.map((prop) => processProperty(prop, iface))
|
||||
.filter((e) => e !== undefined);
|
||||
}
|
||||
|
||||
function mapperFor(elt, t) {
|
||||
const p = t.identifier('parsers');
|
||||
const wrap = (identifier) => t.memberExpression(p, identifier);
|
||||
|
||||
if (t.isNumberTypeAnnotation(elt)) {
|
||||
return t.callExpression(wrap(t.identifier('numberParser')), [t.stringLiteral(elt.name)]);
|
||||
} else if (t.isArrayTypeAnnotation(elt)) {
|
||||
return wrap(t.identifier('arrayParser'));
|
||||
} else if (t.isAnyTypeAnnotation(elt)) {
|
||||
return wrap(t.identifier('objectParser'));
|
||||
} else if (t.isBooleanTypeAnnotation(elt)) {
|
||||
return wrap(t.identifier('booleanParser'));
|
||||
} else if (t.isGenericTypeAnnotation(elt)) {
|
||||
const type = elt.typeAnnotation.id.name;
|
||||
if (type == 'Adapter') {
|
||||
return wrap(t.identifier('moduleOrObjectParser'));
|
||||
}
|
||||
if (type == 'NumberOrBoolean') {
|
||||
return wrap(t.identifier('numberOrBooleanParser'));
|
||||
}
|
||||
return wrap(t.identifier('objectParser'));
|
||||
}
|
||||
}
|
||||
|
||||
function parseDefaultValue(elt, value, t) {
|
||||
let literalValue;
|
||||
if (t.isStringTypeAnnotation(elt)) {
|
||||
if (value == '""' || value == "''") {
|
||||
literalValue = t.stringLiteral('');
|
||||
} else {
|
||||
literalValue = t.stringLiteral(value);
|
||||
}
|
||||
} else if (t.isNumberTypeAnnotation(elt)) {
|
||||
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
|
||||
} else if (t.isArrayTypeAnnotation(elt)) {
|
||||
const array = parsers.objectParser(value);
|
||||
literalValue = t.arrayExpression(array.map((value) => {
|
||||
if (typeof value == 'string') {
|
||||
return t.stringLiteral(value);
|
||||
} else {
|
||||
throw new Error('Unable to parse array');
|
||||
}
|
||||
}));
|
||||
} else if (t.isAnyTypeAnnotation(elt)) {
|
||||
literalValue = t.arrayExpression([]);
|
||||
} else if (t.isBooleanTypeAnnotation(elt)) {
|
||||
literalValue = t.booleanLiteral(parsers.booleanParser(value));
|
||||
} else if (t.isGenericTypeAnnotation(elt)) {
|
||||
const type = elt.typeAnnotation.id.name;
|
||||
if (type == 'NumberOrBoolean') {
|
||||
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]);
|
||||
});
|
||||
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 literalValue;
|
||||
}
|
||||
|
||||
function inject(t, list) {
|
||||
let comments = '';
|
||||
const results = list.map((elt) => {
|
||||
if (!elt.name) {
|
||||
return;
|
||||
}
|
||||
const props = ['env', 'help'].map((key) => {
|
||||
if (elt[key]) {
|
||||
return t.objectProperty(t.stringLiteral(key), t.stringLiteral(elt[key]));
|
||||
}
|
||||
}).filter((e) => e !== undefined);
|
||||
if (elt.required) {
|
||||
props.push(t.objectProperty(t.stringLiteral('required'), t.booleanLiteral(true)))
|
||||
}
|
||||
const action = mapperFor(elt, t);
|
||||
if (action) {
|
||||
props.push(t.objectProperty(t.stringLiteral('action'), action))
|
||||
}
|
||||
if (elt.defaultValue) {
|
||||
const parsedValue = parseDefaultValue(elt, elt.defaultValue, t);
|
||||
if (parsedValue) {
|
||||
props.push(t.objectProperty(t.stringLiteral('default'), parsedValue));
|
||||
} else {
|
||||
throw new Error(`Unable to parse value for ${elt.name} `);
|
||||
}
|
||||
}
|
||||
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));
|
||||
},
|
||||
ExportDeclaration: function(path) {
|
||||
// Export declaration on an interface
|
||||
if (path.node && path.node.declaration && path.node.declaration.type == 'InterfaceDeclaration') {
|
||||
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(results))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const auxiliaryCommentBefore = `
|
||||
**** GENERATED CODE ****
|
||||
This code has been generated by resources/buildConfigDefinitions.js
|
||||
Do not edit manually, but update Options/index.js
|
||||
`
|
||||
|
||||
const babel = require("@babel/core");
|
||||
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [ plugin, '@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,33 +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
|
||||
|
||||
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 master to LATEST branch"
|
||||
git push --force --quiet "https://${GH_TOKEN}@github.com/parse-community/parse-server.git" master:latest
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-object-rest-spread"
|
||||
],
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": {
|
||||
"node": "8"
|
||||
}
|
||||
}]
|
||||
],
|
||||
"sourceMaps": "inline",
|
||||
"retainLines": true
|
||||
}
|
||||
+3
-3
@@ -14,19 +14,19 @@
|
||||
"Container": true,
|
||||
"equal": true,
|
||||
"notEqual": true,
|
||||
"it_only_db": true,
|
||||
"it_exclude_dbs": true,
|
||||
"describe_only_db": true,
|
||||
"describe_only": true,
|
||||
"on_db": true,
|
||||
"defaultConfiguration": true,
|
||||
"expectSuccess": true,
|
||||
"range": true,
|
||||
"expectError": true,
|
||||
"jequal": true,
|
||||
"create": true,
|
||||
"arrayContains": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": [0]
|
||||
"no-console": [0],
|
||||
"no-var": "error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const Config = require("../src/Config");
|
||||
const Config = require('../lib/Config');
|
||||
|
||||
var loginWithWrongCredentialsShouldFail = function(username, password) {
|
||||
const loginWithWrongCredentialsShouldFail = function(username, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Parse.User.logIn(username, password)
|
||||
.then(() => reject('login should have failed'))
|
||||
@@ -16,13 +16,18 @@ var loginWithWrongCredentialsShouldFail = function(username, password) {
|
||||
});
|
||||
};
|
||||
|
||||
var isAccountLockoutError = function(username, password, duration, waitTime) {
|
||||
const isAccountLockoutError = function(username, password, duration, waitTime) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Parse.User.logIn(username, password)
|
||||
.then(() => reject('login should have failed'))
|
||||
.catch(err => {
|
||||
if (err.message === 'Your account is locked due to multiple failed login attempts. Please try again after ' + duration + ' minute(s)') {
|
||||
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,31 +37,41 @@ var 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',
|
||||
publicServerURL: 'http://localhost:1337/1',
|
||||
})
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
const user = new Parse.User();
|
||||
user.setUsername('username1');
|
||||
user.setPassword('password');
|
||||
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,20 +81,27 @@ 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(() => {
|
||||
new Config('test');
|
||||
Config.get('test');
|
||||
fail('set duration to an invalid number test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
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,20 +112,27 @@ 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(() => {
|
||||
new Config('test');
|
||||
Config.get('test');
|
||||
fail('set threshold to an invalid number test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
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,20 +143,26 @@ 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(() => {
|
||||
new Config('test');
|
||||
Config.get('test');
|
||||
fail('threshold value < 1 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
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,20 +173,27 @@ 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(() => {
|
||||
new Config('test');
|
||||
Config.get('test');
|
||||
fail('threshold value > 999 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
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,20 +204,26 @@ 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(() => {
|
||||
new Config('test');
|
||||
Config.get('test');
|
||||
fail('duration value < 1 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
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,20 +234,27 @@ 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(() => {
|
||||
new Config('test');
|
||||
Config.get('test');
|
||||
fail('duration value > 99999 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
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(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username2");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
const user = new Parse.User();
|
||||
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(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username3");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
const user = new Parse.User();
|
||||
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(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username4");
|
||||
user.setPassword("correct password");
|
||||
const user = new Parse.User();
|
||||
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,83 +1,84 @@
|
||||
const AdaptableController = require('../lib/Controllers/AdaptableController')
|
||||
.AdaptableController;
|
||||
const FilesAdapter = require('../lib/Adapters/Files/FilesAdapter').default;
|
||||
const FilesController = require('../lib/Controllers/FilesController')
|
||||
.FilesController;
|
||||
|
||||
var AdaptableController = require("../src/Controllers/AdaptableController").AdaptableController;
|
||||
var FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
|
||||
var FilesController = require("../src/Controllers/FilesController").FilesController;
|
||||
|
||||
var MockController = function(options) {
|
||||
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) => {
|
||||
var adapter = new FilesAdapter();
|
||||
var controller = new FilesController(adapter);
|
||||
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) => {
|
||||
var adapter = new FilesAdapter();
|
||||
it('should throw when creating a new mock controller', done => {
|
||||
const adapter = new FilesAdapter();
|
||||
expect(() => {
|
||||
new MockController(adapter);
|
||||
}).toThrow();
|
||||
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() {}
|
||||
var adapter = new FilesAdapter();
|
||||
var controller = new FilesController(adapter);
|
||||
var otherAdapter = new WrongAdapter();
|
||||
const adapter = new FilesAdapter();
|
||||
const controller = new FilesController(adapter);
|
||||
const otherAdapter = new WrongAdapter();
|
||||
expect(() => {
|
||||
controller.adapter = otherAdapter;
|
||||
}).toThrow();
|
||||
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() {}
|
||||
var adapter = new WrongAdapter();
|
||||
const adapter = new WrongAdapter();
|
||||
expect(() => {
|
||||
new FilesController(adapter);
|
||||
}).toThrow();
|
||||
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) => {
|
||||
var adapter = {
|
||||
createFile: function() { },
|
||||
deleteFile: function() { },
|
||||
getFileData: function() { },
|
||||
getFileLocation: function() { },
|
||||
}
|
||||
it('should accept an object adapter', done => {
|
||||
const adapter = {
|
||||
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() {};
|
||||
|
||||
var adapter = new AGoodAdapter();
|
||||
const adapter = new AGoodAdapter();
|
||||
expect(() => {
|
||||
new FilesController(adapter);
|
||||
}).not.toThrow();
|
||||
|
||||
+64
-64
@@ -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');
|
||||
|
||||
var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
|
||||
var FilesAdapter = require("parse-server-fs-adapter").default;
|
||||
var S3Adapter = require("parse-server-s3-adapter").default;
|
||||
var ParsePushAdapter = require("parse-server-push-adapter").default;
|
||||
const Config = require('../src/Config');
|
||||
describe('AdapterLoader', () => {
|
||||
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) => {
|
||||
var adapterPath = require('path').resolve("./spec/MockAdapter");
|
||||
|
||||
var adapter = loadAdapter({
|
||||
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) => {
|
||||
var adapterPath = require('path').resolve("./spec/MockAdapter");
|
||||
var adapter = loadAdapter(adapterPath);
|
||||
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) => {
|
||||
var adapterPath = require('path').resolve("./src/Adapters/Files/FilesAdapter");
|
||||
var adapter = loadAdapter({
|
||||
adapter: adapterPath
|
||||
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,
|
||||
});
|
||||
|
||||
expect(typeof adapter).toBe('object');
|
||||
@@ -46,9 +46,9 @@ describe("AdapterLoader", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate an adapter from npm module", (done) => {
|
||||
var adapter = loadAdapter({
|
||||
module: 'parse-server-fs-adapter'
|
||||
it('should instantiate an adapter from npm module', done => {
|
||||
const adapter = loadAdapter({
|
||||
module: '@parse/fs-files-adapter',
|
||||
});
|
||||
|
||||
expect(typeof adapter).toBe('object');
|
||||
@@ -59,83 +59,83 @@ describe("AdapterLoader", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate an adapter from function/Class", (done) => {
|
||||
var adapter = loadAdapter({
|
||||
adapter: FilesAdapter
|
||||
it('should instantiate an adapter from function/Class', done => {
|
||||
const adapter = loadAdapter({
|
||||
adapter: FilesAdapter,
|
||||
});
|
||||
expect(adapter instanceof FilesAdapter).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate the default adapter from Class", (done) => {
|
||||
var adapter = loadAdapter(null, FilesAdapter);
|
||||
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) => {
|
||||
var defaultAdapter = new FilesAdapter();
|
||||
var adapter = loadAdapter(null, defaultAdapter);
|
||||
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) => {
|
||||
var originalAdapter = new FilesAdapter();
|
||||
var adapter = loadAdapter(originalAdapter);
|
||||
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) => {
|
||||
var Adapter = function(options) {
|
||||
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';
|
||||
}
|
||||
}
|
||||
var adapterOptions = {
|
||||
param: "key",
|
||||
doSomething: function() {}
|
||||
};
|
||||
const adapterOptions = {
|
||||
param: 'key',
|
||||
doSomething: function() {},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
var adapter = loadAdapter(adapterOptions, Adapter);
|
||||
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) => {
|
||||
var options = {
|
||||
it('should load push adapter from options', done => {
|
||||
const options = {
|
||||
android: {
|
||||
senderId: 'yolo',
|
||||
apiKey: 'yolo'
|
||||
}
|
||||
}
|
||||
apiKey: 'yolo',
|
||||
},
|
||||
};
|
||||
expect(() => {
|
||||
var adapter = loadAdapter(undefined, ParsePushAdapter, options);
|
||||
const adapter = loadAdapter(undefined, ParsePushAdapter, options);
|
||||
expect(adapter.constructor).toBe(ParsePushAdapter);
|
||||
expect(adapter).not.toBe(undefined);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load custom push adapter from string (#3544)", (done) => {
|
||||
var adapterPath = require('path').resolve("./spec/MockPushAdapter");
|
||||
var options = {
|
||||
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({
|
||||
push: pushAdapterOptions,
|
||||
}).then(() => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const pushAdapter = config.pushWorker.adapter;
|
||||
expect(pushAdapter.getValidPushTypes()).toEqual(['ios']);
|
||||
expect(pushAdapter.options).toEqual(pushAdapterOptions);
|
||||
@@ -144,12 +144,12 @@ describe("AdapterLoader", ()=>{
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should load S3Adapter from direct passing", (done) => {
|
||||
var s3Adapter = new S3Adapter("key", "secret", "bucket")
|
||||
it('should load S3Adapter from direct passing', done => {
|
||||
const s3Adapter = new S3Adapter('key', 'secret', 'bucket');
|
||||
expect(() => {
|
||||
var adapter = loadAdapter(s3Adapter, FilesAdapter);
|
||||
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();
|
||||
var 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();
|
||||
var 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();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+357
-178
@@ -1,157 +1,185 @@
|
||||
var auth = require('../src/Auth');
|
||||
var Config = require('../src/Config');
|
||||
var rest = require('../src/rest');
|
||||
var 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) => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
it('uses find condition from request.body', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
const iosAudienceRequest = {
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
var request = {
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {
|
||||
where: {
|
||||
query: '{ "test": "android" }'
|
||||
}
|
||||
query: '{ "test": "android" }',
|
||||
},
|
||||
},
|
||||
query: {},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
const router = new AudiencesRouter();
|
||||
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) => {
|
||||
var results = res.response.results;
|
||||
.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) => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
it('uses find condition from request.query', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
const iosAudienceRequest = {
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
var request = {
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
where: {
|
||||
'query': '{ "test": "android" }'
|
||||
}
|
||||
query: '{ "test": "android" }',
|
||||
},
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
const router = new AudiencesRouter();
|
||||
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) => {
|
||||
var results = res.response.results;
|
||||
.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) => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
it('query installations with limit = 0', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
const iosAudienceRequest = {
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
var request = {
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0
|
||||
limit: 0,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
new Config('test');
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
Config.get('test');
|
||||
const router = new AudiencesRouter();
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
.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 => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
it_exclude_dbs(['postgres'])('query installations with count = 1', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
const iosAudienceRequest = {
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
var request = {
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
count: 1
|
||||
count: 1,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest))
|
||||
const router = new AudiencesRouter();
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() =>
|
||||
rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
)
|
||||
)
|
||||
.then(() => router.handleFind(request))
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
@@ -159,66 +187,110 @@ describe('AudiencesRouter', () => {
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('query installations with limit = 0 and count = 1', (done) => {
|
||||
var config = new Config('test');
|
||||
var androidAudienceRequest = {
|
||||
'name': 'Android Users',
|
||||
'query': '{ "test": "android" }'
|
||||
};
|
||||
var iosAudienceRequest = {
|
||||
'name': 'Iphone Users',
|
||||
'query': '{ "test": "ios" }'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1
|
||||
},
|
||||
info: {}
|
||||
};
|
||||
|
||||
var router = new AudiencesRouter();
|
||||
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should create, read, update and delete audiences throw api', (done) => {
|
||||
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
|
||||
.then(() => {
|
||||
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then((results) => {
|
||||
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,62 +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 get with master key', done => {
|
||||
Parse._request('GET', `push_audiences/someId`, {}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only update with master key', done => {
|
||||
Parse._request('PUT', `push_audiences/someId`, {
|
||||
name: 'My Audience 2',
|
||||
}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only delete with master key', done => {
|
||||
Parse._request('DELETE', `push_audiences/someId`, {}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])(
|
||||
'should support legacy parse.com audience fields',
|
||||
done => {
|
||||
const database = Config.get(Parse.applicationId).database.adapter
|
||||
.database;
|
||||
const now = new Date();
|
||||
Parse._request(
|
||||
'POST',
|
||||
'push_audiences',
|
||||
{ name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) },
|
||||
{ useMasterKey: true }
|
||||
).then(audience => {
|
||||
database.collection('test__Audience').updateOne(
|
||||
{ _id: audience.objectId },
|
||||
{
|
||||
$set: {
|
||||
times_used: 1,
|
||||
_last_used: now,
|
||||
},
|
||||
},
|
||||
{},
|
||||
error => {
|
||||
expect(error).toEqual(null);
|
||||
database
|
||||
.collection('test__Audience')
|
||||
.find({ _id: audience.objectId })
|
||||
.toArray((error, rows) => {
|
||||
expect(error).toEqual(null);
|
||||
expect(rows[0]['times_used']).toEqual(1);
|
||||
expect(rows[0]['_last_used']).toEqual(now);
|
||||
Parse._request(
|
||||
'GET',
|
||||
'push_audiences/' + audience.objectId,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
)
|
||||
.then(audience => {
|
||||
expect(audience.name).toEqual('My Audience');
|
||||
expect(audience.query.deviceType).toEqual('ios');
|
||||
expect(audience.timesUsed).toEqual(1);
|
||||
expect(audience.lastUsed).toEqual(now.toISOString());
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
done.fail(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('should be able to search on audiences', done => {
|
||||
Parse._request(
|
||||
'POST',
|
||||
'push_audiences',
|
||||
{ name: 'neverUsed', query: JSON.stringify({ deviceType: 'ios' }) },
|
||||
{ useMasterKey: true }
|
||||
).then(() => {
|
||||
const query = {
|
||||
timesUsed: { $exists: false },
|
||||
lastUsed: { $exists: false },
|
||||
};
|
||||
Parse._request(
|
||||
'GET',
|
||||
'push_audiences?order=-createdAt&limit=1',
|
||||
{ where: query },
|
||||
{ useMasterKey: true }
|
||||
)
|
||||
.then(results => {
|
||||
expect(results.results.length).toEqual(1);
|
||||
const audience = results.results[0];
|
||||
expect(audience.name).toEqual('neverUsed');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
done.fail(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only create with master key', (done) => {
|
||||
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only find with master key', (done) => {
|
||||
Parse._request('GET', 'push_audiences', {})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only get with master key', (done) => {
|
||||
Parse._request('GET', `push_audiences/someId`, {})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only update with master key', (done) => {
|
||||
Parse._request('PUT', `push_audiences/someId`, { name: 'My Audience 2' })
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only delete with master key', (done) => {
|
||||
Parse._request('DELETE', `push_audiences/someId`, {})
|
||||
.then(
|
||||
() => {},
|
||||
(error) => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
done();
|
||||
}
|
||||
);
|
||||
it('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');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+191
-41
@@ -1,11 +1,11 @@
|
||||
describe('Auth', () => {
|
||||
var Auth = require('../src/Auth.js').Auth;
|
||||
|
||||
const { Auth, getAuthForSessionToken } = require('../lib/Auth.js');
|
||||
const Config = require('../lib/Config');
|
||||
describe('getUserRoles', () => {
|
||||
var auth;
|
||||
var config;
|
||||
var currentRoles = null;
|
||||
var currentUserId = 'userId';
|
||||
let auth;
|
||||
let config;
|
||||
let currentRoles = null;
|
||||
const currentUserId = 'userId';
|
||||
|
||||
beforeEach(() => {
|
||||
currentRoles = ['role:userId'];
|
||||
@@ -14,80 +14,230 @@ 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) => {
|
||||
var 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);
|
||||
|
||||
var 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) => {
|
||||
var loadRolesSpy = spyOn(auth, '_loadRoles').and.callThrough();
|
||||
auth.getUserRoles()
|
||||
.then((roles) => {
|
||||
it('should only query the roles once', done => {
|
||||
const loadRolesSpy = spyOn(auth, '_loadRoles').and.callThrough();
|
||||
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);
|
||||
|
||||
var firstGet = config.cacheController.role.get.calls.first();
|
||||
const firstGet = config.cacheController.role.get.calls.first();
|
||||
expect(firstGet.args[0]).toEqual(currentUserId);
|
||||
expect(roles).toEqual(currentRoles);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
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) => {
|
||||
var bcryptOriginal = require('bcrypt-nodejs');
|
||||
var bcryptNew = require('bcryptjs');
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+876
-160
File diff suppressed because it is too large
Load Diff
+105
-51
@@ -1,32 +1,33 @@
|
||||
'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;
|
||||
|
||||
var testDefinitions = {
|
||||
'arg0': 'PROGRAM_ARG_0',
|
||||
'arg1': {
|
||||
const testDefinitions = {
|
||||
arg0: 'PROGRAM_ARG_0',
|
||||
arg1: {
|
||||
env: 'PROGRAM_ARG_1',
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
'arg2': {
|
||||
arg2: {
|
||||
env: 'PROGRAM_ARG_2',
|
||||
action: function(value) {
|
||||
var intValue = parseInt(value);
|
||||
const intValue = parseInt(value);
|
||||
if (!Number.isInteger(intValue)) {
|
||||
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 +37,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 +59,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 +73,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 +91,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 +125,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 +172,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();
|
||||
});
|
||||
@@ -157,6 +202,12 @@ describe('definitions', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw when using deprecated facebookAppIds', () => {
|
||||
expect(() => {
|
||||
definitions.facebookAppIds.action();
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('LiveQuery definitions', () => {
|
||||
@@ -167,7 +218,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');
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
var CacheController = require('../src/Controllers/CacheController.js').default;
|
||||
const CacheController = require('../lib/Controllers/CacheController.js')
|
||||
.default;
|
||||
|
||||
describe('CacheController', function() {
|
||||
var FakeCacheAdapter;
|
||||
var FakeAppID = 'foo';
|
||||
var KEY = 'hello';
|
||||
let FakeCacheAdapter;
|
||||
const FakeAppID = 'foo';
|
||||
const KEY = 'hello';
|
||||
|
||||
beforeEach(() => {
|
||||
FakeCacheAdapter = {
|
||||
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) => {
|
||||
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
it('should expose role and user caches', done => {
|
||||
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
|
||||
expect(cache.role).not.toEqual(null);
|
||||
expect(cache.role.get).not.toEqual(null);
|
||||
@@ -28,27 +28,26 @@ describe('CacheController', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
['role', 'user'].forEach((cacheName) => {
|
||||
['role', 'user'].forEach(cacheName => {
|
||||
it('should prefix ' + cacheName + ' cache', () => {
|
||||
var cache = new CacheController(FakeCacheAdapter, FakeAppID)[cacheName];
|
||||
const cache = new CacheController(FakeCacheAdapter, FakeAppID)[cacheName];
|
||||
|
||||
cache.put(KEY, 'world');
|
||||
var firstPut = FakeCacheAdapter.put.calls.first();
|
||||
const firstPut = FakeCacheAdapter.put.calls.first();
|
||||
expect(firstPut.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':'));
|
||||
|
||||
cache.get(KEY);
|
||||
var firstGet = FakeCacheAdapter.get.calls.first();
|
||||
const firstGet = FakeCacheAdapter.get.calls.first();
|
||||
expect(firstGet.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':'));
|
||||
|
||||
cache.del(KEY);
|
||||
var firstDel = FakeCacheAdapter.del.calls.first();
|
||||
const firstDel = FakeCacheAdapter.del.calls.first();
|
||||
expect(firstDel.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear the entire cache', () => {
|
||||
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
|
||||
cache.clear();
|
||||
expect(FakeCacheAdapter.clear.calls.count()).toEqual(1);
|
||||
@@ -60,15 +59,13 @@ 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();
|
||||
|
||||
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
const cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
|
||||
cache.get('foo').then(done, () => {
|
||||
fail('Promise should not be rejected.');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+101
-95
@@ -1,10 +1,11 @@
|
||||
var Client = require('../src/LiveQuery/Client').Client;
|
||||
var 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() {
|
||||
var parseWebSocket = new ParseWebSocket({});
|
||||
var client = new Client(1, parseWebSocket);
|
||||
const parseWebSocket = new ParseWebSocket({});
|
||||
const client = new Client(1, parseWebSocket);
|
||||
|
||||
expect(client.id).toBe(1);
|
||||
expect(client.parseWebSocket).toBe(parseWebSocket);
|
||||
@@ -12,8 +13,8 @@ describe('Client', function() {
|
||||
});
|
||||
|
||||
it('can push response', function() {
|
||||
var parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
Client.pushResponse(parseWebSocket, 'message');
|
||||
|
||||
@@ -21,13 +22,13 @@ describe('Client', function() {
|
||||
});
|
||||
|
||||
it('can push error', function() {
|
||||
var parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
Client.pushError(parseWebSocket, 1, 'error', true);
|
||||
|
||||
var lastCall = parseWebSocket.send.calls.first();
|
||||
var messageJSON = JSON.parse(lastCall.args[0]);
|
||||
const lastCall = parseWebSocket.send.calls.first();
|
||||
const messageJSON = JSON.parse(lastCall.args[0]);
|
||||
expect(messageJSON.op).toBe('error');
|
||||
expect(messageJSON.error).toBe('error');
|
||||
expect(messageJSON.code).toBe(1);
|
||||
@@ -35,13 +36,13 @@ describe('Client', function() {
|
||||
});
|
||||
|
||||
it('can add subscription information', function() {
|
||||
var subscription = {};
|
||||
var fields = ['test'];
|
||||
var subscriptionInfo = {
|
||||
const subscription = {};
|
||||
const fields = ['test'];
|
||||
const subscriptionInfo = {
|
||||
subscription: subscription,
|
||||
fields: fields
|
||||
}
|
||||
var client = new Client(1, {});
|
||||
fields: fields,
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
client.addSubscriptionInfo(1, subscriptionInfo);
|
||||
|
||||
expect(client.subscriptionInfos.size).toBe(1);
|
||||
@@ -49,73 +50,76 @@ describe('Client', function() {
|
||||
});
|
||||
|
||||
it('can get subscription information', function() {
|
||||
var subscription = {};
|
||||
var fields = ['test'];
|
||||
var subscriptionInfo = {
|
||||
const subscription = {};
|
||||
const fields = ['test'];
|
||||
const subscriptionInfo = {
|
||||
subscription: subscription,
|
||||
fields: fields
|
||||
}
|
||||
var client = new Client(1, {});
|
||||
fields: fields,
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
client.addSubscriptionInfo(1, subscriptionInfo);
|
||||
var subscriptionInfoAgain = client.getSubscriptionInfo(1);
|
||||
const subscriptionInfoAgain = client.getSubscriptionInfo(1);
|
||||
|
||||
expect(subscriptionInfoAgain).toBe(subscriptionInfo);
|
||||
});
|
||||
|
||||
it('can delete subscription information', function() {
|
||||
var subscription = {};
|
||||
var fields = ['test'];
|
||||
var subscriptionInfo = {
|
||||
const subscription = {};
|
||||
const fields = ['test'];
|
||||
const subscriptionInfo = {
|
||||
subscription: subscription,
|
||||
fields: fields
|
||||
}
|
||||
var client = new Client(1, {});
|
||||
fields: fields,
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
client.addSubscriptionInfo(1, subscriptionInfo);
|
||||
client.deleteSubscriptionInfo(1);
|
||||
|
||||
expect(client.subscriptionInfos.size).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('can generate ParseObject JSON with null selected field', function() {
|
||||
var parseObjectJSON = {
|
||||
key : 'value',
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
createdAt: '2015-12-07T21:27:13.746Z',
|
||||
ACL: 'test',
|
||||
};
|
||||
var client = new Client(1, {});
|
||||
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() {
|
||||
var parseObjectJSON = {
|
||||
key : 'value',
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
objectId: 'test',
|
||||
updatedAt: '2015-12-07T21:27:13.746Z',
|
||||
createdAt: '2015-12-07T21:27:13.746Z',
|
||||
ACL: 'test',
|
||||
};
|
||||
var client = new Client(1, {});
|
||||
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() {
|
||||
var parseObjectJSON = {
|
||||
key : 'value',
|
||||
const parseObjectJSON = {
|
||||
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',
|
||||
};
|
||||
var client = new Client(1, {});
|
||||
const client = new Client(1, {});
|
||||
|
||||
expect(client._toJSONWithFields(parseObjectJSON, ['test'])).toEqual({
|
||||
className: 'test',
|
||||
@@ -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() {
|
||||
var parseObjectJSON = {
|
||||
key : 'value',
|
||||
const parseObjectJSON = {
|
||||
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',
|
||||
};
|
||||
var client = new Client(1, {});
|
||||
var limitedParseObject = client._toJSONWithFields(parseObjectJSON, ['name']);
|
||||
const client = new Client(1, {});
|
||||
const limitedParseObject = client._toJSONWithFields(parseObjectJSON, [
|
||||
'name',
|
||||
]);
|
||||
|
||||
expect(limitedParseObject).toEqual({
|
||||
className: 'test',
|
||||
@@ -151,64 +157,64 @@ describe('Client', function() {
|
||||
});
|
||||
|
||||
it('can push connect response', function() {
|
||||
var parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
var client = new Client(1, parseWebSocket);
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushConnect();
|
||||
|
||||
var lastCall = parseWebSocket.send.calls.first();
|
||||
var messageJSON = JSON.parse(lastCall.args[0]);
|
||||
const lastCall = parseWebSocket.send.calls.first();
|
||||
const messageJSON = JSON.parse(lastCall.args[0]);
|
||||
expect(messageJSON.op).toBe('connected');
|
||||
expect(messageJSON.clientId).toBe(1);
|
||||
});
|
||||
|
||||
it('can push subscribe response', function() {
|
||||
var parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
var client = new Client(1, parseWebSocket);
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushSubscribe(2);
|
||||
|
||||
var lastCall = parseWebSocket.send.calls.first();
|
||||
var messageJSON = JSON.parse(lastCall.args[0]);
|
||||
const lastCall = parseWebSocket.send.calls.first();
|
||||
const messageJSON = JSON.parse(lastCall.args[0]);
|
||||
expect(messageJSON.op).toBe('subscribed');
|
||||
expect(messageJSON.clientId).toBe(1);
|
||||
expect(messageJSON.requestId).toBe(2);
|
||||
});
|
||||
|
||||
it('can push unsubscribe response', function() {
|
||||
var parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
var client = new Client(1, parseWebSocket);
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushUnsubscribe(2);
|
||||
|
||||
var lastCall = parseWebSocket.send.calls.first();
|
||||
var messageJSON = JSON.parse(lastCall.args[0]);
|
||||
const lastCall = parseWebSocket.send.calls.first();
|
||||
const messageJSON = JSON.parse(lastCall.args[0]);
|
||||
expect(messageJSON.op).toBe('unsubscribed');
|
||||
expect(messageJSON.clientId).toBe(1);
|
||||
expect(messageJSON.requestId).toBe(2);
|
||||
});
|
||||
|
||||
it('can push create response', function() {
|
||||
var parseObjectJSON = {
|
||||
key : 'value',
|
||||
const parseObjectJSON = {
|
||||
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',
|
||||
};
|
||||
var parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
var client = new Client(1, parseWebSocket);
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushCreate(2, parseObjectJSON);
|
||||
|
||||
var lastCall = parseWebSocket.send.calls.first();
|
||||
var messageJSON = JSON.parse(lastCall.args[0]);
|
||||
const lastCall = parseWebSocket.send.calls.first();
|
||||
const messageJSON = JSON.parse(lastCall.args[0]);
|
||||
expect(messageJSON.op).toBe('create');
|
||||
expect(messageJSON.clientId).toBe(1);
|
||||
expect(messageJSON.requestId).toBe(2);
|
||||
@@ -216,23 +222,23 @@ describe('Client', function() {
|
||||
});
|
||||
|
||||
it('can push enter response', function() {
|
||||
var parseObjectJSON = {
|
||||
key : 'value',
|
||||
const parseObjectJSON = {
|
||||
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',
|
||||
};
|
||||
var parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
var client = new Client(1, parseWebSocket);
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushEnter(2, parseObjectJSON);
|
||||
|
||||
var lastCall = parseWebSocket.send.calls.first();
|
||||
var messageJSON = JSON.parse(lastCall.args[0]);
|
||||
const lastCall = parseWebSocket.send.calls.first();
|
||||
const messageJSON = JSON.parse(lastCall.args[0]);
|
||||
expect(messageJSON.op).toBe('enter');
|
||||
expect(messageJSON.clientId).toBe(1);
|
||||
expect(messageJSON.requestId).toBe(2);
|
||||
@@ -240,23 +246,23 @@ describe('Client', function() {
|
||||
});
|
||||
|
||||
it('can push update response', function() {
|
||||
var parseObjectJSON = {
|
||||
key : 'value',
|
||||
const parseObjectJSON = {
|
||||
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',
|
||||
};
|
||||
var parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
var client = new Client(1, parseWebSocket);
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushUpdate(2, parseObjectJSON);
|
||||
|
||||
var lastCall = parseWebSocket.send.calls.first();
|
||||
var messageJSON = JSON.parse(lastCall.args[0]);
|
||||
const lastCall = parseWebSocket.send.calls.first();
|
||||
const messageJSON = JSON.parse(lastCall.args[0]);
|
||||
expect(messageJSON.op).toBe('update');
|
||||
expect(messageJSON.clientId).toBe(1);
|
||||
expect(messageJSON.requestId).toBe(2);
|
||||
@@ -264,23 +270,23 @@ describe('Client', function() {
|
||||
});
|
||||
|
||||
it('can push leave response', function() {
|
||||
var parseObjectJSON = {
|
||||
key : 'value',
|
||||
const parseObjectJSON = {
|
||||
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',
|
||||
};
|
||||
var parseWebSocket = {
|
||||
send: jasmine.createSpy('send')
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
var client = new Client(1, parseWebSocket);
|
||||
const client = new Client(1, parseWebSocket);
|
||||
client.pushLeave(2, parseObjectJSON);
|
||||
|
||||
var lastCall = parseWebSocket.send.calls.first();
|
||||
var messageJSON = JSON.parse(lastCall.args[0]);
|
||||
const lastCall = parseWebSocket.send.calls.first();
|
||||
const messageJSON = JSON.parse(lastCall.args[0]);
|
||||
expect(messageJSON.op).toBe('leave');
|
||||
expect(messageJSON.clientId).toBe(1);
|
||||
expect(messageJSON.requestId).toBe(2);
|
||||
|
||||
+27
-19
@@ -1,41 +1,49 @@
|
||||
var 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);
|
||||
});
|
||||
});
|
||||
|
||||
+1689
-793
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 => {
|
||||
var 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) => {
|
||||
var 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.');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,51 +1,63 @@
|
||||
var DatabaseController = require('../src/Controllers/DatabaseController.js');
|
||||
var validateQuery = DatabaseController._validateQuery;
|
||||
const DatabaseController = require('../lib/Controllers/DatabaseController.js');
|
||||
const validateQuery = DatabaseController._validateQuery;
|
||||
|
||||
describe('DatabaseController', function() {
|
||||
|
||||
describe('validateQuery', function() {
|
||||
|
||||
it('should restructure simple cases of SERVER-13732', (done) => {
|
||||
var query = {$or: [{a: 1}, {a: 2}], _rperm: {$in: ['a', 'b']}, foo: 3};
|
||||
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}]});
|
||||
expect(query).toEqual({
|
||||
$or: [
|
||||
{ a: 1, _rperm: { $in: ['a', 'b'] }, foo: 3 },
|
||||
{ a: 2, _rperm: { $in: ['a', 'b'] }, foo: 3 },
|
||||
],
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not restructure SERVER-13732 queries with $nears', (done) => {
|
||||
var query = {$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}};
|
||||
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: {}}});
|
||||
expect(query).toEqual({
|
||||
$or: [{ a: 1 }, { b: 1 }],
|
||||
c: { $nearSphere: {} },
|
||||
});
|
||||
|
||||
query = {$or: [{a: 1}, {b: 1}], c: {$near: {}}};
|
||||
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$near: {}}});
|
||||
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it('should push refactored keys down a tree for SERVER-13732', (done) => {
|
||||
var query = {a: 1, $or: [{$or: [{b: 1}, {b: 2}]},
|
||||
{$or: [{c: 1}, {c: 2}]}]};
|
||||
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}]}]});
|
||||
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();
|
||||
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();
|
||||
it('should accept valid queries', done => {
|
||||
expect(() => validateQuery({ $or: [{ a: 1 }, { b: 2 }] })).not.toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+412
-276
File diff suppressed because it is too large
Load Diff
@@ -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,49 +1,58 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const fakeRequestForConfig = function() {
|
||||
return new Config('test');
|
||||
return Config.get('test');
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
|
||||
describe('EventEmitterPubSub', function() {
|
||||
it('can publish and subscribe', function() {
|
||||
var publisher = EventEmitterPubSub.createPublisher();
|
||||
var subscriber = EventEmitterPubSub.createSubscriber();
|
||||
const publisher = EventEmitterPubSub.createPublisher();
|
||||
const subscriber = EventEmitterPubSub.createSubscriber();
|
||||
subscriber.subscribe('testChannel');
|
||||
// Register mock checked for subscriber
|
||||
var isChecked = false;
|
||||
let isChecked = false;
|
||||
subscriber.on('message', function(channel, message) {
|
||||
isChecked = true;
|
||||
expect(channel).toBe('testChannel');
|
||||
@@ -19,12 +20,12 @@ describe('EventEmitterPubSub', function() {
|
||||
});
|
||||
|
||||
it('can unsubscribe', function() {
|
||||
var publisher = EventEmitterPubSub.createPublisher();
|
||||
var subscriber = EventEmitterPubSub.createSubscriber();
|
||||
const publisher = EventEmitterPubSub.createPublisher();
|
||||
const subscriber = EventEmitterPubSub.createSubscriber();
|
||||
subscriber.subscribe('testChannel');
|
||||
subscriber.unsubscribe('testChannel');
|
||||
// Register mock checked for subscriber
|
||||
var isCalled = false;
|
||||
let isCalled = false;
|
||||
subscriber.on('message', function() {
|
||||
isCalled = true;
|
||||
});
|
||||
@@ -35,7 +36,7 @@ describe('EventEmitterPubSub', function() {
|
||||
});
|
||||
|
||||
it('can unsubscribe not subscribing channel', function() {
|
||||
var subscriber = EventEmitterPubSub.createSubscriber();
|
||||
const subscriber = EventEmitterPubSub.createSubscriber();
|
||||
|
||||
// Make sure subscriber does not throw exception
|
||||
subscriber.unsubscribe('testChannel');
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var gridStoreAdapter = new GridStoreAdapter('mongodb://localhost:27017/parse');
|
||||
var filesController = new FilesController(gridStoreAdapter)
|
||||
var result = filesController.expandFilesInObject(config, function(){});
|
||||
describe('FilesController', () => {
|
||||
it('should properly expand objects', done => {
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const gridStoreAdapter = new GridFSBucketAdapter(
|
||||
'mongodb://localhost:27017/parse'
|
||||
);
|
||||
const filesController = new FilesController(gridStoreAdapter);
|
||||
const result = filesController.expandFilesInObject(config, function() {});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
var fullFile = {
|
||||
const fullFile = {
|
||||
type: '__type',
|
||||
url: "http://an.url"
|
||||
}
|
||||
url: 'http://an.url',
|
||||
};
|
||||
|
||||
var anObject = {
|
||||
aFile: fullFile
|
||||
}
|
||||
const anObject = {
|
||||
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 @@
|
||||
var MongoClient = require("mongodb").MongoClient;
|
||||
var GridStore = require("mongodb").GridStore;
|
||||
|
||||
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
||||
var Config = require("../src/Config");
|
||||
var FilesController = require('../src/Controllers/FilesController').default;
|
||||
|
||||
|
||||
// Small additional tests to improve overall coverage
|
||||
describe_only_db('mongo')("GridStoreAdapter",() =>{
|
||||
it("should properly instanciate the GridStore when deleting a file", (done) => {
|
||||
|
||||
var databaseURI = 'mongodb://localhost:27017/parse';
|
||||
var config = new Config(Parse.applicationId);
|
||||
var gridStoreAdapter = new GridStoreAdapter(databaseURI);
|
||||
var filesController = new FilesController(gridStoreAdapter);
|
||||
|
||||
// save original unlink before redefinition
|
||||
var originalUnlink = GridStore.prototype.unlink;
|
||||
|
||||
var 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);
|
||||
});
|
||||
});
|
||||
+148
-181
@@ -1,116 +1,97 @@
|
||||
'use strict';
|
||||
|
||||
var 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');
|
||||
|
||||
var port = 13371;
|
||||
var httpRequestServer = "http://localhost:" + port;
|
||||
const port = 13371;
|
||||
const httpRequestServer = 'http://localhost:' + port;
|
||||
|
||||
var 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) => {
|
||||
var 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) => {
|
||||
var calls = 0;
|
||||
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) => {
|
||||
var 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(var 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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+19
-22
@@ -1,42 +1,40 @@
|
||||
const InMemoryCache = require('../src/Adapters/Cache/InMemoryCache').default;
|
||||
|
||||
const InMemoryCache = require('../lib/Adapters/Cache/InMemoryCache').default;
|
||||
|
||||
describe('InMemoryCache', function() {
|
||||
var BASE_TTL = {
|
||||
ttl: 100
|
||||
const BASE_TTL = {
|
||||
ttl: 100,
|
||||
};
|
||||
var NO_EXPIRE_TTL = {
|
||||
ttl: NaN
|
||||
const NO_EXPIRE_TTL = {
|
||||
ttl: NaN,
|
||||
};
|
||||
var KEY = 'hello';
|
||||
var KEY_2 = KEY + '_2';
|
||||
|
||||
var VALUE = 'world';
|
||||
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) => {
|
||||
var cache = new InMemoryCache(BASE_TTL);
|
||||
it('should destroy a expire items in the cache', done => {
|
||||
const cache = new InMemoryCache(BASE_TTL);
|
||||
|
||||
cache.put(KEY, VALUE);
|
||||
|
||||
var value = cache.get(KEY);
|
||||
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) => {
|
||||
var cache = new InMemoryCache(NO_EXPIRE_TTL);
|
||||
it('should delete items', done => {
|
||||
const cache = new InMemoryCache(NO_EXPIRE_TTL);
|
||||
cache.put(KEY, VALUE);
|
||||
cache.put(KEY_2, VALUE);
|
||||
expect(cache.get(KEY)).toEqual(VALUE);
|
||||
@@ -52,8 +50,8 @@ describe('InMemoryCache', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should clear all items', (done) => {
|
||||
var cache = new InMemoryCache(NO_EXPIRE_TTL);
|
||||
it('should clear all items', done => {
|
||||
const cache = new InMemoryCache(NO_EXPIRE_TTL);
|
||||
cache.put(KEY, VALUE);
|
||||
cache.put(KEY_2, VALUE);
|
||||
|
||||
@@ -67,8 +65,7 @@ describe('InMemoryCache', function() {
|
||||
});
|
||||
|
||||
it('should deafult TTL to 5 seconds', () => {
|
||||
var cache = new InMemoryCache({});
|
||||
const cache = new InMemoryCache({});
|
||||
expect(cache.ttl).toEqual(5 * 1000);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
var InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').default;
|
||||
const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter')
|
||||
.default;
|
||||
|
||||
describe('InMemoryCacheAdapter', function() {
|
||||
var KEY = 'hello';
|
||||
var VALUE = 'world';
|
||||
const KEY = 'hello';
|
||||
const VALUE = 'world';
|
||||
|
||||
function wait(sleep) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, sleep);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
it('should expose promisifyed methods', (done) => {
|
||||
var cache = new InMemoryCacheAdapter({
|
||||
ttl: NaN
|
||||
it('should expose promisifyed methods', done => {
|
||||
const cache = new InMemoryCacheAdapter({
|
||||
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) => {
|
||||
var cache = new InMemoryCacheAdapter({
|
||||
ttl: NaN
|
||||
it('should get/set/clear', done => {
|
||||
const cache = new InMemoryCacheAdapter({
|
||||
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) => {
|
||||
var cache = new InMemoryCacheAdapter({
|
||||
ttl: 10
|
||||
it('should expire after ttl', done => {
|
||||
const cache = new InMemoryCacheAdapter({
|
||||
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);
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
+225
-105
@@ -1,148 +1,204 @@
|
||||
var auth = require('../src/Auth');
|
||||
var Config = require('../src/Config');
|
||||
var rest = require('../src/rest');
|
||||
var 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) => {
|
||||
var config = new Config('test');
|
||||
var androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
it('uses find condition from request.body', done => {
|
||||
const config = Config.get('test');
|
||||
const androidDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
const iosDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
var request = {
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {
|
||||
where: {
|
||||
deviceType: 'android'
|
||||
}
|
||||
deviceType: 'android',
|
||||
},
|
||||
},
|
||||
query: {},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
const router = new InstallationsRouter();
|
||||
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) => {
|
||||
var results = res.response.results;
|
||||
})
|
||||
.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) => {
|
||||
var config = new Config('test');
|
||||
var androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
it('uses find condition from request.query', done => {
|
||||
const config = Config.get('test');
|
||||
const androidDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
const iosDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
var request = {
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
where: {
|
||||
deviceType: 'android'
|
||||
}
|
||||
deviceType: 'android',
|
||||
},
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
const router = new InstallationsRouter();
|
||||
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) => {
|
||||
var results = res.response.results;
|
||||
})
|
||||
.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) => {
|
||||
var config = new Config('test');
|
||||
var androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
it('query installations with limit = 0', done => {
|
||||
const config = Config.get('test');
|
||||
const androidDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
const iosDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
var request = {
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0
|
||||
limit: 0,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
new Config('test');
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
Config.get('test');
|
||||
const router = new InstallationsRouter();
|
||||
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) => {
|
||||
var response = res.response;
|
||||
})
|
||||
.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 => {
|
||||
var config = new Config('test');
|
||||
var androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
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',
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
const iosDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
var request = {
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
count: 1
|
||||
count: 1,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest))
|
||||
const router = new InstallationsRouter();
|
||||
rest
|
||||
.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
androidDeviceRequest
|
||||
)
|
||||
.then(() =>
|
||||
rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Installation',
|
||||
iosDeviceRequest
|
||||
)
|
||||
)
|
||||
.then(() => router.handleFind(request))
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
@@ -150,44 +206,108 @@ describe('InstallationsRouter', () => {
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with limit = 0 and count = 1', (done) => {
|
||||
var config = new Config('test');
|
||||
var androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
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',
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
const iosDeviceRequest = {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abd',
|
||||
deviceType: 'ios',
|
||||
};
|
||||
var request = {
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1
|
||||
count: 1,
|
||||
},
|
||||
info: {}
|
||||
info: {},
|
||||
};
|
||||
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
const router = new InstallationsRouter();
|
||||
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();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
+190
-102
@@ -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();
|
||||
})
|
||||
jobSchedule
|
||||
.save(null, { useMasterKey: true })
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should fail creating _JobSchedule without masterKey', (done) => {
|
||||
it('should fail creating _JobSchedule without masterKey', done => {
|
||||
const jobSchedule = new Parse.Object('_JobSchedule');
|
||||
jobSchedule.set({
|
||||
'jobName': 'SomeJob'
|
||||
jobName: 'SomeJob',
|
||||
});
|
||||
jobSchedule.save(null).then(done.fail)
|
||||
.catch(done);
|
||||
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)', 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) => {
|
||||
rp.get(Parse.serverURL + '/cloud_code/jobs/data', 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) => {
|
||||
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 => {
|
||||
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 (PUT /jobs/id)', (done) => {
|
||||
rp.del(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 (/jobs)', (done) => {
|
||||
rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions).then(done, done.fail);
|
||||
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) => {
|
||||
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: 'job'
|
||||
}
|
||||
}
|
||||
jobName: 'job',
|
||||
},
|
||||
},
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
|
||||
request(options)
|
||||
.then(res => {
|
||||
expect(res.data.objectId).not.toBeUndefined();
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
.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 creating a job with an invalid name', (done) => {
|
||||
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: 'job'
|
||||
}
|
||||
}
|
||||
jobName: 'job',
|
||||
},
|
||||
},
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options)
|
||||
request(options)
|
||||
.then(done.fail)
|
||||
.catch(done);
|
||||
.catch(() => done());
|
||||
});
|
||||
|
||||
it('should update a job', (done) => {
|
||||
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: 'job1'
|
||||
}
|
||||
}
|
||||
jobName: 'job1',
|
||||
},
|
||||
},
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
return rp.put(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, Object.assign(options, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job2'
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
|
||||
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(1);
|
||||
expect(res[0].jobName).toBe('job2');
|
||||
.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 fail updating a job with an invalid name', (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'
|
||||
}
|
||||
}
|
||||
jobName: 'job1',
|
||||
},
|
||||
},
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
return rp.put(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, Object.assign(options, {
|
||||
body: {
|
||||
job_schedule: {
|
||||
jobName: 'job2'
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
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);
|
||||
.catch(() => done());
|
||||
});
|
||||
|
||||
it('should destroy a job', (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'
|
||||
}
|
||||
}
|
||||
jobName: 'job',
|
||||
},
|
||||
},
|
||||
});
|
||||
rp.post(Parse.serverURL + '/cloud_code/jobs', options).then((res) => {
|
||||
expect(res.objectId).not.toBeUndefined();
|
||||
return rp.del(Parse.serverURL + '/cloud_code/jobs/' + res.objectId, masterKeyOptions);
|
||||
})
|
||||
.then(() => {
|
||||
return rp.get(Parse.serverURL + '/cloud_code/jobs', masterKeyOptions);
|
||||
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((res) => {
|
||||
expect(res.length).toBe(0);
|
||||
.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) => {
|
||||
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 @@
|
||||
var logging = require('../src/Adapters/Logger/WinstonLogger');
|
||||
var 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();
|
||||
var firstLog = process.stdout.write.calls.first().args[0];
|
||||
expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' }) + '\n');
|
||||
return reconfigureServer({
|
||||
jsonLogs: false
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+116
-40
@@ -1,35 +1,42 @@
|
||||
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
var 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
|
||||
var query = {};
|
||||
const query = {};
|
||||
|
||||
var loggerController = new LoggerController(new WinstonLoggerAdapter());
|
||||
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
|
||||
var result = LoggerController.parseOptions();
|
||||
const result = LoggerController.parseOptions();
|
||||
expect(result.size).toEqual(10);
|
||||
expect(result.order).toEqual('desc');
|
||||
expect(result.level).toEqual('info');
|
||||
@@ -37,17 +44,17 @@ describe('LoggerController', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('can process a query without throwing', (done) => {
|
||||
it('can parse an ascending query without throwing', done => {
|
||||
// Make mock request
|
||||
var query = {
|
||||
from: "2016-01-01Z00:00:00",
|
||||
until: "2016-01-01Z00:00:00",
|
||||
const query = {
|
||||
from: '2016-01-01Z00:00:00',
|
||||
until: '2016-01-01Z00:00:00',
|
||||
size: 5,
|
||||
order: 'asc',
|
||||
level: 'error'
|
||||
level: 'error',
|
||||
};
|
||||
|
||||
var result = LoggerController.parseOptions(query);
|
||||
const result = LoggerController.parseOptions(query);
|
||||
|
||||
expect(result.from.getTime()).toEqual(1451606400000);
|
||||
expect(result.until.getTime()).toEqual(1451606400000);
|
||||
@@ -58,34 +65,103 @@ describe('LoggerController', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('can check process a query without throwing', (done) => {
|
||||
// Make mock request
|
||||
var query = {
|
||||
from: "2016-01-01",
|
||||
until: "2016-01-30",
|
||||
it('can process an ascending query without throwing', done => {
|
||||
const query = {
|
||||
size: 5,
|
||||
order: 'desc',
|
||||
level: 'error'
|
||||
order: 'asc',
|
||||
level: 'error',
|
||||
};
|
||||
|
||||
var loggerController = new LoggerController(new WinstonLoggerAdapter());
|
||||
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 => {
|
||||
const adapter = new WinstonLoggerAdapter();
|
||||
const logger = new LoggerController(adapter, null, { verbose: true });
|
||||
spyOn(adapter, 'log');
|
||||
logger.silly('yo!');
|
||||
expect(adapter.log).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should replace implementations with logLevel', done => {
|
||||
const adapter = new WinstonLoggerAdapter();
|
||||
const logger = new LoggerController(adapter, null, { logLevel: 'error' });
|
||||
spyOn(adapter, 'log');
|
||||
logger.warn('yo!');
|
||||
logger.info('yo!');
|
||||
logger.debug('yo!');
|
||||
logger.verbose('yo!');
|
||||
logger.silly('yo!');
|
||||
expect(adapter.log).not.toHaveBeenCalled();
|
||||
logger.error('error');
|
||||
expect(adapter.log).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
+134
-22
@@ -1,26 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const request = require('request');
|
||||
var LogsRouter = require('../src/Routers/LogsRouter').LogsRouter;
|
||||
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
var 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
|
||||
var request = {
|
||||
const request = {
|
||||
auth: {
|
||||
isMaster: true
|
||||
isMaster: true,
|
||||
},
|
||||
query: {},
|
||||
config: {
|
||||
loggerController: loggerController
|
||||
}
|
||||
loggerController: loggerController,
|
||||
},
|
||||
};
|
||||
|
||||
var router = new LogsRouter();
|
||||
const router = new LogsRouter();
|
||||
|
||||
expect(() => {
|
||||
router.validateRequest(request);
|
||||
@@ -28,19 +30,19 @@ describe('LogsRouter', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('can check invalid construction of controller', (done) => {
|
||||
it('can check invalid construction of controller', done => {
|
||||
// Make mock request
|
||||
var request = {
|
||||
const request = {
|
||||
auth: {
|
||||
isMaster: true
|
||||
isMaster: true,
|
||||
},
|
||||
query: {},
|
||||
config: {
|
||||
loggerController: undefined // missing controller
|
||||
}
|
||||
loggerController: undefined, // missing controller
|
||||
},
|
||||
};
|
||||
|
||||
var router = new LogsRouter();
|
||||
const router = new LogsRouter();
|
||||
|
||||
expect(() => {
|
||||
router.validateRequest(request);
|
||||
@@ -49,17 +51,127 @@ 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();
|
||||
});
|
||||
});
|
||||
|
||||
const headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies simple passwords in GET login requests with special characters are scrubbed from the verbose log
|
||||
*/
|
||||
it('does scrub simple passwords on GET login', done => {
|
||||
reconfigureServer({
|
||||
verbose: true,
|
||||
}).then(function() {
|
||||
request({
|
||||
headers: headers,
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies complex passwords in GET login requests with special characters are scrubbed from the verbose log
|
||||
*/
|
||||
it('does scrub complex passwords on GET login', done => {
|
||||
reconfigureServer({
|
||||
verbose: true,
|
||||
})
|
||||
.then(function() {
|
||||
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);
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies fields in POST login requests are NOT present in the verbose log
|
||||
*/
|
||||
it('does not have password field in POST login', done => {
|
||||
reconfigureServer({
|
||||
verbose: true,
|
||||
}).then(function() {
|
||||
request({
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/login',
|
||||
body: {
|
||||
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');
|
||||
expect(body[2].message).toEqual(
|
||||
'REQUEST for [POST] /1/login: {\n "username": "test",\n "password": "********"\n}'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+197
-26
@@ -1,21 +1,20 @@
|
||||
var middlewares = require('../src/middlewares');
|
||||
var AppCache = require('../src/cache').AppCache;
|
||||
const middlewares = require('../lib/middlewares');
|
||||
const AppCache = require('../lib/cache').AppCache;
|
||||
|
||||
describe('middlewares', () => {
|
||||
|
||||
var fakeReq, fakeRes;
|
||||
let fakeReq, fakeRes;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeReq = {
|
||||
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);
|
||||
var contentType = 'image/jpeg';
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -133,4 +133,175 @@ describe('middlewares', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if the ip does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.ip = 'ip3';
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the ip does belong to masterKeyIps list', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.ip = 'ip1';
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.connection = { remoteAddress: 'ip3' };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.connection = { remoteAddress: 'ip1' };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if the socket.remoteAddress does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.socket = { remoteAddress: 'ip3' };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.socket = { remoteAddress: 'ip1' };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if the connection.socket.remoteAddress does not belong to masterKeyIps list', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.connection = { socket: { remoteAddress: 'ip3' } };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1', 'ip2'],
|
||||
});
|
||||
fakeReq.connection = { socket: { remoteAddress: 'ip1' } };
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow any ip to use masterKey if masterKeyIps is empty', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: [],
|
||||
});
|
||||
fakeReq.ip = 'ip1';
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if xff header does belong to masterKeyIps', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1'],
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if xff header with one ip does belong to masterKeyIps', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1'],
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = 'ip1';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not succeed if xff header does not belong to masterKeyIps', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip4'],
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should not succeed if xff header is empty and masterKeyIps is set', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['ip1'],
|
||||
});
|
||||
fakeReq.headers['x-parse-master-key'] = 'masterKey';
|
||||
fakeReq.headers['x-forwarded-for'] = '';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
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,43 +1,50 @@
|
||||
'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},
|
||||
}
|
||||
},
|
||||
"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({
|
||||
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 },
|
||||
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' },
|
||||
@@ -66,7 +73,11 @@ describe('MongoSchemaCollection', () => {
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
}
|
||||
protectedFields: { '*': [] },
|
||||
},
|
||||
indexes: {
|
||||
name1: { deviceToken: 1 },
|
||||
},
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter')
|
||||
.default;
|
||||
const { MongoClient } = require('mongodb');
|
||||
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',
|
||||
@@ -35,11 +44,12 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// https://github.com/ParsePlatform/parse-server/pull/148#issuecomment-180407057
|
||||
// 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,68 +59,82 @@ 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);
|
||||
var obj = results[0];
|
||||
const obj = results[0];
|
||||
expect(obj._id).toEqual('abcde');
|
||||
expect(obj.objectId).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -237,4 +267,23 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handleShutdown, close connection', done => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
|
||||
const schema = {
|
||||
fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
},
|
||||
};
|
||||
|
||||
adapter.createObject('MyClass', schema, {}).then(() => {
|
||||
expect(adapter.database.serverConfig.isConnected()).toEqual(true);
|
||||
adapter.handleShutdown();
|
||||
expect(adapter.database.serverConfig.isConnected()).toEqual(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+431
-145
@@ -1,233 +1,319 @@
|
||||
// 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) => {
|
||||
var input = {five: 5};
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {five: {type: 'Number'}}
|
||||
it('a basic number', done => {
|
||||
const input = { five: 5 };
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: { five: { type: 'Number' } },
|
||||
});
|
||||
jequal(input, output);
|
||||
done();
|
||||
});
|
||||
|
||||
it('an object with null values', (done) => {
|
||||
var input = {objectWithNullValues: {isNull: null, notNull: 3}};
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {objectWithNullValues: {type: 'object'}}
|
||||
it('an object with null values', done => {
|
||||
const input = { objectWithNullValues: { isNull: null, notNull: 3 } };
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: { objectWithNullValues: { type: 'object' } },
|
||||
});
|
||||
jequal(input, output);
|
||||
done();
|
||||
});
|
||||
|
||||
it('built-in timestamps', (done) => {
|
||||
var input = {
|
||||
createdAt: "2015-10-06T21:24:50.332Z",
|
||||
updatedAt: "2015-10-06T21:24:50.332Z"
|
||||
it('built-in timestamps with date', done => {
|
||||
const input = {
|
||||
createdAt: '2015-10-06T21:24:50.332Z',
|
||||
updatedAt: '2015-10-06T21:24:50.332Z',
|
||||
};
|
||||
var 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) => {
|
||||
var pointer = {
|
||||
it('array of pointers', done => {
|
||||
const pointer = {
|
||||
__type: 'Pointer',
|
||||
objectId: 'myId',
|
||||
className: 'Blah',
|
||||
};
|
||||
var 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) => {
|
||||
var input = {deleteMe: {__op: 'Delete'}};
|
||||
var 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 => {
|
||||
var 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) => {
|
||||
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
||||
var 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) => {
|
||||
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
||||
var 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) => {
|
||||
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
||||
var 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) => {
|
||||
var 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) => {
|
||||
var input = {
|
||||
objectId: {'$in': ['one', 'two', 'three']},
|
||||
it('objectId in a list', done => {
|
||||
const input = {
|
||||
objectId: { $in: ['one', 'two', 'three'] },
|
||||
};
|
||||
var output = transform.transformWhere(null, input);
|
||||
const output = transform.transformWhere(null, input);
|
||||
jequal(input.objectId, output._id);
|
||||
done();
|
||||
});
|
||||
|
||||
it('built-in timestamps', (done) => {
|
||||
var input = {createdAt: new Date(), updatedAt: new Date()};
|
||||
var 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) => {
|
||||
var input = {_p_userPointer: '_User$123'};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
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) => {
|
||||
var input = {_p_userPointer: null};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
it('null pointer', done => {
|
||||
const input = { _p_userPointer: null };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { userPointer: { type: 'Pointer', targetClass: '_User' } },
|
||||
});
|
||||
expect(output.userPointer).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
|
||||
it('file', (done) => {
|
||||
var input = {picture: 'pic.jpg'};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { picture: { type: 'File' }},
|
||||
it('file', done => {
|
||||
const input = { picture: 'pic.jpg' };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
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) => {
|
||||
var input = {location: [180, -180]};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { location: { type: 'GeoPoint' }},
|
||||
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' } },
|
||||
});
|
||||
expect(typeof output.location).toEqual('object');
|
||||
expect(output.location).toEqual(
|
||||
{__type: 'GeoPoint', longitude: 180, latitude: -180}
|
||||
);
|
||||
expect(output.location).toEqual({
|
||||
__type: 'GeoPoint',
|
||||
latitude: lat,
|
||||
longitude: lng,
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('bytes', (done) => {
|
||||
var input = {binaryData: "aGVsbG8gd29ybGQ="};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { binaryData: { type: 'Bytes' }},
|
||||
it('mongo polygon to parse', done => {
|
||||
const lat = -45;
|
||||
const lng = 45;
|
||||
// Mongo stores polygon in WGS84 lng/lat
|
||||
const input = {
|
||||
location: { type: 'Polygon', coordinates: [[[lat, lng], [lat, lng]]] },
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { location: { type: 'Polygon' } },
|
||||
});
|
||||
expect(typeof output.location).toEqual('object');
|
||||
expect(output.location).toEqual({
|
||||
__type: 'Polygon',
|
||||
coordinates: [[lng, lat], [lng, lat]],
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('bytes', done => {
|
||||
const input = { binaryData: 'aGVsbG8gd29ybGQ=' };
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
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) => {
|
||||
var input = {arr: [{_testKey: 'testValue' }]};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
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) => {
|
||||
var input = {
|
||||
somePointer: {__type: 'Pointer', className: 'Micro', objectId: 'oft'}
|
||||
it('changes new pointer key', done => {
|
||||
const input = {
|
||||
somePointer: { __type: 'Pointer', className: 'Micro', objectId: 'oft' },
|
||||
};
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {somePointer: {type: 'Pointer'}}
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
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) => {
|
||||
var input = {
|
||||
userPointer: {__type: 'Pointer', className: '_User', objectId: 'qwerty'}
|
||||
it('changes existing pointer keys', done => {
|
||||
const input = {
|
||||
userPointer: {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: 'qwerty',
|
||||
},
|
||||
};
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {userPointer: {type: 'Pointer'}}
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
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) => {
|
||||
var input = {
|
||||
it('writes the old ACL format in addition to rperm and wperm on create', done => {
|
||||
const input = {
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin'],
|
||||
};
|
||||
|
||||
var 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();
|
||||
@@ -236,11 +322,11 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('removes Relation types', (done) => {
|
||||
var input = {
|
||||
it('removes Relation types', done => {
|
||||
const input = {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
};
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
const output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
},
|
||||
@@ -249,14 +335,14 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('writes the old ACL format in addition to rperm and wperm on update', (done) => {
|
||||
var input = {
|
||||
it('writes the old ACL format in addition to rperm and wperm on update', done => {
|
||||
const input = {
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin']
|
||||
_wperm: ['Kevin'],
|
||||
};
|
||||
|
||||
var output = transform.transformUpdate(null, input, { fields: {} });
|
||||
var set = output.$set;
|
||||
const output = transform.transformUpdate(null, input, { fields: {} });
|
||||
const set = output.$set;
|
||||
expect(typeof set).toEqual('object');
|
||||
expect(typeof set._acl).toEqual('object');
|
||||
expect(set._acl['Kevin'].w).toBeTruthy();
|
||||
@@ -266,24 +352,26 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('untransforms from _rperm and _wperm to ACL', (done) => {
|
||||
var input = {
|
||||
_rperm: ["*"],
|
||||
_wperm: ["Kevin"]
|
||||
it('untransforms from _rperm and _wperm to ACL', done => {
|
||||
const input = {
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin'],
|
||||
};
|
||||
var 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) => {
|
||||
var input = {
|
||||
it('untransforms mongodb number types', done => {
|
||||
const input = {
|
||||
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
|
||||
double: new mongodb.Double(Number.MAX_VALUE)
|
||||
}
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
double: new mongodb.Double(Number.MAX_VALUE),
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: {
|
||||
long: { type: 'Number' },
|
||||
double: { type: 'Number' },
|
||||
@@ -294,39 +382,111 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('Date object where iso attribute is of type Date', (done) => {
|
||||
var input = {
|
||||
ts : { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') }
|
||||
}
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields : {
|
||||
ts : { type : 'Date' }
|
||||
}
|
||||
it('Date object where iso attribute is of type Date', done => {
|
||||
const input = {
|
||||
ts: { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') },
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: {
|
||||
ts: { type: 'Date' },
|
||||
},
|
||||
});
|
||||
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
|
||||
done();
|
||||
});
|
||||
|
||||
it('Date object where iso attribute is of type String', (done) => {
|
||||
var input = {
|
||||
ts : { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' }
|
||||
}
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields : {
|
||||
ts : { type : 'Date' }
|
||||
}
|
||||
it('Date object where iso attribute is of type String', done => {
|
||||
const input = {
|
||||
ts: { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' },
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: {
|
||||
ts: { type: 'Date' },
|
||||
},
|
||||
});
|
||||
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
|
||||
done();
|
||||
});
|
||||
|
||||
it('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) => {
|
||||
var input = {
|
||||
it('removes Relation types', done => {
|
||||
const input = {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
};
|
||||
var output = transform.transformUpdate(null, input, {
|
||||
const output = transform.transformUpdate(null, input, {
|
||||
fields: {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
},
|
||||
@@ -335,3 +495,129 @@ describe('transformUpdate', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformConstraint', () => {
|
||||
describe('$relativeTime', () => {
|
||||
it('should error on $eq, $ne, and $exists', () => {
|
||||
expect(() => {
|
||||
transform.transformConstraint({
|
||||
$eq: {
|
||||
ttl: {
|
||||
$relativeTime: '12 days ago',
|
||||
},
|
||||
},
|
||||
});
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
transform.transformConstraint({
|
||||
$ne: {
|
||||
ttl: {
|
||||
$relativeTime: '12 days ago',
|
||||
},
|
||||
},
|
||||
});
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
transform.transformConstraint({
|
||||
$exists: {
|
||||
$relativeTime: '12 days ago',
|
||||
},
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('relativeTimeToDate', () => {
|
||||
const now = new Date('2017-09-26T13:28:16.617Z');
|
||||
|
||||
describe('In the future', () => {
|
||||
it('should parse valid natural time', () => {
|
||||
const text = 'in 1 year 2 weeks 12 days 10 hours 24 minutes 30 seconds';
|
||||
const { result, status, info } = transform.relativeTimeToDate(text, now);
|
||||
expect(result.toISOString()).toBe('2018-10-22T23:52:46.617Z');
|
||||
expect(status).toBe('success');
|
||||
expect(info).toBe('future');
|
||||
});
|
||||
});
|
||||
|
||||
describe('In the past', () => {
|
||||
it('should parse valid natural time', () => {
|
||||
const text = '2 days 12 hours 1 minute 12 seconds ago';
|
||||
const { result, status, info } = transform.relativeTimeToDate(text, now);
|
||||
expect(result.toISOString()).toBe('2017-09-24T01:27:04.617Z');
|
||||
expect(status).toBe('success');
|
||||
expect(info).toBe('past');
|
||||
});
|
||||
});
|
||||
|
||||
describe('From now', () => {
|
||||
it('should equal current time', () => {
|
||||
const text = 'now';
|
||||
const { result, status, info } = transform.relativeTimeToDate(text, now);
|
||||
expect(result.toISOString()).toBe('2017-09-26T13:28:16.617Z');
|
||||
expect(status).toBe('success');
|
||||
expect(info).toBe('present');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error cases', () => {
|
||||
it('should error if string is completely gibberish', () => {
|
||||
expect(
|
||||
transform.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')
|
||||
).toEqual({
|
||||
status: 'error',
|
||||
info: "Time should either start with 'in' or end with 'ago'",
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if string contains neither `ago` nor `in`', () => {
|
||||
expect(transform.relativeTimeToDate('12 hours 1 minute')).toEqual({
|
||||
status: 'error',
|
||||
info: "Time should either start with 'in' or end with 'ago'",
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if there are missing units or numbers', () => {
|
||||
expect(transform.relativeTimeToDate('in 12 hours 1')).toEqual({
|
||||
status: 'error',
|
||||
info: 'Invalid time string. Dangling unit or number.',
|
||||
});
|
||||
|
||||
expect(transform.relativeTimeToDate('12 hours minute ago')).toEqual({
|
||||
status: 'error',
|
||||
info: 'Invalid time string. Dangling unit or number.',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error on floating point numbers', () => {
|
||||
expect(transform.relativeTimeToDate('in 12.3 hours')).toEqual({
|
||||
status: 'error',
|
||||
info: "'12.3' is not an integer.",
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if numbers are invalid', () => {
|
||||
expect(transform.relativeTimeToDate('12 hours 123a minute ago')).toEqual({
|
||||
status: 'error',
|
||||
info: "'123a' is not an integer.",
|
||||
});
|
||||
});
|
||||
|
||||
it('should error on invalid interval units', () => {
|
||||
expect(transform.relativeTimeToDate('4 score 7 years ago')).toEqual({
|
||||
status: 'error',
|
||||
info: "Invalid interval: 'score'",
|
||||
});
|
||||
});
|
||||
|
||||
it("should error when string contains 'ago' and 'in'", () => {
|
||||
expect(transform.relativeTimeToDate('in 1 day 2 minutes ago')).toEqual({
|
||||
status: 'error',
|
||||
info: "Time cannot have both 'in' and 'ago'",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
var NullCacheAdapter = require('../src/Adapters/Cache/NullCacheAdapter').default;
|
||||
const NullCacheAdapter = require('../lib/Adapters/Cache/NullCacheAdapter')
|
||||
.default;
|
||||
|
||||
describe('NullCacheAdapter', function() {
|
||||
var KEY = 'hello';
|
||||
var VALUE = 'world';
|
||||
const KEY = 'hello';
|
||||
const VALUE = 'world';
|
||||
|
||||
it('should expose promisifyed methods', (done) => {
|
||||
var cache = new NullCacheAdapter({
|
||||
ttl: NaN
|
||||
it('should expose promisifyed methods', done => {
|
||||
const cache = new NullCacheAdapter({
|
||||
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) => {
|
||||
var cache = new NullCacheAdapter({
|
||||
ttl: NaN
|
||||
it('should get/set/clear', done => {
|
||||
const cache = new NullCacheAdapter({
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+96
-83
@@ -1,141 +1,154 @@
|
||||
var 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) => {
|
||||
var 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) => {
|
||||
var 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) => {
|
||||
var 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) => {
|
||||
var request = {
|
||||
host: "dummy.com",
|
||||
path: "path"
|
||||
it('Should properly generate request signature', done => {
|
||||
let request = {
|
||||
host: 'dummy.com',
|
||||
path: 'path',
|
||||
};
|
||||
|
||||
var oauth_params = {
|
||||
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',
|
||||
};
|
||||
|
||||
var consumer_secret = "world";
|
||||
var auth_token_secret = "secret";
|
||||
request = OAuth.signRequest(request, oauth_params, consumer_secret, auth_token_secret);
|
||||
jequal(request.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"');
|
||||
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) => {
|
||||
var options = {
|
||||
host: "dummy.com",
|
||||
consumer_key: "hello",
|
||||
consumer_secret: "world",
|
||||
auth_token: "token",
|
||||
auth_token_secret: "secret",
|
||||
it('Should properly build request', done => {
|
||||
const options = {
|
||||
host: 'dummy.com',
|
||||
consumer_key: 'hello',
|
||||
consumer_secret: 'world',
|
||||
auth_token: 'token',
|
||||
auth_token_secret: 'secret',
|
||||
// Custom oauth params for tests
|
||||
oauth_params: {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA"
|
||||
}
|
||||
oauth_nonce: 'AAAAAAAAAAAAAAAAA',
|
||||
},
|
||||
};
|
||||
var path = "path";
|
||||
var method = "get";
|
||||
const path = 'path';
|
||||
const method = 'get';
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
var req = oauthClient.buildRequest(method, path, {"query": "param"});
|
||||
const oauthClient = new OAuth(options);
|
||||
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");
|
||||
var errors = data.errors;
|
||||
jequal(typeof errors[0], "object");
|
||||
jequal(typeof data, 'object');
|
||||
jequal(typeof data.errors, 'object');
|
||||
const errors = data.errors;
|
||||
jequal(typeof errors[0], 'object');
|
||||
// Cannot authenticate error
|
||||
jequal(errors[0].code, 32);
|
||||
done();
|
||||
}
|
||||
|
||||
it("Should fail a GET request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
it('Should fail a GET request', done => {
|
||||
const options = {
|
||||
host: 'api.twitter.com',
|
||||
consumer_key: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
consumer_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
};
|
||||
var path = "/1.1/help/configuration.json";
|
||||
var params = {"lang": "en"};
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.get(path, params).then(function(data){
|
||||
const path = '/1.1/help/configuration.json';
|
||||
const params = { lang: 'en' };
|
||||
const oauthClient = new OAuth(options);
|
||||
oauthClient.get(path, params).then(function(data) {
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it("Should fail a POST request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
it('Should fail a POST request', done => {
|
||||
const options = {
|
||||
host: 'api.twitter.com',
|
||||
consumer_key: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
consumer_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
const body = {
|
||||
lang: 'en',
|
||||
};
|
||||
var path = "/1.1/account/settings.json";
|
||||
const path = '/1.1/account/settings.json';
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
const oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data) {
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it("Should fail a request", (done) => {
|
||||
var options = {
|
||||
host: "localhost",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
it('Should fail a request', done => {
|
||||
const options = {
|
||||
host: 'localhost',
|
||||
consumer_key: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
consumer_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
const body = {
|
||||
lang: 'en',
|
||||
};
|
||||
var path = "/";
|
||||
const path = '/';
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(){
|
||||
jequal(false, true);
|
||||
done();
|
||||
}).catch(function(){
|
||||
jequal(true, true);
|
||||
done();
|
||||
})
|
||||
const oauthClient = new OAuth(options);
|
||||
oauthClient
|
||||
.post(path, null, body)
|
||||
.then(function() {
|
||||
jequal(false, true);
|
||||
done();
|
||||
})
|
||||
.catch(function() {
|
||||
jequal(true, true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("Should fail with missing options", (done) => {
|
||||
var options = undefined;
|
||||
it('Should fail with missing options', done => {
|
||||
const options = undefined;
|
||||
try {
|
||||
new OAuth(options);
|
||||
} catch (error) {
|
||||
|
||||
+445
-112
@@ -1,24 +1,24 @@
|
||||
'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', () => {
|
||||
var setup = function() {
|
||||
var sendToInstallationSpy = jasmine.createSpy();
|
||||
const setup = function() {
|
||||
const sendToInstallationSpy = jasmine.createSpy();
|
||||
|
||||
var pushAdapter = {
|
||||
const pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
const promises = installations.map((installation) => {
|
||||
const badge = body.data.badge;
|
||||
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(() => {
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
const installations = [];
|
||||
while (installations.length != 10) {
|
||||
const installation = new Parse.Object('_Installation');
|
||||
installation.set(
|
||||
'installationId',
|
||||
'installation_' + installations.length
|
||||
);
|
||||
installation.set(
|
||||
'deviceToken',
|
||||
'device_token_' + installations.length
|
||||
);
|
||||
installation.set('badge', installations.length);
|
||||
installation.set('originalBadge', installations.length);
|
||||
installation.set('deviceType', 'ios');
|
||||
installations.push(installation);
|
||||
}
|
||||
return Parse.Object.saveAll(installations);
|
||||
@@ -63,82 +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();
|
||||
});
|
||||
@@ -146,59 +172,366 @@ 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 => {
|
||||
return Promise.resolve({
|
||||
transmitted: true,
|
||||
device: device,
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
const provideInstallations = function(num) {
|
||||
if (!num) {
|
||||
num = 2;
|
||||
}
|
||||
|
||||
const installations = [];
|
||||
while (installations.length !== num) {
|
||||
// add Android installations
|
||||
const installation = new Parse.Object('_Installation');
|
||||
installation.set(
|
||||
'installationId',
|
||||
'installation_' + installations.length
|
||||
);
|
||||
installation.set('deviceToken', 'device_token_' + installations.length);
|
||||
installation.set('deviceType', 'android');
|
||||
installations.push(installation);
|
||||
}
|
||||
|
||||
return installations;
|
||||
};
|
||||
|
||||
const losingAdapter = {
|
||||
send: function(body, installations) {
|
||||
// simulate having lost an installation before this was called
|
||||
// thus invalidating our 'count' in _PushStatus
|
||||
installations.pop();
|
||||
|
||||
return successfulAny(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ['android'];
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies that _PushStatus cannot get stuck in a 'running' state
|
||||
* Simulates a simple push where 1 installation is removed between _PushStatus
|
||||
* count being set and the pushes being sent
|
||||
*/
|
||||
it("does not get stuck with _PushStatus 'running' on 1 installation lost", done => {
|
||||
reconfigureServer({
|
||||
push: { adapter: losingAdapter },
|
||||
})
|
||||
.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();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies that _PushStatus cannot get stuck in a 'running' state
|
||||
* Simulates a simple push where 1 installation is added between _PushStatus
|
||||
* count being set and the pushes being sent
|
||||
*/
|
||||
it("does not get stuck with _PushStatus 'running' on 1 installation added", done => {
|
||||
const installations = provideInstallations();
|
||||
|
||||
// add 1 iOS installation which we will omit & add later on
|
||||
const iOSInstallation = new Parse.Object('_Installation');
|
||||
iOSInstallation.set(
|
||||
'installationId',
|
||||
'installation_' + installations.length
|
||||
);
|
||||
iOSInstallation.set('deviceToken', 'device_token_' + installations.length);
|
||||
iOSInstallation.set('deviceType', 'ios');
|
||||
installations.push(iOSInstallation);
|
||||
|
||||
reconfigureServer({
|
||||
push: {
|
||||
adapter: {
|
||||
send: function(body, installations) {
|
||||
// simulate having added an installation before this was called
|
||||
// thus invalidating our 'count' in _PushStatus
|
||||
installations.push(iOSInstallation);
|
||||
|
||||
return successfulAny(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ['android'];
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Object.saveAll(installations);
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: { alert: 'We fixed our status!' },
|
||||
where: { deviceType: { $ne: 'random' } },
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({ useMasterKey: true });
|
||||
})
|
||||
.then(results => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
expect(result.get('numSent')).toEqual(3);
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies that _PushStatus cannot get stuck in a 'running' state
|
||||
* Simulates an extended push, where some installations may be removed,
|
||||
* resulting in a non-zero count
|
||||
*/
|
||||
it("does not get stuck with _PushStatus 'running' on many installations removed", done => {
|
||||
const devices = 1000;
|
||||
const installations = provideInstallations(devices);
|
||||
|
||||
reconfigureServer({
|
||||
push: { adapter: losingAdapter },
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Object.saveAll(installations);
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: { alert: 'We fixed our status!' },
|
||||
where: { deviceType: 'android' },
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({ useMasterKey: true });
|
||||
})
|
||||
.then(results => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
// expect # less than # of batches used, assuming each batch is 100 pushes
|
||||
expect(result.get('numSent')).toEqual(devices - devices / 100);
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies that _PushStatus cannot get stuck in a 'running' state
|
||||
* Simulates an extended push, where some installations may be added,
|
||||
* resulting in a non-zero count
|
||||
*/
|
||||
it("does not get stuck with _PushStatus 'running' on many installations added", done => {
|
||||
const devices = 1000;
|
||||
const installations = provideInstallations(devices);
|
||||
|
||||
// add 1 iOS installation which we will omit & add later on
|
||||
const iOSInstallations = [];
|
||||
|
||||
while (iOSInstallations.length !== devices / 100) {
|
||||
const iOSInstallation = new Parse.Object('_Installation');
|
||||
iOSInstallation.set(
|
||||
'installationId',
|
||||
'installation_' + installations.length
|
||||
);
|
||||
iOSInstallation.set(
|
||||
'deviceToken',
|
||||
'device_token_' + installations.length
|
||||
);
|
||||
iOSInstallation.set('deviceType', 'ios');
|
||||
installations.push(iOSInstallation);
|
||||
iOSInstallations.push(iOSInstallation);
|
||||
}
|
||||
|
||||
reconfigureServer({
|
||||
push: {
|
||||
adapter: {
|
||||
send: function(body, installations) {
|
||||
// simulate having added an installation before this was called
|
||||
// thus invalidating our 'count' in _PushStatus
|
||||
installations.push(iOSInstallations.pop());
|
||||
|
||||
return successfulAny(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ['android'];
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Object.saveAll(installations);
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.Push.send(
|
||||
{
|
||||
data: { alert: 'We fixed our status!' },
|
||||
where: { deviceType: { $ne: 'random' } },
|
||||
},
|
||||
{ useMasterKey: true }
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// query for push status
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({ useMasterKey: true });
|
||||
})
|
||||
.then(results => {
|
||||
// verify status is NOT broken
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
// expect # less than # of batches used, assuming each batch is 100 pushes
|
||||
expect(result.get('numSent')).toEqual(devices + devices / 100);
|
||||
expect(result.get('count')).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+874
-1206
File diff suppressed because it is too large
Load Diff
+1079
-838
File diff suppressed because it is too large
Load Diff
@@ -1,69 +1,80 @@
|
||||
var ParseCloudCodePublisher = require('../src/LiveQuery/ParseCloudCodePublisher').ParseCloudCodePublisher;
|
||||
var Parse = require('parse/node');
|
||||
const ParseCloudCodePublisher = require('../lib/LiveQuery/ParseCloudCodePublisher')
|
||||
.ParseCloudCodePublisher;
|
||||
const Parse = require('parse/node');
|
||||
|
||||
describe('ParseCloudCodePublisher', function() {
|
||||
beforeEach(function(done) {
|
||||
// Mock ParsePubSub
|
||||
var mockParsePubSub = {
|
||||
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() {
|
||||
var config = {}
|
||||
const config = {};
|
||||
new ParseCloudCodePublisher(config);
|
||||
|
||||
var ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
const ParsePubSub = require('../lib/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
expect(ParsePubSub.createPublisher).toHaveBeenCalledWith(config);
|
||||
});
|
||||
|
||||
it('can handle cloud code afterSave request', function() {
|
||||
var publisher = new ParseCloudCodePublisher({});
|
||||
const publisher = new ParseCloudCodePublisher({});
|
||||
publisher._onCloudCodeMessage = jasmine.createSpy('onCloudCodeMessage');
|
||||
var request = {};
|
||||
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() {
|
||||
var publisher = new ParseCloudCodePublisher({});
|
||||
const publisher = new ParseCloudCodePublisher({});
|
||||
publisher._onCloudCodeMessage = jasmine.createSpy('onCloudCodeMessage');
|
||||
var request = {};
|
||||
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() {
|
||||
var publisher = new ParseCloudCodePublisher({});
|
||||
var currentParseObject = new Parse.Object('Test');
|
||||
const publisher = new ParseCloudCodePublisher({});
|
||||
const currentParseObject = new Parse.Object('Test');
|
||||
currentParseObject.set('key', 'value');
|
||||
var originalParseObject = new Parse.Object('Test');
|
||||
const originalParseObject = new Parse.Object('Test');
|
||||
originalParseObject.set('key', 'originalValue');
|
||||
var request = {
|
||||
const request = {
|
||||
object: currentParseObject,
|
||||
original: originalParseObject
|
||||
original: originalParseObject,
|
||||
};
|
||||
publisher._onCloudCodeMessage('afterSave', request);
|
||||
|
||||
var args = publisher.parsePublisher.publish.calls.mostRecent().args;
|
||||
const args = publisher.parsePublisher.publish.calls.mostRecent().args;
|
||||
expect(args[0]).toBe('afterSave');
|
||||
var message = JSON.parse(args[1]);
|
||||
const message = JSON.parse(args[1]);
|
||||
expect(message.currentParseObject).toEqual(request.object._toFullJSON());
|
||||
expect(message.originalParseObject).toEqual(request.original._toFullJSON());
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
jasmine.restoreLibrary('../src/LiveQuery/ParsePubSub', 'ParsePubSub');
|
||||
afterEach(function() {
|
||||
jasmine.restoreLibrary('../lib/LiveQuery/ParsePubSub', 'ParsePubSub');
|
||||
});
|
||||
});
|
||||
|
||||
+642
-369
File diff suppressed because it is too large
Load Diff
+680
-517
File diff suppressed because it is too large
Load Diff
+132
-116
@@ -1,74 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
var 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 = new Config('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 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();
|
||||
});
|
||||
});
|
||||
|
||||
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) => {
|
||||
const config = new Config('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({});
|
||||
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({
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+691
-421
File diff suppressed because it is too large
Load Diff
+908
-526
File diff suppressed because it is too large
Load Diff
+1351
-458
File diff suppressed because it is too large
Load Diff
+1734
-1646
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,416 @@
|
||||
const TestObject = Parse.Object.extend('TestObject');
|
||||
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',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
describe('Parse.Polygon testing', () => {
|
||||
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);
|
||||
});
|
||||
|
||||
it('polygon save closed path', done => {
|
||||
const coords = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', new Parse.Polygon(coords));
|
||||
return obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
})
|
||||
.then(result => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, coords);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon equalTo (open/closed) path', done => {
|
||||
const openPoints = [[0, 0], [0, 1], [1, 1], [1, 0]];
|
||||
const closedPoints = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
|
||||
const openPolygon = new Parse.Polygon(openPoints);
|
||||
const closedPolygon = new Parse.Polygon(closedPoints);
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', openPolygon);
|
||||
return obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('polygon', openPolygon);
|
||||
return query.find();
|
||||
})
|
||||
.then(results => {
|
||||
const polygon = results[0].get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closedPoints);
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('polygon', closedPolygon);
|
||||
return query.find();
|
||||
})
|
||||
.then(results => {
|
||||
const polygon = results[0].get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closedPoints);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon update', done => {
|
||||
const oldCoords = [[0, 0], [0, 1], [1, 1], [1, 0]];
|
||||
const oldPolygon = new Parse.Polygon(oldCoords);
|
||||
const newCoords = [[2, 2], [2, 3], [3, 3], [3, 2]];
|
||||
const newPolygon = new Parse.Polygon(newCoords);
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', oldPolygon);
|
||||
return obj
|
||||
.save()
|
||||
.then(() => {
|
||||
obj.set('polygon', newPolygon);
|
||||
return obj.save();
|
||||
})
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
})
|
||||
.then(result => {
|
||||
const polygon = result.get('polygon');
|
||||
newCoords.push(newCoords[0]);
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, newCoords);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('polygon invalid value', done => {
|
||||
const coords = [['foo', 'bar'], [0, 1], [1, 0], [1, 1], [0, 0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', { __type: 'Polygon', coordinates: coords });
|
||||
return obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
})
|
||||
.then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('polygon three points minimum', done => {
|
||||
const coords = [[0, 0]];
|
||||
const obj = new TestObject();
|
||||
// use raw so we test the server validates properly
|
||||
obj.set('polygon', { __type: 'Polygon', coordinates: coords });
|
||||
obj.save().then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('polygon three different points minimum', done => {
|
||||
const coords = [[0, 0], [0, 1], [0, 0]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', new Parse.Polygon(coords));
|
||||
obj.save().then(done.fail, () => done());
|
||||
});
|
||||
|
||||
it('polygon counterclockwise', done => {
|
||||
const coords = [[1, 1], [0, 1], [0, 0], [1, 0]];
|
||||
const closed = [[1, 1], [0, 1], [0, 0], [1, 0], [1, 1]];
|
||||
const obj = new TestObject();
|
||||
obj.set('polygon', new Parse.Polygon(coords));
|
||||
obj
|
||||
.save()
|
||||
.then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.get(obj.id);
|
||||
})
|
||||
.then(result => {
|
||||
const polygon = result.get('polygon');
|
||||
equal(polygon instanceof Parse.Polygon, true);
|
||||
equal(polygon.coordinates, closed);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
describe('with location', () => {
|
||||
beforeAll(() => require('../lib/TestUtils').destroyAllDataPermanently());
|
||||
|
||||
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 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', () => {
|
||||
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 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 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 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());
|
||||
});
|
||||
});
|
||||
+67
-37
@@ -1,80 +1,105 @@
|
||||
var ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
const ParsePubSub = require('../lib/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
|
||||
describe('ParsePubSub', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
// Mock RedisPubSub
|
||||
var mockRedisPubSub = {
|
||||
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
|
||||
var mockEventEmitterPubSub = {
|
||||
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 },
|
||||
});
|
||||
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var 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({});
|
||||
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var 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 },
|
||||
});
|
||||
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var 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({});
|
||||
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var 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();
|
||||
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var 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();
|
||||
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
const RedisPubSub = require('../lib/Adapters/PubSub/RedisPubSub')
|
||||
.RedisPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
jasmine.restoreLibrary('../src/Adapters/PubSub/RedisPubSub', 'RedisPubSub');
|
||||
jasmine.restoreLibrary('../src/Adapters/PubSub/EventEmitterPubSub', 'EventEmitterPubSub');
|
||||
afterEach(function() {
|
||||
jasmine.restoreLibrary('../lib/Adapters/PubSub/RedisPubSub', 'RedisPubSub');
|
||||
jasmine.restoreLibrary(
|
||||
'../lib/Adapters/PubSub/EventEmitterPubSub',
|
||||
'EventEmitterPubSub'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
|
||||
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||
const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter')
|
||||
.default;
|
||||
const mongoURI =
|
||||
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const PostgresStorageAdapter = require('../lib/Adapters/Storage/Postgres/PostgresStorageAdapter')
|
||||
.default;
|
||||
const postgresURI =
|
||||
'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||
const Parse = require('parse/node');
|
||||
const rp = require('request-promise');
|
||||
const request = require('../lib/request');
|
||||
let databaseAdapter;
|
||||
|
||||
const fullTextHelper = () => {
|
||||
@@ -29,11 +33,12 @@ const fullTextHelper = () => {
|
||||
const requests = [];
|
||||
for (const i in subjects) {
|
||||
const request = {
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
body: {
|
||||
subject: subjects[i]
|
||||
subject: subjects[i],
|
||||
comment: subjects[i],
|
||||
},
|
||||
path: "/1/classes/TestObject"
|
||||
path: '/1/classes/TestObject',
|
||||
};
|
||||
requests.push(request);
|
||||
}
|
||||
@@ -41,419 +46,570 @@ const fullTextHelper = () => {
|
||||
appId: 'test',
|
||||
restAPIKey: 'test',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
databaseAdapter
|
||||
databaseAdapter,
|
||||
}).then(() => {
|
||||
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
|
||||
return Parse.Promise.as();
|
||||
}
|
||||
return databaseAdapter.createIndex('TestObject', {subject: 'text'});
|
||||
}).then(() => {
|
||||
return rp.post({
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/batch',
|
||||
body: {
|
||||
requests
|
||||
requests,
|
||||
},
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
describe('Parse.Query Full Text Search testing', () => {
|
||||
it('fullTextSearch: $search', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(3);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $search, sort', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const order = '$score';
|
||||
const keys = '$score';
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, order, keys, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(3);
|
||||
expect(resp.results[0].score);
|
||||
expect(resp.results[1].score);
|
||||
expect(resp.results[2].score);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $language', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'leche',
|
||||
$language: 'spanish'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $diacriticSensitive', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(1);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $search, invalid input', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: true
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $language, invalid input', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'leche',
|
||||
$language: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $caseSensitive, invalid input', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'Coffee',
|
||||
$caseSensitive: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $diacriticSensitive, invalid input', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe_only_db('mongo')('Parse.Query Full Text Search testing', () => {
|
||||
it('fullTextSearch: $search, index not exist', (done) => {
|
||||
return reconfigureServer({
|
||||
appId: 'test',
|
||||
restAPIKey: 'test',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
databaseAdapter: new MongoStorageAdapter({ uri: mongoURI })
|
||||
}).then(() => {
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/batch',
|
||||
body: {
|
||||
requests: [
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
subject: "coffee is java"
|
||||
it('fullTextSearch: $search', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee',
|
||||
},
|
||||
path: "/1/classes/TestObject"
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
subject: "java is coffee"
|
||||
},
|
||||
path: "/1/classes/TestObject"
|
||||
}
|
||||
]
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(
|
||||
resp => {
|
||||
expect(resp.data.results.length).toBe(3);
|
||||
done();
|
||||
},
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`Text Index should not exist: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INTERNAL_SERVER_ERROR);
|
||||
done();
|
||||
});
|
||||
e => done.fail(e)
|
||||
);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $diacriticSensitive - false', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
it('fullTextSearch: $search, sort', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const order = '$score';
|
||||
const keys = '$score';
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, order, keys, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(response => {
|
||||
const resp = response.data;
|
||||
expect(resp.results.length).toBe(3);
|
||||
expect(resp.results[0].score);
|
||||
expect(resp.results[1].score);
|
||||
expect(resp.results[2].score);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $caseSensitive', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'Coffee',
|
||||
$caseSensitive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
it('fullTextSearch: $language', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'leche',
|
||||
$language: 'spanish',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
expect(resp.data.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $diacriticSensitive', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
expect(resp.data.results.length).toBe(1);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $search, invalid input', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
expect(err.data.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $language, invalid input', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'leche',
|
||||
$language: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
expect(err.data.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $caseSensitive, invalid input', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'Coffee',
|
||||
$caseSensitive: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
expect(err.data.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $diacriticSensitive, invalid input', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
fail(`no request should succeed: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
expect(err.data.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
}).then((resp) => {
|
||||
expect(resp.results.length).toBe(1);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe_only_db('postgres')('Parse.Query Full Text Search testing', () => {
|
||||
it('fullTextSearch: $diacriticSensitive - false', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`$diacriticSensitive - false should not supported: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
describe_only_db('mongo')(
|
||||
'[mongodb] Parse.Query Full Text Search testing',
|
||||
() => {
|
||||
it('fullTextSearch: does not create text index if compound index exist', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
return databaseAdapter.dropAllIndexes('TestObject');
|
||||
})
|
||||
.then(() => {
|
||||
return databaseAdapter.getIndexes('TestObject');
|
||||
})
|
||||
.then(indexes => {
|
||||
expect(indexes.length).toEqual(1);
|
||||
return databaseAdapter.createIndex('TestObject', {
|
||||
subject: 'text',
|
||||
comment: 'text',
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return databaseAdapter.getIndexes('TestObject');
|
||||
})
|
||||
.then(indexes => {
|
||||
expect(indexes.length).toEqual(2);
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
expect(resp.data.results.length).toEqual(3);
|
||||
return databaseAdapter.getIndexes('TestObject');
|
||||
})
|
||||
.then(indexes => {
|
||||
expect(indexes.length).toEqual(2);
|
||||
request({
|
||||
url: 'http://localhost:8378/1/schemas/TestObject',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(body.indexes._id_).toBeDefined();
|
||||
expect(body.indexes._id_._id).toEqual(1);
|
||||
expect(body.indexes.subject_text_comment_text).toBeDefined();
|
||||
expect(body.indexes.subject_text_comment_text.subject).toEqual(
|
||||
'text'
|
||||
);
|
||||
expect(body.indexes.subject_text_comment_text.comment).toEqual(
|
||||
'text'
|
||||
);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $caseSensitive', (done) => {
|
||||
fullTextHelper().then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'Coffee',
|
||||
$caseSensitive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return rp.post({
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
json: { where, '_method': 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test'
|
||||
}
|
||||
});
|
||||
}).then((resp) => {
|
||||
fail(`$caseSensitive should not supported: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
it('fullTextSearch: does not create text index if schema compound index exist', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
return databaseAdapter.dropAllIndexes('TestObject');
|
||||
})
|
||||
.then(() => {
|
||||
return databaseAdapter.getIndexes('TestObject');
|
||||
})
|
||||
.then(indexes => {
|
||||
expect(indexes.length).toEqual(1);
|
||||
return request({
|
||||
method: 'PUT',
|
||||
url: 'http://localhost:8378/1/schemas/TestObject',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
indexes: {
|
||||
text_test: { subject: 'text', comment: 'text' },
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return databaseAdapter.getIndexes('TestObject');
|
||||
})
|
||||
.then(indexes => {
|
||||
expect(indexes.length).toEqual(2);
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'coffee',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
expect(resp.data.results.length).toEqual(3);
|
||||
return databaseAdapter.getIndexes('TestObject');
|
||||
})
|
||||
.then(indexes => {
|
||||
expect(indexes.length).toEqual(2);
|
||||
request({
|
||||
url: 'http://localhost:8378/1/schemas/TestObject',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(response => {
|
||||
const body = response.data;
|
||||
expect(body.indexes._id_).toBeDefined();
|
||||
expect(body.indexes._id_._id).toEqual(1);
|
||||
expect(body.indexes.text_test).toBeDefined();
|
||||
expect(body.indexes.text_test.subject).toEqual('text');
|
||||
expect(body.indexes.text_test.comment).toEqual('text');
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $diacriticSensitive - false', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
expect(resp.data.results.length).toBe(2);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('fullTextSearch: $caseSensitive', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'Coffee',
|
||||
$caseSensitive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
expect(resp.data.results.length).toBe(1);
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
describe_only_db('postgres')(
|
||||
'[postgres] Parse.Query Full Text Search testing',
|
||||
() => {
|
||||
it('fullTextSearch: $diacriticSensitive - false', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'CAFÉ',
|
||||
$diacriticSensitive: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
fail(
|
||||
`$diacriticSensitive - false should not supported: ${JSON.stringify(
|
||||
resp
|
||||
)}`
|
||||
);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
expect(err.data.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fullTextSearch: $caseSensitive', done => {
|
||||
fullTextHelper()
|
||||
.then(() => {
|
||||
const where = {
|
||||
subject: {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: 'Coffee',
|
||||
$caseSensitive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: { where, _method: 'GET' },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'test',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(resp => {
|
||||
fail(`$caseSensitive should not supported: ${JSON.stringify(resp)}`);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
expect(err.data.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
+4059
-2344
File diff suppressed because it is too large
Load Diff
+689
-592
File diff suppressed because it is too large
Load Diff
+540
-450
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
'use strict';
|
||||
/* Tests for ParseServer.js */
|
||||
const express = require('express');
|
||||
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', () => {
|
||||
let server;
|
||||
beforeAll(done => {
|
||||
const app = express();
|
||||
app.get('/health', function(req, res) {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
});
|
||||
});
|
||||
server = app.listen(13376, undefined, done);
|
||||
});
|
||||
|
||||
afterAll(done => {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
it('validate good server url', done => {
|
||||
Parse.serverURL = 'http://localhost:13376';
|
||||
ParseServer.verifyServerUrl(function(result) {
|
||||
if (!result) {
|
||||
done.fail('Did not pass valid url');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('mark bad server url', done => {
|
||||
Parse.serverURL = 'notavalidurl';
|
||||
ParseServer.verifyServerUrl(function(result) {
|
||||
if (result) {
|
||||
done.fail('Did not mark invalid url');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handleShutdown, close connection', done => {
|
||||
const mongoURI =
|
||||
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const postgresURI =
|
||||
'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||
let databaseAdapter;
|
||||
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
|
||||
databaseAdapter = new PostgresStorageAdapter({
|
||||
uri: process.env.PARSE_SERVER_TEST_DATABASE_URI || postgresURI,
|
||||
collectionPrefix: 'test_',
|
||||
});
|
||||
} else {
|
||||
databaseAdapter = new MongoStorageAdapter({
|
||||
uri: mongoURI,
|
||||
collectionPrefix: 'test_',
|
||||
});
|
||||
}
|
||||
const newConfiguration = Object.assign({}, defaultConfiguration, {
|
||||
databaseAdapter,
|
||||
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();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// Tests behavior of Parse Sessions
|
||||
//
|
||||
|
||||
'use strict';
|
||||
|
||||
function setupTestUsers() {
|
||||
const user1 = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
const user3 = new Parse.User();
|
||||
|
||||
user1.set('username', 'testuser_1');
|
||||
user2.set('username', 'testuser_2');
|
||||
user3.set('username', 'testuser_3');
|
||||
|
||||
user1.set('password', 'password');
|
||||
user2.set('password', 'password');
|
||||
user3.set('password', 'password');
|
||||
|
||||
return user1
|
||||
.signUp()
|
||||
.then(() => {
|
||||
return user2.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return user3.signUp();
|
||||
});
|
||||
}
|
||||
|
||||
describe('Parse.Session', () => {
|
||||
// multiple sessions with masterKey + sessionToken
|
||||
it('should retain original sessionTokens with masterKey & sessionToken set', done => {
|
||||
setupTestUsers()
|
||||
.then(user => {
|
||||
const query = new Parse.Query(Parse.Session);
|
||||
return query.find({
|
||||
useMasterKey: true,
|
||||
sessionToken: user.get('sessionToken'),
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
const foundKeys = [];
|
||||
expect(results.length).toBe(3);
|
||||
for (const key in results) {
|
||||
const sessionToken = results[key].get('sessionToken');
|
||||
if (foundKeys[sessionToken]) {
|
||||
fail('Duplicate session token present in response');
|
||||
break;
|
||||
}
|
||||
foundKeys[sessionToken] = 1;
|
||||
}
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(err);
|
||||
});
|
||||
});
|
||||
|
||||
// single session returned, with just one sessionToken
|
||||
it('should retain original sessionTokens with just sessionToken set', done => {
|
||||
let knownSessionToken;
|
||||
setupTestUsers()
|
||||
.then(user => {
|
||||
knownSessionToken = user.get('sessionToken');
|
||||
const query = new Parse.Query(Parse.Session);
|
||||
return query.find({
|
||||
sessionToken: knownSessionToken,
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toBe(1);
|
||||
const sessionToken = results[0].get('sessionToken');
|
||||
expect(sessionToken).toBe(knownSessionToken);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(err);
|
||||
});
|
||||
});
|
||||
|
||||
// multiple users with masterKey + sessionToken
|
||||
it('token on users should retain original sessionTokens with masterKey & sessionToken set', done => {
|
||||
setupTestUsers()
|
||||
.then(user => {
|
||||
const query = new Parse.Query(Parse.User);
|
||||
return query.find({
|
||||
useMasterKey: true,
|
||||
sessionToken: user.get('sessionToken'),
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
const foundKeys = [];
|
||||
expect(results.length).toBe(3);
|
||||
for (const key in results) {
|
||||
const sessionToken = results[key].get('sessionToken');
|
||||
if (foundKeys[sessionToken] && sessionToken !== undefined) {
|
||||
fail('Duplicate session token present in response');
|
||||
break;
|
||||
}
|
||||
foundKeys[sessionToken] = 1;
|
||||
}
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(err);
|
||||
});
|
||||
});
|
||||
|
||||
// multiple users with just sessionToken
|
||||
it('token on users should retain original sessionTokens with just sessionToken set', done => {
|
||||
let knownSessionToken;
|
||||
setupTestUsers()
|
||||
.then(user => {
|
||||
knownSessionToken = user.get('sessionToken');
|
||||
const query = new Parse.Query(Parse.User);
|
||||
return query.find({
|
||||
sessionToken: knownSessionToken,
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
const foundKeys = [];
|
||||
expect(results.length).toBe(3);
|
||||
for (const key in results) {
|
||||
const sessionToken = results[key].get('sessionToken');
|
||||
if (foundKeys[sessionToken] && sessionToken !== undefined) {
|
||||
fail('Duplicate session token present in response');
|
||||
break;
|
||||
}
|
||||
foundKeys[sessionToken] = 1;
|
||||
}
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
+3295
-2567
File diff suppressed because it is too large
Load Diff
+16
-16
@@ -1,42 +1,42 @@
|
||||
var ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
|
||||
const ParseWebSocket = require('../lib/LiveQuery/ParseWebSocketServer')
|
||||
.ParseWebSocket;
|
||||
|
||||
describe('ParseWebSocket', function() {
|
||||
|
||||
it('can be initialized', function() {
|
||||
var ws = {};
|
||||
var parseWebSocket = new ParseWebSocket(ws);
|
||||
const ws = {};
|
||||
const parseWebSocket = new ParseWebSocket(ws);
|
||||
|
||||
expect(parseWebSocket.ws).toBe(ws);
|
||||
});
|
||||
|
||||
it('can handle events defined in typeMap', function() {
|
||||
var ws = {
|
||||
on: jasmine.createSpy('on')
|
||||
const ws = {
|
||||
on: jasmine.createSpy('on'),
|
||||
};
|
||||
var callback = {};
|
||||
var parseWebSocket = new ParseWebSocket(ws);
|
||||
const callback = {};
|
||||
const parseWebSocket = new ParseWebSocket(ws);
|
||||
parseWebSocket.on('disconnect', callback);
|
||||
|
||||
expect(parseWebSocket.ws.on).toHaveBeenCalledWith('close', callback);
|
||||
});
|
||||
|
||||
it('can handle events which are not defined in typeMap', function() {
|
||||
var ws = {
|
||||
on: jasmine.createSpy('on')
|
||||
const ws = {
|
||||
on: jasmine.createSpy('on'),
|
||||
};
|
||||
var callback = {};
|
||||
var parseWebSocket = new ParseWebSocket(ws);
|
||||
const callback = {};
|
||||
const parseWebSocket = new ParseWebSocket(ws);
|
||||
parseWebSocket.on('open', callback);
|
||||
|
||||
expect(parseWebSocket.ws.on).toHaveBeenCalledWith('open', callback);
|
||||
});
|
||||
|
||||
it('can send a message', function() {
|
||||
var ws = {
|
||||
send: jasmine.createSpy('send')
|
||||
const ws = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
var parseWebSocket = new ParseWebSocket(ws);
|
||||
parseWebSocket.send('message')
|
||||
const parseWebSocket = new ParseWebSocket(ws);
|
||||
parseWebSocket.send('message');
|
||||
|
||||
expect(parseWebSocket.ws.send).toHaveBeenCalledWith('message');
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
var ParseWebSocketServer = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocketServer;
|
||||
const ParseWebSocketServer = require('../lib/LiveQuery/ParseWebSocketServer')
|
||||
.ParseWebSocketServer;
|
||||
|
||||
describe('ParseWebSocketServer', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
// Mock ws server
|
||||
var EventEmitter = require('events');
|
||||
var mockServer = function() {
|
||||
const EventEmitter = require('events');
|
||||
const mockServer = function() {
|
||||
return new EventEmitter();
|
||||
};
|
||||
jasmine.mockLibrary('ws', 'Server', mockServer);
|
||||
@@ -13,14 +13,18 @@ describe('ParseWebSocketServer', function() {
|
||||
});
|
||||
|
||||
it('can handle connect event when ws is open', function(done) {
|
||||
var onConnectCallback = jasmine.createSpy('onConnectCallback');
|
||||
var http = require('http');
|
||||
var server = http.createServer();
|
||||
var parseWebSocketServer = new ParseWebSocketServer(server, onConnectCallback, 5).server;
|
||||
var ws = {
|
||||
const onConnectCallback = jasmine.createSpy('onConnectCallback');
|
||||
const http = require('http');
|
||||
const server = http.createServer();
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
+1282
-941
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user