mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
593a7a6264 | ||
|
|
4addf9dfa9 | ||
|
|
2547fd08ae | ||
|
|
581f919d1a | ||
|
|
0b1a61cd85 | ||
|
|
943b84b13f | ||
|
|
70f60267c5 | ||
|
|
f0f04d6351 | ||
|
|
1d8edd73c3 | ||
|
|
8d802b9461 | ||
|
|
271608bc82 | ||
|
|
5c5ef0313b | ||
|
|
29fec01a42 | ||
|
|
92b699920e | ||
|
|
9bfa0c60c4 | ||
|
|
0181fb51b3 | ||
|
|
e6006e800e | ||
|
|
a45d1848db | ||
|
|
6381e15e6d | ||
|
|
73195627c0 | ||
|
|
053bbefda9 | ||
|
|
032d5e55ca | ||
|
|
fe5947ce4c | ||
|
|
0e900cbefd | ||
|
|
41358d2226 | ||
|
|
73260897cd | ||
|
|
d3e6c0dea3 | ||
|
|
b4e27e1160 | ||
|
|
79b0c790f0 | ||
|
|
64f608ec9f | ||
|
|
67260b5fe0 | ||
|
|
411daf4d41 | ||
|
|
7a3c9cd33a | ||
|
|
193e5a4278 | ||
|
|
6ae0675010 | ||
|
|
a520ea0243 | ||
|
|
58bdeeee2d | ||
|
|
fc7f9f76f5 | ||
|
|
ef8d534679 | ||
|
|
1c9852e53f | ||
|
|
3f0de0c2ed | ||
|
|
eccdeccb59 | ||
|
|
f16f8423db | ||
|
|
bd60a5c1d1 | ||
|
|
40b9f953ca | ||
|
|
15eafd4dcc | ||
|
|
11cde12245 | ||
|
|
5861996cb0 | ||
|
|
ca70ad0141 | ||
|
|
5a22df13a5 | ||
|
|
5b7144176c | ||
|
|
4979503f63 | ||
|
|
e32a37c2ec | ||
|
|
f7f17cdb23 | ||
|
|
ecf422b2d9 | ||
|
|
49dc8df1de | ||
|
|
c3c5f06efd | ||
|
|
2a454fec35 | ||
|
|
01b4c4e0cb | ||
|
|
3714d68aaf | ||
|
|
77daa3ca18 | ||
|
|
0b574f04a9 | ||
|
|
a89a30182e | ||
|
|
96a9588a6e | ||
|
|
66cdfaed4b | ||
|
|
9262147a6f | ||
|
|
8b4e9ca637 | ||
|
|
2160d10990 | ||
|
|
e7f2995143 | ||
|
|
d5940b17b6 | ||
|
|
f864141663 | ||
|
|
711db9ccd2 | ||
|
|
6a6829ce80 | ||
|
|
d67be492f5 | ||
|
|
ea45f69af6 | ||
|
|
43c3d8e2a2 | ||
|
|
d8a1a614e8 | ||
|
|
553db0ada5 | ||
|
|
388a59d816 | ||
|
|
de4c1ee418 | ||
|
|
cb15c0b616 | ||
|
|
8dd864a033 | ||
|
|
c26db2248f | ||
|
|
b2a3479dbf | ||
|
|
deedf7b370 | ||
|
|
5f849ca662 | ||
|
|
0279b5e5f3 | ||
|
|
4cb6e7d209 | ||
|
|
56bb505df2 | ||
|
|
ceb5a1b5f0 | ||
|
|
0958b15297 | ||
|
|
5d9dbea07b | ||
|
|
f331f6644c | ||
|
|
df029b82eb | ||
|
|
edba550cf6 | ||
|
|
c7775eb0b9 | ||
|
|
0b44820a7d | ||
|
|
b7322064d8 | ||
|
|
ac3f5cd686 | ||
|
|
9fd34fd25e | ||
|
|
7ff3a3813d | ||
|
|
754f5d0505 | ||
|
|
0e8c3c2457 | ||
|
|
423a50133a | ||
|
|
a3d416de4a | ||
|
|
a2dfcc8da8 | ||
|
|
f4734a65c0 | ||
|
|
aa071e034e | ||
|
|
4f46eceacf | ||
|
|
81ebbb8954 | ||
|
|
e82395e268 | ||
|
|
0159360a5d | ||
|
|
aab10bea79 | ||
|
|
752f0e9143 | ||
|
|
025e7a3218 | ||
|
|
ca286b7108 | ||
|
|
a6c988176e | ||
|
|
e069b20ef9 | ||
|
|
ae30ec647b | ||
|
|
fc53fa9c32 | ||
|
|
c1dcaf1271 | ||
|
|
a9067260fc | ||
|
|
2e569ca7dd | ||
|
|
8111c3338e | ||
|
|
fb36dfa26f | ||
|
|
998b271994 | ||
|
|
a7821f6e57 | ||
|
|
ddb6f7bc57 | ||
|
|
8124bf4600 | ||
|
|
df74a1f8ae | ||
|
|
e0704b440c | ||
|
|
01b05b060f | ||
|
|
a270632570 | ||
|
|
b9afccd338 | ||
|
|
37daafc7b0 | ||
|
|
8359284329 | ||
|
|
2b0c39caaa | ||
|
|
11f7712651 | ||
|
|
7292fa7f11 | ||
|
|
4d17bed374 | ||
|
|
143bd11638 | ||
|
|
bd1689190f | ||
|
|
08bac9f790 | ||
|
|
f2df0303b2 | ||
|
|
fbb2b086af | ||
|
|
127629d4f7 | ||
|
|
6676c6a4b9 | ||
|
|
b6fb3d8d51 | ||
|
|
6264f99ad5 | ||
|
|
ee9099d6ad | ||
|
|
c83a787510 | ||
|
|
85567310d4 | ||
|
|
8f1c1f419b | ||
|
|
d800ff85e2 | ||
|
|
8c2c76dd26 | ||
|
|
6e2fba4ae4 | ||
|
|
e4a7fbbf98 | ||
|
|
edb7b70ced | ||
|
|
11c6170ed1 | ||
|
|
d137e27eb9 | ||
|
|
a28257b2b7 | ||
|
|
f22cea5ca9 | ||
|
|
0bc53753b0 | ||
|
|
1eff210a51 | ||
|
|
4ea455b20a | ||
|
|
e9e6a4764e | ||
|
|
4785891e99 | ||
|
|
8e1e89b794 | ||
|
|
cf6ce5b9a3 | ||
|
|
6be9ee5491 | ||
|
|
fe5f0a8980 | ||
|
|
303851d86d | ||
|
|
0cfae4d603 | ||
|
|
7e0e48457b | ||
|
|
7b5f89e037 | ||
|
|
7bc83c372d | ||
|
|
28c1824394 | ||
|
|
19271fa1ef | ||
|
|
b80d8a0d3d | ||
|
|
e9dfb68a37 | ||
|
|
fbbc23772b | ||
|
|
5082609937 | ||
|
|
9abf17730f | ||
|
|
bb9ce773bb | ||
|
|
1029ddf563 | ||
|
|
71fb6cef5e | ||
|
|
2fb4f89324 | ||
|
|
1ae3613608 | ||
|
|
5f335a9d30 | ||
|
|
fb6150feb1 | ||
|
|
908c48098a | ||
|
|
b347bff641 | ||
|
|
801308d9b7 | ||
|
|
af700d7a4a | ||
|
|
9c2cbd254e | ||
|
|
33ebedfd6a | ||
|
|
f3c656829a | ||
|
|
86fdd173a5 | ||
|
|
583887c43c | ||
|
|
8f0ae7081d | ||
|
|
5c4806ba6d | ||
|
|
78646a3e91 | ||
|
|
edf6ab6875 | ||
|
|
142b3beab6 | ||
|
|
b6c19a53ee | ||
|
|
48865c765f | ||
|
|
94178df4d2 | ||
|
|
4a2f7ff2db | ||
|
|
22c790f23c | ||
|
|
0e78c28146 | ||
|
|
23b77f7261 | ||
|
|
f23c0a57ee | ||
|
|
4a5ed1095c | ||
|
|
e788d49af0 | ||
|
|
278027a955 | ||
|
|
f4fe19a55e | ||
|
|
403fb3e062 | ||
|
|
3df10dca88 | ||
|
|
68dbe005b5 | ||
|
|
8c4be63a4c | ||
|
|
e2a5228127 | ||
|
|
151bea25ee | ||
|
|
af55cd1efb | ||
|
|
0faaec3224 | ||
|
|
021fe4d336 | ||
|
|
5dc24334a5 | ||
|
|
86f35014f4 | ||
|
|
758975cf03 | ||
|
|
a6a6f7ff60 | ||
|
|
305b037176 | ||
|
|
b88b0c578f | ||
|
|
d8ba9e8b7d | ||
|
|
60d506615d | ||
|
|
de36d9640b | ||
|
|
7af320932a | ||
|
|
c253daa9c9 | ||
|
|
09f871b786 | ||
|
|
c0c516271c | ||
|
|
2cea0030f3 | ||
|
|
6979bb430b | ||
|
|
d8f0f9e158 | ||
|
|
27b135b1c2 | ||
|
|
954a8a4159 | ||
|
|
a60935dbfb | ||
|
|
509f90b937 | ||
|
|
300150e50f | ||
|
|
b5af4d017c | ||
|
|
9260e8b05f | ||
|
|
2ec2bb5660 | ||
|
|
3ba6e613b3 | ||
|
|
4dcefd6386 | ||
|
|
9c522be00d | ||
|
|
4974dbea37 | ||
|
|
d4593931db | ||
|
|
2183b84565 | ||
|
|
a41cbcbc7f | ||
|
|
ad707457be | ||
|
|
5feceaa6d6 | ||
|
|
e30989c7d3 | ||
|
|
7e037ffa72 | ||
|
|
0ec1e8ca7f | ||
|
|
e97579ea85 | ||
|
|
e5fa3b5e27 | ||
|
|
56f3e63112 | ||
|
|
263ca5e052 | ||
|
|
ddb0fb8a27 | ||
|
|
f9dca6072a | ||
|
|
ba0e529f81 | ||
|
|
109bc9a0ce | ||
|
|
90e9994195 | ||
|
|
97c124bbbe | ||
|
|
22c1a87d80 | ||
|
|
4c927dd898 | ||
|
|
408764a7b7 | ||
|
|
86a0f1ad02 | ||
|
|
0773523bc2 | ||
|
|
f6312a1b98 | ||
|
|
cab69b4913 | ||
|
|
ac348f5c4f | ||
|
|
7f6fb11d78 | ||
|
|
cb7b54941b | ||
|
|
ccf2b14b98 | ||
|
|
340eb46fe1 | ||
|
|
c5fdd91aa3 | ||
|
|
e8aa1ad312 | ||
|
|
fc576cb415 | ||
|
|
364604e181 | ||
|
|
58a2ee32fa | ||
|
|
eb1cfcf351 | ||
|
|
fe62e92aa1 | ||
|
|
33e3993a37 | ||
|
|
5676a6fbda | ||
|
|
28bd37884d | ||
|
|
f6516a1d1e | ||
|
|
9d2d208609 | ||
|
|
ac11e03e24 | ||
|
|
be9fb435dd | ||
|
|
05805a6a70 | ||
|
|
e0c0608457 | ||
|
|
26ce63513c | ||
|
|
f727e8dd4e | ||
|
|
3e5b90ca3d | ||
|
|
10ace495d8 | ||
|
|
4b2a780e03 | ||
|
|
4d78446cb9 | ||
|
|
d65f4a6f75 | ||
|
|
2aa14adf87 | ||
|
|
8eafe45664 | ||
|
|
a99bd82c43 | ||
|
|
307a725884 | ||
|
|
5ca4844bef | ||
|
|
23e47f2e28 | ||
|
|
9ab488b6a0 | ||
|
|
e1de9f3a12 | ||
|
|
7fa3c9916c | ||
|
|
0fd6ce35e2 | ||
|
|
3a08ec9ce8 | ||
|
|
9429659d90 | ||
|
|
5f67caefde | ||
|
|
277df54dc8 | ||
|
|
9ecb9a3595 | ||
|
|
3164b478ea | ||
|
|
430ae378f2 | ||
|
|
7616b8d3ec | ||
|
|
fc73518b98 | ||
|
|
24a2416d10 | ||
|
|
c0249283ac | ||
|
|
2f1ee2186b | ||
|
|
e690b73bb5 | ||
|
|
122c3fd1a3 | ||
|
|
80de86c8ae | ||
|
|
a5a172918e | ||
|
|
6e0a25dea0 | ||
|
|
452887bd05 | ||
|
|
e6d31a07ab | ||
|
|
a87ab6f1c4 | ||
|
|
cbcd44bd72 | ||
|
|
1a90405fad | ||
|
|
b6b09e68de | ||
|
|
fc3ebd0bd0 | ||
|
|
ae36200d1f | ||
|
|
f27dff0ae6 | ||
|
|
cdb46dcb2e | ||
|
|
55690254de | ||
|
|
39078e8a2b | ||
|
|
fa736f1df7 | ||
|
|
36891f4ed7 | ||
|
|
27cfa9645a | ||
|
|
e41e3c3202 | ||
|
|
1a661b73a6 | ||
|
|
d79b478e0b | ||
|
|
e4c2e65263 | ||
|
|
eb1b2eb83f | ||
|
|
f293b1d0a3 | ||
|
|
01df251847 | ||
|
|
3044a382d6 | ||
|
|
62caea6c28 | ||
|
|
4dda71698b | ||
|
|
f5755e8f73 | ||
|
|
115fd7278c | ||
|
|
eee11a7acb | ||
|
|
8d8192bbcc | ||
|
|
5865d59b5a | ||
|
|
946f8a3872 | ||
|
|
f1c9d97d2f | ||
|
|
3dbf0b22f4 | ||
|
|
ddf8fd432b | ||
|
|
39c267cacb | ||
|
|
a957df0a91 | ||
|
|
831b29b015 | ||
|
|
da94a1e8be | ||
|
|
72cfec0e0c | ||
|
|
fa49f05fdd | ||
|
|
fdd60a4a30 | ||
|
|
1cf23a3431 | ||
|
|
15a07859b0 | ||
|
|
7964d0a999 | ||
|
|
fc42388305 | ||
|
|
c9fc80984a | ||
|
|
88d913f3a2 | ||
|
|
3b2da1d734 | ||
|
|
93b41170fd | ||
|
|
a60c3f48ce | ||
|
|
b799ec7bc3 | ||
|
|
499c78a922 | ||
|
|
09bd9e3b2c | ||
|
|
66c4b98b55 | ||
|
|
921617ad40 | ||
|
|
397e6bc976 | ||
|
|
ac1f762f07 | ||
|
|
ab3b4addbf | ||
|
|
c8e1ab851f | ||
|
|
d1a6caeee3 | ||
|
|
02edf27f05 | ||
|
|
dfac9725dd | ||
|
|
78ea02968a | ||
|
|
8719afdc1b | ||
|
|
4f89ec39cd | ||
|
|
6f292059ba | ||
|
|
033bc317e6 | ||
|
|
40c4fcdebf | ||
|
|
c81f48aae6 | ||
|
|
025482ea1a | ||
|
|
f310e6678e | ||
|
|
2ba724938f | ||
|
|
676d2e28de | ||
|
|
bae19f2c76 | ||
|
|
0f49f314b2 | ||
|
|
fa96f0c389 | ||
|
|
d07dd4c49f | ||
|
|
7d234e054b | ||
|
|
514095dc35 | ||
|
|
069275d3df | ||
|
|
fcfe2a0376 | ||
|
|
1125c88f56 | ||
|
|
bf645233b0 | ||
|
|
9bf21ef093 | ||
|
|
32f7230aca | ||
|
|
f1ff9fecce | ||
|
|
d1426c19dd | ||
|
|
a2f8a1d2b3 | ||
|
|
18235352c4 | ||
|
|
3c1da3cc2a | ||
|
|
868ad5bdfd | ||
|
|
2498a954ce | ||
|
|
a040aafcd8 | ||
|
|
7a2e906202 | ||
|
|
9658d216f3 | ||
|
|
4634bfb3e4 | ||
|
|
6dc8d287d6 | ||
|
|
1fb7679b22 | ||
|
|
83f686355e | ||
|
|
6ca1351b8f | ||
|
|
a92b23b97d | ||
|
|
a55466fde8 | ||
|
|
61aa5a8d62 | ||
|
|
4e9e817a80 | ||
|
|
8338783075 | ||
|
|
944e5addd3 | ||
|
|
9de347872f | ||
|
|
5bf878be22 | ||
|
|
2f2ff377eb | ||
|
|
48b33d3889 | ||
|
|
08c63f324a | ||
|
|
b641712d4d | ||
|
|
d1cdea00af | ||
|
|
d599fdd4fe | ||
|
|
04dc2d2233 | ||
|
|
d2098d7ee4 | ||
|
|
9e725f616b | ||
|
|
799e59618c | ||
|
|
a73f150a1a | ||
|
|
a2474fa0f1 | ||
|
|
19080ed7e7 | ||
|
|
b7d15cc37f | ||
|
|
1f2f831cbe | ||
|
|
e4cfe5af24 | ||
|
|
147b493e23 | ||
|
|
a861c4e506 | ||
|
|
2a4fbf7a15 | ||
|
|
98d1ae808a | ||
|
|
c32ed52f04 | ||
|
|
3e413683f7 | ||
|
|
35b4c063fb | ||
|
|
29ffbabd3d | ||
|
|
57cecd800c | ||
|
|
c36ca0c44e | ||
|
|
a96b0afac5 | ||
|
|
1feedef21d | ||
|
|
006540cda5 | ||
|
|
ab06055369 | ||
|
|
7da4debbe0 | ||
|
|
c37a4ea1a1 | ||
|
|
9d078003a6 | ||
|
|
a685ed8814 | ||
|
|
0ff35e18f0 | ||
|
|
1a75101146 | ||
|
|
627b4164bb | ||
|
|
d1f77a9a98 | ||
|
|
35ed09a7b4 | ||
|
|
2eb861db66 | ||
|
|
ae0ba7b5a9 | ||
|
|
726c866a4d | ||
|
|
f7f5bab75d | ||
|
|
9ed3fcc536 | ||
|
|
8130658583 | ||
|
|
f23d80ec9a | ||
|
|
510a890a03 | ||
|
|
a69a88f3a4 | ||
|
|
f796d69d19 | ||
|
|
452b737be4 | ||
|
|
634d672ad1 | ||
|
|
803b9be97a | ||
|
|
2cafd6919f | ||
|
|
f75c8b3a4d | ||
|
|
693707b33c | ||
|
|
39f11a5134 | ||
|
|
b2a5092627 | ||
|
|
d9f8486790 | ||
|
|
5838a49396 | ||
|
|
63e2ceeb02 | ||
|
|
7c8ad7e071 | ||
|
|
0ec78d478b | ||
|
|
2cc1b0cfa9 | ||
|
|
324cd85775 | ||
|
|
5518edc2a5 | ||
|
|
d559cb2382 | ||
|
|
5baa53d858 | ||
|
|
729a0c101d | ||
|
|
afbc662dcd | ||
|
|
7e868b2dcc | ||
|
|
6415a35433 | ||
|
|
11301d9590 | ||
|
|
a00d795404 | ||
|
|
2e50416cba | ||
|
|
fcd914bdfd | ||
|
|
db76aa376f | ||
|
|
65518cdd64 | ||
|
|
e7e2369132 | ||
|
|
ac705a8da6 | ||
|
|
aeb6880b85 | ||
|
|
bea655e61b | ||
|
|
abeecad907 | ||
|
|
38f02af511 | ||
|
|
c6c9c97b54 | ||
|
|
103839ce60 | ||
|
|
04172eda5a | ||
|
|
5176efbe16 | ||
|
|
3997b1aa5a | ||
|
|
cd525802a6 | ||
|
|
17374eff8d | ||
|
|
181b12771d | ||
|
|
1c162101be | ||
|
|
7d2929abe2 | ||
|
|
1487a73f16 | ||
|
|
50b9ae4803 | ||
|
|
c5e3be8b64 | ||
|
|
dd1d18dbe6 | ||
|
|
2561987d20 | ||
|
|
0850c184d3 | ||
|
|
98f8db36e8 | ||
|
|
c9a138d9ca | ||
|
|
c4499d25e0 | ||
|
|
0896f33824 | ||
|
|
4864518315 | ||
|
|
3ed3c7b62f | ||
|
|
05ae010b91 | ||
|
|
d428041a83 | ||
|
|
a763f7c2fc | ||
|
|
135b0e0254 | ||
|
|
e444ca8425 | ||
|
|
aa072dabff | ||
|
|
c9be5a3aac | ||
|
|
cf0a4b246f | ||
|
|
14938bbe7a | ||
|
|
1ae1d42c32 | ||
|
|
4052470820 | ||
|
|
7dca7e20b0 | ||
|
|
e440046be4 | ||
|
|
f4b1f7b951 | ||
|
|
d944255e4e | ||
|
|
a55b2b6209 | ||
|
|
00de555ecb | ||
|
|
474a893a22 | ||
|
|
fe8160449c | ||
|
|
74ee8613d8 | ||
|
|
c928dcc118 | ||
|
|
9f149e6db5 | ||
|
|
69d5a2f87c | ||
|
|
3a96140103 | ||
|
|
614e1ac8e5 | ||
|
|
88fa7bad92 | ||
|
|
6b28075194 | ||
|
|
8b43d2644f | ||
|
|
361d4a34ce | ||
|
|
e93a1ad7fc | ||
|
|
392102eb97 | ||
|
|
eefa2ccac7 | ||
|
|
fab8cfdfc7 | ||
|
|
9bad87905f | ||
|
|
b2183680be | ||
|
|
fece2a4b47 | ||
|
|
03108e6347 | ||
|
|
c416cad43f | ||
|
|
5165c80570 | ||
|
|
643bdc8227 | ||
|
|
15fc186a51 | ||
|
|
ea09213511 | ||
|
|
559205bc64 | ||
|
|
66b8a8474e | ||
|
|
b24ff151d5 | ||
|
|
0d856c1f23 | ||
|
|
4d4361451c | ||
|
|
d7d4699832 | ||
|
|
8c09c3dae1 | ||
|
|
5d887e18f0 | ||
|
|
40965186c0 | ||
|
|
b40e16647b | ||
|
|
3b4ae2d0a0 | ||
|
|
1854928fe7 | ||
|
|
e4998c256a | ||
|
|
4bfe2c5014 | ||
|
|
d0c3535a39 | ||
|
|
c2cfa14627 | ||
|
|
19e7407f55 | ||
|
|
6cfcb4ddf8 | ||
|
|
10ee229e60 | ||
|
|
1291f70a16 | ||
|
|
26df50924b | ||
|
|
e698f61a58 | ||
|
|
e60901de6a | ||
|
|
b2f36e192a | ||
|
|
10d2988985 | ||
|
|
37c502bed0 | ||
|
|
b00572de65 | ||
|
|
da5b5ccefa | ||
|
|
7c850fc1ad | ||
|
|
90df7841ef | ||
|
|
0b88302bea | ||
|
|
583bb34995 | ||
|
|
71ae7bee94 | ||
|
|
449ca115b1 | ||
|
|
d4bd21fcbc | ||
|
|
a926712951 | ||
|
|
5f564f32f5 | ||
|
|
874d10fc74 | ||
|
|
4371ca164c | ||
|
|
608cba9e8c | ||
|
|
5cbf3eb8dd | ||
|
|
37953d146b | ||
|
|
a3179e43fb | ||
|
|
7b431ad064 | ||
|
|
3d7589c1bc | ||
|
|
ee8a3c94d5 | ||
|
|
e2a3213351 | ||
|
|
5c7b346292 | ||
|
|
0542f860f4 | ||
|
|
e0a9f19895 | ||
|
|
21e73ac276 | ||
|
|
957b5927b1 | ||
|
|
234d0093ff | ||
|
|
d14d451028 | ||
|
|
d33dd68cc5 | ||
|
|
0d094767cf | ||
|
|
ab827e3c2f | ||
|
|
d49d539a81 | ||
|
|
6e9529f81c | ||
|
|
2d94a885d2 | ||
|
|
b7b57f7ea7 | ||
|
|
b185508508 | ||
|
|
7b91ee98ed | ||
|
|
9776362ab2 | ||
|
|
59b4047de8 | ||
|
|
54b21c2f73 | ||
|
|
5d37755d88 | ||
|
|
ddd01181be | ||
|
|
bf47a0b2b6 | ||
|
|
3945e0c680 | ||
|
|
61b62e4e9f | ||
|
|
0708af17d7 | ||
|
|
cecb2a1cb1 | ||
|
|
ab0ea09067 | ||
|
|
414ee6740d | ||
|
|
65bd59d95f | ||
|
|
ac839f75c7 | ||
|
|
c57c6b33b1 | ||
|
|
4c2636e23c | ||
|
|
2f51e751de | ||
|
|
a727e1ccd3 | ||
|
|
f076078bf1 | ||
|
|
3f66c302b7 | ||
|
|
eef51cf9ad | ||
|
|
1023baf20d | ||
|
|
51970fb470 | ||
|
|
d57e384dcb | ||
|
|
34851c0ae5 | ||
|
|
686cc4ab95 | ||
|
|
fc1cdd4408 | ||
|
|
3fb3ce1ab7 | ||
|
|
512251eb58 | ||
|
|
f312f81cbd | ||
|
|
af30f669cc | ||
|
|
c050a65d49 | ||
|
|
c419106a38 | ||
|
|
99ecbb39f5 | ||
|
|
e9e561f5e8 | ||
|
|
73a3db44ab | ||
|
|
281568edd2 | ||
|
|
b433fb9b4e | ||
|
|
30197a7d84 | ||
|
|
ab6925a5e2 | ||
|
|
a770c4cba8 | ||
|
|
de2a2ee245 | ||
|
|
c22bafb951 | ||
|
|
bc96f0beb1 | ||
|
|
cedac3fda6 | ||
|
|
616ac71623 | ||
|
|
325d09ca4c | ||
|
|
ef92a79818 | ||
|
|
91ace4e718 | ||
|
|
cbbd66964a | ||
|
|
1bd804693c | ||
|
|
1eb2a87f9f | ||
|
|
0b2dc12143 | ||
|
|
213bfea62d | ||
|
|
a17e54c397 | ||
|
|
5773143973 | ||
|
|
406f237119 | ||
|
|
bc384e97e1 | ||
|
|
18906f17b4 | ||
|
|
442c723350 | ||
|
|
9053b941ee | ||
|
|
7e52c18893 | ||
|
|
8c92b80b8f | ||
|
|
27dd2ebf5d | ||
|
|
c1ef0440b0 | ||
|
|
f4889a3393 | ||
|
|
19da14f3df | ||
|
|
91de7504fe | ||
|
|
ec6a39718d | ||
|
|
0e3636d880 | ||
|
|
89df778ac8 | ||
|
|
acc23d0828 | ||
|
|
3a28352c96 | ||
|
|
f99b5588ab | ||
|
|
51664c8f33 | ||
|
|
8c12f80d28 | ||
|
|
c346e15a18 | ||
|
|
4fd67a51fb | ||
|
|
fffc83c0d9 | ||
|
|
04e8e3e233 | ||
|
|
02af61dc9a | ||
|
|
57156cf52b | ||
|
|
01af755d18 | ||
|
|
3f8a1aae02 | ||
|
|
edfa4092c0 | ||
|
|
431d864ac3 | ||
|
|
647987534f | ||
|
|
ca7d8580e3 | ||
|
|
9c528c6fe8 | ||
|
|
2b3bf7c2b7 | ||
|
|
6540ad1483 | ||
|
|
b5625bc2e5 | ||
|
|
09279d1987 | ||
|
|
dbf64a6d83 | ||
|
|
eeb3331167 | ||
|
|
8a6903aa8d | ||
|
|
434ac91129 | ||
|
|
f0ebb7bae6 | ||
|
|
781e81d36b | ||
|
|
73bca3b64c | ||
|
|
6311c95785 | ||
|
|
efa7366250 | ||
|
|
6b6e684d55 | ||
|
|
5d99075663 | ||
|
|
c5636e1b77 | ||
|
|
ab1858616b | ||
|
|
632c8054da | ||
|
|
7afc08abe4 | ||
|
|
33c9730be2 | ||
|
|
97d3deb73b | ||
|
|
a0e7739526 | ||
|
|
7064f1d3ed | ||
|
|
4774e90937 | ||
|
|
6055f2a552 | ||
|
|
b2819df7e5 | ||
|
|
5dc5ef239e | ||
|
|
f25bcc490a | ||
|
|
aa5ebd49f0 | ||
|
|
106ac95622 | ||
|
|
1f4393cd4d | ||
|
|
e361ccf5a5 | ||
|
|
342a3a92fe | ||
|
|
507fa54570 | ||
|
|
1b8e613dfb | ||
|
|
f259a3b17e | ||
|
|
3dccd61222 | ||
|
|
cbf3f3c1be |
@@ -0,0 +1,21 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
*.md
|
||||
PATENTS
|
||||
LICENSE
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
.gitignore
|
||||
.travis.yml
|
||||
.istanbul.yml
|
||||
.git
|
||||
.github
|
||||
|
||||
# Build folder
|
||||
lib/
|
||||
|
||||
# Tests
|
||||
spec/
|
||||
|
||||
# IDEs
|
||||
.idea/
|
||||
@@ -0,0 +1,2 @@
|
||||
lib/*
|
||||
coverage/*
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"flowtype"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": ["error", 2],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"no-trailing-spaces": 2,
|
||||
"eol-last": 2,
|
||||
"space-in-parens": ["error", "never"],
|
||||
"no-multiple-empty-lines": 1,
|
||||
"prefer-const": "error",
|
||||
"space-infix-ops": "error"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,43 @@
|
||||
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.
|
||||
We use GitHub Issues for bugs.
|
||||
|
||||
Make sure these boxes are checked before submitting your issue -- thanks for reporting issues back to 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
|
||||
|
||||
- [ ] You've met the [prerequisites](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#prerequisites).
|
||||
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
|
||||
|
||||
- [ ] You're running the [latest version](https://github.com/ParsePlatform/parse-server/releases) of Parse Server.
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
|
||||
- [ ] You've searched through [existing issues](https://github.com/ParsePlatform/parse-server/issues?utf8=%E2%9C%93&q=). Chances are that your issue has been reported or resolved before.
|
||||
### Issue Description
|
||||
|
||||
#### Environment Setup
|
||||
Describe your issue in as much detail as possible.
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
#### Steps to reproduce
|
||||
Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable.
|
||||
|
||||
#### Expected Results
|
||||
|
||||
#### Logs/Trace
|
||||
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.
|
||||
|
||||
@@ -23,6 +23,9 @@ coverage
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# build folder for automated releases
|
||||
latest
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
@@ -41,6 +44,7 @@ lib/
|
||||
|
||||
# cache folder
|
||||
.cache
|
||||
.eslintcache
|
||||
|
||||
# Mac DS_Store files
|
||||
.DS_Store
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
instrumentation:
|
||||
excludes: ["**/spec/**"]
|
||||
+65
-12
@@ -1,16 +1,69 @@
|
||||
language: node_js
|
||||
dist: trusty
|
||||
node_js:
|
||||
- '4.6'
|
||||
- '6.1'
|
||||
services:
|
||||
- postgresql
|
||||
- redis-server
|
||||
- docker
|
||||
addons:
|
||||
postgresql: '9.5'
|
||||
apt_packages:
|
||||
- postgresql-9.5-postgis-2.3
|
||||
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
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/.bin/istanbul cover'
|
||||
matrix:
|
||||
- MONGODB_VERSION=3.0.8
|
||||
- MONGODB_VERSION=3.2.6
|
||||
- PARSE_SERVER_TEST_DB=postgres
|
||||
- PARSE_SERVER_TEST_CACHE=redis
|
||||
matrix:
|
||||
fast_finish: true
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4.3"
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/babel-istanbul/lib/cli.js cover -x **/spec/**'
|
||||
matrix:
|
||||
- MONGODB_VERSION=2.6.11
|
||||
- MONGODB_VERSION=3.0.8
|
||||
- master
|
||||
- /^[0-9]+.[0-9]+.[0-9]+(-.*)?$/
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.mongodb/versions/downloads
|
||||
after_success: ./node_modules/.bin/codecov
|
||||
- node_modules
|
||||
- "$HOME/.mongodb/versions"
|
||||
after_script:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
after_failure:
|
||||
- (cd resources; python travis_after_all.py)
|
||||
after_success:
|
||||
- (cd resources; python travis_after_all.py)
|
||||
- export $(cat resources/.to_export_back)
|
||||
|
||||
deploy:
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email:
|
||||
secure: T3LwSJFcu632SDfCoavcKL39NktWLEOOFzonAjeHAglmMyDp9hvU8xHwQ4JJy2CRA03c+ezAe2gH3Va+jfxvh1JtFUan+p88vaCHHKuNwPSp4WZBPt1ZTDmG+ACG6j+ZWHK+eP7hLvtlCi/M9/v4/bVojpm7v01LLiM+MRhnE9E7eLemQC4zb6RVtf2oGQ3mX51kMeya218kEm5bsQPpRJElI9jwletFa8qYK5AFgaCHpll059RRHQTTE9MTNcN58P/Kf6Qb3KGpKNoaHTXoOD4U6LcXLWvdHONVB6AzGefxg2b/dvtaO4nd5lDooMBR3u9kWpopXkyAuf+fO/8je9JTxO6CDUtDjHRcR2mCEXWX6rculIAbNXOv1ccRYCTQ8aFtYnFlMSb1+QIAdyT6HHmeT5ktk3+XJRuEv0PJJvqHTo+j7tPngjiv5sPNutgGFlOFO90omTbzEkIT/D/LsgXbWm2QKYWejbLiuSINg+FiFiZN90doCf4aCpm2y1Q/9GzvK+eXcpqzPCGxOykE7EuPZK3+S/ZU2oaWL2uSpbKPtC0qxebrneR307aVEa7C85HCkTMMBzE4tjUr8h5HhLqtWvqmDcnBo3PfQhf9cEO6jQLd3qhEVJmcwKz/yOCfgPXlCbRqiKk7j94perpNCIalXtMI9ySYjJNjHsB4600=
|
||||
api_key:
|
||||
secure: WLjhHVAdUkDy6UdNWveTpZqphw9olN0alCpyPpU0cJjlN/hk7YxPP+YHOSVnMZaRZoHM0LL11jPKLf970nymBfvJHDWxKk71c/5xyTX8pBtSxmGmGy23a5g2VrHVMd2JxsI7NEAH500tlFz+01t4E3Steo0NpAkHR3Q51kj01gQy8IumsksfhRc9uvjZ7zjm99Yk4L+cxyei79v4esfpx7Bgm0HTfAAAG/19F+r0hvvFiV517SREDU/YtcX/rIjVepEy1uiLKEohnmYtVIRSA3Hjh6VlHVhdj2WDd/dYrxi/Ioysq2zOM9pZVLamO9asB1e6JrTEipWE9jSZXxsbnfFzuNLxcOjKv1wd3OzQKF/7pGKGiWRTv5Xm19D/FrXoE/ULs6bHcC/Ke8Gs2RxPlOCdvAFehCkyf5P4HOPCQdR7o8Yiuvt+5JWKBflElNbSd4nWgwUOo3Yv8vC4Vj53fwmM+Uqfu3IgYFWktYxCm4RWIKMfB/gtmjcF6QYFfjvEMwAvRfBV81kuynCsnubXWzQeBE/b3JOhBfpGciKCjKfy+tS6bZfFjCtQV98hMMiCPre8Y7PahHDYc65wU9Ake+ZE+dDaSbeV3DZ5JeifLJHzKW2J2dWeRANkOaiSwO9VBC9/rht5ulK5qQ1pB2+sKDToAaiKy6RMlB/HgIoYqsU=
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
|
||||
repo: ParsePlatform/parse-server
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: ./resources/npm-git.sh
|
||||
on:
|
||||
branch: master
|
||||
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: ./resources/docker-hub.sh
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# Upgrading Parse Server to version 2.3.0
|
||||
|
||||
Parse Server version 2.3.0 begins using unique indexes to ensure the User's username and email are unique. This is not a backwards incompatible change, but it may in some cases cause a significant performance regression until the index finishes building. Building the unique index before upgrading your Parse Server version will eliminate the performance impact, and is a recommended step before upgrading any app to Parse Server 2.3.0. New apps starting with version 2.3.0 do not need to take any steps before beginning their project.
|
||||
|
||||
If you are using MongoDB in Cluster or Replica Set mode, we recommend reading Mongo's [documentation on index building](https://docs.mongodb.com/v3.0/tutorial/build-indexes-on-replica-sets/) first. If you are not using these features, you can execute the following commands from the Mongo shell to build the unique index. You may also want to create a backup first.
|
||||
|
||||
```js
|
||||
// Select the database that your Parse App uses
|
||||
use parse;
|
||||
|
||||
// Select the collection your Parse App uses for users. For migrated apps, this probably includes a collectionPrefix.
|
||||
var coll = db['your_prefix:_User'];
|
||||
|
||||
// You can check if the indexes already exists by running coll.getIndexes()
|
||||
coll.getIndexes();
|
||||
|
||||
// The indexes you want should look like this. If they already exist, you can skip creating them.
|
||||
{
|
||||
"v" : 1,
|
||||
"unique" : true,
|
||||
"key" : {
|
||||
"username" : 1
|
||||
},
|
||||
"name" : "username_1",
|
||||
"ns" : "parse.your_prefix:_User",
|
||||
"background" : true,
|
||||
"sparse" : true
|
||||
}
|
||||
|
||||
{
|
||||
"v" : 1,
|
||||
"unique" : true,
|
||||
"key" : {
|
||||
"email" : 1
|
||||
},
|
||||
"name" : "email_1",
|
||||
"ns" : "parse.your_prefix:_User",
|
||||
"background" : true,
|
||||
"sparse" : true
|
||||
}
|
||||
|
||||
// Create the username index.
|
||||
// "background: true" is mandatory and avoids downtime while the index builds.
|
||||
// "sparse: true" is also mandatory because Parse Server uses sparse indexes.
|
||||
coll.ensureIndex({ username: 1 }, { background: true, unique: true, sparse: true });
|
||||
|
||||
// Create the email index.
|
||||
// "background: true" is still mandatory.
|
||||
// "sparse: true" is also mandatory both because Parse Server uses sparse indexes, and because email addresses are not required by the Parse API.
|
||||
coll.ensureIndex({ email: 1 }, { background: true, unique: true, sparse: true });
|
||||
```
|
||||
|
||||
There are some issues you may run into during this process:
|
||||
|
||||
## Mongo complains that the index already exists, but with different options
|
||||
|
||||
In this case, you will need to remove the incorrect index. If your app relies on the existence of the index in order to be performant, you can create a new index, with "-1" for the direction of the field, so that it counts as different options. Then, drop the conflicting index, and create the unique index.
|
||||
|
||||
## There is already non-unique data in the username or email field
|
||||
|
||||
This is possible if you have explicitly set some user's emails to null. If this is bogus data, and those null fields should be unset, you can unset the null emails with this command. If your app relies on the difference between null and unset emails, you will need to upgrade your app to treat null and unset emails the same before building the index and upgrading to Parse Server 2.3.0.
|
||||
|
||||
```js
|
||||
coll.update({ email: { $exists: true, $eq: null } }, { $unset: { email: '' } }, { multi: true })
|
||||
```
|
||||
|
||||
## There is already non-unique data in the username or email field, and it's not nulls
|
||||
|
||||
This is possible due to a race condition in previous versions of Parse Server. If you have this problem, it is unlikely that you have a lot of rows with duplicate data. We recommend you clean up the data manually, by removing or modifying the offending rows.
|
||||
|
||||
This command, can be used to find the duplicate data:
|
||||
|
||||
```js
|
||||
coll.aggregate([
|
||||
{$match: {"username": {"$ne": null}}},
|
||||
{$group: {_id: "$username", uniqueIds: {$addToSet: "$_id"}, count: {$sum: 1}}},
|
||||
{$match: {count: {"$gt": 1}}},
|
||||
{$project: {id: "$uniqueIds", username: "$_id", _id : 0} },
|
||||
{$unwind: "$id" },
|
||||
{$out: '_duplicates'} // Save the list of duplicates to a new, "_duplicates" collection. Remove this line to just output the list.
|
||||
], {allowDiskUse:true})
|
||||
```
|
||||
+455
-3
@@ -1,5 +1,459 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 2.3.7
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.6...2.3.7)
|
||||
|
||||
#### New Features
|
||||
* New endpoint to resend verification email, thanks to [Xy Ziemba](https://github.com/xyziemba)
|
||||
|
||||
#### Improvements
|
||||
* Add TTL option for Redis Cache Adapter, thanks to [Ryan Foster](https://github.com/f0ster)
|
||||
* Update Postgres Storage Adapter, thanks to [Vitaly Tomilov](https://github.com/vitaly-t)
|
||||
|
||||
#### Bug Fixes
|
||||
* Add index on Role.name, fixes (#3579), thanks to [Natan Rolnik](https://github.com/natanrolnik)
|
||||
* Fix default value of userSensitiveFields, fixes (#3593), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
#### Dependency Updates:
|
||||
* [body-parser@1.17.1](https://www.npmjs.com/package/body-parser)
|
||||
* [express@4.15.2](https://www.npmjs.com/package/express)
|
||||
* [request@2.81.0](https://www.npmjs.com/package/request)
|
||||
* [winston-daily-rotate-file@1.4.5](https://www.npmjs.com/package/winston-daily-rotate-file)
|
||||
* [ws@2.2.0](https://www.npmjs.com/package/ws)
|
||||
|
||||
|
||||
### 2.3.6
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.5...2.3.6)
|
||||
|
||||
#### Improvements
|
||||
* Adds support for injecting a middleware for instumentation in the CLI, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Alleviate mongodb bug with $or queries [SERVER-13732](https://jira.mongodb.org/browse/SERVER-13732), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
|
||||
|
||||
#### Bug Fixes
|
||||
* Fix issue affecting password policy and empty passwords, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
* Fix issue when logging url in non string objects, thanks to [Paulo Vítor S Reis](https://github.com/paulovitin)
|
||||
|
||||
#### Dependencies updates:
|
||||
* [ws@2.1.0](https://npmjs.com/package/ws)
|
||||
* [uws@0.13.0](https://npmjs.com/package/uws)
|
||||
* [pg-promise@5.6.2](https://npmjs.com/package/pg-promise)
|
||||
|
||||
|
||||
### 2.3.5
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.3...2.3.5)
|
||||
|
||||
#### Bug Fixes
|
||||
* Allow empty client key
|
||||
(#3497), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Fix LiveQuery unsafe user
|
||||
(#3525), thanks to [David Starke](https://github.com/dstarke)
|
||||
* Use `flushdb` instead of `flushall` in RedisCacheAdapter
|
||||
(#3523), thanks to [Jeremy Louie](https://github.com/JeremyPlease)
|
||||
* Fix saving GeoPoints and Files in `_GlobalConfig` (Make sure we don't treat
|
||||
dot notation keys as topLevel atoms)
|
||||
(#3531), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.3.3
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.2...2.3.3)
|
||||
|
||||
#### Breaking Changes
|
||||
* **Minimum Node engine bumped to 4.6** (#3480), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug Fixes
|
||||
* Add logging on failure to create file (#3424), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Log Parse Errors so they are intelligible (#3431), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* MongoDB $or Queries avoid SERVER-13732 bug (#3476), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
|
||||
* Mongo object to Parse object date serialization - avoid re-serialization of iso of type Date (#3389), thanks to [nodechefMatt](https://github.com/nodechefMatt)
|
||||
|
||||
#### Improvements
|
||||
* Ground preparations for push scalability (#3080), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Use uWS as optional dependency for ws server (#3231), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.3.2
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.1...2.3.2)
|
||||
|
||||
#### New features
|
||||
* Add parseFrameURL for masking user-facing pages (#3267), thanks to [Lenart Rudel](https://github.com/lenart)
|
||||
|
||||
#### Bug fixes
|
||||
* Fix Parse-Server to work with winston-daily-rotate-1.4.2 (#3335), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
#### Improvements
|
||||
* Add support for regex string for password policy validatorPattern setting (#3331), thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
* LiveQuery should match subobjects with dot notation (#3322), thanks to [David Starke](https://github.com/dstarke)
|
||||
* Reduce time to process high number of installations for push (#3264), thanks to [jeacott1](https://github.com/jeacott1)
|
||||
* Fix trivial typo in error message (#3238), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
### 2.3.1
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.0...2.3.1)
|
||||
|
||||
A major issue was introduced when refactoring the authentication modules.
|
||||
This release addresses only that issue.
|
||||
|
||||
### 2.3.0
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.2.25...2.3.0)
|
||||
|
||||
#### Breaking changes
|
||||
* Parse.Cloud.useMasterKey() is a no-op, please refer to (Cloud Code migration guide)[https://github.com/ParsePlatform/parse-server/wiki/Compatibility-with-Hosted-Parse#cloud-code]
|
||||
* Authentication helpers are now proper adapters, deprecates oauth option in favor of auth.
|
||||
* DEPRECATES: facebookAppIds, use `auth: { facebook: { appIds: ["AAAAAAAAA" ] } }`
|
||||
* `email` field is not returned anymore for `Parse.User` queries. (Provided only on the user itself if provided).
|
||||
|
||||
#### New Features
|
||||
* Adds ability to restrict access through Class Level Permissions to only authenticated users [see docs](http://parseplatform.github.io/docs/ios/guide/#requires-authentication-permission-requires-parse-server---230)
|
||||
* Adds ability to strip sensitive data from `_User` responses, strips emails by default, thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Adds password history support for password policies, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
|
||||
#### Improvements
|
||||
* Bump parse-server-s3-adapter to 1.0.6, thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Using PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS let you create user sessions when passing {installationId: "xxx-xxx"} on signup in cloud code, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Add CLI option to pass `host` parameter when creating parse-server from CLI, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
|
||||
#### Bug fixes
|
||||
* Ensure batch routes are only using posix paths, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Ensure falsy options from CLI are properly taken into account, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Fixes issues affecting calls to `matchesKeyInQuery` with pointers.
|
||||
* Ensure that `select` keys can be changed in triggers (beforeFind...), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
#### Housekeeping
|
||||
* Enables and enforces linting with eslint, thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
### 2.2.25
|
||||
|
||||
Postgres support requires v9.5
|
||||
|
||||
#### New Features
|
||||
* Dockerizing Parse Server, thanks to [Kirill Kravinsky](https://github.com/woyorus)
|
||||
* Login with qq, wechat, weibo, thanks to [haifeizhang]()
|
||||
* Password policy, validation and expiration, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
* Health check on /health, thanks to [Kirill Kravinsky](https://github.com/woyorus)
|
||||
* Reuse SchemaCache across requests option, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
|
||||
#### Improvements
|
||||
* Better support for CLI options, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Specity a database timeout with maxTimeMS, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Adds the username to reset password success pages, thanks to [Halim Qarroum](https://github.com/HQarroum)
|
||||
* Better support for Redis cache adapter, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Better coverage of Postgres, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
|
||||
#### Bug Fixes
|
||||
* Fixes issue when sending push to multiple installations, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fixes issues with twitter authentication, thanks to [jonas-db](https://github.com/jonas-db)
|
||||
* Ignore createdAt fields update, thanks to [Yuki Takeichi](https://github.com/yuki-takeichi)
|
||||
* Improve support for array equality with LiveQuery, thanks to [David Poetzsch-Heffter](https://github.com/dpoetzsch)
|
||||
* Improve support for batch endpoint when serverURL and publicServerURL have different paths, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Support saving relation objects, thanks to [Yuki Takeichi](https://github.com/yuki-takeichi)
|
||||
|
||||
### 2.2.24
|
||||
|
||||
#### New Features
|
||||
* LiveQuery: Bring your own adapter (#2902), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* LiveQuery: Adds "update" operator to update a query subscription (#2935), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Improvements
|
||||
* Better Postgres support, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Logs the function name when failing (#2963), thanks to [Michael Helvey](https://github.com/michaelhelvey)
|
||||
* CLI: forces closing the connections with SIGINT/SIGTERM (#2964), thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Reduce the number of calls to the `_SCHEMA` table (#2912), thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* LiveQuery: Support for Role ACL's, thanks to [Aaron Blondeau](https://github.com/aaron-blondeau-dose)
|
||||
|
||||
#### Bug Fixes
|
||||
* Better support for checking application and client keys, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Google OAuth, better support for android and web logins, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.2.23
|
||||
|
||||
* Run liveQuery server from CLI with a different port, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Support for Postgres databaseURI, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Support for Postgres options, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Improved support for google login (id_token and access_token), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Improvements with VKontakte login, thanks to [Eugene Antropov](https://github.com/antigp)
|
||||
* Improved support for `select` and `include`, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
* Fix error when updating installation with useMasterKey (#2888), thanks to [Jeremy Louie](https://github.com/JeremyPlease)
|
||||
* Fix bug affecting usage of multiple `notEqualTo`, thanks to [Jeremy Louie](https://github.com/JeremyPlease)
|
||||
* Improved support for null values in arrays, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.2.22
|
||||
|
||||
* Minimum nodejs engine is now 4.5
|
||||
|
||||
#### New Features
|
||||
* New: CLI for parse-live-query-server, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: Start parse-live-query-server for parse-server CLI, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug fixes
|
||||
* Fix: Include with pointers are not conflicting with get CLP anymore, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Removes dependency on babel-polyfill, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Support nested select calls, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Use native column selection instead of runtime, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: installationId header is properly used when updating `_Installation` objects, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: don't crash parse-server on improperly formatted live-query messages, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Passwords are properly stripped out of logs, thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Fix: Lookup for email in username if email is not set, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.2.21
|
||||
|
||||
* Fix: Reverts removal of babel-polyfill
|
||||
|
||||
### 2.2.20
|
||||
|
||||
* New: Adds CloudCode handler for `beforeFind`, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: RedisCacheAdapter for syncing schema, role and user caches across servers, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: Latest master build available at `ParsePlatform/parse-server#latest`, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Better support for upgradeToRevocableSession with missing session token, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Removes babel-polyfill runtime dependency, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Cluster option now support a boolean value for automatically choosing the right number of processes, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Filenames now appear correctly, thanks to [Lama Chandrasena](https://github.com/lama-buddy)
|
||||
* Fix: `_acl` is properly updated, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
|
||||
Other fixes by [Mathias Rangel Wulff](https://github.com/mathiasrw)
|
||||
|
||||
### 2.2.19
|
||||
|
||||
* New: support for upgrading to revocable sessions, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: NullCacheAdapter for disabling caching, thanks to [Yuki Takeichi](https://github.com/yuki-takeichi)
|
||||
* New: Account lockout policy [#2601](https://github.com/ParsePlatform/parse-server/pull/2601), thanks to [Diwakar Cherukumilli](https://github.com/cherukumilli)
|
||||
* New: Jobs endpoint for defining and run jobs (no scheduling), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: Add --cluster option to the CLI, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: Support for login with vk.com, thanks to [Nurdaulet Bolatov](https://github.com/nbolatov)
|
||||
* New: experimental support for postgres databases, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: parse-server doesn't call next() after successful responses, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Nested objects are properly includeed with Pointer Permissions on, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: null values in include calls are properly handled, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Schema validations now runs after beforeSave hooks, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: usersname and passwords are properly type checked, thanks to [Bam Wang](https://github.com/bamwang)
|
||||
* Fix: logging in info log would log also in error log, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: removes extaneous logging from ParseLiveQueryServer, thanks to [Flavio Torres](https://github.com/flavionegrao)
|
||||
* Fix: support for Range requests for files, thanks to [Brage G. Staven](https://github.com/Bragegs)
|
||||
|
||||
### 2.2.18
|
||||
|
||||
* Fix: Improve support for objects in push alert, thanks to [Antoine Lenoir](https://github.com/alenoir)
|
||||
* Fix; Prevent pointed from getting clobbered when they are changed in a beforeSave, thanks to [sud](https://github.com/sud80)
|
||||
* Fix: Improve support for "Bytes" type, thanks to [CongHoang](https://github.com/conghoang)
|
||||
* Fix: Better logging compatability with Parse.com, thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* New: Add Janrain Capture and Janrain Engage auth provider, thanks to [Andrew Lane](https://github.com/AndrewLane)
|
||||
* Improved: Include content length header in files response, thanks to [Steven Van Bael](https://github.com/vbsteven)
|
||||
* Improved: Support byte range header for files, thanks to [Brage G. Staven](https://github.com/Bragegs)
|
||||
* Improved: Validations for LinkedIn access_tokens, thanks to [Felix Dumit](https://github.com/felix-dumit)
|
||||
* Improved: Experimental postgres support, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Perf: Use native bcrypt implementation if available, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
|
||||
### [2.2.17](https://github.com/ParsePlatform/parse-server/tree/2.2.17) (07/23/2016)
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.2.16...2.2.17)
|
||||
|
||||
* Cloud code logs [\#2370](https://github.com/ParsePlatform/parse-server/pull/2370) ([flovilmart](https://github.com/flovilmart))
|
||||
* Make sure \_PushStatus operations are run in order [\#2367](https://github.com/ParsePlatform/parse-server/pull/2367) ([flovilmart](https://github.com/flovilmart))
|
||||
* Typo fix for error message when can't ensure uniqueness of user email addresses [\#2360](https://github.com/ParsePlatform/parse-server/pull/2360) ([AndrewLane](https://github.com/AndrewLane))
|
||||
* LiveQuery constrains matching fix [\#2357](https://github.com/ParsePlatform/parse-server/pull/2357) ([simonas-notcat](https://github.com/simonas-notcat))
|
||||
* Fix typo in logging for commander parseConfigFile [\#2352](https://github.com/ParsePlatform/parse-server/pull/2352) ([AndrewLane](https://github.com/AndrewLane))
|
||||
* Fix minor typos in test names [\#2351](https://github.com/ParsePlatform/parse-server/pull/2351) ([acinader](https://github.com/acinader))
|
||||
* Makes sure we don't strip authData or session token from users using masterKey [\#2348](https://github.com/ParsePlatform/parse-server/pull/2348) ([flovilmart](https://github.com/flovilmart))
|
||||
* Run coverage with istanbul [\#2340](https://github.com/ParsePlatform/parse-server/pull/2340) ([flovilmart](https://github.com/flovilmart))
|
||||
* Run next\(\) after successfully sending data to the client [\#2338](https://github.com/ParsePlatform/parse-server/pull/2338) ([blacha](https://github.com/blacha))
|
||||
* Cache all the mongodb/version folder [\#2336](https://github.com/ParsePlatform/parse-server/pull/2336) ([flovilmart](https://github.com/flovilmart))
|
||||
* updates usage of setting: emailVerifyTokenValidityDuration [\#2331](https://github.com/ParsePlatform/parse-server/pull/2331) ([cherukumilli](https://github.com/cherukumilli))
|
||||
* Update Mongodb client to 2.2.4 [\#2329](https://github.com/ParsePlatform/parse-server/pull/2329) ([flovilmart](https://github.com/flovilmart))
|
||||
* Allow usage of analytics adapter [\#2327](https://github.com/ParsePlatform/parse-server/pull/2327) ([deashay](https://github.com/deashay))
|
||||
* Fix flaky tests [\#2324](https://github.com/ParsePlatform/parse-server/pull/2324) ([flovilmart](https://github.com/flovilmart))
|
||||
* don't serve null authData values [\#2320](https://github.com/ParsePlatform/parse-server/pull/2320) ([yuzeh](https://github.com/yuzeh))
|
||||
* Fix null relation problem [\#2319](https://github.com/ParsePlatform/parse-server/pull/2319) ([flovilmart](https://github.com/flovilmart))
|
||||
* Clear the connectionPromise upon close or error [\#2314](https://github.com/ParsePlatform/parse-server/pull/2314) ([flovilmart](https://github.com/flovilmart))
|
||||
* Report validation errors with correct error code [\#2299](https://github.com/ParsePlatform/parse-server/pull/2299) ([flovilmart](https://github.com/flovilmart))
|
||||
* Parses correctly Parse.Files and Dates when sent to Cloud Code Functions [\#2297](https://github.com/ParsePlatform/parse-server/pull/2297) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adding proper generic Not Implemented. [\#2292](https://github.com/ParsePlatform/parse-server/pull/2292) ([vitaly-t](https://github.com/vitaly-t))
|
||||
* Adds schema caching capabilities \(5s by default\) [\#2286](https://github.com/ParsePlatform/parse-server/pull/2286) ([flovilmart](https://github.com/flovilmart))
|
||||
* add digits oauth provider [\#2284](https://github.com/ParsePlatform/parse-server/pull/2284) ([ranhsd](https://github.com/ranhsd))
|
||||
* Improve installations query [\#2281](https://github.com/ParsePlatform/parse-server/pull/2281) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adding request headers to cloud functions fixes \#1461 [\#2274](https://github.com/ParsePlatform/parse-server/pull/2274) ([blacha](https://github.com/blacha))
|
||||
* Creates a new sessionToken when updating password [\#2266](https://github.com/ParsePlatform/parse-server/pull/2266) ([flovilmart](https://github.com/flovilmart))
|
||||
* Add Gitter chat link to the README. [\#2264](https://github.com/ParsePlatform/parse-server/pull/2264) ([nlutsenko](https://github.com/nlutsenko))
|
||||
* Restores ability to include non pointer keys [\#2263](https://github.com/ParsePlatform/parse-server/pull/2263) ([flovilmart](https://github.com/flovilmart))
|
||||
* Allow next middleware handle error in handleParseErrors [\#2260](https://github.com/ParsePlatform/parse-server/pull/2260) ([mejcz](https://github.com/mejcz))
|
||||
* Exposes the ClientSDK infos if available [\#2259](https://github.com/ParsePlatform/parse-server/pull/2259) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adds support for multiple twitter auths options [\#2256](https://github.com/ParsePlatform/parse-server/pull/2256) ([flovilmart](https://github.com/flovilmart))
|
||||
* validate\_purchase fix for SANDBOX requests [\#2253](https://github.com/ParsePlatform/parse-server/pull/2253) ([valeryvaskabovich](https://github.com/valeryvaskabovich))
|
||||
|
||||
### 2.2.16 (7/10/2016)
|
||||
|
||||
* New: Expose InMemoryCacheAdapter publicly, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* New: Add ability to prevent login with unverified email, thanks to [Diwakar Cherukumilli](https://github.com/cherukumilli)
|
||||
* Improved: Better error message for incorrect type, thanks to [Andrew Lane](https://github.com/AndrewLane)
|
||||
* Improved: Better error message for permission denied, thanks to [Blayne Chard](https://github.com/blacha)
|
||||
* Improved: Update authData on login, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Improved: Ability to not check for old files on Parse.com, thanks to [OzgeAkin](https://github.com/OzgeAkin)
|
||||
* Fix: Issues with email adapter validation, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Fix: Issues with nested $or queries, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.2.15 (6/30/2016)
|
||||
|
||||
* Fix: Type in description for Parse.Error.INVALID_QUERY, thanks to [Andrew Lane](https://github.com/AndrewLane)
|
||||
* Improvement: Stop requiring verifyUserEmails for password reset functionality, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Improvement: Kill without validation, thanks to [Drew Gross](https://github.com/drew-gross)
|
||||
* Fix: Deleting a file does not delete from fs.files, thanks to [David Keita](https://github.com/maninga)
|
||||
* Fix: Postgres stoage adapter fix, thanks to [Vitaly Tomilov](https://github.com/vitaly-t)
|
||||
* Fix: Results invalid session when providing an invalid session token, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: issue creating an anonymous user, thanks to [Hussam Moqhim](https://github.com/hmoqhim)
|
||||
* Fix: make http response serializable, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: Add postmark email adapter alternative [Glenn Reyes](https://github.com/glennreyes)
|
||||
|
||||
### 2.2.14 (6/25/2016)
|
||||
|
||||
* Hotfix: Fix Parse.Cloud.HTTPResponse serialization
|
||||
|
||||
### 2.2.13 (6/12/2016)
|
||||
|
||||
* Hotfix: Pin version of deepcopy
|
||||
|
||||
### 2.2.12 (6/9/2016)
|
||||
|
||||
* New: Custom error codes in cloud code response.error, thanks to [Jeremy Pease](https://github.com/JeremyPlease)
|
||||
* Fix: Crash in beforeSave when response is not an object, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Fix: Allow "get" on installations
|
||||
* Fix: Fix overly restrictive Class Level Permissions, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Fix nested date parsing in Cloud Code, thanks to [Marco Cheung](https://github.com/Marco129)
|
||||
* Fix: Support very old file formats from Parse.com
|
||||
|
||||
### 2.2.11 (5/31/2016)
|
||||
|
||||
* Security: Censor user password in logs, thanks to [Marco Cheung](https://github.com/Marco129)
|
||||
* New: Add PARSE_SERVER_LOGS_FOLDER env var for setting log folder, thanks to [KartikeyaRokde](https://github.com/KartikeyaRokde)
|
||||
* New: Webhook key support, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Perf: Add cache adapter and default caching of certain objects, thanks to [Blayne Chard](https://github.com/blacha)
|
||||
* Improvement: Better error messages for schema type mismatches, thanks to [Jeremy Pease](https://github.com/JeremyPlease)
|
||||
* Improvement: Better error messages for reset password emails
|
||||
* Improvement: Webhook key support in CLI, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Fix: Remove read only fields when using beforeSave, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Fix: Use content type provided by JS SDK, thanks to [Blayne Chard](https://github.com/blacha) and [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Tell the dashboard the stored push data is available, thanks to [Jeremy Pease](https://github.com/JeremyPlease)
|
||||
* Fix: Add support for HTTP Basic Auth, thanks to [Hussam Moqhim](https://github.com/hmoqhim)
|
||||
* Fix: Support for MongoDB version 3.2.6, (note: do not use MongoDB 3.2 with migrated apps that still have traffic on Parse.com), thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Fix: Prevent `pm2` from crashing when push notifications fail, thanks to [benishak](https://github.com/benishak)
|
||||
* Fix: Add full list of default _Installation fields, thanks to [Jeremy Pease](https://github.com/JeremyPlease)
|
||||
* Fix: Strip objectId out of hooks responses, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Fix: Fix external webhook response format, thanks to [Tyler Brock](https://github.com/TylerBrock)
|
||||
* Fix: Fix beforeSave when object is passed to `success`, thanks to [Madhav Bhagat](https://github.com/codebreach)
|
||||
* Fix: Remove use of deprecated APIs, thanks to [Emad Ehsan](https://github.com/emadehsan)
|
||||
* Fix: Crash when multiple Parse Servers on the same machine try to write to the same logs folder, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Fix: Various issues with key names in `Parse.Object`s
|
||||
* Fix: Treat Bytes type properly
|
||||
* Fix: Caching bugs that caused writes by masterKey or other session token to not show up to users reading with a different session token
|
||||
* Fix: Pin mongo driver version, preventing a regression in version 2.1.19
|
||||
* Fix: Various issues with pointer fields not being treated properly
|
||||
* Fix: Issues with pointed getting un-fetched due to changes in beforeSave
|
||||
* Fix: Fixed crash when deleting classes that have CLPs
|
||||
|
||||
### 2.2.10 (5/15/2016)
|
||||
|
||||
* Fix: Write legacy ACLs to Mongo so that clients that still go through Parse.com can read them, thanks to [Tyler Brock](https://github.com/TylerBrock) and [carmenlau](https://github.com/carmenlau)
|
||||
* Fix: Querying installations with limit = 0 and count = 1 now works, thanks to [ssk7833](https://github.com/ssk7833)
|
||||
* Fix: Return correct error when violating unique index, thanks to [Marco Cheung](https://github.com/Marco129)
|
||||
* Fix: Allow unsetting user's email, thanks to [Marco Cheung](https://github.com/Marco129)
|
||||
* New: Support for Node 6.1
|
||||
|
||||
### 2.2.9 (5/9/2016)
|
||||
|
||||
* Fix: Fix a regression that caused Parse Server to crash when a null parameter is passed to a Cloud function
|
||||
|
||||
### 2.2.8 (5/8/2016)
|
||||
|
||||
* New: Support for Pointer Permissions
|
||||
* New: Expose logger in Cloud Code
|
||||
* New: Option to revoke sessions on password reset
|
||||
* New: Option to expire inactive sessions
|
||||
* Perf: Improvements in ACL checking query
|
||||
* Fix: Issues when sending pushes to list of devices that contains invalid values
|
||||
* Fix: Issues caused by using babel-polyfill outside of Parse Server, but in the same express app
|
||||
* Fix: Remove creation of extra session tokens
|
||||
* Fix: Return authData when querying with master key
|
||||
* Fix: Bugs when deleting webhooks
|
||||
* Fix: Ignore _RevocableSession header, which might be sent by the JS SDK
|
||||
* Fix: Issues with querying via URL params
|
||||
* Fix: Properly encode "Date" parameters to cloud code functions
|
||||
|
||||
|
||||
### 2.2.7 (4/15/2016)
|
||||
|
||||
* Adds support for --verbose and verbose option when running ParseServer [\#1414](https://github.com/ParsePlatform/parse-server/pull/1414) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adds limit = 0 as a valid parameter for queries [\#1493](https://github.com/ParsePlatform/parse-server/pull/1493) ([seijiakiyama](https://github.com/seijiakiyama))
|
||||
* Makes sure we preserve Installations when updating a token \(\#1475\) [\#1486](https://github.com/ParsePlatform/parse-server/pull/1486) ([flovilmart](https://github.com/flovilmart))
|
||||
* Hotfix for tests [\#1503](https://github.com/ParsePlatform/parse-server/pull/1503) ([flovilmart](https://github.com/flovilmart))
|
||||
* Enable logs [\#1502](https://github.com/ParsePlatform/parse-server/pull/1502) ([drew-gross](https://github.com/drew-gross))
|
||||
* Do some triple equals for great justice [\#1499](https://github.com/ParsePlatform/parse-server/pull/1499) ([TylerBrock](https://github.com/TylerBrock))
|
||||
* Apply credential stripping to all untransforms for \_User [\#1498](https://github.com/ParsePlatform/parse-server/pull/1498) ([TylerBrock](https://github.com/TylerBrock))
|
||||
* Checking if object has defined key for Pointer constraints in liveQuery [\#1487](https://github.com/ParsePlatform/parse-server/pull/1487) ([simonas-notcat](https://github.com/simonas-notcat))
|
||||
* Remove collection prefix and default mongo URI [\#1479](https://github.com/ParsePlatform/parse-server/pull/1479) ([drew-gross](https://github.com/drew-gross))
|
||||
* Store collection prefix in mongo adapter, and clean up adapter interface [\#1472](https://github.com/ParsePlatform/parse-server/pull/1472) ([drew-gross](https://github.com/drew-gross))
|
||||
* Move field deletion logic into mongo adapter [\#1471](https://github.com/ParsePlatform/parse-server/pull/1471) ([drew-gross](https://github.com/drew-gross))
|
||||
* Adds support for Long and Double mongodb types \(fixes \#1316\) [\#1470](https://github.com/ParsePlatform/parse-server/pull/1470) ([flovilmart](https://github.com/flovilmart))
|
||||
* Schema.js database agnostic [\#1468](https://github.com/ParsePlatform/parse-server/pull/1468) ([flovilmart](https://github.com/flovilmart))
|
||||
* Remove console.log [\#1465](https://github.com/ParsePlatform/parse-server/pull/1465) ([drew-gross](https://github.com/drew-gross))
|
||||
* Push status nits [\#1462](https://github.com/ParsePlatform/parse-server/pull/1462) ([flovilmart](https://github.com/flovilmart))
|
||||
* Fixes \#1444 [\#1451](https://github.com/ParsePlatform/parse-server/pull/1451) ([flovilmart](https://github.com/flovilmart))
|
||||
* Removing sessionToken and authData from \_User objects included in a query [\#1450](https://github.com/ParsePlatform/parse-server/pull/1450) ([simonas-notcat](https://github.com/simonas-notcat))
|
||||
* Move mongo field type logic into mongoadapter [\#1432](https://github.com/ParsePlatform/parse-server/pull/1432) ([drew-gross](https://github.com/drew-gross))
|
||||
* Prevents \_User lock out when setting ACL on signup or afterwards [\#1429](https://github.com/ParsePlatform/parse-server/pull/1429) ([flovilmart](https://github.com/flovilmart))
|
||||
* Update .travis.yml [\#1428](https://github.com/ParsePlatform/parse-server/pull/1428) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adds relation fields to objects [\#1424](https://github.com/ParsePlatform/parse-server/pull/1424) ([flovilmart](https://github.com/flovilmart))
|
||||
* Update .travis.yml [\#1423](https://github.com/ParsePlatform/parse-server/pull/1423) ([flovilmart](https://github.com/flovilmart))
|
||||
* Sets the defaultSchemas keys in the SchemaCollection [\#1421](https://github.com/ParsePlatform/parse-server/pull/1421) ([flovilmart](https://github.com/flovilmart))
|
||||
* Fixes \#1417 [\#1420](https://github.com/ParsePlatform/parse-server/pull/1420) ([drew-gross](https://github.com/drew-gross))
|
||||
* Untransform should treat Array's as nested objects [\#1416](https://github.com/ParsePlatform/parse-server/pull/1416) ([blacha](https://github.com/blacha))
|
||||
* Adds X-Parse-Push-Status-Id header [\#1412](https://github.com/ParsePlatform/parse-server/pull/1412) ([flovilmart](https://github.com/flovilmart))
|
||||
* Schema format cleanup [\#1407](https://github.com/ParsePlatform/parse-server/pull/1407) ([drew-gross](https://github.com/drew-gross))
|
||||
* Updates the publicServerURL option [\#1397](https://github.com/ParsePlatform/parse-server/pull/1397) ([flovilmart](https://github.com/flovilmart))
|
||||
* Fix exception with non-expiring session tokens. [\#1386](https://github.com/ParsePlatform/parse-server/pull/1386) ([0x18B2EE](https://github.com/0x18B2EE))
|
||||
* Move mongo schema format related logic into mongo adapter [\#1385](https://github.com/ParsePlatform/parse-server/pull/1385) ([drew-gross](https://github.com/drew-gross))
|
||||
* WIP: Huge performance improvement on roles queries [\#1383](https://github.com/ParsePlatform/parse-server/pull/1383) ([flovilmart](https://github.com/flovilmart))
|
||||
* Removes GCS Adapter from provided adapters [\#1339](https://github.com/ParsePlatform/parse-server/pull/1339) ([flovilmart](https://github.com/flovilmart))
|
||||
* DBController refactoring [\#1228](https://github.com/ParsePlatform/parse-server/pull/1228) ([flovilmart](https://github.com/flovilmart))
|
||||
* Spotify authentication [\#1226](https://github.com/ParsePlatform/parse-server/pull/1226) ([1nput0utput](https://github.com/1nput0utput))
|
||||
* Expose DatabaseAdapter to simplify application tests [\#1121](https://github.com/ParsePlatform/parse-server/pull/1121) ([steven-supersolid](https://github.com/steven-supersolid))
|
||||
|
||||
### 2.2.6 (4/5/2016)
|
||||
|
||||
* Important Fix: Disables find on installation from clients [\#1374](https://github.com/ParsePlatform/parse-server/pull/1374) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adds missing options to the CLI [\#1368](https://github.com/ParsePlatform/parse-server/pull/1368) ([flovilmart](https://github.com/flovilmart))
|
||||
* Removes only master on travis [\#1367](https://github.com/ParsePlatform/parse-server/pull/1367) ([flovilmart](https://github.com/flovilmart))
|
||||
* Auth.\_loadRoles should not query the same role twice. [\#1366](https://github.com/ParsePlatform/parse-server/pull/1366) ([blacha](https://github.com/blacha))
|
||||
|
||||
### 2.2.5 (4/4/2016)
|
||||
|
||||
* Improves config loading and tests [\#1363](https://github.com/ParsePlatform/parse-server/pull/1363) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adds travis configuration to deploy NPM on new version tags [\#1361](https://github.com/ParsePlatform/parse-server/pull/1361) ([gfosco](https://github.com/gfosco))
|
||||
* Inject the default schemas properties when loading it [\#1357](https://github.com/ParsePlatform/parse-server/pull/1357) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adds console transport when testing with VERBOSE=1 [\#1351](https://github.com/ParsePlatform/parse-server/pull/1351) ([flovilmart](https://github.com/flovilmart))
|
||||
* Make notEqual work on relations [\#1350](https://github.com/ParsePlatform/parse-server/pull/1350) ([flovilmart](https://github.com/flovilmart))
|
||||
* Accept only bool for $exists in LiveQuery [\#1315](https://github.com/ParsePlatform/parse-server/pull/1315) ([drew-gross](https://github.com/drew-gross))
|
||||
* Adds more options when using CLI/config [\#1305](https://github.com/ParsePlatform/parse-server/pull/1305) ([flovilmart](https://github.com/flovilmart))
|
||||
* Update error message [\#1297](https://github.com/ParsePlatform/parse-server/pull/1297) ([drew-gross](https://github.com/drew-gross))
|
||||
* Properly let masterKey add fields [\#1291](https://github.com/ParsePlatform/parse-server/pull/1291) ([flovilmart](https://github.com/flovilmart))
|
||||
* Point to \#1271 as how to write a good issue report [\#1290](https://github.com/ParsePlatform/parse-server/pull/1290) ([drew-gross](https://github.com/drew-gross))
|
||||
* Adds ability to override mount with publicServerURL for production uses [\#1287](https://github.com/ParsePlatform/parse-server/pull/1287) ([flovilmart](https://github.com/flovilmart))
|
||||
* Single object queries to use include and keys [\#1280](https://github.com/ParsePlatform/parse-server/pull/1280) ([jeremyjackson89](https://github.com/jeremyjackson89))
|
||||
* Improves report for Push error in logs and \_PushStatus [\#1269](https://github.com/ParsePlatform/parse-server/pull/1269) ([flovilmart](https://github.com/flovilmart))
|
||||
* Removes all stdout/err logs while testing [\#1268](https://github.com/ParsePlatform/parse-server/pull/1268) ([flovilmart](https://github.com/flovilmart))
|
||||
* Matching queries with doesNotExist constraint [\#1250](https://github.com/ParsePlatform/parse-server/pull/1250) ([andrecardoso](https://github.com/andrecardoso))
|
||||
* Added session length option for session tokens to server configuration [\#997](https://github.com/ParsePlatform/parse-server/pull/997) ([Kenishi](https://github.com/Kenishi))
|
||||
* Regression test for \#1259 [\#1286](https://github.com/ParsePlatform/parse-server/pull/1286) ([drew-gross](https://github.com/drew-gross))
|
||||
* Regression test for \#871 [\#1283](https://github.com/ParsePlatform/parse-server/pull/1283) ([drew-gross](https://github.com/drew-gross))
|
||||
* Add a test to repro \#701 [\#1281](https://github.com/ParsePlatform/parse-server/pull/1281) ([drew-gross](https://github.com/drew-gross))
|
||||
* Fix for \#1334: using relative cloud code files broken [\#1353](https://github.com/ParsePlatform/parse-server/pull/1353) ([airdrummingfool](https://github.com/airdrummingfool))
|
||||
* Fix Issue/1288 [\#1346](https://github.com/ParsePlatform/parse-server/pull/1346) ([flovilmart](https://github.com/flovilmart))
|
||||
* Fixes \#1271 [\#1295](https://github.com/ParsePlatform/parse-server/pull/1295) ([drew-gross](https://github.com/drew-gross))
|
||||
* Fixes issue \#1302 [\#1314](https://github.com/ParsePlatform/parse-server/pull/1314) ([flovilmart](https://github.com/flovilmart))
|
||||
* Fixes bug related to include in queries [\#1312](https://github.com/ParsePlatform/parse-server/pull/1312) ([flovilmart](https://github.com/flovilmart))
|
||||
|
||||
|
||||
### 2.2.4 (3/29/2016)
|
||||
|
||||
* Hotfix: fixed imports issue for S3Adapter, GCSAdapter, FileSystemAdapter [\#1263](https://github.com/ParsePlatform/parse-server/pull/1263) ([drew-gross](https://github.com/drew-gross)
|
||||
* Fix: Clean null authData values on _User update [\#1199](https://github.com/ParsePlatform/parse-server/pull/1199) ([yuzeh](https://github.com/yuzeh))
|
||||
|
||||
### 2.2.3 (3/29/2016)
|
||||
|
||||
* Fixed bug with invalid email verification link on email update. [\#1253](https://github.com/ParsePlatform/parse-server/pull/1253) ([kzielonka](https://github.com/kzielonka))
|
||||
@@ -20,7 +474,7 @@
|
||||
|
||||
* Important Fix: Mounts createLiveQueryServer, fix babel induced problem [\#1153](https://github.com/ParsePlatform/parse-server/pull/1153) (flovilmart)
|
||||
* Move ParseServer to it's own file [\#1166](https://github.com/ParsePlatform/parse-server/pull/1166) (flovilmart)
|
||||
* Update README.md - remove deploy buttons - replace with community links [\#1139](https://github.com/ParsePlatform/parse-server/pull/1139) (drew-gross)
|
||||
* Update README.md * remove deploy buttons * replace with community links [\#1139](https://github.com/ParsePlatform/parse-server/pull/1139) (drew-gross)
|
||||
* Adds bootstrap.sh [\#1138](https://github.com/ParsePlatform/parse-server/pull/1138) (flovilmart)
|
||||
* Fix: Do not override username [\#1142](https://github.com/ParsePlatform/parse-server/pull/1142) (flovilmart)
|
||||
* Fix: Add pushId back to GCM payload [\#1168](https://github.com/ParsePlatform/parse-server/pull/1168) (wangmengyan95)
|
||||
@@ -204,5 +658,3 @@
|
||||
* Fix: Various files related bugs
|
||||
* Fix: Force minimum node version 4.3 due to security issues in earlier version
|
||||
* Performance Improvement: Improved caching
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ We really want Parse to be yours, to see it grow and thrive in the open source c
|
||||
* Take testing seriously! Aim to increase the test coverage with every pull request.
|
||||
* Run the tests for the file you are working on with `npm test spec/MyFile.spec.js`
|
||||
* Run the tests for the whole project and look at the coverage report to make sure your tests are exhaustive by running `npm test` and looking at (project-root)/lcov-report/parse-server/FileUnderTest.js.html
|
||||
* Lint your code by running `npm run lint` to make sure all your code is not gonna be rejected by the CI.
|
||||
|
||||
##### Code of Conduct
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
FROM node:boron
|
||||
|
||||
RUN mkdir -p /parse-server
|
||||
COPY ./ /parse-server/
|
||||
|
||||
RUN mkdir -p /parse-server/config
|
||||
VOLUME /parse-server/config
|
||||
|
||||
RUN mkdir -p /parse-server/cloud
|
||||
VOLUME /parse-server/cloud
|
||||
|
||||
WORKDIR /parse-server
|
||||
|
||||
RUN npm install && \
|
||||
npm run build
|
||||
|
||||
ENV PORT=1337
|
||||
|
||||
EXPOSE $PORT
|
||||
|
||||
ENTRYPOINT ["npm", "start", "--"]
|
||||
@@ -4,22 +4,34 @@
|
||||
[](https://codecov.io/github/ParsePlatform/parse-server?branch=master)
|
||||
[](https://www.npmjs.com/package/parse-server)
|
||||
|
||||
[](https://gitter.im/ParsePlatform/Chat)
|
||||
|
||||
Parse Server is an [open source version of the Parse backend](http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/) that can be deployed to any infrastructure that can run Node.js.
|
||||
|
||||
Parse Server works with the Express web application framework. It can be added to existing web applications, or run by itself.
|
||||
|
||||
# Getting Started
|
||||
|
||||
April 2016 - We created a series of video screencasts, please check them out here: [http://blog.parse.com/learn/parse-server-video-series-april-2016/](http://blog.parse.com/learn/parse-server-video-series-april-2016/)
|
||||
|
||||
The fastest and easiest way to get started is to run MongoDB and Parse Server locally.
|
||||
|
||||
## Running Parse Server locally
|
||||
## Running Parse Server
|
||||
|
||||
### Locally
|
||||
```
|
||||
$ npm install -g parse-server mongodb-runner
|
||||
$ mongodb-runner start
|
||||
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY
|
||||
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test
|
||||
```
|
||||
|
||||
### Inside a Docker container
|
||||
```
|
||||
$ docker build --tag parse-server .
|
||||
$ docker run --name my-mongo -d mongo
|
||||
$ docker run --name my-parse-server --link my-mongo:mongo 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.
|
||||
|
||||
That's it! You are now running a standalone version of Parse Server on your machine.
|
||||
@@ -104,14 +116,15 @@ Once you have a better understanding of how the project works, please refer to t
|
||||
|
||||
We have provided a basic [Node.js application](https://github.com/ParsePlatform/parse-server-example) that uses the Parse Server module on Express and can be easily deployed to various infrastructure providers:
|
||||
|
||||
* [Heroku and mLab](https://github.com/ParsePlatform/parse-server/wiki/Deploying-Parse-Server#deploying-to-heroku-and-mLab)
|
||||
* [Heroku and mLab](https://devcenter.heroku.com/articles/deploying-a-parse-server-to-heroku)
|
||||
* [AWS and Elastic Beanstalk](http://mobile.awsblog.com/post/TxCD57GZLM2JR/How-to-set-up-Parse-Server-on-AWS-using-AWS-Elastic-Beanstalk)
|
||||
* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-run-parse-server-on-ubuntu-14-04)
|
||||
* [NodeChef](https://nodechef.com/blog/post/6/migrate-from-parse-to-nodechef%E2%80%99s-managed-parse-server)
|
||||
* [Google App Engine](https://medium.com/@justinbeckwith/deploying-parse-server-to-google-app-engine-6bc0b7451d50)
|
||||
* [Microsoft Azure](https://azure.microsoft.com/en-us/blog/azure-welcomes-parse-developers/)
|
||||
* [SashiDo](https://blog.sashido.io/tag/migration/)
|
||||
* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-run-parse-server-on-ubuntu-14-04)
|
||||
* [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)
|
||||
|
||||
### Parse Server + Express
|
||||
|
||||
@@ -141,13 +154,27 @@ 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 new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc.)?** Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY`
|
||||
|
||||
# Documentation
|
||||
|
||||
The full documentation for Parse Server is available in the [wiki](https://github.com/ParsePlatform/parse-server/wiki). The [Parse Server guide](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide) is a good place to get started. If you're interested in developing for Parse Server, the [Development guide](https://github.com/ParsePlatform/parse-server/wiki/Development-Guide) will help you get set up.
|
||||
|
||||
## Migrating an Existing Parse App
|
||||
|
||||
The hosted version of Parse will be fully retired on January 28th, 2017. If you are planning to migrate an app, you need to begin work as soon as possible. There are a few areas where Parse Server does not provide compatibility with the hosted version of Parse. Learn more in the [Migration guide](https://github.com/ParsePlatform/parse-server/wiki/Migrating-an-Existing-Parse-App).
|
||||
The hosted version of Parse will be fully retired on January 28th, 2017. If you are planning to migrate an app, you need to begin work as soon as possible. There are a few areas where Parse Server does not provide compatibility with the hosted version of Parse. Learn more in the [Migration guide](https://parse.com/migration).
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -159,7 +186,7 @@ For the full list of available options, run `parse-server --help`.
|
||||
|
||||
* `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.
|
||||
* `databaseURI` **(required)** - The connection string for your database, i.e. `mongodb://user:pass@host.com/dbname`.
|
||||
* `databaseURI` **(required)** - The connection string for your database, i.e. `mongodb://user:pass@host.com/dbname`. Be sure to [URL encode your password](https://app.zencoder.com/docs/guides/getting-started/special-characters-in-usernames-and-passwords) if your password has special characters.
|
||||
* `port` - The default port is 1337, specify this parameter to use a different port.
|
||||
* `serverURL` - URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code.
|
||||
* `cloud` - The absolute path to your cloud code `main.js` file.
|
||||
@@ -179,26 +206,58 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
|
||||
* `fileKey` - For migrated apps, this is necessary to provide access to files already hosted on Parse.
|
||||
* `allowClientClassCreation` - Set to false to disable client class creation. Defaults to true.
|
||||
* `enableAnonymousUsers` - Set to false to disable anonymous users. Defaults to true.
|
||||
* `oauth` - Used to configure support for [3rd party authentication](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#oauth).
|
||||
* `auth` - Used to configure support for [3rd party authentication](https://github.com/ParsePlatform/parse-server/wiki/OAuth--and-Custom-Authentication).
|
||||
* `facebookAppIds` - An array of valid Facebook application IDs that users may authenticate with.
|
||||
* `mountPath` - Mount path for the server. Defaults to `/parse`.
|
||||
* `filesAdapter` - The default behavior (GridStore) can be changed by creating an adapter class (see [`FilesAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js)).
|
||||
* `maxUploadSize` - Max file size for uploads. Defaults to 20 MB.
|
||||
* `loggerAdapter` - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)).
|
||||
* `databaseAdapter` - The backing store can be changed by creating an adapter class (see `DatabaseAdapter.js`). Defaults to `MongoStorageAdapter`.
|
||||
* `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
|
||||
|
||||
Verifying user email addresses and enabling password reset via email requries 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:
|
||||
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:
|
||||
|
||||
```js
|
||||
var server = ParseServer({
|
||||
...otherOptions,
|
||||
// Enable email verification
|
||||
verifyUserEmails: true,
|
||||
|
||||
// if `verifyUserEmails` is `true` and
|
||||
// if `emailVerifyTokenValidityDuration` is `undefined` then
|
||||
// email verify token never expires
|
||||
// else
|
||||
// email verify token expires after `emailVerifyTokenValidityDuration`
|
||||
//
|
||||
// `emailVerifyTokenValidityDuration` defaults to `undefined`
|
||||
//
|
||||
// email verify token below expires in 2 hours (= 2 * 60 * 60 == 7200 seconds)
|
||||
emailVerifyTokenValidityDuration: 2 * 60 * 60, // in seconds (2 hours = 7200 seconds)
|
||||
|
||||
// set preventLoginWithUnverifiedEmail to false to allow user to login without verifying their email
|
||||
// set preventLoginWithUnverifiedEmail to true to prevent user from login if their email is not verified
|
||||
preventLoginWithUnverifiedEmail: false, // defaults to false
|
||||
|
||||
// The public URL of your app.
|
||||
// This will appear in the link that is used to verify email addresses and reset passwords.
|
||||
publicServerURL: 'https://example.com',
|
||||
// Set the mount path as it is in serverURL
|
||||
publicServerURL: 'https://example.com/parse',
|
||||
// Your apps name. This will appear in the subject and body of the emails that are sent.
|
||||
appName: 'Parse App',
|
||||
// The email adapter
|
||||
@@ -212,11 +271,39 @@ var server = ParseServer({
|
||||
// Your API key from mailgun.com
|
||||
apiKey: 'key-mykey',
|
||||
}
|
||||
},
|
||||
|
||||
// account lockout policy setting (OPTIONAL) - defaults to undefined
|
||||
// if the account lockout policy is set and there are more than `threshold` number of failed login attempts then the `login` api call returns error code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts. Please try again after <duration> minute(s)`. After `duration` minutes of no login attempts, the application will allow the user to try login again.
|
||||
accountLockout: {
|
||||
duration: 5, // duration policy setting determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000.
|
||||
threshold: 3, // threshold policy setting determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000.
|
||||
},
|
||||
// optional settings to enforce password policies
|
||||
passwordPolicy: {
|
||||
// Two optional settings to enforce strong passwords. Either one or both can be specified.
|
||||
// If both are specified, both checks must pass to accept the password
|
||||
// 1. a RegExp object or a regex string representing the pattern to enforce
|
||||
validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit
|
||||
// 2. a callback function to be invoked to validate the password
|
||||
validatorCallback: (password) => { return validatePassword(password) },
|
||||
doNotAllowUsername: true, // optional setting to disallow username in passwords
|
||||
maxPasswordAge: 90, // optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset.
|
||||
maxPasswordHistory: 5, // optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history.
|
||||
//optional setting to set a validity duration for password reset links (in seconds)
|
||||
resetTokenValidityDuration: 24*60*60, // expire after 24 hours
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You can also use other email adapters contributed by the community such as [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter).
|
||||
You can also use other email adapters contributed by the community such as:
|
||||
- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter)
|
||||
- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter)
|
||||
- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter)
|
||||
- [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-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter)
|
||||
- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter)
|
||||
|
||||
### Using environment variables to configure Parse Server
|
||||
|
||||
@@ -239,6 +326,9 @@ $ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY
|
||||
|
||||
For the full list of configurable environment variables, run `parse-server --help`.
|
||||
|
||||
### Available Adapters
|
||||
[Parse Server Modules (Adapters)](https://github.com/parse-server-modules)
|
||||
|
||||
### Configuring File Adapters
|
||||
|
||||
Parse Server allows developers to choose from several options when hosting files:
|
||||
@@ -261,6 +351,18 @@ If you believe you've found an issue with Parse Server, make sure these boxes ar
|
||||
|
||||
- [ ] You've searched through [existing issues](https://github.com/ParsePlatform/parse-server/issues?utf8=%E2%9C%93&q=). Chances are that your issue has been reported or resolved before.
|
||||
|
||||
# Want to ride the bleeding edge?
|
||||
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
# 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).
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require("../lib/cli/parse-live-query-server");
|
||||
+60
-14
@@ -4,7 +4,7 @@ GREEN='\033[0;32m'
|
||||
NC='\033[0m'
|
||||
BOLD='\033[1m'
|
||||
CHECK="${GREEN}\xE2\x9C\x93${NC}"
|
||||
DEFAULT_MONGODB_URI='mongodb://localhost:127.0.0.1:27017/parse'
|
||||
DEFAULT_MONGODB_URI='mongodb://127.0.0.1:27017/parse'
|
||||
|
||||
confirm() {
|
||||
DEFAULT=$1;
|
||||
@@ -53,15 +53,60 @@ check_npm() {
|
||||
|
||||
|
||||
echo ''
|
||||
echo 'This will setup parse-server in the current directory'
|
||||
echo '
|
||||
`.-://////:-..`
|
||||
`:/oooooooooooooooo+:.`
|
||||
`:+oooooooooooooooooooooo+/`
|
||||
:+ooooooooooooooooooooooooooo/.
|
||||
.+oooooooooooooo/:.....-:+ooooooo-
|
||||
.+ooooooooooooo/` .:///:-` -+oooooo:
|
||||
`+ooooooooooooo: `/ooooooo+- `ooooooo-
|
||||
:oooooooooooooo :ooooooooo+` /oooooo+
|
||||
+ooooooooooooo/ +ooooooooo+ /ooooooo.
|
||||
oooooooooooooo+ ooooooooo` .oooooooo.
|
||||
+ooooooooooo+/: `ooooooo` .:ooooooooo.
|
||||
:ooooooo+.````````````` /+oooooooooo+
|
||||
`+oooooo- `ooo+ /oooooooooooooooooooo-
|
||||
.+ooooo/ :/:` -ooooooooooooooooooo:
|
||||
.+ooooo+:-..-/ooooooooooooooooooo-
|
||||
:+ooooooooooooooooooooooooooo/.
|
||||
`:+oooooooooooooooooooooo+/`
|
||||
`:/oooooooooooooooo+:.`
|
||||
`.-://////:-..`
|
||||
|
||||
parse-server
|
||||
|
||||
'
|
||||
|
||||
|
||||
INSTALL_DIR=""
|
||||
printf "Enter an installation directory\n"
|
||||
printf "(%s): " "${PWD}"
|
||||
read -r INSTALL_DIR
|
||||
|
||||
if [ "$INSTALL_DIR" = "" ]; then
|
||||
INSTALL_DIR="${PWD}"
|
||||
fi
|
||||
|
||||
echo ''
|
||||
printf "This will setup parse-server in %s\n" "${INSTALL_DIR}"
|
||||
confirm 'Y' 'Do you want to continue? (Y/n): '
|
||||
|
||||
check_node
|
||||
check_npm
|
||||
|
||||
echo "Setting up parse-server in $PWD"
|
||||
printf "Setting up parse-server in %s\n" "${INSTALL_DIR}"
|
||||
|
||||
if [ -f './package.json' ]; then
|
||||
if [ -d "${INSTALL_DIR}" ]; then
|
||||
echo "${CHECK} ${INSTALL_DIR} exists"
|
||||
else
|
||||
mkdir -p "${INSTALL_DIR}"
|
||||
echo "${CHECK} Created ${INSTALL_DIR}"
|
||||
fi
|
||||
|
||||
cd "${INSTALL_DIR}"
|
||||
|
||||
if [ -f "package.json" ]; then
|
||||
echo "\n${RED}package.json exists${NC}"
|
||||
confirm 'N' "Do you want to continue? \n${RED}this will erase your configuration${NC} (y/N): "
|
||||
fi
|
||||
@@ -77,33 +122,33 @@ i=0
|
||||
while [ "$APP_NAME" = "" ]
|
||||
do
|
||||
[[ $i != 0 ]] && printf "${RED}An application name is required!${NC}\n"
|
||||
printf 'Enter your Application Name: '
|
||||
printf "Enter your ${BOLD}Application Name${NC}: "
|
||||
read -r APP_NAME
|
||||
i=$(($i+1))
|
||||
done
|
||||
|
||||
printf 'Enter your appId (leave empty to generate): '
|
||||
printf "Enter your ${BOLD}Application Id${NC} (leave empty to generate): "
|
||||
read -r APP_ID
|
||||
|
||||
[[ $APP_ID = '' ]] && APP_ID=$(genstring) && printf "\n$APP_ID\n\n"
|
||||
|
||||
printf 'Enter your masterKey (leave empty to generate): '
|
||||
printf "Enter your ${BOLD}Master Key${NC} (leave empty to generate): "
|
||||
read -r MASTER_KEY
|
||||
|
||||
[[ $MASTER_KEY = '' ]] && MASTER_KEY=$(genstring) && printf "\n$MASTER_KEY\n\n"
|
||||
|
||||
printf "Enter your mongodbURI (%s): " $DEFAULT_MONGODB_URI
|
||||
printf "Enter your ${BOLD}mongodbURI${NC} (%s): " $DEFAULT_MONGODB_URI
|
||||
read -r MONGODB_URI
|
||||
|
||||
[[ $MONGODB_URI = '' ]] && MONGODB_URI="$DEFAULT_MONGODB_URI"
|
||||
|
||||
cat > ./config.json << EOF
|
||||
{
|
||||
"appId": "$APP_ID",
|
||||
"masterKey": "$MASTER_KEY",
|
||||
"appName": "$APP_NAME",
|
||||
"appId": "${APP_ID}",
|
||||
"masterKey": "${MASTER_KEY}",
|
||||
"appName": "${APP_NAME}",
|
||||
"cloud": "./cloud/main",
|
||||
"mongodbURI": "$MONGODB_URI"
|
||||
"databaseURI": "${MONGODB_URI}"
|
||||
}
|
||||
EOF
|
||||
echo "${CHECK} Created config.json"
|
||||
@@ -113,8 +158,9 @@ NPM_APP_NAME=$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
|
||||
cat > ./package.json << EOF
|
||||
{
|
||||
"name": "$NPM_APP_NAME",
|
||||
"description": "parse-server for $APP_NAME",
|
||||
"scripts": {
|
||||
"start": "parse-server ./config.json"
|
||||
"start": "parse-server config.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"parse-server": "^2.0.0"
|
||||
@@ -149,7 +195,7 @@ fi
|
||||
|
||||
echo "\n${CHECK} running npm install\n"
|
||||
|
||||
npm install
|
||||
npm install -s
|
||||
|
||||
CURL_CMD=$(cat << EOF
|
||||
curl -X POST -H 'X-Parse-Application-Id: ${APP_ID}' \\
|
||||
|
||||
+58
-49
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.2.3",
|
||||
"version": "2.3.7",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -18,65 +18,74 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.5.0",
|
||||
"babel-runtime": "^6.5.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"body-parser": "^1.14.2",
|
||||
"colors": "^1.1.2",
|
||||
"commander": "^2.9.0",
|
||||
"deepcopy": "^0.6.1",
|
||||
"express": "^4.13.4",
|
||||
"lru-cache": "^4.0.0",
|
||||
"mailgun-js": "^0.7.7",
|
||||
"mime": "^1.3.4",
|
||||
"mongodb": "~2.1.0",
|
||||
"multer": "^1.1.0",
|
||||
"parse": "^1.8.0",
|
||||
"parse-server-fs-adapter": "^1.0.0",
|
||||
"parse-server-gcs-adapter": "^1.0.0",
|
||||
"parse-server-push-adapter": "^1.0.0",
|
||||
"parse-server-s3-adapter": "^1.0.0",
|
||||
"parse-server-simple-mailgun-adapter": "^1.0.0",
|
||||
"redis": "^2.5.0-1",
|
||||
"request": "^2.65.0",
|
||||
"tv4": "^1.2.7",
|
||||
"winston": "^2.1.1",
|
||||
"winston-daily-rotate-file": "^1.0.1",
|
||||
"ws": "^1.0.1"
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.17.1",
|
||||
"commander": "2.9.0",
|
||||
"deepcopy": "0.6.3",
|
||||
"express": "4.15.2",
|
||||
"intersect": "1.0.1",
|
||||
"lodash": "4.17.4",
|
||||
"lru-cache": "4.0.2",
|
||||
"mime": "1.3.4",
|
||||
"mongodb": "2.2.24",
|
||||
"multer": "1.3.0",
|
||||
"parse": "1.9.2",
|
||||
"parse-server-fs-adapter": "1.0.1",
|
||||
"parse-server-push-adapter": "1.2.0",
|
||||
"parse-server-s3-adapter": "1.0.6",
|
||||
"parse-server-simple-mailgun-adapter": "1.0.0",
|
||||
"pg-promise": "5.6.2",
|
||||
"redis": "2.6.5",
|
||||
"request": "2.81.0",
|
||||
"semver": "5.2.0",
|
||||
"tv4": "1.2.7",
|
||||
"winston": "2.3.1",
|
||||
"winston-daily-rotate-file": "1.4.5",
|
||||
"ws": "2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.5.1",
|
||||
"babel-core": "^6.5.1",
|
||||
"babel-istanbul": "^0.6.0",
|
||||
"babel-plugin-syntax-flow": "^6.5.0",
|
||||
"babel-plugin-transform-flow-strip-types": "^6.5.0",
|
||||
"babel-preset-es2015": "^6.5.0",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"babel-register": "^6.5.1",
|
||||
"codecov": "^1.0.1",
|
||||
"cross-env": "^1.0.7",
|
||||
"deep-diff": "^0.3.3",
|
||||
"flow-bin": "^0.22.0",
|
||||
"gaze": "^0.5.2",
|
||||
"jasmine": "^2.3.2",
|
||||
"mongodb-runner": "3.1.15",
|
||||
"nodemon": "^1.8.1"
|
||||
|
||||
"babel-cli": "6.23.0",
|
||||
"babel-core": "6.23.1",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-plugin-syntax-flow": "6.13.0",
|
||||
"babel-plugin-transform-flow-strip-types": "6.22.0",
|
||||
"babel-preset-es2015": "6.22.0",
|
||||
"babel-preset-stage-0": "6.22.0",
|
||||
"babel-register": "6.23.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"cross-env": "3.1.4",
|
||||
"deep-diff": "0.3.4",
|
||||
"eslint": "^3.16.1",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
"gaze": "1.1.1",
|
||||
"istanbul": "1.0.0-alpha.1",
|
||||
"jasmine": "2.5.3",
|
||||
"jasmine-spec-reporter": "^3.1.0",
|
||||
"mongodb-runner": "3.4.0",
|
||||
"nodemon": "1.11.0",
|
||||
"request-promise": "4.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm run build && node bin/dev",
|
||||
"build": "./node_modules/.bin/babel src/ -d lib/",
|
||||
"pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} ./node_modules/.bin/mongodb-runner start",
|
||||
"test": "cross-env NODE_ENV=test TESTING=1 ./node_modules/.bin/babel-node $COVERAGE_OPTION ./node_modules/jasmine/bin/jasmine.js",
|
||||
"test:win": "npm run pretest && cross-env NODE_ENV=test TESTING=1 ./node_modules/.bin/babel-node ./node_modules/babel-istanbul/lib/cli.js cover -x **/spec/** ./node_modules/jasmine/bin/jasmine.js && npm run posttest",
|
||||
"posttest": "./node_modules/.bin/mongodb-runner stop",
|
||||
"coverage": "cross-env COVERAGE_OPTION='./node_modules/babel-istanbul/lib/cli.js cover -x **/spec/**' npm test",
|
||||
"lint": "eslint --cache ./",
|
||||
"build": "babel src/ -d lib/ --copy-files",
|
||||
"pretest": "npm run lint",
|
||||
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 $COVERAGE_OPTION jasmine",
|
||||
"test:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 jasmine",
|
||||
"coverage": "cross-env COVERAGE_OPTION='./node_modules/.bin/istanbul cover' npm test",
|
||||
"coverage:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/jasmine/bin/jasmine.js",
|
||||
"start": "node ./bin/parse-server",
|
||||
"prepublish": "npm run build"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.3"
|
||||
"node": ">=4.6"
|
||||
},
|
||||
"bin": {
|
||||
"parse-server": "./bin/parse-server"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "1.0.2",
|
||||
"uws": "^0.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
docker login -e="$DOCKER_EMAIL" -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
|
||||
docker build -t parseplatform/parse-server:$TRAVIS_TAG .
|
||||
docker push parseplatform/parse-server:$TRAVIS_TAG
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/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/parseplatform/parse-server.git" master:latest
|
||||
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
https://github.com/dmakhno/travis_after_all/blob/master/travis_after_all.py
|
||||
https://github.com/graphql/graphql-js/blob/master/resources/travis_after_all.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
|
||||
try:
|
||||
import urllib.request as urllib2
|
||||
except ImportError:
|
||||
import urllib2
|
||||
|
||||
log = logging.getLogger("travis.leader")
|
||||
log.addHandler(logging.StreamHandler())
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
TRAVIS_JOB_NUMBER = 'TRAVIS_JOB_NUMBER'
|
||||
TRAVIS_BUILD_ID = 'TRAVIS_BUILD_ID'
|
||||
POLLING_INTERVAL = 'LEADER_POLLING_INTERVAL'
|
||||
|
||||
build_id = os.getenv(TRAVIS_BUILD_ID)
|
||||
polling_interval = int(os.getenv(POLLING_INTERVAL, '5'))
|
||||
|
||||
#assume, first job is the leader
|
||||
is_leader = lambda job_number: job_number.endswith('.1')
|
||||
|
||||
if not os.getenv(TRAVIS_JOB_NUMBER):
|
||||
# seems even for builds with only one job, this won't get here
|
||||
log.fatal("Don't use defining leader for build without matrix")
|
||||
exit(1)
|
||||
elif is_leader(os.getenv(TRAVIS_JOB_NUMBER)):
|
||||
log.info("This is a leader")
|
||||
else:
|
||||
#since python is subprocess, env variables are exported back via file
|
||||
with open(".to_export_back", "w") as export_var:
|
||||
export_var.write("BUILD_MINION=YES")
|
||||
log.info("This is a minion")
|
||||
exit(0)
|
||||
|
||||
|
||||
class MatrixElement(object):
|
||||
def __init__(self, json_raw):
|
||||
self.is_finished = json_raw['finished_at'] is not None
|
||||
self.is_succeeded = json_raw['result'] == 0
|
||||
self.number = json_raw['number']
|
||||
self.is_leader = is_leader(self.number)
|
||||
|
||||
|
||||
def matrix_snapshot():
|
||||
"""
|
||||
:return: Matrix List
|
||||
"""
|
||||
response = urllib2.build_opener().open("https://api.travis-ci.org/builds/{0}".format(build_id)).read()
|
||||
raw_json = json.loads(response)
|
||||
matrix_without_leader = [MatrixElement(element) for element in raw_json["matrix"]]
|
||||
return matrix_without_leader
|
||||
|
||||
|
||||
def wait_others_to_finish():
|
||||
def others_finished():
|
||||
"""
|
||||
Dumps others to finish
|
||||
Leader cannot finish, it is working now
|
||||
:return: tuple(True or False, List of not finished jobs)
|
||||
"""
|
||||
snapshot = matrix_snapshot()
|
||||
finished = [el.is_finished for el in snapshot if not el.is_leader]
|
||||
return reduce(lambda a, b: a and b, finished), [el.number for el in snapshot if
|
||||
not el.is_leader and not el.is_finished]
|
||||
|
||||
while True:
|
||||
finished, waiting_list = others_finished()
|
||||
if finished: break
|
||||
log.info("Leader waits for minions {0}...".format(waiting_list)) # just in case do not get "silence timeout"
|
||||
time.sleep(polling_interval)
|
||||
|
||||
|
||||
try:
|
||||
wait_others_to_finish()
|
||||
|
||||
final_snapshot = matrix_snapshot()
|
||||
log.info("Final Results: {0}".format([(e.number, e.is_succeeded) for e in final_snapshot]))
|
||||
|
||||
BUILD_AGGREGATE_STATUS = 'BUILD_AGGREGATE_STATUS'
|
||||
others_snapshot = [el for el in final_snapshot if not el.is_leader]
|
||||
if reduce(lambda a, b: a and b, [e.is_succeeded for e in others_snapshot]):
|
||||
os.environ[BUILD_AGGREGATE_STATUS] = "others_succeeded"
|
||||
elif reduce(lambda a, b: a and b, [not e.is_succeeded for e in others_snapshot]):
|
||||
log.error("Others Failed")
|
||||
os.environ[BUILD_AGGREGATE_STATUS] = "others_failed"
|
||||
else:
|
||||
log.warn("Others Unknown")
|
||||
os.environ[BUILD_AGGREGATE_STATUS] = "unknown"
|
||||
#since python is subprocess, env variables are exported back via file
|
||||
with open(".to_export_back", "w") as export_var:
|
||||
export_var.write("BUILD_LEADER=YES {0}={1}".format(BUILD_AGGREGATE_STATUS, os.environ[BUILD_AGGREGATE_STATUS]))
|
||||
|
||||
except Exception as e:
|
||||
log.fatal(e)
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"env": {
|
||||
"jasmine": true
|
||||
},
|
||||
"globals": {
|
||||
"Parse": true,
|
||||
"reconfigureServer": true,
|
||||
"createTestUser": true,
|
||||
"jfail": true,
|
||||
"ok": true,
|
||||
"strictEqual": true,
|
||||
"TestObject": true,
|
||||
"Item": true,
|
||||
"Container": true,
|
||||
"equal": true,
|
||||
"notEqual": true,
|
||||
"it_exclude_dbs": true,
|
||||
"describe_only_db": true,
|
||||
"on_db": true,
|
||||
"defaultConfiguration": true,
|
||||
"expectSuccess": true,
|
||||
"range": true,
|
||||
"expectError": true,
|
||||
"jequal": true,
|
||||
"create": true,
|
||||
"arrayContains": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": [0]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
"use strict";
|
||||
|
||||
const Config = require("../src/Config");
|
||||
|
||||
var loginWithWrongCredentialsShouldFail = function(username, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Parse.User.logIn(username, password)
|
||||
.then(() => reject('login should have failed'))
|
||||
.catch(err => {
|
||||
if (err.message === 'Invalid username/password.') {
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var isAccountLockoutError = function(username, password, duration, waitTime) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Parse.User.logIn(username, password)
|
||||
.then(() => reject('login should have failed'))
|
||||
.catch(err => {
|
||||
if (err.message === 'Your account is locked due to multiple failed login attempts. Please try again after ' + duration + ' minute(s)') {
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}, waitTime);
|
||||
});
|
||||
};
|
||||
|
||||
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();
|
||||
user.setUsername('username1');
|
||||
user.setPassword('password');
|
||||
return user.signUp(null);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 1');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 2');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 3');
|
||||
})
|
||||
.then(() => done())
|
||||
.catch(err => {
|
||||
fail('allow unlimited failed login attempts failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if duration is set to an invalid number', done => {
|
||||
reconfigureServer({
|
||||
appName: 'duration',
|
||||
accountLockout: {
|
||||
duration: 'invalid value',
|
||||
threshold: 5
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('set duration to an invalid number test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
done();
|
||||
} else {
|
||||
fail('set duration to an invalid number test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if threshold is set to an invalid number', done => {
|
||||
reconfigureServer({
|
||||
appName: 'threshold',
|
||||
accountLockout: {
|
||||
duration: 5,
|
||||
threshold: 'invalid number'
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('set threshold to an invalid number test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
done();
|
||||
} else {
|
||||
fail('set threshold to an invalid number test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if threshold is < 1', done => {
|
||||
reconfigureServer({
|
||||
appName: 'threshold',
|
||||
accountLockout: {
|
||||
duration: 5,
|
||||
threshold: 0
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('threshold value < 1 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if threshold is > 999', done => {
|
||||
reconfigureServer({
|
||||
appName: 'threshold',
|
||||
accountLockout: {
|
||||
duration: 5,
|
||||
threshold: 1000
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('threshold value > 999 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout threshold should be an integer greater than 0 and less than 1000') {
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value > 999 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if duration is <= 0', done => {
|
||||
reconfigureServer({
|
||||
appName: 'duration',
|
||||
accountLockout: {
|
||||
duration: 0,
|
||||
threshold: 5
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('duration value < 1 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
done();
|
||||
} else {
|
||||
fail('duration value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throw error if duration is > 99999', done => {
|
||||
reconfigureServer({
|
||||
appName: 'duration',
|
||||
accountLockout: {
|
||||
duration: 100000,
|
||||
threshold: 5
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
.then(() => {
|
||||
new Config('test');
|
||||
fail('duration value > 99999 is invalid test failed');
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err === 'Account lockout duration should be greater than 0 and less than 100000') {
|
||||
done();
|
||||
} else {
|
||||
fail('duration value > 99999 is invalid test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('lock account if failed login attempts are above threshold', done => {
|
||||
reconfigureServer({
|
||||
appName: 'lockout threshold',
|
||||
accountLockout: {
|
||||
duration: 1,
|
||||
threshold: 2
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username2");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username2', 'wrong password', 1, 1);
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('lock account after failed login attempts test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('lock account for accountPolicy.duration minutes if failed login attempts are above threshold', done => {
|
||||
reconfigureServer({
|
||||
appName: 'lockout threshold',
|
||||
accountLockout: {
|
||||
duration: 0.05, // 0.05*60 = 3 secs
|
||||
threshold: 2
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username3");
|
||||
user.setPassword("failedLoginAttemptsThreshold");
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username3', 'wrong password', 0.05, 1);
|
||||
})
|
||||
.then(() => {
|
||||
// account should still be locked even after 2 seconds.
|
||||
return isAccountLockoutError('username3', 'wrong password', 0.05, 2000);
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('account should be locked for duration mins test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allow login for locked account after accountPolicy.duration minutes', done => {
|
||||
reconfigureServer({
|
||||
appName: 'lockout threshold',
|
||||
accountLockout: {
|
||||
duration: 0.05, // 0.05*60 = 3 secs
|
||||
threshold: 2
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
var user = new Parse.User();
|
||||
user.setUsername("username4");
|
||||
user.setPassword("correct password");
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
})
|
||||
.then(() => {
|
||||
// allow locked user to login after 3 seconds with a valid userid and password
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Parse.User.logIn('username4', 'correct password')
|
||||
.then(() => resolve())
|
||||
.catch(err => reject(err));
|
||||
}, 3001);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('allow login for locked account after accountPolicy.duration minutes test failed: ' + JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
@@ -10,7 +10,6 @@ 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);
|
||||
@@ -22,7 +21,7 @@ describe("AdaptableController", ()=>{
|
||||
expect(controller.adapter).toBe(adapter);
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it("should throw when creating a new mock controller", (done) => {
|
||||
var adapter = new FilesAdapter();
|
||||
expect(() => {
|
||||
@@ -30,9 +29,9 @@ describe("AdaptableController", ()=>{
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it("should fail setting the wrong adapter to the controller", (done) => {
|
||||
function WrongAdapter() {};
|
||||
function WrongAdapter() {}
|
||||
var adapter = new FilesAdapter();
|
||||
var controller = new FilesController(adapter);
|
||||
var otherAdapter = new WrongAdapter();
|
||||
@@ -41,47 +40,47 @@ describe("AdaptableController", ()=>{
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it("should fail to instantiate a controller with wrong adapter", (done) => {
|
||||
function WrongAdapter() {};
|
||||
function WrongAdapter() {}
|
||||
var adapter = new WrongAdapter();
|
||||
expect(() => {
|
||||
new FilesController(adapter);
|
||||
}).toThrow();
|
||||
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(config, filename, data) { },
|
||||
deleteFile: function(config, filename) { },
|
||||
getFileData: function(config, filename) { },
|
||||
getFileLocation: function(config, filename) { },
|
||||
createFile: function() { },
|
||||
deleteFile: function() { },
|
||||
getFileData: function() { },
|
||||
getFileLocation: function() { },
|
||||
}
|
||||
expect(() => {
|
||||
new FilesController(adapter);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it("should accept an object adapter", (done) => {
|
||||
function AGoodAdapter() {};
|
||||
AGoodAdapter.prototype.createFile = function(config, filename, data) { };
|
||||
AGoodAdapter.prototype.deleteFile = function(config, filename) { };
|
||||
AGoodAdapter.prototype.getFileData = function(config, filename) { };
|
||||
AGoodAdapter.prototype.getFileLocation = function(config, filename) { };
|
||||
|
||||
function AGoodAdapter() {}
|
||||
AGoodAdapter.prototype.createFile = function() { };
|
||||
AGoodAdapter.prototype.deleteFile = function() { };
|
||||
AGoodAdapter.prototype.getFileData = function() { };
|
||||
AGoodAdapter.prototype.getFileLocation = function() { };
|
||||
|
||||
var adapter = new AGoodAdapter();
|
||||
expect(() => {
|
||||
new FilesController(adapter);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+38
-10
@@ -2,8 +2,8 @@
|
||||
var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
|
||||
var FilesAdapter = require("parse-server-fs-adapter").default;
|
||||
var S3Adapter = require("parse-server-s3-adapter").default;
|
||||
var GCSAdapter = require("parse-server-gcs-adapter").default;
|
||||
var ParsePushAdapter = require("parse-server-push-adapter").default;
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe("AdapterLoader", ()=>{
|
||||
|
||||
@@ -46,6 +46,19 @@ describe("AdapterLoader", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate an adapter from npm module", (done) => {
|
||||
var adapter = loadAdapter({
|
||||
module: 'parse-server-fs-adapter'
|
||||
});
|
||||
|
||||
expect(typeof adapter).toBe('object');
|
||||
expect(typeof adapter.createFile).toBe('function');
|
||||
expect(typeof adapter.deleteFile).toBe('function');
|
||||
expect(typeof adapter.getFileData).toBe('function');
|
||||
expect(typeof adapter.getFileLocation).toBe('function');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should instantiate an adapter from function/Class", (done) => {
|
||||
var adapter = loadAdapter({
|
||||
adapter: FilesAdapter
|
||||
@@ -106,6 +119,30 @@ describe("AdapterLoader", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load custom push adapter from string (#3544)", (done) => {
|
||||
var adapterPath = require('path').resolve("./spec/MockPushAdapter");
|
||||
var options = {
|
||||
ios: {
|
||||
bundleId: 'bundle.id'
|
||||
}
|
||||
}
|
||||
const pushAdapterOptions = {
|
||||
adapter: adapterPath,
|
||||
options
|
||||
};
|
||||
expect(() => {
|
||||
reconfigureServer({
|
||||
push: pushAdapterOptions,
|
||||
}).then(() => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const pushAdapter = config.pushWorker.adapter;
|
||||
expect(pushAdapter.getValidPushTypes()).toEqual(['ios']);
|
||||
expect(pushAdapter.options).toEqual(pushAdapterOptions);
|
||||
done();
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should load S3Adapter from direct passing", (done) => {
|
||||
var s3Adapter = new S3Adapter("key", "secret", "bucket")
|
||||
expect(() => {
|
||||
@@ -114,13 +151,4 @@ describe("AdapterLoader", ()=>{
|
||||
}).not.toThrow();
|
||||
done();
|
||||
})
|
||||
|
||||
it("should load GCSAdapter from direct passing", (done) => {
|
||||
var gcsAdapter = new GCSAdapter("projectId", "path/to/keyfile", "bucket")
|
||||
expect(() => {
|
||||
var adapter = loadAdapter(gcsAdapter, FilesAdapter);
|
||||
expect(adapter).toBe(gcsAdapter);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
const analyticsAdapter = {
|
||||
appOpened: function() {},
|
||||
trackEvent: function() {}
|
||||
}
|
||||
|
||||
describe('AnalyticsController', () => {
|
||||
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();
|
||||
})
|
||||
});
|
||||
|
||||
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();
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,93 @@
|
||||
describe('Auth', () => {
|
||||
var Auth = require('../src/Auth.js').Auth;
|
||||
|
||||
describe('getUserRoles', () => {
|
||||
var auth;
|
||||
var config;
|
||||
var currentRoles = null;
|
||||
var currentUserId = 'userId';
|
||||
|
||||
beforeEach(() => {
|
||||
currentRoles = ['role:userId'];
|
||||
|
||||
config = {
|
||||
cacheController: {
|
||||
role: {
|
||||
get: () => Promise.resolve(currentRoles),
|
||||
set: jasmine.createSpy('set')
|
||||
}
|
||||
}
|
||||
}
|
||||
spyOn(config.cacheController.role, 'get').and.callThrough();
|
||||
|
||||
auth = new Auth({
|
||||
config: config,
|
||||
isMaster: false,
|
||||
user: {
|
||||
id: currentUserId
|
||||
},
|
||||
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);
|
||||
|
||||
var 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) => {
|
||||
expect(roles).toEqual(currentRoles);
|
||||
return auth.getUserRoles()
|
||||
})
|
||||
.then(() => auth.getUserRoles())
|
||||
.then(() => auth.getUserRoles())
|
||||
.then((roles) => {
|
||||
// Should only call the cache adapter once.
|
||||
expect(config.cacheController.role.get.calls.count()).toEqual(1);
|
||||
expect(loadRolesSpy.calls.count()).toEqual(1);
|
||||
|
||||
var 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([]))
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
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');
|
||||
bcryptOriginal.hash('my1Long:password', null, null, function(err, res) {
|
||||
bcryptNew.compare('my1Long:password', res, function(err, res) {
|
||||
expect(res).toBeTruthy();
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,315 @@
|
||||
var request = require('request');
|
||||
var Config = require("../src/Config");
|
||||
var defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
|
||||
var authenticationLoader = require('../src/Adapters/Auth');
|
||||
var path = require('path');
|
||||
|
||||
describe('AuthenticationProviers', function() {
|
||||
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
|
||||
it("Should validate structure of " + providerName, (done) => {
|
||||
var provider = require("../src/Adapters/Auth/" + providerName);
|
||||
jequal(typeof provider.validateAuthData, "function");
|
||||
jequal(typeof provider.validateAppId, "function");
|
||||
jequal(provider.validateAuthData({}, {}).constructor, Promise.prototype.constructor);
|
||||
jequal(provider.validateAppId("app", "key", {}).constructor, Promise.prototype.constructor);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var getMockMyOauthProvider = function() {
|
||||
return {
|
||||
authData: {
|
||||
id: "12345",
|
||||
access_token: "12345",
|
||||
expiration_date: new Date().toJSON(),
|
||||
},
|
||||
shouldError: false,
|
||||
loggedOut: false,
|
||||
synchronizedUserId: null,
|
||||
synchronizedAuthToken: null,
|
||||
synchronizedExpiration: null,
|
||||
|
||||
authenticate: function(options) {
|
||||
if (this.shouldError) {
|
||||
options.error(this, "An error occurred");
|
||||
} else if (this.shouldCancel) {
|
||||
options.error(this, null);
|
||||
} else {
|
||||
options.success(this, this.authData);
|
||||
}
|
||||
},
|
||||
restoreAuthentication: function(authData) {
|
||||
if (!authData) {
|
||||
this.synchronizedUserId = null;
|
||||
this.synchronizedAuthToken = null;
|
||||
this.synchronizedExpiration = null;
|
||||
return true;
|
||||
}
|
||||
this.synchronizedUserId = authData.id;
|
||||
this.synchronizedAuthToken = authData.access_token;
|
||||
this.synchronizedExpiration = authData.expiration_date;
|
||||
return true;
|
||||
},
|
||||
getAuthType: function() {
|
||||
return "myoauth";
|
||||
},
|
||||
deauthenticate: function() {
|
||||
this.loggedOut = true;
|
||||
this.restoreAuthentication(null);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Parse.User.extend({
|
||||
extended: function() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var createOAuthUser = function(callback) {
|
||||
var jsonBody = {
|
||||
authData: {
|
||||
myoauth: getMockMyOauthProvider().authData
|
||||
}
|
||||
};
|
||||
|
||||
var options = {
|
||||
headers: {'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Installation-Id': 'yolo',
|
||||
'Content-Type': 'application/json' },
|
||||
url: 'http://localhost:8378/1/users',
|
||||
body: JSON.stringify(jsonBody)
|
||||
};
|
||||
|
||||
return request.post(options, callback);
|
||||
}
|
||||
|
||||
it("should create user with REST API", done => {
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
ok(b.sessionToken);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
var sessionToken = b.sessionToken;
|
||||
var q = new Parse.Query("_Session");
|
||||
q.equalTo('sessionToken', sessionToken);
|
||||
q.first({useMasterKey: true}).then((res) => {
|
||||
if (!res) {
|
||||
fail('should not fail fetching the session');
|
||||
done();
|
||||
return;
|
||||
}
|
||||
expect(res.get("installationId")).toEqual('yolo');
|
||||
done();
|
||||
}).fail(() => {
|
||||
fail('should not fail fetching the session');
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it("should only create a single user with REST API", (done) => {
|
||||
var objectId;
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
objectId = b.objectId;
|
||||
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
expect(b.objectId).toBe(objectId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("unlink and link with custom provider", (done) => {
|
||||
var provider = getMockMyOauthProvider();
|
||||
Parse.User._registerAuthenticationProvider(provider);
|
||||
Parse.User._logInWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
ok(model.extended(), "Should have used the subclass.");
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||
|
||||
model._unlinkFrom("myoauth", {
|
||||
success: function(model) {
|
||||
|
||||
ok(!model._isLinked("myoauth"),
|
||||
"User should not be linked to myoauth");
|
||||
ok(!provider.synchronizedUserId, "User id should be cleared");
|
||||
ok(!provider.synchronizedAuthToken, "Auth token should be cleared");
|
||||
ok(!provider.synchronizedExpiration,
|
||||
"Expiration should be cleared");
|
||||
// make sure the auth data is properly deleted
|
||||
var config = new Config(Parse.applicationId);
|
||||
config.database.adapter.find('_User', {
|
||||
fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation),
|
||||
}, { objectId: model.id }, {})
|
||||
.then(res => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0]._auth_data_myoauth).toBeUndefined();
|
||||
expect(res[0]._auth_data_myoauth).not.toBeNull();
|
||||
|
||||
model._linkWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(provider.synchronizedUserId, "User id should have a value");
|
||||
ok(provider.synchronizedAuthToken,
|
||||
"Auth token should have a value");
|
||||
ok(provider.synchronizedExpiration,
|
||||
"Expiration should have a value");
|
||||
ok(model._isLinked("myoauth"),
|
||||
"User should be linked to myoauth");
|
||||
done();
|
||||
},
|
||||
error: function() {
|
||||
ok(false, "linking again should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
error: function() {
|
||||
ok(false, "unlinking should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function() {
|
||||
ok(false, "linking should have worked");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function validateValidator(validator) {
|
||||
expect(typeof validator).toBe('function');
|
||||
}
|
||||
|
||||
function validateAuthenticationHandler(authenticatonHandler) {
|
||||
expect(authenticatonHandler).not.toBeUndefined();
|
||||
expect(typeof authenticatonHandler.getValidatorForProvider).toBe('function');
|
||||
expect(typeof authenticatonHandler.getValidatorForProvider).toBe('function');
|
||||
}
|
||||
|
||||
function validateAuthenticationAdapter(authAdapter) {
|
||||
expect(authAdapter).not.toBeUndefined();
|
||||
if (!authAdapter) { return; }
|
||||
expect(typeof authAdapter.validateAuthData).toBe('function');
|
||||
expect(typeof authAdapter.validateAppId).toBe('function');
|
||||
}
|
||||
|
||||
it('properly loads custom adapter', (done) => {
|
||||
var validAuthData = {
|
||||
id: 'hello',
|
||||
token: 'world'
|
||||
}
|
||||
const adapter = {
|
||||
validateAppId: function() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
validateAuthData: function(authData) {
|
||||
if (authData.id == validAuthData.id && authData.token == validAuthData.token) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
|
||||
const authDataSpy = spyOn(adapter, 'validateAuthData').and.callThrough();
|
||||
const appIdSpy = spyOn(adapter, 'validateAppId').and.callThrough();
|
||||
|
||||
const authenticationHandler = authenticationLoader({
|
||||
customAuthentication: adapter
|
||||
});
|
||||
|
||||
validateAuthenticationHandler(authenticationHandler);
|
||||
const validator = authenticationHandler.getValidatorForProvider('customAuthentication');
|
||||
validateValidator(validator);
|
||||
|
||||
validator(validAuthData).then(() => {
|
||||
expect(authDataSpy).toHaveBeenCalled();
|
||||
// AppIds are not provided in the adapter, should not be called
|
||||
expect(appIdSpy).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly loads custom adapter module object', (done) => {
|
||||
const authenticationHandler = authenticationLoader({
|
||||
customAuthentication: path.resolve('./spec/support/CustomAuth.js')
|
||||
});
|
||||
|
||||
validateAuthenticationHandler(authenticationHandler);
|
||||
const validator = authenticationHandler.getValidatorForProvider('customAuthentication');
|
||||
validateValidator(validator);
|
||||
|
||||
validator({
|
||||
token: 'my-token'
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly loads custom adapter module object', (done) => {
|
||||
const authenticationHandler = authenticationLoader({
|
||||
customAuthentication: { module: path.resolve('./spec/support/CustomAuthFunction.js'), options: { token: 'valid-token' }}
|
||||
});
|
||||
|
||||
validateAuthenticationHandler(authenticationHandler);
|
||||
const validator = authenticationHandler.getValidatorForProvider('customAuthentication');
|
||||
validateValidator(validator);
|
||||
|
||||
validator({
|
||||
token: 'valid-token'
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly loads a default adapter with options', () => {
|
||||
const options = {
|
||||
facebook: {
|
||||
appIds: ['a', 'b']
|
||||
}
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter('facebook', options);
|
||||
validateAuthenticationAdapter(adapter);
|
||||
expect(appIds).toEqual(['a', 'b']);
|
||||
expect(providerOptions).toEqual(options.facebook);
|
||||
});
|
||||
|
||||
it('properly loads a custom adapter with options', () => {
|
||||
const options = {
|
||||
custom: {
|
||||
validateAppId: () => {},
|
||||
validateAuthData: () => {},
|
||||
appIds: ['a', 'b']
|
||||
}
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter('custom', options);
|
||||
validateAuthenticationAdapter(adapter);
|
||||
expect(appIds).toEqual(['a', 'b']);
|
||||
expect(providerOptions).toEqual(options.custom);
|
||||
});
|
||||
});
|
||||
+146
-54
@@ -1,29 +1,31 @@
|
||||
var commander = require("../src/cli/utils/commander").default;
|
||||
'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';
|
||||
|
||||
var definitions = {
|
||||
"arg0": "PROGRAM_ARG_0",
|
||||
"arg1": {
|
||||
env: "PROGRAM_ARG_1",
|
||||
var testDefinitions = {
|
||||
'arg0': 'PROGRAM_ARG_0',
|
||||
'arg1': {
|
||||
env: 'PROGRAM_ARG_1',
|
||||
required: true
|
||||
},
|
||||
"arg2": {
|
||||
env: "PROGRAM_ARG_2",
|
||||
'arg2': {
|
||||
env: 'PROGRAM_ARG_2',
|
||||
action: function(value) {
|
||||
var value = parseInt(value);
|
||||
if (!Number.isInteger(value)) {
|
||||
throw "port is invalid";
|
||||
var intValue = parseInt(value);
|
||||
if (!Number.isInteger(intValue)) {
|
||||
throw 'arg2 is invalid';
|
||||
}
|
||||
return value;
|
||||
return intValue;
|
||||
}
|
||||
},
|
||||
"arg3": {},
|
||||
"arg4": {
|
||||
default: "arg4Value"
|
||||
'arg3': {},
|
||||
'arg4': {
|
||||
default: 'arg4Value'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe("commander additions", () => {
|
||||
|
||||
describe('commander additions', () => {
|
||||
afterEach((done) => {
|
||||
commander.options = [];
|
||||
delete commander.arg0;
|
||||
@@ -32,56 +34,146 @@ describe("commander additions", () => {
|
||||
delete commander.arg3;
|
||||
delete commander.arg4;
|
||||
done();
|
||||
})
|
||||
|
||||
it("should load properly definitions from args", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "--arg1", "arg1Value", "--arg2", "2", "--arg3", "some"]);
|
||||
expect(commander.arg0).toEqual("arg0Value");
|
||||
expect(commander.arg1).toEqual("arg1Value");
|
||||
});
|
||||
|
||||
it('should load properly definitions from args', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', '--arg1', 'arg1Value', '--arg2', '2', '--arg3', 'some']);
|
||||
expect(commander.arg0).toEqual('arg0Value');
|
||||
expect(commander.arg1).toEqual('arg1Value');
|
||||
expect(commander.arg2).toEqual(2);
|
||||
expect(commander.arg3).toEqual("some");
|
||||
expect(commander.arg4).toEqual("arg4Value");
|
||||
expect(commander.arg3).toEqual('some');
|
||||
expect(commander.arg4).toEqual('arg4Value');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load properly definitions from env", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
|
||||
it('should load properly definitions from env', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse([], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
"PROGRAM_ARG_2": "3",
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
'PROGRAM_ARG_2': '3',
|
||||
});
|
||||
expect(commander.arg0).toEqual("arg0ENVValue");
|
||||
expect(commander.arg1).toEqual("arg1ENVValue");
|
||||
expect(commander.arg0).toEqual('arg0ENVValue');
|
||||
expect(commander.arg1).toEqual('arg1ENVValue');
|
||||
expect(commander.arg2).toEqual(3);
|
||||
expect(commander.arg4).toEqual("arg4Value");
|
||||
expect(commander.arg4).toEqual('arg4Value');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load properly use args over env", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "--arg4", "anotherArg4"], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
"PROGRAM_ARG_2": "4",
|
||||
|
||||
it('should load properly use args over env', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value', '--arg4', ''], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
'PROGRAM_ARG_2': '4',
|
||||
'PROGRAM_ARG_4': 'arg4ENVValue'
|
||||
});
|
||||
expect(commander.arg0).toEqual("arg0Value");
|
||||
expect(commander.arg1).toEqual("arg1ENVValue");
|
||||
expect(commander.arg0).toEqual('arg0Value');
|
||||
expect(commander.arg1).toEqual('arg1ENVValue');
|
||||
expect(commander.arg2).toEqual(4);
|
||||
expect(commander.arg4).toEqual("anotherArg4");
|
||||
expect(commander.arg4).toEqual('');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail in action as port is invalid", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
|
||||
it('should fail in action as port is invalid', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(()=> {
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value"], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
"PROGRAM_ARG_2": "hello",
|
||||
commander.parse(['node','./CLI.spec.js','--arg0', 'arg0Value'], {
|
||||
'PROGRAM_ARG_0': 'arg0ENVValue',
|
||||
'PROGRAM_ARG_1': 'arg1ENVValue',
|
||||
'PROGRAM_ARG_2': 'hello',
|
||||
});
|
||||
}).toThrow("port is invalid");
|
||||
}).toThrow('arg2 is invalid');
|
||||
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',
|
||||
});
|
||||
const options = commander.getOptions();
|
||||
expect(options.arg2).toBe(8888);
|
||||
expect(options.arg3).toBe('hello'); //config value
|
||||
expect(options.arg4).toBe('/1');
|
||||
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',
|
||||
});
|
||||
}).toThrow('arg2 is invalid');
|
||||
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']);
|
||||
}).toThrow('Multiple apps are not supported');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should load config from apps', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigApps.json']);
|
||||
const options = commander.getOptions();
|
||||
expect(options.arg1).toBe('my_app');
|
||||
expect(options.arg2).toBe(8888);
|
||||
expect(options.arg3).toBe('hello'); //config value
|
||||
expect(options.arg4).toBe('/1');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should fail when passing an invalid arguement', (done) => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigUnknownArg.json']);
|
||||
}).toThrow('error: unknown option myArg');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('definitions', () => {
|
||||
it('should have valid types', () => {
|
||||
for (const key in definitions) {
|
||||
const definition = definitions[key];
|
||||
expect(typeof definition).toBe('object');
|
||||
if (typeof definition.env !== 'undefined') {
|
||||
expect(typeof definition.env).toBe('string');
|
||||
}
|
||||
expect(typeof definition.help).toBe('string');
|
||||
if (typeof definition.required !== 'undefined') {
|
||||
expect(typeof definition.required).toBe('boolean');
|
||||
}
|
||||
if (typeof definition.action !== 'undefined') {
|
||||
expect(typeof definition.action).toBe('function');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('LiveQuery definitions', () => {
|
||||
it('should have valid types', () => {
|
||||
for (const key in liveQueryDefinitions) {
|
||||
const definition = liveQueryDefinitions[key];
|
||||
expect(typeof definition).toBe('object');
|
||||
if (typeof definition.env !== 'undefined') {
|
||||
expect(typeof definition.env).toBe('string');
|
||||
}
|
||||
expect(typeof definition.help).toBe('string');
|
||||
if (typeof definition.required !== 'undefined') {
|
||||
expect(typeof definition.required).toBe('boolean');
|
||||
}
|
||||
if (typeof definition.action !== 'undefined') {
|
||||
expect(typeof definition.action).toBe('function');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
var CacheController = require('../src/Controllers/CacheController.js').default;
|
||||
|
||||
describe('CacheController', function() {
|
||||
var FakeCacheAdapter;
|
||||
var FakeAppID = 'foo';
|
||||
var KEY = 'hello';
|
||||
|
||||
beforeEach(() => {
|
||||
FakeCacheAdapter = {
|
||||
get: () => Promise.resolve(null),
|
||||
put: jasmine.createSpy('put'),
|
||||
del: jasmine.createSpy('del'),
|
||||
clear: jasmine.createSpy('clear')
|
||||
}
|
||||
|
||||
spyOn(FakeCacheAdapter, 'get').and.callThrough();
|
||||
});
|
||||
|
||||
|
||||
it('should expose role and user caches', (done) => {
|
||||
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
|
||||
expect(cache.role).not.toEqual(null);
|
||||
expect(cache.role.get).not.toEqual(null);
|
||||
expect(cache.user).not.toEqual(null);
|
||||
expect(cache.user.get).not.toEqual(null);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
['role', 'user'].forEach((cacheName) => {
|
||||
it('should prefix ' + cacheName + ' cache', () => {
|
||||
var cache = new CacheController(FakeCacheAdapter, FakeAppID)[cacheName];
|
||||
|
||||
cache.put(KEY, 'world');
|
||||
var firstPut = FakeCacheAdapter.put.calls.first();
|
||||
expect(firstPut.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':'));
|
||||
|
||||
cache.get(KEY);
|
||||
var firstGet = FakeCacheAdapter.get.calls.first();
|
||||
expect(firstGet.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':'));
|
||||
|
||||
cache.del(KEY);
|
||||
var 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);
|
||||
|
||||
cache.clear();
|
||||
expect(FakeCacheAdapter.clear.calls.count()).toEqual(1);
|
||||
|
||||
cache.user.clear();
|
||||
expect(FakeCacheAdapter.clear.calls.count()).toEqual(2);
|
||||
|
||||
cache.role.clear();
|
||||
expect(FakeCacheAdapter.clear.calls.count()).toEqual(3);
|
||||
});
|
||||
|
||||
it('should handle cache rejections', (done) => {
|
||||
|
||||
FakeCacheAdapter.get = () => Promise.reject();
|
||||
|
||||
var cache = new CacheController(FakeCacheAdapter, FakeAppID);
|
||||
|
||||
cache.get('foo').then(done, () => {
|
||||
fail('Promise should not be rejected.');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -2,7 +2,6 @@ var Client = require('../src/LiveQuery/Client').Client;
|
||||
var ParseWebSocket = require('../src/LiveQuery/ParseWebSocketServer').ParseWebSocket;
|
||||
|
||||
describe('Client', function() {
|
||||
|
||||
it('can be initialized', function() {
|
||||
var parseWebSocket = new ParseWebSocket({});
|
||||
var client = new Client(1, parseWebSocket);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
var ClientSDK = require('../src/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'
|
||||
});
|
||||
expect(clientSDKFromVersion('i1')).toEqual({
|
||||
sdk: 'i',
|
||||
version: '1'
|
||||
});
|
||||
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
|
||||
sdk: 'apple-tv',
|
||||
version: '1.13.0'
|
||||
});
|
||||
expect(clientSDKFromVersion('js1.9.0')).toEqual({
|
||||
sdk: 'js',
|
||||
version: '1.9.0'
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly sastisfy', () => {
|
||||
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'
|
||||
})("js1.8.0")).toBe(false);
|
||||
|
||||
expect(ClientSDK.compatible({
|
||||
js: '>=1.9.0'
|
||||
})(undefined)).toBe(true);
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,230 @@
|
||||
'use strict';
|
||||
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
|
||||
const fs = require('fs');
|
||||
const loremFile = __dirname + '/support/lorem.txt';
|
||||
|
||||
describe("Cloud Code Logger", () => {
|
||||
let user;
|
||||
|
||||
beforeEach(done => {
|
||||
Parse.User.enableUnsafeCurrentUser();
|
||||
return reconfigureServer({
|
||||
// useful to flip to false for fine tuning :).
|
||||
silent: true,
|
||||
}).then(() => {
|
||||
return Parse.User.signUp('tester', 'abc')
|
||||
.then(loggedInUser => user = loggedInUser)
|
||||
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
|
||||
.then(() => done())
|
||||
});
|
||||
});
|
||||
|
||||
// Note that helpers takes care of logout.
|
||||
// see helpers.js:afterEach
|
||||
|
||||
it("should expose log to functions", done => {
|
||||
var logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define("loggerTest", (req, res) => {
|
||||
req.log.info('logTest', 'info log', { info: 'some log' });
|
||||
req.log.error('logTest', 'error log', { error: 'there was an error' });
|
||||
res.success({});
|
||||
});
|
||||
|
||||
Parse.Cloud.run('loggerTest').then(() => {
|
||||
return logController.getLogs({ from: Date.now() - 500, size: 1000 });
|
||||
}).then((res) => {
|
||||
expect(res.length).not.toBe(0);
|
||||
const lastLogs = res.slice(0, 3);
|
||||
const cloudFunctionMessage = lastLogs[0];
|
||||
const errorMessage = lastLogs[1];
|
||||
const infoMessage = lastLogs[2];
|
||||
expect(cloudFunctionMessage.level).toBe('info');
|
||||
expect(cloudFunctionMessage.params).toEqual({});
|
||||
expect(cloudFunctionMessage.message).toMatch(/Ran cloud function loggerTest for user [^ ]* with:\n {2}Input: {}\n {2}Result: {}/);
|
||||
expect(cloudFunctionMessage.functionName).toEqual('loggerTest');
|
||||
expect(errorMessage.level).toBe('error');
|
||||
expect(errorMessage.error).toBe('there was an error');
|
||||
expect(errorMessage.message).toBe('logTest error log');
|
||||
expect(infoMessage.level).toBe('info');
|
||||
expect(infoMessage.info).toBe('some log');
|
||||
expect(infoMessage.message).toBe('logTest info log');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('trigger should obfuscate password', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.beforeSave(Parse.User, (req, res) => {
|
||||
res.success(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(/\*\*\*\*\*\*\*\*/);
|
||||
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) => {
|
||||
req.log.info('beforeSave MyObject', 'info log', { info: 'some log' });
|
||||
req.log.error('beforeSave MyObject', 'error log', { error: 'there was an error' });
|
||||
res.success({});
|
||||
});
|
||||
|
||||
const obj = new Parse.Object('MyObject');
|
||||
obj.save().then(() => {
|
||||
return logController.getLogs({ from: Date.now() - 500, size: 1000 })
|
||||
}).then((res) => {
|
||||
expect(res.length).not.toBe(0);
|
||||
const lastLogs = res.slice(0, 3);
|
||||
const cloudTriggerMessage = lastLogs[0];
|
||||
const errorMessage = lastLogs[1];
|
||||
const infoMessage = lastLogs[2];
|
||||
expect(cloudTriggerMessage.level).toBe('info');
|
||||
expect(cloudTriggerMessage.triggerType).toEqual('beforeSave');
|
||||
expect(cloudTriggerMessage.message).toMatch(/beforeSave triggered for MyObject for user [^ ]*\n {2}Input: {}\n {2}Result: {}/);
|
||||
expect(cloudTriggerMessage.user).toBe(user.id);
|
||||
expect(errorMessage.level).toBe('error');
|
||||
expect(errorMessage.error).toBe('there was an error');
|
||||
expect(errorMessage.message).toBe('beforeSave MyObject error log');
|
||||
expect(infoMessage.level).toBe('info');
|
||||
expect(infoMessage.info).toBe('some log');
|
||||
expect(infoMessage.message).toBe('beforeSave MyObject info log');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should truncate really long lines when asked to', () => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
const longString = fs.readFileSync(loremFile, 'utf8');
|
||||
const truncatedString = logController.truncateLogMessage(longString);
|
||||
expect(truncatedString.length).toBe(1015); // truncate length + the string '... (truncated)'
|
||||
});
|
||||
|
||||
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.run('aFunction', { longString })
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[0];
|
||||
expect(log.level).toEqual('info');
|
||||
expect(log.message).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n {2}Input: {.*?\(truncated\)$/m);
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should log an afterSave', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
Parse.Cloud.afterSave("MyObject", () => { });
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((logs) => {
|
||||
const log = logs[0];
|
||||
expect(log.triggerType).toEqual('afterSave');
|
||||
done();
|
||||
})
|
||||
// catch errors - not that the error is actually useful :(
|
||||
.then(null, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should log a denied beforeSave', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
res.error('uh oh!');
|
||||
});
|
||||
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(
|
||||
() => done.fail('this is not supposed to succeed'),
|
||||
() => new Promise(resolve => setTimeout(resolve, 100))
|
||||
)
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[1]; // 0 is the 'uh oh!' from rejection...
|
||||
expect(log.level).toEqual('error');
|
||||
expect(log.error).toEqual({ code: 141, message: 'uh oh!' });
|
||||
done()
|
||||
});
|
||||
});
|
||||
|
||||
it('should log cloud function success', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.define('aFunction', (req, res) => {
|
||||
res.success('it worked!');
|
||||
});
|
||||
|
||||
Parse.Cloud.run('aFunction', { foo: 'bar' })
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[0];
|
||||
expect(log.level).toEqual('info');
|
||||
expect(log.message).toMatch(
|
||||
/Ran cloud function aFunction for user [^ ]* with:\n {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.run('aFunction', { foo: 'bar' })
|
||||
.then(null, () => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(logs => {
|
||||
const log = logs[2];
|
||||
expect(log.level).toEqual('error');
|
||||
expect(log.message).toMatch(
|
||||
/Failed running cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Error: {"code":141,"message":"it failed!"}/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
xit('should log a changed beforeSave indicating a change', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
Parse.Cloud.beforeSave("MyObject", (req, res) => {
|
||||
const myObj = req.object;
|
||||
myObj.set('aChange', true);
|
||||
res.success(myObj);
|
||||
});
|
||||
|
||||
new Parse.Object('MyObject')
|
||||
.save()
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then(() => {
|
||||
// expect the log to indicate that it has changed
|
||||
/*
|
||||
Here's what it looks like on parse.com...
|
||||
|
||||
Input: {"original":{"clientVersion":"1","createdAt":"2016-06-02T05:29:08.694Z","image":{"__type":"File","name":"tfss-xxxxxxxx.png","url":"http://files.parsetfss.com/xxxxxxxx.png"},"lastScanDate":{"__type":"Date","iso":"2016-06-02T05:28:58.135Z"},"localIdentifier":"XXXXX","objectId":"OFHMX7ZUcI","status":... (truncated)
|
||||
Result: Update changed to {"object":{"__type":"Pointer","className":"Emoticode","objectId":"ksrq7z3Ehc"},"imageThumb":{"__type":"File","name":"tfss-xxxxxxx.png","url":"http://files.parsetfss.com/xxxxx.png"},"status":"success"}
|
||||
*/
|
||||
done();
|
||||
})
|
||||
.then(null, e => done.fail(JSON.stringify(e)));
|
||||
}).pend('needs more work.....');
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
let DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||
|
||||
describe('DatabaseAdapter', () => {
|
||||
it('options and URI are available to adapter', done => {
|
||||
DatabaseAdapter.setAppDatabaseURI('optionsTest', 'mongodb://localhost:27017/optionsTest');
|
||||
DatabaseAdapter.setAppDatabaseOptions('optionsTest', {foo: "bar"});
|
||||
let optionsTestDatabaseConnection = DatabaseAdapter.getDatabaseConnection('optionsTest');
|
||||
|
||||
expect(optionsTestDatabaseConnection instanceof Object).toBe(true);
|
||||
expect(optionsTestDatabaseConnection.adapter._options instanceof Object).toBe(true);
|
||||
expect(optionsTestDatabaseConnection.adapter._options.foo).toBe("bar");
|
||||
|
||||
DatabaseAdapter.setAppDatabaseURI('noOptionsTest', 'mongodb://localhost:27017/noOptionsTest');
|
||||
let noOptionsTestDatabaseConnection = DatabaseAdapter.getDatabaseConnection('noOptionsTest');
|
||||
|
||||
expect(noOptionsTestDatabaseConnection instanceof Object).toBe(true);
|
||||
expect(noOptionsTestDatabaseConnection.adapter._options instanceof Object).toBe(false);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,28 @@
|
||||
'use strict';
|
||||
var DatabaseController = require('../src/Controllers/DatabaseController.js');
|
||||
var validateQuery = DatabaseController._validateQuery;
|
||||
|
||||
let DatabaseController = require('../src/Controllers/DatabaseController');
|
||||
let MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||
describe('DatabaseController', function() {
|
||||
|
||||
describe('DatabaseController', () => {
|
||||
it('can be constructed', done => {
|
||||
let adapter = new MongoStorageAdapter('mongodb://localhost:27017/test');
|
||||
let databaseController = new DatabaseController(adapter, {
|
||||
collectionPrefix: 'test_'
|
||||
describe('validateQuery', function() {
|
||||
|
||||
it('should restructure simple cases of SERVER-13732', (done) => {
|
||||
var query = {$or: [{a: 1}, {a: 2}], _rperm: {$in: ['a', 'b']}, foo: 3};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{a: 1, _rperm: {$in: ['a', 'b']}, foo: 3},
|
||||
{a: 2, _rperm: {$in: ['a', 'b']}, foo: 3}]});
|
||||
done();
|
||||
});
|
||||
databaseController.connect().then(done, error => {
|
||||
console.log('error', error.stack);
|
||||
fail();
|
||||
|
||||
it('should reject invalid queries', (done) => {
|
||||
expect(() => validateQuery({$or: {'a': 1}})).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should accept valid queries', (done) => {
|
||||
expect(() => validateQuery({$or: [{'a': 1}, {'b': 2}]})).not.toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,780 @@
|
||||
"use strict";
|
||||
|
||||
const request = require('request');
|
||||
const requestp = require('request-promise');
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe("Email Verification Token Expiration: ", () => {
|
||||
|
||||
it('show the invalid link page, if the user clicks on the verify email link after the email verify token expires', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
// wait for 1 second - simulate user behavior to some extent
|
||||
setTimeout(() => {
|
||||
expect(sendEmailOptions).not.toBeUndefined();
|
||||
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
||||
done();
|
||||
});
|
||||
}, 1000);
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('emailVerified should set to false, if the user does not verify their email before the email verify token expires', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 0.5, // 0.5 second
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
// wait for 1 second - simulate user behavior to some extent
|
||||
setTimeout(() => {
|
||||
expect(sendEmailOptions).not.toBeUndefined();
|
||||
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
user.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}, 1000);
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('if user clicks on the email verify link before email verification token expiration then show the verify email success page', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
|
||||
done();
|
||||
});
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('if user clicks on the email verify link before email verification token expiration then emailVerified should be true', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
user.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(true);
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('if user clicks on the email verify link before email verification token expiration then user should be able to login', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
}).then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
Parse.User.logIn("testEmailVerifyTokenValidity", "expiringToken")
|
||||
.then(user => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.get('emailVerified')).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername('sets_email_verify_token_expires_at');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.find('_User', {username: 'sets_email_verify_token_expires_at'});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toBe(1);
|
||||
const user = results[0];
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.emailVerified).toEqual(false);
|
||||
expect(typeof user._email_verify_token).toBe('string');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe('object');
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("unsets_email_verify_token_expires_at");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
const config = new Config('test');
|
||||
return config.database.find('_User', {username: 'unsets_email_verify_token_expires_at'}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
return results[0];
|
||||
})
|
||||
.then(user => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.emailVerified).toEqual(true);
|
||||
expect(typeof user._email_verify_token).toBe('undefined');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe('undefined');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
var serverConfig = {
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
};
|
||||
|
||||
// setup server WITHOUT enabling the expire email verify token flag
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get(sendEmailOptions.link, { followRedirect: false, })
|
||||
.on('error', error => reject(error))
|
||||
.on('response', (response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
resolve(user.fetch());
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(true);
|
||||
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
|
||||
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
|
||||
return reconfigureServer(serverConfig);
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
var serverConfig = {
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
};
|
||||
|
||||
// setup server WITHOUT enabling the expire email verify token flag
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
// just get the user again - DO NOT email verify the user
|
||||
return user.fetch();
|
||||
})
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
// RECONFIGURE the server i.e., ENABLE the expire email verify token flag
|
||||
serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
|
||||
return reconfigureServer(serverConfig);
|
||||
})
|
||||
.then(() => {
|
||||
request.get(sendEmailOptions.link, {
|
||||
followRedirect: false,
|
||||
}, (error, response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('setting the email on the user should set a new email verification token and new expiration date for the token when expire email verify token flag is set', done => {
|
||||
|
||||
const user = new Parse.User();
|
||||
let userBeforeEmailReset;
|
||||
|
||||
let sendEmailOptions;
|
||||
const emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
};
|
||||
const serverConfig = {
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
};
|
||||
|
||||
reconfigureServer(serverConfig)
|
||||
.then(() => {
|
||||
user.setUsername("newEmailVerifyTokenOnEmailReset");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
|
||||
return results[0];
|
||||
});
|
||||
})
|
||||
.then(userFromDb => {
|
||||
expect(typeof userFromDb).toBe('object');
|
||||
userBeforeEmailReset = userFromDb;
|
||||
|
||||
// trigger another token generation by setting the email
|
||||
user.set('email', 'user@parse.com');
|
||||
return new Promise((resolve) => {
|
||||
// wait for half a sec to get a new expiration time
|
||||
setTimeout(() => resolve(user.save()), 500);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
|
||||
return results[0];
|
||||
});
|
||||
})
|
||||
.then(userAfterEmailReset => {
|
||||
expect(typeof userAfterEmailReset).toBe('object');
|
||||
expect(userBeforeEmailReset._email_verify_token).not.toEqual(userAfterEmailReset._email_verify_token);
|
||||
expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(userAfterEmailReset.__email_verify_token_expires_at);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should send a new verification email when a resend is requested and the user is UNVERIFIED', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername('resends_verification_token');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
|
||||
return requestp.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {
|
||||
email: 'user@parse.com'
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false // this promise is only rejected if the call itself failed
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(sendVerificationEmailCallCount).toBe(2);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send a new verification email when a resend is requested and the user is VERIFIED', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername('no_new_verification_token_once_verified');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return requestp.get({
|
||||
url: sendEmailOptions.link,
|
||||
followRedirect: false,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.statusCode).toEqual(302);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
|
||||
return requestp.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {
|
||||
email: 'user@parse.com'
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false // this promise is only rejected if the call itself failed
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send a new verification email if this user does not exist', done => {
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
return requestp.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {
|
||||
email: 'user@parse.com'
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
})
|
||||
.then(response => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if no email is supplied', done => {
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
request.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
}, (err, response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.body.code).toBe(Parse.Error.EMAIL_MISSING);
|
||||
expect(response.body.error).toBe('you must provide an email');
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if email is not a string', done => {
|
||||
var sendEmailOptions;
|
||||
var sendVerificationEmailCallCount = 0;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
})
|
||||
.then(() => {
|
||||
request.post({
|
||||
uri: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
body: {email: 3},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false
|
||||
}, (err, response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.body.code).toBe(Parse.Error.INVALID_EMAIL_ADDRESS);
|
||||
expect(response.body.error).toBe('you must provide a valid email string');
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('client should not see the _email_verify_token_expires_at field', done => {
|
||||
var user = new Parse.User();
|
||||
var sendEmailOptions;
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {}
|
||||
}
|
||||
reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
})
|
||||
.then(() => {
|
||||
user.setUsername("testEmailVerifyTokenValidity");
|
||||
user.setPassword("expiringToken");
|
||||
user.set('email', 'user@parse.com');
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
user.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
const auth = require('../src/Auth');
|
||||
const Config = require('../src/Config');
|
||||
const rest = require('../src/rest');
|
||||
|
||||
describe('Enable single schema cache', () => {
|
||||
beforeEach((done) => {
|
||||
reconfigureServer({
|
||||
enableSingleSchemaCache: true,
|
||||
schemaCacheTTL: 30000
|
||||
}).then(() => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const fakeRequestForConfig = function() {
|
||||
return new Config('test');
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
var EventEmitterPubSub = require('../src/LiveQuery/EventEmitterPubSub').EventEmitterPubSub;
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
|
||||
describe('EventEmitterPubSub', function() {
|
||||
|
||||
it('can publish and subscribe', function() {
|
||||
var publisher = EventEmitterPubSub.createPublisher();
|
||||
var subscriber = EventEmitterPubSub.createSubscriber();
|
||||
@@ -26,7 +25,7 @@ describe('EventEmitterPubSub', function() {
|
||||
subscriber.unsubscribe('testChannel');
|
||||
// Register mock checked for subscriber
|
||||
var isCalled = false;
|
||||
subscriber.on('message', function(channel, message) {
|
||||
subscriber.on('message', function() {
|
||||
isCalled = true;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
|
||||
var Parse = require('parse/node').Parse;
|
||||
|
||||
describe('info logs', () => {
|
||||
|
||||
it("Verify INFO logs", (done) => {
|
||||
var fileLoggerAdapter = new FileLoggerAdapter();
|
||||
fileLoggerAdapter.info('testing info logs', () => {
|
||||
fileLoggerAdapter.query({
|
||||
size: 1,
|
||||
level: 'info'
|
||||
}, (results) => {
|
||||
if(results.length == 0) {
|
||||
fail('The adapter should return non-empty results');
|
||||
done();
|
||||
} else {
|
||||
expect(results[0].message).toEqual('testing info logs');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('error logs', () => {
|
||||
|
||||
it("Verify ERROR logs", (done) => {
|
||||
var fileLoggerAdapter = new FileLoggerAdapter();
|
||||
fileLoggerAdapter.error('testing error logs', () => {
|
||||
fileLoggerAdapter.query({
|
||||
size: 1,
|
||||
level: 'error'
|
||||
}, (results) => {
|
||||
if(results.length == 0) {
|
||||
fail('The adapter should return non-empty results');
|
||||
done();
|
||||
}
|
||||
else {
|
||||
expect(results[0].message).toEqual('testing error logs');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,17 @@
|
||||
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
||||
var Config = require("../src/Config");
|
||||
var FilesController = require('../src/Controllers/FilesController').default;
|
||||
const LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
const WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
const GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
||||
const Config = require("../src/Config");
|
||||
const FilesController = require('../src/Controllers/FilesController').default;
|
||||
|
||||
const mockAdapter = {
|
||||
createFile: () => {
|
||||
return Parse.Promise.reject(new Error('it failed'));
|
||||
},
|
||||
deleteFile: () => { },
|
||||
getFileData: () => { },
|
||||
getFileLocation: () => 'xyz'
|
||||
}
|
||||
|
||||
// Small additional tests to improve overall coverage
|
||||
describe("FilesController",() =>{
|
||||
@@ -26,5 +36,30 @@ describe("FilesController",() =>{
|
||||
expect(anObject.aFile.url).toEqual("http://an.url");
|
||||
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should create a server log on failure', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
reconfigureServer({ filesAdapter: mockAdapter })
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 1000)))
|
||||
.then(() => new Parse.File("yolo.txt", [1,2,3], "text/plain").save())
|
||||
.then(
|
||||
() => done.fail('should not succeed'),
|
||||
() => setImmediate(() => Parse.Promise.as('done'))
|
||||
)
|
||||
.then(() => logController.getLogs({ from: Date.now() - 500, size: 1000 }))
|
||||
.then((logs) => {
|
||||
// we get two logs here: 1. the source of the failure to save the file
|
||||
// and 2 the message that will be sent back to the client.
|
||||
const log1 = logs.pop();
|
||||
expect(log1.level).toBe('error');
|
||||
expect(log1.message).toBe('it failed');
|
||||
const log2 = logs.pop();
|
||||
expect(log2.level).toBe('error');
|
||||
expect(log2.code).toBe(130);
|
||||
expect(log2.message).toBe('Could not store file.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
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);
|
||||
|
||||
})
|
||||
});
|
||||
+120
-54
@@ -1,11 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var httpRequest = require("../src/cloud-code/httpRequest"),
|
||||
bodyParser = require('body-parser'),
|
||||
express = require("express");
|
||||
HTTPResponse = require('../src/cloud-code/HTTPResponse').default,
|
||||
bodyParser = require('body-parser'),
|
||||
express = require("express");
|
||||
|
||||
var port = 13371;
|
||||
var httpRequestServer = "http://localhost:"+port;
|
||||
var httpRequestServer = "http://localhost:" + port;
|
||||
|
||||
var app = express();
|
||||
app.use(bodyParser.json({ 'type': '*/*' }));
|
||||
@@ -36,10 +37,9 @@ app.listen(13371);
|
||||
|
||||
|
||||
describe("httpRequest", () => {
|
||||
|
||||
it("should do /hello", (done) => {
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/hello"
|
||||
url: httpRequestServer + "/hello"
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.buffer).toEqual(new Buffer('{"response":"OK"}'));
|
||||
@@ -51,11 +51,11 @@ describe("httpRequest", () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should do /hello with callback and promises", (done) => {
|
||||
var calls = 0;
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/hello",
|
||||
url: httpRequestServer + "/hello",
|
||||
success: function() { calls++; },
|
||||
error: function() { calls++; }
|
||||
}).then(function(httpResponse){
|
||||
@@ -70,11 +70,11 @@ describe("httpRequest", () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should do not follow redirects by default", (done) => {
|
||||
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/301"
|
||||
url: httpRequestServer + "/301"
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(301);
|
||||
done();
|
||||
@@ -83,11 +83,11 @@ describe("httpRequest", () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should follow redirects when set", (done) => {
|
||||
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/301",
|
||||
url: httpRequestServer + "/301",
|
||||
followRedirects: true
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(200);
|
||||
@@ -100,17 +100,17 @@ describe("httpRequest", () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should fail on 404", (done) => {
|
||||
var calls = 0;
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/404",
|
||||
success: function() {
|
||||
url: httpRequestServer + "/404",
|
||||
success: function() {
|
||||
calls++;
|
||||
fail("should not succeed");
|
||||
done();
|
||||
},
|
||||
error: function(httpResponse) {
|
||||
error: function(httpResponse) {
|
||||
calls++;
|
||||
expect(calls).toBe(1);
|
||||
expect(httpResponse.status).toBe(404);
|
||||
@@ -121,11 +121,11 @@ describe("httpRequest", () => {
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
it("should fail on 404", (done) => {
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/404",
|
||||
}).then(function(httpResponse){
|
||||
url: httpRequestServer + "/404",
|
||||
}).then(function(){
|
||||
fail("should not succeed");
|
||||
done();
|
||||
}, function(httpResponse){
|
||||
@@ -136,14 +136,14 @@ describe("httpRequest", () => {
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
it("should post on echo", (done) => {
|
||||
var calls = 0;
|
||||
httpRequest({
|
||||
method: "POST",
|
||||
url: httpRequestServer+"/echo",
|
||||
url: httpRequestServer + "/echo",
|
||||
body: {
|
||||
foo: "bar"
|
||||
foo: "bar"
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -155,48 +155,48 @@ describe("httpRequest", () => {
|
||||
expect(httpResponse.status).toBe(200);
|
||||
expect(httpResponse.data).toEqual({foo: "bar"});
|
||||
done();
|
||||
}, function(httpResponse){
|
||||
}, function(){
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should encode a query string body by default", (done) => {
|
||||
let options = {
|
||||
body: {"foo": "bar"},
|
||||
const options = {
|
||||
body: {"foo": "bar"},
|
||||
}
|
||||
let result = httpRequest.encodeBody(options);
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual('foo=bar');
|
||||
expect(result.headers['Content-Type']).toEqual('application/x-www-form-urlencoded');
|
||||
done();
|
||||
|
||||
|
||||
})
|
||||
|
||||
|
||||
it("should encode a JSON body", (done) => {
|
||||
let options = {
|
||||
body: {"foo": "bar"},
|
||||
const options = {
|
||||
body: {"foo": "bar"},
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
}
|
||||
let result = httpRequest.encodeBody(options);
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual('{"foo":"bar"}');
|
||||
done();
|
||||
|
||||
|
||||
})
|
||||
it("should encode a www-form body", (done) => {
|
||||
let options = {
|
||||
it("should encode a www-form body", (done) => {
|
||||
const options = {
|
||||
body: {"foo": "bar", "bar": "baz"},
|
||||
headers: {'cOntent-tYpe': 'application/x-www-form-urlencoded'}
|
||||
}
|
||||
let result = httpRequest.encodeBody(options);
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual("foo=bar&bar=baz");
|
||||
done();
|
||||
});
|
||||
it("should not encode a wrong content type", (done) => {
|
||||
let options = {
|
||||
body:{"foo": "bar", "bar": "baz"},
|
||||
const options = {
|
||||
body:{"foo": "bar", "bar": "baz"},
|
||||
headers: {'cOntent-tYpe': 'mime/jpeg'}
|
||||
}
|
||||
let result = httpRequest.encodeBody(options);
|
||||
const result = httpRequest.encodeBody(options);
|
||||
expect(result.body).toEqual({"foo": "bar", "bar": "baz"});
|
||||
done();
|
||||
});
|
||||
@@ -204,34 +204,23 @@ describe("httpRequest", () => {
|
||||
it("should fail gracefully", (done) => {
|
||||
httpRequest({
|
||||
url: "http://not a good url",
|
||||
success: function() {
|
||||
success: function() {
|
||||
fail("should not succeed");
|
||||
done();
|
||||
},
|
||||
error: function(error) {
|
||||
error: function(error) {
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error).not.toBeNull();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should get a cat image', (done) => {
|
||||
httpRequest({
|
||||
url: 'http://thecatapi.com/api/images/get?format=src&type=jpg',
|
||||
followRedirects: true
|
||||
}).then((res) => {
|
||||
expect(res.buffer).not.toBe(null);
|
||||
expect(res.text).not.toBe(null);
|
||||
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);
|
||||
@@ -245,7 +234,7 @@ describe("httpRequest", () => {
|
||||
|
||||
it("should params string to query string", (done) => {
|
||||
httpRequest({
|
||||
url: httpRequestServer+"/qs",
|
||||
url: httpRequestServer + "/qs",
|
||||
params: "foo=bar&foo2=bar2"
|
||||
}).then(function(httpResponse){
|
||||
expect(httpResponse.status).toBe(200);
|
||||
@@ -257,4 +246,81 @@ describe("httpRequest", () => {
|
||||
})
|
||||
});
|
||||
|
||||
it('should not crash with undefined body', () => {
|
||||
const httpResponse = new HTTPResponse({});
|
||||
expect(httpResponse.body).toBeUndefined();
|
||||
expect(httpResponse.data).toBeUndefined();
|
||||
expect(httpResponse.text).toBeUndefined();
|
||||
expect(httpResponse.buffer).toBeUndefined();
|
||||
});
|
||||
|
||||
it('serialized httpResponse correctly with body string', () => {
|
||||
const httpResponse = new HTTPResponse({}, 'hello');
|
||||
expect(httpResponse.text).toBe('hello');
|
||||
expect(httpResponse.data).toBe(undefined);
|
||||
expect(httpResponse.body).toBe('hello');
|
||||
|
||||
const serialized = JSON.stringify(httpResponse);
|
||||
const result = JSON.parse(serialized);
|
||||
expect(result.text).toBe('hello');
|
||||
expect(result.data).toBe(undefined);
|
||||
expect(result.body).toBe(undefined);
|
||||
});
|
||||
|
||||
it('serialized httpResponse correctly with body object', () => {
|
||||
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(result.text).toEqual('{"foo":"bar"}');
|
||||
expect(result.data).toEqual({foo: 'bar'});
|
||||
expect(result.body).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('serialized httpResponse correctly with body buffer string', () => {
|
||||
const httpResponse = new HTTPResponse({}, new Buffer('hello'));
|
||||
expect(httpResponse.text).toBe('hello');
|
||||
expect(httpResponse.data).toBe(undefined);
|
||||
|
||||
const serialized = JSON.stringify(httpResponse);
|
||||
const result = JSON.parse(serialized);
|
||||
expect(result.text).toBe('hello');
|
||||
expect(result.data).toBe(undefined);
|
||||
});
|
||||
|
||||
it('serialized httpResponse correctly with body buffer JSON Object', () => {
|
||||
const json = '{"foo":"bar"}';
|
||||
const httpResponse = new HTTPResponse({}, new Buffer(json));
|
||||
const serialized = JSON.stringify(httpResponse);
|
||||
const result = JSON.parse(serialized);
|
||||
expect(result.text).toEqual('{"foo":"bar"}');
|
||||
expect(result.data).toEqual({foo: 'bar'});
|
||||
});
|
||||
|
||||
it('serialized httpResponse with Parse._encode should be allright', () => {
|
||||
const json = '{"foo":"bar"}';
|
||||
const httpResponse = new HTTPResponse({}, new Buffer(json));
|
||||
const encoded = Parse._encode(httpResponse);
|
||||
let foundData, foundText, foundBody = false;
|
||||
for(var key in encoded) {
|
||||
if (key == 'data') {
|
||||
foundData = true;
|
||||
}
|
||||
if (key == 'text') {
|
||||
foundText = true;
|
||||
}
|
||||
if (key == 'body') {
|
||||
foundBody = true;
|
||||
}
|
||||
}
|
||||
expect(foundData).toBe(true);
|
||||
expect(foundText).toBe(true);
|
||||
expect(foundBody).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
const InMemoryCache = require('../src/Adapters/Cache/InMemoryCache').default;
|
||||
|
||||
|
||||
describe('InMemoryCache', function() {
|
||||
var BASE_TTL = {
|
||||
ttl: 10
|
||||
};
|
||||
var NO_EXPIRE_TTL = {
|
||||
ttl: NaN
|
||||
};
|
||||
var KEY = 'hello';
|
||||
var KEY_2 = KEY + '_2';
|
||||
|
||||
var 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);
|
||||
|
||||
cache.put(KEY, VALUE);
|
||||
|
||||
var value = cache.get(KEY);
|
||||
expect(value).toEqual(VALUE);
|
||||
|
||||
wait(BASE_TTL.ttl * 5).then(() => {
|
||||
value = cache.get(KEY)
|
||||
expect(value).toEqual(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete items', (done) => {
|
||||
var cache = new InMemoryCache(NO_EXPIRE_TTL);
|
||||
cache.put(KEY, VALUE);
|
||||
cache.put(KEY_2, VALUE);
|
||||
expect(cache.get(KEY)).toEqual(VALUE);
|
||||
expect(cache.get(KEY_2)).toEqual(VALUE);
|
||||
|
||||
cache.del(KEY);
|
||||
expect(cache.get(KEY)).toEqual(null);
|
||||
expect(cache.get(KEY_2)).toEqual(VALUE);
|
||||
|
||||
cache.del(KEY_2);
|
||||
expect(cache.get(KEY)).toEqual(null);
|
||||
expect(cache.get(KEY_2)).toEqual(null);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should clear all items', (done) => {
|
||||
var cache = new InMemoryCache(NO_EXPIRE_TTL);
|
||||
cache.put(KEY, VALUE);
|
||||
cache.put(KEY_2, VALUE);
|
||||
|
||||
expect(cache.get(KEY)).toEqual(VALUE);
|
||||
expect(cache.get(KEY_2)).toEqual(VALUE);
|
||||
cache.clear();
|
||||
|
||||
expect(cache.get(KEY)).toEqual(null);
|
||||
expect(cache.get(KEY_2)).toEqual(null);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should deafult TTL to 5 seconds', () => {
|
||||
var cache = new InMemoryCache({});
|
||||
expect(cache.ttl).toEqual(5 * 1000);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
var InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').default;
|
||||
|
||||
describe('InMemoryCacheAdapter', function() {
|
||||
var KEY = 'hello';
|
||||
var 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
|
||||
});
|
||||
|
||||
// Verify all methods return promises.
|
||||
Promise.all([
|
||||
cache.put(KEY, VALUE),
|
||||
cache.del(KEY),
|
||||
cache.get(KEY),
|
||||
cache.clear()
|
||||
]).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get/set/clear', (done) => {
|
||||
var cache = new InMemoryCacheAdapter({
|
||||
ttl: NaN
|
||||
});
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(() => cache.clear())
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('should expire after ttl', (done) => {
|
||||
var cache = new InMemoryCacheAdapter({
|
||||
ttl: 10
|
||||
});
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(wait.bind(null, 50))
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
})
|
||||
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
var auth = require('../src/Auth');
|
||||
var Config = require('../src/Config');
|
||||
var rest = require('../src/rest');
|
||||
var InstallationsRouter = require('../src/Routers/InstallationsRouter').InstallationsRouter;
|
||||
|
||||
describe('InstallationsRouter', () => {
|
||||
it('uses find condition from request.body', (done) => {
|
||||
var config = new Config('test');
|
||||
var androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {
|
||||
where: {
|
||||
deviceType: 'android'
|
||||
}
|
||||
},
|
||||
query: {},
|
||||
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 results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses find condition from request.query', (done) => {
|
||||
var config = new Config('test');
|
||||
var androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
where: {
|
||||
deviceType: 'android'
|
||||
}
|
||||
},
|
||||
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 results = res.response.results;
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
}).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'
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0
|
||||
},
|
||||
info: {}
|
||||
};
|
||||
|
||||
new Config('test');
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest);
|
||||
}).then(() => {
|
||||
return router.handleFind(request);
|
||||
}).then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query installations with count = 1', done => {
|
||||
var config = new Config('test');
|
||||
var androidDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android'
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
count: 1
|
||||
},
|
||||
info: {}
|
||||
};
|
||||
|
||||
var router = new InstallationsRouter();
|
||||
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
|
||||
.then(() => rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest))
|
||||
.then(() => router.handleFind(request))
|
||||
.then((res) => {
|
||||
var response = res.response;
|
||||
expect(response.results.length).toEqual(2);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
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'
|
||||
};
|
||||
var iosDeviceRequest = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abd',
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
var request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1
|
||||
},
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
var logging = require('../src/Adapters/Logger/WinstonLogger');
|
||||
var winston = require('winston');
|
||||
|
||||
class TestTransport extends winston.Transport {
|
||||
log(level, msg, meta, callback) {
|
||||
callback(null, true);
|
||||
}
|
||||
}
|
||||
|
||||
describe('Logger', () => {
|
||||
it('should add transport', () => {
|
||||
const testTransport = new (TestTransport)({
|
||||
name: 'test'
|
||||
});
|
||||
spyOn(testTransport, 'log');
|
||||
logging.addTransport(testTransport);
|
||||
expect(Object.keys(logging.logger.transports).length).toBe(4);
|
||||
logging.logger.info('hi');
|
||||
expect(testTransport.log).toHaveBeenCalled();
|
||||
logging.removeTransport(testTransport);
|
||||
expect(Object.keys(logging.logger.transports).length).toBe(3);
|
||||
});
|
||||
|
||||
it('should have files transports', (done) => {
|
||||
reconfigureServer().then(() => {
|
||||
const transports = logging.logger.transports;
|
||||
const transportKeys = Object.keys(transports);
|
||||
expect(transportKeys.length).toBe(3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable files logs', (done) => {
|
||||
reconfigureServer({
|
||||
logsFolder: null
|
||||
}).then(() => {
|
||||
const transports = logging.logger.transports;
|
||||
const transportKeys = Object.keys(transports);
|
||||
expect(transportKeys.length).toBe(1);
|
||||
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
|
||||
});
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,20 +1,19 @@
|
||||
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
|
||||
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
|
||||
describe('LoggerController', () => {
|
||||
it('can check process a query witout throwing', (done) => {
|
||||
it('can check process a query without throwing', (done) => {
|
||||
// Make mock request
|
||||
var query = {};
|
||||
|
||||
var loggerController = new LoggerController(new FileLoggerAdapter());
|
||||
var loggerController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
expect(() => {
|
||||
loggerController.getLogs(query).then(function(res) {
|
||||
expect(res.length).not.toBe(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
fail("should not fail");
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
}).not.toThrow();
|
||||
@@ -38,7 +37,7 @@ describe('LoggerController', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('can process a query witout throwing', (done) => {
|
||||
it('can process a query without throwing', (done) => {
|
||||
// Make mock request
|
||||
var query = {
|
||||
from: "2016-01-01Z00:00:00",
|
||||
@@ -59,7 +58,7 @@ describe('LoggerController', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('can check process a query witout throwing', (done) => {
|
||||
it('can check process a query without throwing', (done) => {
|
||||
// Make mock request
|
||||
var query = {
|
||||
from: "2016-01-01",
|
||||
@@ -69,14 +68,14 @@ describe('LoggerController', () => {
|
||||
level: 'error'
|
||||
};
|
||||
|
||||
var loggerController = new LoggerController(new FileLoggerAdapter());
|
||||
var loggerController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
expect(() => {
|
||||
loggerController.getLogs(query).then(function(res) {
|
||||
expect(res.length).toBe(0);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
fail("should not fail");
|
||||
done();
|
||||
})
|
||||
@@ -85,7 +84,7 @@ describe('LoggerController', () => {
|
||||
|
||||
it('should throw without an adapter', (done) => {
|
||||
expect(() => {
|
||||
var loggerController = new LoggerController();
|
||||
new LoggerController();
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
const request = require('request');
|
||||
var LogsRouter = require('../src/Routers/LogsRouter').LogsRouter;
|
||||
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
|
||||
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
|
||||
const loggerController = new LoggerController(new FileLoggerAdapter());
|
||||
const loggerController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
describe('LogsRouter', () => {
|
||||
it('can check valid master key of request', (done) => {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
var middlewares = require('../src/middlewares');
|
||||
var AppCache = require('../src/cache').AppCache;
|
||||
|
||||
describe('middlewares', () => {
|
||||
|
||||
var fakeReq, fakeRes;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeReq = {
|
||||
originalUrl: 'http://example.com/parse/',
|
||||
url: 'http://example.com/',
|
||||
body: {
|
||||
_ApplicationId: 'FakeAppId'
|
||||
},
|
||||
headers: {},
|
||||
get: (key) => {
|
||||
return fakeReq.headers[key.toLowerCase()]
|
||||
}
|
||||
};
|
||||
fakeRes = jasmine.createSpyObj('fakeRes', ['end', 'status']);
|
||||
AppCache.put(fakeReq.body._ApplicationId, {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
AppCache.del(fakeReq.body._ApplicationId);
|
||||
});
|
||||
|
||||
it('should use _ContentType if provided', (done) => {
|
||||
expect(fakeReq.headers['content-type']).toEqual(undefined);
|
||||
var contentType = 'image/jpeg';
|
||||
fakeReq.body._ContentType = contentType;
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeReq.headers['content-type']).toEqual(contentType);
|
||||
expect(fakeReq.body._ContentType).toEqual(undefined);
|
||||
done()
|
||||
});
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but no key supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but supplied key is incorrect', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-rest-api-key'] = 'wrongKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but different key is supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-client-key'] = 'clientKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
|
||||
it('should succeed when any one of the configured keys supplied', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
clientKey: 'clientKey',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-rest-api-key'] = 'restAPIKey';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when client key supplied but empty', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
clientKey: '',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-client-key'] = '';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when no keys are configured and none supplied', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey'
|
||||
});
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
const BodyParams = {
|
||||
clientVersion: '_ClientVersion',
|
||||
installationId: '_InstallationId',
|
||||
sessionToken: '_SessionToken',
|
||||
masterKey: '_MasterKey',
|
||||
javascriptKey: '_JavaScriptKey'
|
||||
};
|
||||
|
||||
const BodyKeys = Object.keys(BodyParams);
|
||||
|
||||
BodyKeys.forEach((infoKey) => {
|
||||
const bodyKey = BodyParams[infoKey];
|
||||
const keyValue = 'Fake' + bodyKey;
|
||||
// javascriptKey is the only one that gets defaulted,
|
||||
const otherKeys = BodyKeys.filter((otherKey) => otherKey !== infoKey && otherKey !== 'javascriptKey');
|
||||
|
||||
it(`it should pull ${bodyKey} into req.info`, (done) => {
|
||||
fakeReq.body[bodyKey] = keyValue;
|
||||
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeReq.body[bodyKey]).toEqual(undefined);
|
||||
expect(fakeReq.info[infoKey]).toEqual(keyValue);
|
||||
|
||||
otherKeys.forEach((otherKey) => {
|
||||
expect(fakeReq.info[otherKey]).toEqual(undefined);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,21 @@
|
||||
module.exports = options => {
|
||||
if (!options) {
|
||||
throw "Options were not provided"
|
||||
}
|
||||
return {
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
if (!options) {
|
||||
throw "Options were not provided"
|
||||
}
|
||||
const adapter = {
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => Promise.resolve()
|
||||
}
|
||||
};
|
||||
if (options.sendMail) {
|
||||
adapter.sendMail = options.sendMail
|
||||
}
|
||||
if (options.sendPasswordResetEmail) {
|
||||
adapter.sendPasswordResetEmail = options.sendPasswordResetEmail
|
||||
}
|
||||
if (options.sendVerificationEmail) {
|
||||
adapter.sendVerificationEmail = options.sendVerificationEmail;
|
||||
}
|
||||
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
module.exports = function(options) {
|
||||
return {
|
||||
options: options,
|
||||
send: function() {},
|
||||
getValidPushTypes: function() {
|
||||
return Object.keys(options.options);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
|
||||
const MongoSchemaCollection = require('../src/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({
|
||||
className: '_Installation',
|
||||
fields: {
|
||||
installationId: { type: 'String' },
|
||||
deviceToken: { type: 'String' },
|
||||
deviceType: { type: 'String' },
|
||||
channels: { type: 'Array' },
|
||||
user: { type: 'Pointer', targetClass: '_User' },
|
||||
pushType: { type: 'String' },
|
||||
GCMSenderId: { type: 'String' },
|
||||
timeZone: { type: 'String' },
|
||||
localeIdentifier: { type: 'String' },
|
||||
badge: { type: 'Number' },
|
||||
appVersion: { type: 'String' },
|
||||
appName: { type: 'String' },
|
||||
appIdentifier: { type: 'String' },
|
||||
parseVersion: { type: 'String' },
|
||||
ACL: { type: 'ACL' },
|
||||
createdAt: { type: 'Date' },
|
||||
updatedAt: { type: 'Date' },
|
||||
objectId: { type: 'String' },
|
||||
},
|
||||
classLevelPermissions: {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
}
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -2,12 +2,22 @@
|
||||
|
||||
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
|
||||
// These tests are specific to the mongo storage adapter + mongo storage format
|
||||
// and will eventually be moved into their own repo
|
||||
describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
beforeEach(done => {
|
||||
new MongoStorageAdapter({ uri: databaseURI })
|
||||
.deleteAllClasses()
|
||||
.then(done, fail);
|
||||
});
|
||||
|
||||
describe('MongoStorageAdapter', () => {
|
||||
it('auto-escapes symbols in auth information', () => {
|
||||
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
|
||||
new MongoStorageAdapter('mongodb://user!with@+ symbols:password!with@+ symbols@localhost:1234/parse', {})
|
||||
.connect();
|
||||
new MongoStorageAdapter({
|
||||
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',
|
||||
jasmine.any(Object)
|
||||
@@ -16,8 +26,9 @@ describe('MongoStorageAdapter', () => {
|
||||
|
||||
it("doesn't double escape already URI-encoded information", () => {
|
||||
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
|
||||
new MongoStorageAdapter('mongodb://user!with%40%2B%20symbols:password!with%40%2B%20symbols@localhost:1234/parse', {})
|
||||
.connect();
|
||||
new MongoStorageAdapter({
|
||||
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',
|
||||
jasmine.any(Object)
|
||||
@@ -27,11 +38,203 @@ describe('MongoStorageAdapter', () => {
|
||||
// https://github.com/ParsePlatform/parse-server/pull/148#issuecomment-180407057
|
||||
it('preserves replica sets', () => {
|
||||
spyOn(MongoClient, 'connect').and.returnValue(Promise.resolve(null));
|
||||
new MongoStorageAdapter('mongodb://test:testpass@ds056315-a0.mongolab.com:59325,ds059315-a1.mongolab.com:59315/testDBname?replicaSet=rs-ds059415', {})
|
||||
.connect();
|
||||
new MongoStorageAdapter({
|
||||
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',
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('stores objectId in _id', done => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(obj._id).toEqual('abcde');
|
||||
expect(obj.objectId).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('find succeeds when query is within maxTimeMS', (done) => {
|
||||
const maxTimeMS = 250;
|
||||
const adapter = new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
mongoOptions: { maxTimeMS },
|
||||
});
|
||||
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS / 2})` }))
|
||||
.then(
|
||||
() => done(),
|
||||
(err) => {
|
||||
done.fail(`maxTimeMS should not affect fast queries ${err}`);
|
||||
}
|
||||
);
|
||||
})
|
||||
|
||||
it('find fails when query exceeds maxTimeMS', (done) => {
|
||||
const maxTimeMS = 250;
|
||||
const adapter = new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
mongoOptions: { maxTimeMS },
|
||||
});
|
||||
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS * 2})` }))
|
||||
.then(
|
||||
() => {
|
||||
done.fail('Find succeeded despite taking too long!');
|
||||
},
|
||||
(err) => {
|
||||
expect(err.name).toEqual('MongoError');
|
||||
expect(err.code).toEqual(50);
|
||||
expect(err.message).toEqual('operation exceeded time limit');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('stores pointers with a _p_ prefix', (done) => {
|
||||
const obj = {
|
||||
objectId: 'bar',
|
||||
aPointer: {
|
||||
__type: 'Pointer',
|
||||
className: 'JustThePointer',
|
||||
objectId: 'qwerty'
|
||||
}
|
||||
};
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
adapter.createObject('APointerDarkly', { fields: {
|
||||
objectId: { type: 'String' },
|
||||
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
|
||||
}}, obj)
|
||||
.then(() => adapter._rawFind('APointerDarkly', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const output = results[0];
|
||||
expect(typeof output._id).toEqual('string');
|
||||
expect(typeof output._p_aPointer).toEqual('string');
|
||||
expect(output._p_aPointer).toEqual('JustThePointer$qwerty');
|
||||
expect(output.aPointer).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles object and subdocument', done => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
const schema = { fields : { subdoc: { type: 'Object' } } };
|
||||
const obj = { subdoc: {foo: 'bar', wu: 'tan'} };
|
||||
adapter.createObject('MyClass', schema, obj)
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(typeof mob.subdoc).toBe('object');
|
||||
expect(mob.subdoc.foo).toBe('bar');
|
||||
expect(mob.subdoc.wu).toBe('tan');
|
||||
const obj = { 'subdoc.wu': 'clan' };
|
||||
return adapter.findOneAndUpdate('MyClass', schema, {}, obj);
|
||||
})
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(typeof mob.subdoc).toBe('object');
|
||||
expect(mob.subdoc.foo).toBe('bar');
|
||||
expect(mob.subdoc.wu).toBe('clan');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles creating an array, object, date', (done) => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
const obj = {
|
||||
array: [1, 2, 3],
|
||||
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)
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date instanceof Date).toBe(true);
|
||||
return adapter.find('MyClass', schema, {}, {});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date.__type).toBe('Date');
|
||||
expect(mob.date.iso).toBe('2016-05-26T20:55:01.154Z');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
fail();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("handles updating a single object with array, object date", (done) => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
|
||||
const schema = { fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
} };
|
||||
|
||||
|
||||
adapter.createObject('MyClass', schema, {})
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const update = {
|
||||
array: [1, 2, 3],
|
||||
object: {foo: 'bar'},
|
||||
date: {
|
||||
__type: 'Date',
|
||||
iso: '2016-05-26T20:55:01.154Z',
|
||||
},
|
||||
};
|
||||
const query = {};
|
||||
return adapter.findOneAndUpdate('MyClass', schema, query, update)
|
||||
})
|
||||
.then(results => {
|
||||
const mob = results;
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date.__type).toBe('Date');
|
||||
expect(mob.date.iso).toBe('2016-05-26T20:55:01.154Z');
|
||||
return adapter._rawFind('MyClass', {});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
const mob = results[0];
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date instanceof Date).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
fail();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
// These tests are unit tests designed to only test transform.js.
|
||||
"use strict";
|
||||
|
||||
const transform = require('../src/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'}}
|
||||
});
|
||||
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'}}
|
||||
});
|
||||
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"
|
||||
};
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
|
||||
expect(output._created_at instanceof Date).toBe(true);
|
||||
expect(output._updated_at instanceof Date).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('array of pointers', (done) => {
|
||||
var pointer = {
|
||||
__type: 'Pointer',
|
||||
objectId: 'myId',
|
||||
className: 'Blah',
|
||||
};
|
||||
var 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: {} });
|
||||
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();
|
||||
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]);
|
||||
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'}}
|
||||
});
|
||||
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'}}
|
||||
});
|
||||
expect(out).toEqual({ locations: { start: geoPoint } });
|
||||
done();
|
||||
});
|
||||
|
||||
it('objectId', (done) => {
|
||||
var 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']},
|
||||
};
|
||||
var 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: {} });
|
||||
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, {
|
||||
fields: { userPointer: { type: 'Pointer', targetClass: '_User' } },
|
||||
});
|
||||
expect(typeof output.userPointer).toEqual('object');
|
||||
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, {
|
||||
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' }},
|
||||
});
|
||||
expect(typeof output.picture).toEqual('object');
|
||||
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' }},
|
||||
});
|
||||
expect(typeof output.location).toEqual('object');
|
||||
expect(output.location).toEqual(
|
||||
{__type: 'GeoPoint', longitude: 180, latitude: -180}
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('bytes', (done) => {
|
||||
var input = {binaryData: "aGVsbG8gd29ybGQ="};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { binaryData: { type: 'Bytes' }},
|
||||
});
|
||||
expect(typeof output.binaryData).toEqual('object');
|
||||
expect(output.binaryData).toEqual(
|
||||
{__type: 'Bytes', base64: "aGVsbG8gd29ybGQ="}
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('nested array', (done) => {
|
||||
var input = {arr: [{_testKey: 'testValue' }]};
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { arr: { type: 'Array' } },
|
||||
});
|
||||
expect(Array.isArray(output.arr)).toEqual(true);
|
||||
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 output = transform.mongoObjectToParseObject(null, input, {
|
||||
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'}
|
||||
};
|
||||
var 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'}
|
||||
};
|
||||
var 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 = {
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin'],
|
||||
};
|
||||
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
|
||||
expect(typeof output._acl).toEqual('object');
|
||||
expect(output._acl['Kevin'].w).toBeTruthy();
|
||||
expect(output._acl['Kevin'].r).toBeUndefined();
|
||||
expect(output._rperm).toEqual(input._rperm);
|
||||
expect(output._wperm).toEqual(input._wperm);
|
||||
done();
|
||||
});
|
||||
|
||||
it('removes Relation types', (done) => {
|
||||
var input = {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
};
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, {
|
||||
fields: {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
},
|
||||
});
|
||||
expect(output).toEqual({});
|
||||
done();
|
||||
});
|
||||
|
||||
it('writes the old ACL format in addition to rperm and wperm on update', (done) => {
|
||||
var input = {
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin']
|
||||
};
|
||||
|
||||
var output = transform.transformUpdate(null, input, { fields: {} });
|
||||
var set = output.$set;
|
||||
expect(typeof set).toEqual('object');
|
||||
expect(typeof set._acl).toEqual('object');
|
||||
expect(set._acl['Kevin'].w).toBeTruthy();
|
||||
expect(set._acl['Kevin'].r).toBeUndefined();
|
||||
expect(set._rperm).toEqual(input._rperm);
|
||||
expect(set._wperm).toEqual(input._wperm);
|
||||
done();
|
||||
});
|
||||
|
||||
it('untransforms from _rperm and _wperm to ACL', (done) => {
|
||||
var input = {
|
||||
_rperm: ["*"],
|
||||
_wperm: ["Kevin"]
|
||||
};
|
||||
var output = transform.mongoObjectToParseObject(null, input, { fields: {} });
|
||||
expect(output._rperm).toEqual(['*']);
|
||||
expect(output._wperm).toEqual(['Kevin']);
|
||||
expect(output.ACL).toBeUndefined()
|
||||
done();
|
||||
});
|
||||
|
||||
it('untransforms mongodb number types', (done) => {
|
||||
var input = {
|
||||
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
|
||||
double: new mongodb.Double(Number.MAX_VALUE)
|
||||
}
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: {
|
||||
long: { type: 'Number' },
|
||||
double: { type: 'Number' },
|
||||
},
|
||||
});
|
||||
expect(output.long).toBe(Number.MAX_SAFE_INTEGER);
|
||||
expect(output.double).toBe(Number.MAX_VALUE);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Date object where iso attribute is of type Date', (done) => {
|
||||
var input = {
|
||||
ts : { __type: 'Date', iso: new Date('2017-01-18T00:00:00.000Z') }
|
||||
}
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields : {
|
||||
ts : { type : 'Date' }
|
||||
}
|
||||
});
|
||||
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
|
||||
done();
|
||||
});
|
||||
|
||||
it('Date object where iso attribute is of type String', (done) => {
|
||||
var input = {
|
||||
ts : { __type: 'Date', iso: '2017-01-18T00:00:00.000Z' }
|
||||
}
|
||||
var output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields : {
|
||||
ts : { type : 'Date' }
|
||||
}
|
||||
});
|
||||
expect(output.ts.iso).toEqual('2017-01-18T00:00:00.000Z');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformUpdate', () => {
|
||||
it('removes Relation types', (done) => {
|
||||
var input = {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
};
|
||||
var output = transform.transformUpdate(null, input, {
|
||||
fields: {
|
||||
aRelation: { __type: 'Relation', className: 'Stuff' },
|
||||
},
|
||||
});
|
||||
expect(output).toEqual({});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
var NullCacheAdapter = require('../src/Adapters/Cache/NullCacheAdapter').default;
|
||||
|
||||
describe('NullCacheAdapter', function() {
|
||||
var KEY = 'hello';
|
||||
var VALUE = 'world';
|
||||
|
||||
it('should expose promisifyed methods', (done) => {
|
||||
var cache = new NullCacheAdapter({
|
||||
ttl: NaN
|
||||
});
|
||||
|
||||
// Verify all methods return promises.
|
||||
Promise.all([
|
||||
cache.put(KEY, VALUE),
|
||||
cache.del(KEY),
|
||||
cache.get(KEY),
|
||||
cache.clear()
|
||||
]).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get/set/clear', (done) => {
|
||||
var cache = new NullCacheAdapter({
|
||||
ttl: NaN
|
||||
});
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(() => cache.clear())
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,326 +0,0 @@
|
||||
var OAuth = require("../src/authDataManager/OAuth1Client");
|
||||
var request = require('request');
|
||||
var Config = require("../src/Config");
|
||||
|
||||
describe('OAuth', function() {
|
||||
|
||||
it("Nonce should have right length", (done) => {
|
||||
jequal(OAuth.nonce().length, 30);
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString({c:1, a:2, b:3})
|
||||
jequal(string, "a=2&b=3&c=1");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build empty parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString()
|
||||
jequal(string, "");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build signature string", (done) => {
|
||||
var string = OAuth.buildSignatureString("get", "http://dummy.com", "");
|
||||
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly generate request signature", (done) => {
|
||||
var request = {
|
||||
host: "dummy.com",
|
||||
path: "path"
|
||||
};
|
||||
|
||||
var oauth_params = {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA",
|
||||
oauth_consumer_key: "hello",
|
||||
oauth_token: "token"
|
||||
};
|
||||
|
||||
var consumer_secret = "world";
|
||||
var auth_token_secret = "secret";
|
||||
request = OAuth.signRequest(request, oauth_params, consumer_secret, auth_token_secret);
|
||||
jequal(request.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"');
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build request", (done) => {
|
||||
var options = {
|
||||
host: "dummy.com",
|
||||
consumer_key: "hello",
|
||||
consumer_secret: "world",
|
||||
auth_token: "token",
|
||||
auth_token_secret: "secret",
|
||||
// Custom oauth params for tests
|
||||
oauth_params: {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA"
|
||||
}
|
||||
};
|
||||
var path = "path";
|
||||
var method = "get";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
var req = oauthClient.buildRequest(method, path, {"query": "param"});
|
||||
|
||||
jequal(req.host, options.host);
|
||||
jequal(req.path, "/"+path+"?query=param");
|
||||
jequal(req.method, "GET");
|
||||
jequal(req.headers['Content-Type'], 'application/x-www-form-urlencoded');
|
||||
jequal(req.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="wNkyEkDE%2F0JZ2idmqyrgHdvC0rs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"')
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
function validateCannotAuthenticateError(data, done) {
|
||||
jequal(typeof data, "object");
|
||||
jequal(typeof data.errors, "object");
|
||||
var errors = data.errors;
|
||||
jequal(typeof errors[0], "object");
|
||||
// Cannot authenticate error
|
||||
jequal(errors[0].code, 32);
|
||||
done();
|
||||
}
|
||||
|
||||
it("Should fail a GET request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var path = "/1.1/help/configuration.json";
|
||||
var params = {"lang": "en"};
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.get(path, params).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a POST request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/1.1/account/settings.json";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a request", (done) => {
|
||||
var options = {
|
||||
host: "localhost",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
jequal(false, true);
|
||||
done();
|
||||
}).catch(function(){
|
||||
jequal(true, true);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter"].map(function(providerName){
|
||||
it("Should validate structure of "+providerName, (done) => {
|
||||
var provider = require("../src/authDataManager/"+providerName);
|
||||
jequal(typeof provider.validateAuthData, "function");
|
||||
jequal(typeof provider.validateAppId, "function");
|
||||
jequal(provider.validateAuthData({}, {}).constructor, Promise.prototype.constructor);
|
||||
jequal(provider.validateAppId("app", "key", {}).constructor, Promise.prototype.constructor);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var getMockMyOauthProvider = function() {
|
||||
return {
|
||||
authData: {
|
||||
id: "12345",
|
||||
access_token: "12345",
|
||||
expiration_date: new Date().toJSON(),
|
||||
},
|
||||
shouldError: false,
|
||||
loggedOut: false,
|
||||
synchronizedUserId: null,
|
||||
synchronizedAuthToken: null,
|
||||
synchronizedExpiration: null,
|
||||
|
||||
authenticate: function(options) {
|
||||
if (this.shouldError) {
|
||||
options.error(this, "An error occurred");
|
||||
} else if (this.shouldCancel) {
|
||||
options.error(this, null);
|
||||
} else {
|
||||
options.success(this, this.authData);
|
||||
}
|
||||
},
|
||||
restoreAuthentication: function(authData) {
|
||||
if (!authData) {
|
||||
this.synchronizedUserId = null;
|
||||
this.synchronizedAuthToken = null;
|
||||
this.synchronizedExpiration = null;
|
||||
return true;
|
||||
}
|
||||
this.synchronizedUserId = authData.id;
|
||||
this.synchronizedAuthToken = authData.access_token;
|
||||
this.synchronizedExpiration = authData.expiration_date;
|
||||
return true;
|
||||
},
|
||||
getAuthType: function() {
|
||||
return "myoauth";
|
||||
},
|
||||
deauthenticate: function() {
|
||||
this.loggedOut = true;
|
||||
this.restoreAuthentication(null);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var ExtendedUser = Parse.User.extend({
|
||||
extended: function() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var createOAuthUser = function(callback) {
|
||||
var jsonBody = {
|
||||
authData: {
|
||||
myoauth: getMockMyOauthProvider().authData
|
||||
}
|
||||
};
|
||||
|
||||
var options = {
|
||||
headers: {'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Installation-Id': 'yolo',
|
||||
'Content-Type': 'application/json' },
|
||||
url: 'http://localhost:8378/1/users',
|
||||
body: JSON.stringify(jsonBody)
|
||||
};
|
||||
|
||||
return request.post(options, callback);
|
||||
}
|
||||
|
||||
it("should create user with REST API", (done) => {
|
||||
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
ok(b.sessionToken);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
var sessionToken = b.sessionToken;
|
||||
var q = new Parse.Query("_Session");
|
||||
q.equalTo('sessionToken', sessionToken);
|
||||
q.first({useMasterKey: true}).then((res) => {
|
||||
expect(res.get("installationId")).toEqual('yolo');
|
||||
done();
|
||||
}).fail((err) => {
|
||||
fail('should not fail fetching the session');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("should only create a single user with REST API", (done) => {
|
||||
var objectId;
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
objectId = b.objectId;
|
||||
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
expect(b.objectId).toBe(objectId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("unlink and link with custom provider", (done) => {
|
||||
var provider = getMockMyOauthProvider();
|
||||
Parse.User._registerAuthenticationProvider(provider);
|
||||
Parse.User._logInWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
ok(model.extended(), "Should have used the subclass.");
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||
|
||||
model._unlinkFrom("myoauth", {
|
||||
success: function(model) {
|
||||
|
||||
ok(!model._isLinked("myoauth"),
|
||||
"User should not be linked to myoauth");
|
||||
ok(!provider.synchronizedUserId, "User id should be cleared");
|
||||
ok(!provider.synchronizedAuthToken, "Auth token should be cleared");
|
||||
ok(!provider.synchronizedExpiration,
|
||||
"Expiration should be cleared");
|
||||
// make sure the auth data is properly deleted
|
||||
var config = new Config(Parse.applicationId);
|
||||
config.database.mongoFind('_User', {
|
||||
_id: model.id
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0]._auth_data_myoauth).toBeUndefined();
|
||||
expect(res[0]._auth_data_myoauth).not.toBeNull();
|
||||
|
||||
model._linkWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(provider.synchronizedUserId, "User id should have a value");
|
||||
ok(provider.synchronizedAuthToken,
|
||||
"Auth token should have a value");
|
||||
ok(provider.synchronizedExpiration,
|
||||
"Expiration should have a value");
|
||||
ok(model._isLinked("myoauth"),
|
||||
"User should be linked to myoauth");
|
||||
done();
|
||||
},
|
||||
error: function(model, error) {
|
||||
ok(false, "linking again should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
error: function(model, error) {
|
||||
ok(false, "unlinking should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function(model, error) {
|
||||
ok(false, "linking should have worked");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
@@ -0,0 +1,136 @@
|
||||
var OAuth = require("../src/Adapters/Auth/OAuth1Client");
|
||||
|
||||
describe('OAuth', function() {
|
||||
it("Nonce should have right length", (done) => {
|
||||
jequal(OAuth.nonce().length, 30);
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString({c:1, a:2, b:3})
|
||||
jequal(string, "a=2&b=3&c=1");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build empty parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString()
|
||||
jequal(string, "");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build signature string", (done) => {
|
||||
var string = OAuth.buildSignatureString("get", "http://dummy.com", "");
|
||||
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly generate request signature", (done) => {
|
||||
var request = {
|
||||
host: "dummy.com",
|
||||
path: "path"
|
||||
};
|
||||
|
||||
var oauth_params = {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA",
|
||||
oauth_consumer_key: "hello",
|
||||
oauth_token: "token"
|
||||
};
|
||||
|
||||
var consumer_secret = "world";
|
||||
var auth_token_secret = "secret";
|
||||
request = OAuth.signRequest(request, oauth_params, consumer_secret, auth_token_secret);
|
||||
jequal(request.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"');
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build request", (done) => {
|
||||
var options = {
|
||||
host: "dummy.com",
|
||||
consumer_key: "hello",
|
||||
consumer_secret: "world",
|
||||
auth_token: "token",
|
||||
auth_token_secret: "secret",
|
||||
// Custom oauth params for tests
|
||||
oauth_params: {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA"
|
||||
}
|
||||
};
|
||||
var path = "path";
|
||||
var method = "get";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
var req = oauthClient.buildRequest(method, path, {"query": "param"});
|
||||
|
||||
jequal(req.host, options.host);
|
||||
jequal(req.path, "/" + path + "?query=param");
|
||||
jequal(req.method, "GET");
|
||||
jequal(req.headers['Content-Type'], 'application/x-www-form-urlencoded');
|
||||
jequal(req.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="wNkyEkDE%2F0JZ2idmqyrgHdvC0rs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"')
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
function validateCannotAuthenticateError(data, done) {
|
||||
jequal(typeof data, "object");
|
||||
jequal(typeof data.errors, "object");
|
||||
var errors = data.errors;
|
||||
jequal(typeof errors[0], "object");
|
||||
// Cannot authenticate error
|
||||
jequal(errors[0].code, 32);
|
||||
done();
|
||||
}
|
||||
|
||||
it("Should fail a GET request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var path = "/1.1/help/configuration.json";
|
||||
var params = {"lang": "en"};
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.get(path, params).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a POST request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/1.1/account/settings.json";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a request", (done) => {
|
||||
var options = {
|
||||
host: "localhost",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(){
|
||||
jequal(false, true);
|
||||
done();
|
||||
}).catch(function(){
|
||||
jequal(true, true);
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
+157
-45
@@ -1,23 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const request = require('request');
|
||||
|
||||
const delayPromise = (delay) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, delay);
|
||||
});
|
||||
}
|
||||
|
||||
describe('Parse.Push', () => {
|
||||
|
||||
var setup = function() {
|
||||
var sendToInstallationSpy = jasmine.createSpy();
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
let promises = installations.map((installation) => {
|
||||
const promises = installations.map((installation) => {
|
||||
sendToInstallationSpy(installation);
|
||||
|
||||
if (installation.deviceType == "ios") {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(installation.originalBadge+1).toEqual(installation.badge);
|
||||
expect(installation.originalBadge + 1).toEqual(installation.badge);
|
||||
} else {
|
||||
expect(installation.badge).toBeUndefined();
|
||||
}
|
||||
return Promise.resolve({
|
||||
err: null,
|
||||
deviceType: installation.deviceType,
|
||||
result: true
|
||||
device: installation,
|
||||
transmitted: true
|
||||
})
|
||||
});
|
||||
return Promise.all(promises);
|
||||
@@ -27,65 +37,167 @@ describe('Parse.Push', () => {
|
||||
}
|
||||
}
|
||||
|
||||
setServerConfiguration({
|
||||
return reconfigureServer({
|
||||
appId: Parse.applicationId,
|
||||
masterKey: Parse.masterKey,
|
||||
serverURL: Parse.serverURL,
|
||||
push: {
|
||||
adapter: pushAdapter
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
return Parse.Object.saveAll(installations);
|
||||
})
|
||||
.then(() => {
|
||||
return {
|
||||
sendToInstallationSpy,
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_"+installations.length);
|
||||
installation.set("deviceToken","device_token_"+installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
return Parse.Object.saveAll(installations);
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
|
||||
it('should properly send push', (done) => {
|
||||
return setup().then(() => {
|
||||
return setup().then(({ sendToInstallationSpy }) => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'Increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
})
|
||||
.then(() => {
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'Increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
.then(() => {
|
||||
return delayPromise(500);
|
||||
})
|
||||
.then(() => {
|
||||
expect(sendToInstallationSpy.calls.count()).toEqual(10);
|
||||
})
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
console.error();
|
||||
fail('should not fail sending push')
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly send push with lowercaseIncrement', (done) => {
|
||||
return setup().then(() => {
|
||||
return setup().then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
return delayPromise(500);
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
console.error();
|
||||
fail('should not fail sending push')
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow clients to query _PushStatus', done => {
|
||||
setup()
|
||||
.then(() => Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true}))
|
||||
.then(() => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
expect(body.error).toEqual('unauthorized');
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow master key to query _PushStatus', done => {
|
||||
setup()
|
||||
.then(() => Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true}))
|
||||
.then(() => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
try {
|
||||
expect(body.results.length).toEqual(1);
|
||||
expect(body.results[0].query).toEqual('{"deviceType":"ios"}');
|
||||
expect(body.results[0].payload).toEqual('{"badge":"increment","alert":"Hello world!"}');
|
||||
} catch(e) {
|
||||
jfail(e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error if missing push configuration', done => {
|
||||
reconfigureServer({push: null})
|
||||
.then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
badge: 'increment',
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
}, (err) => {
|
||||
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
+543
-437
File diff suppressed because it is too large
Load Diff
+891
-568
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ var ParseCloudCodePublisher = require('../src/LiveQuery/ParseCloudCodePublisher'
|
||||
var Parse = require('parse/node');
|
||||
|
||||
describe('ParseCloudCodePublisher', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
// Mock ParsePubSub
|
||||
var mockParsePubSub = {
|
||||
@@ -21,7 +20,7 @@ describe('ParseCloudCodePublisher', function() {
|
||||
|
||||
it('can initialize', function() {
|
||||
var config = {}
|
||||
var publisher = new ParseCloudCodePublisher(config);
|
||||
new ParseCloudCodePublisher(config);
|
||||
|
||||
var ParsePubSub = require('../src/LiveQuery/ParsePubSub').ParsePubSub;
|
||||
expect(ParsePubSub.createPublisher).toHaveBeenCalledWith(config);
|
||||
@@ -33,7 +32,7 @@ describe('ParseCloudCodePublisher', function() {
|
||||
var request = {};
|
||||
publisher.onCloudCodeAfterSave(request);
|
||||
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith('afterSave', request);
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterSave', request);
|
||||
});
|
||||
|
||||
it('can handle cloud code afterDelete request', function() {
|
||||
@@ -42,7 +41,7 @@ describe('ParseCloudCodePublisher', function() {
|
||||
var request = {};
|
||||
publisher.onCloudCodeAfterDelete(request);
|
||||
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith('afterDelete', request);
|
||||
expect(publisher._onCloudCodeMessage).toHaveBeenCalledWith(Parse.applicationId + 'afterDelete', request);
|
||||
});
|
||||
|
||||
it('can handle cloud code request', function() {
|
||||
|
||||
+112
-19
@@ -36,6 +36,35 @@ describe('Parse.File testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('works with _ContentType', done => {
|
||||
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/files/file',
|
||||
body: JSON.stringify({
|
||||
_ApplicationId: 'test',
|
||||
_JavaScriptKey: 'test',
|
||||
_ContentType: 'text/html',
|
||||
base64: 'PGh0bWw+PC9odG1sPgo='
|
||||
})
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.name).toMatch(/_file.html/);
|
||||
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*file.html$/);
|
||||
request.get(b.url, (error, response, body) => {
|
||||
try {
|
||||
expect(response.headers['content-type']).toMatch('^text/html');
|
||||
expect(error).toBe(null);
|
||||
expect(body).toEqual('<html></html>\n');
|
||||
} catch(e) {
|
||||
jfail(e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('works without Content-Type', done => {
|
||||
var headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
@@ -84,7 +113,7 @@ describe('Parse.File testing', () => {
|
||||
'X-Parse-Master-Key': 'test'
|
||||
},
|
||||
url: 'http://localhost:8378/1/files/' + b.name
|
||||
}, (error, response, body) => {
|
||||
}, (error, response) => {
|
||||
expect(error).toBe(null);
|
||||
expect(response.statusCode).toEqual(200);
|
||||
request.get({
|
||||
@@ -93,9 +122,13 @@ describe('Parse.File testing', () => {
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
},
|
||||
url: b.url
|
||||
}, (error, response, body) => {
|
||||
}, (error, response) => {
|
||||
expect(error).toBe(null);
|
||||
expect(response.statusCode).toEqual(404);
|
||||
try {
|
||||
expect(response.statusCode).toEqual(404);
|
||||
} catch(e) {
|
||||
jfail(e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -182,7 +215,7 @@ describe('Parse.File testing', () => {
|
||||
notEqual(file.name(), "hello.txt");
|
||||
done();
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
});
|
||||
|
||||
it("save file in object", done => {
|
||||
@@ -207,9 +240,9 @@ describe('Parse.File testing', () => {
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
});
|
||||
|
||||
it("save file in object with escaped characters in filename", done => {
|
||||
@@ -235,9 +268,9 @@ describe('Parse.File testing', () => {
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
});
|
||||
|
||||
it("autosave file in object", done => {
|
||||
@@ -257,9 +290,9 @@ describe('Parse.File testing', () => {
|
||||
notEqual(file.name(), "hello.txt");
|
||||
done();
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
});
|
||||
|
||||
it("autosave file in object in object", done => {
|
||||
@@ -286,9 +319,9 @@ describe('Parse.File testing', () => {
|
||||
notEqual(file.name(), "hello.txt");
|
||||
done();
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
});
|
||||
|
||||
it("saving an already saved file", done => {
|
||||
@@ -307,9 +340,9 @@ describe('Parse.File testing', () => {
|
||||
equal(file.name(), previousName);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
});
|
||||
|
||||
it("two saves at the same time", done => {
|
||||
@@ -337,11 +370,11 @@ describe('Parse.File testing', () => {
|
||||
object.save({
|
||||
file: file
|
||||
}, expectSuccess({
|
||||
success: function(obj) {
|
||||
success: function() {
|
||||
ok(object.toJSON().file.url);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
}, done));
|
||||
});
|
||||
|
||||
it("content-type used with no extension", done => {
|
||||
@@ -358,7 +391,11 @@ describe('Parse.File testing', () => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.name).toMatch(/\.html$/);
|
||||
request.get(b.url, (error, response, body) => {
|
||||
request.get(b.url, (error, response) => {
|
||||
if (!response) {
|
||||
fail('response should be set');
|
||||
return done();
|
||||
}
|
||||
expect(response.headers['content-type']).toMatch(/^text\/html/);
|
||||
done();
|
||||
});
|
||||
@@ -463,10 +500,13 @@ describe('Parse.File testing', () => {
|
||||
expect(fileAgain.name()).toEqual('meep');
|
||||
expect(fileAgain.url()).toEqual('http://meep.meep');
|
||||
done();
|
||||
}).catch((e) => {
|
||||
jfail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('creates correct url for old files hosted on parse', done => {
|
||||
it('creates correct url for old files hosted on files.parsetfss.com', done => {
|
||||
var file = {
|
||||
__type: 'File',
|
||||
url: 'http://irrelevant.elephant/',
|
||||
@@ -483,6 +523,32 @@ describe('Parse.File testing', () => {
|
||||
'http://files.parsetfss.com/test/tfss-123.txt'
|
||||
);
|
||||
done();
|
||||
}).catch((e) => {
|
||||
jfail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('creates correct url for old files hosted on files.parse.com', done => {
|
||||
var file = {
|
||||
__type: 'File',
|
||||
url: 'http://irrelevant.elephant/',
|
||||
name: 'd6e80979-a128-4c57-a167-302f874700dc-123.txt'
|
||||
};
|
||||
var obj = new Parse.Object('OldFileTest');
|
||||
obj.set('oldfile', file);
|
||||
obj.save().then(() => {
|
||||
var query = new Parse.Query('OldFileTest');
|
||||
return query.first();
|
||||
}).then((result) => {
|
||||
var fileAgain = result.get('oldfile');
|
||||
expect(fileAgain.url()).toEqual(
|
||||
'http://files.parse.com/test/d6e80979-a128-4c57-a167-302f874700dc-123.txt'
|
||||
);
|
||||
done();
|
||||
}).catch((e) => {
|
||||
jfail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -497,9 +563,36 @@ describe('Parse.File testing', () => {
|
||||
var query = new Parse.Query('FileTest');
|
||||
return query.first();
|
||||
}).then(result => {
|
||||
let fileAgain = result.get('file');
|
||||
const fileAgain = result.get('file');
|
||||
expect(fileAgain.url()).toMatch(/123.txt$/);
|
||||
done();
|
||||
}).catch((e) => {
|
||||
jfail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('return with publicServerURL when provided', done => {
|
||||
reconfigureServer({
|
||||
publicServerURL: 'https://mydomain/parse'
|
||||
}).then(() => {
|
||||
var file = {
|
||||
__type: 'File',
|
||||
name: '123.txt'
|
||||
};
|
||||
var obj = new Parse.Object('FileTest');
|
||||
obj.set('file', file);
|
||||
return obj.save()
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('FileTest');
|
||||
return query.first();
|
||||
}).then(result => {
|
||||
const fileAgain = result.get('file');
|
||||
expect(fileAgain.url().indexOf('https://mydomain/parse')).toBe(0);
|
||||
done();
|
||||
}).catch((e) => {
|
||||
jfail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+18
-16
@@ -76,7 +76,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
obj.set('index', i);
|
||||
objects.push(obj);
|
||||
});
|
||||
Parse.Object.saveAll(objects).then((list) => {
|
||||
Parse.Object.saveAll(objects).then(() => {
|
||||
var query = new Parse.Query(TestObject);
|
||||
var point = new Parse.GeoPoint(1.0, -1.0);
|
||||
query.withinRadians('location', point, 3.14);
|
||||
@@ -85,8 +85,8 @@ describe('Parse.GeoPoint testing', () => {
|
||||
equal(results.length, 3);
|
||||
done();
|
||||
}, (err) => {
|
||||
console.log(err);
|
||||
fail();
|
||||
fail("Couldn't query GeoPoint");
|
||||
jfail(err)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
obj.set('index', i);
|
||||
objects.push(obj);
|
||||
});
|
||||
Parse.Object.saveAll(objects, function(list) {
|
||||
Parse.Object.saveAll(objects, function() {
|
||||
var query = new Parse.Query(TestObject);
|
||||
var point = new Parse.GeoPoint(1.0, -1.0);
|
||||
query.withinRadians('location', point, 3.14 * 0.5);
|
||||
@@ -123,7 +123,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
obj.set('index', i);
|
||||
objects.push(obj);
|
||||
});
|
||||
Parse.Object.saveAll(objects, function(list) {
|
||||
Parse.Object.saveAll(objects, function() {
|
||||
var query = new Parse.Query(TestObject);
|
||||
var point = new Parse.GeoPoint(1.0, -1.0);
|
||||
query.withinRadians('location', point, 3.14 * 0.25);
|
||||
@@ -154,10 +154,11 @@ describe('Parse.GeoPoint testing', () => {
|
||||
};
|
||||
|
||||
it('geo max distance in km everywhere', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinKilometers('location', sfo, 4000.0);
|
||||
// Honolulu is 4300 km away from SFO on a sphere ;)
|
||||
query.withinKilometers('location', sfo, 4800.0);
|
||||
query.find({
|
||||
success: function(results) {
|
||||
equal(results.length, 3);
|
||||
@@ -168,7 +169,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in km california', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinKilometers('location', sfo, 3700.0);
|
||||
@@ -184,7 +185,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in km bay area', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinKilometers('location', sfo, 100.0);
|
||||
@@ -199,7 +200,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in km mid peninsula', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinKilometers('location', sfo, 10.0);
|
||||
@@ -213,10 +214,10 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in miles everywhere', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinMiles('location', sfo, 2500.0);
|
||||
query.withinMiles('location', sfo, 2600.0);
|
||||
query.find({
|
||||
success: function(results) {
|
||||
equal(results.length, 3);
|
||||
@@ -227,7 +228,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in miles california', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinMiles('location', sfo, 2200.0);
|
||||
@@ -243,10 +244,11 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in miles bay area', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinMiles('location', sfo, 75.0);
|
||||
// 100km is 62 miles...
|
||||
query.withinMiles('location', sfo, 62.0);
|
||||
query.find({
|
||||
success: function(results) {
|
||||
equal(results.length, 1);
|
||||
@@ -258,7 +260,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
});
|
||||
|
||||
it('geo max distance in miles mid peninsula', (done) => {
|
||||
makeSomeGeoPoints(function(list) {
|
||||
makeSomeGeoPoints(function() {
|
||||
var sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
|
||||
var query = new Parse.Query(TestObject);
|
||||
query.withinMiles('location', sfo, 10.0);
|
||||
|
||||
+116
-25
@@ -1,15 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
var request = require('request');
|
||||
var Parse = require('parse/node').Parse;
|
||||
let Config = require('../src/Config');
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe('a GlobalConfig', () => {
|
||||
beforeEach(done => {
|
||||
let config = new Config('test');
|
||||
config.database.adaptiveCollection('_GlobalConfig')
|
||||
.then(coll => coll.upsertOne({ '_id': 1 }, { $set: { params: { companies: ['US', 'DK'] } } }))
|
||||
.then(() => { 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();
|
||||
});
|
||||
});
|
||||
|
||||
it('can be retrieved', (done) => {
|
||||
@@ -21,8 +32,10 @@ describe('a GlobalConfig', () => {
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body.params.companies).toEqual(['US', 'DK']);
|
||||
try {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body.params.companies).toEqual(['US', 'DK']);
|
||||
} catch(e) { jfail(e); }
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -43,6 +56,80 @@ describe('a GlobalConfig', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('can add and retrive files', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { file: { __type: 'File', name: 'name', url: 'http://url' } } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
Parse.Config.get().then((res) => {
|
||||
const file = res.get('file');
|
||||
expect(file.name()).toBe('name');
|
||||
expect(file.url()).toBe('http://url');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can add and retrive Geopoints', (done) => {
|
||||
const geopoint = new Parse.GeoPoint(10,-20);
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { point: geopoint.toJSON() } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
Parse.Config.get().then((res) => {
|
||||
const point = res.get('point');
|
||||
expect(point.latitude).toBe(10);
|
||||
expect(point.longitude).toBe(-20);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('properly handles delete op', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
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);
|
||||
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) => {
|
||||
try {
|
||||
expect(response.statusCode).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); }
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fail to update if master key is missing', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
@@ -60,23 +147,27 @@ describe('a GlobalConfig', () => {
|
||||
});
|
||||
|
||||
it('failed getting config when it is missing', (done) => {
|
||||
let config = new Config('test');
|
||||
config.database.adaptiveCollection('_GlobalConfig')
|
||||
.then(coll => coll.deleteOne({ '_id': 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({});
|
||||
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({});
|
||||
done();
|
||||
});
|
||||
}).catch((e) => {
|
||||
jfail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+468
-360
@@ -1,390 +1,498 @@
|
||||
"use strict";
|
||||
/* global describe, it, expect, fail, Parse */
|
||||
var request = require('request');
|
||||
var triggers = require('../src/triggers');
|
||||
var HooksController = require('../src/Controllers/HooksController').default;
|
||||
var express = require("express");
|
||||
var bodyParser = require('body-parser');
|
||||
// Inject the hooks API
|
||||
Parse.Hooks = require("../src/cloud-code/Parse.Hooks");
|
||||
|
||||
var port = 12345;
|
||||
var hookServerURL = "http://localhost:"+port;
|
||||
var hookServerURL = "http://localhost:" + port;
|
||||
const AppCache = require('../src/cache').AppCache;
|
||||
|
||||
var app = express();
|
||||
app.use(bodyParser.json({ 'type': '*/*' }))
|
||||
app.listen(12345);
|
||||
|
||||
|
||||
describe('Hooks', () => {
|
||||
|
||||
it("should have some hooks registered", (done) => {
|
||||
Parse.Hooks.getFunctions().then((res) => {
|
||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||
done();
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should have some triggers registered", (done) => {
|
||||
Parse.Hooks.getTriggers().then( (res) => {
|
||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||
done();
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("should have no hooks registered", (done) => {
|
||||
Parse.Hooks.getFunctions().then((res) => {
|
||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should CRUD a function registration", (done) => {
|
||||
// Create
|
||||
Parse.Hooks.createFunction("My-Test-Function", "http://someurl").then((res) => {
|
||||
expect(res.functionName).toBe("My-Test-Function");
|
||||
expect(res.url).toBe("http://someurl")
|
||||
// Find
|
||||
return Parse.Hooks.getFunction("My-Test-Function");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res).not.toBe(null);
|
||||
expect(res).not.toBe(undefined);
|
||||
expect(res.url).toBe("http://someurl");
|
||||
// delete
|
||||
return Parse.Hooks.updateFunction("My-Test-Function", "http://anotherurl");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res.functionName).toBe("My-Test-Function");
|
||||
expect(res.url).toBe("http://anotherurl")
|
||||
|
||||
return Parse.Hooks.deleteFunction("My-Test-Function");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
// Find again! but should be deleted
|
||||
return Parse.Hooks.getFunction("My-Test-Function");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
fail("Should not succeed")
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(null);
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe("no function named: My-Test-Function is defined")
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should CRUD a trigger registration", (done) => {
|
||||
// Create
|
||||
Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => {
|
||||
expect(res.className).toBe("MyClass");
|
||||
expect(res.triggerName).toBe("beforeDelete");
|
||||
expect(res.url).toBe("http://someurl")
|
||||
// Find
|
||||
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res).not.toBe(null);
|
||||
expect(res).not.toBe(undefined);
|
||||
expect(res.url).toBe("http://someurl");
|
||||
// delete
|
||||
return Parse.Hooks.updateTrigger("MyClass","beforeDelete", "http://anotherurl");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res.className).toBe("MyClass");
|
||||
expect(res.url).toBe("http://anotherurl")
|
||||
|
||||
return Parse.Hooks.deleteTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
// Find again! but should be deleted
|
||||
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then(function(){
|
||||
fail("should not succeed");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(null);
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe("class MyClass does not exist")
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail to register hooks without Master Key", (done) => {
|
||||
request.post(Parse.serverURL+"/hooks/functions", {
|
||||
headers: {
|
||||
"X-Parse-Application-Id": Parse.applicationId,
|
||||
"X-Parse-REST-API-Key": Parse.restKey,
|
||||
},
|
||||
body: JSON.stringify({ url: "http://hello.word", functionName: "SomeFunction"})
|
||||
}, (err, res, body) => {
|
||||
body = JSON.parse(body);
|
||||
expect(body.error).toBe("unauthorized");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail trying to create two times the same function", (done) => {
|
||||
Parse.Hooks.createFunction("my_new_function", "http://url.com").then( () => {
|
||||
return Parse.Hooks.createFunction("my_new_function", "http://url.com")
|
||||
}, () => {
|
||||
fail("should create a new function");
|
||||
}).then( () => {
|
||||
fail("should not be able to create the same function");
|
||||
it("should have no triggers registered", (done) => {
|
||||
Parse.Hooks.getTriggers().then((res) => {
|
||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should CRUD a function registration", (done) => {
|
||||
// Create
|
||||
Parse.Hooks.createFunction("My-Test-Function", "http://someurl")
|
||||
.then(response => {
|
||||
expect(response.functionName).toBe("My-Test-Function");
|
||||
expect(response.url).toBe("http://someurl")
|
||||
// Find
|
||||
return Parse.Hooks.getFunction("My-Test-Function")
|
||||
}).then(response => {
|
||||
expect(response.objectId).toBeUndefined();
|
||||
expect(response.url).toBe("http://someurl");
|
||||
return Parse.Hooks.updateFunction("My-Test-Function", "http://anotherurl");
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.objectId).toBeUndefined();
|
||||
expect(res.functionName).toBe("My-Test-Function");
|
||||
expect(res.url).toBe("http://anotherurl")
|
||||
// delete
|
||||
return Parse.Hooks.removeFunction("My-Test-Function")
|
||||
})
|
||||
.then(() => {
|
||||
// Find again! but should be deleted
|
||||
return Parse.Hooks.getFunction("My-Test-Function")
|
||||
.then(res => {
|
||||
fail("Failed to delete hook")
|
||||
fail(res)
|
||||
done();
|
||||
return Promise.resolve();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe("no function named: My-Test-Function is defined")
|
||||
done();
|
||||
return Promise.resolve();
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should CRUD a trigger registration", (done) => {
|
||||
// Create
|
||||
Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => {
|
||||
expect(res.className).toBe("MyClass");
|
||||
expect(res.triggerName).toBe("beforeDelete");
|
||||
expect(res.url).toBe("http://someurl")
|
||||
// Find
|
||||
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res).not.toBe(null);
|
||||
expect(res).not.toBe(undefined);
|
||||
expect(res.objectId).toBeUndefined();
|
||||
expect(res.url).toBe("http://someurl");
|
||||
// delete
|
||||
return Parse.Hooks.updateTrigger("MyClass","beforeDelete", "http://anotherurl");
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
}).then((res) => {
|
||||
expect(res.className).toBe("MyClass");
|
||||
expect(res.url).toBe("http://anotherurl")
|
||||
expect(res.objectId).toBeUndefined();
|
||||
|
||||
return Parse.Hooks.removeTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
}).then(() => {
|
||||
// Find again! but should be deleted
|
||||
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
}).then(function(){
|
||||
fail("should not succeed");
|
||||
done();
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
expect(err).not.toBe(null);
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe('function name: my_new_function already exits')
|
||||
return Parse.Hooks.deleteFunction("my_new_function");
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail trying to create two times the same trigger", (done) => {
|
||||
Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then( () => {
|
||||
return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com")
|
||||
}, () => {
|
||||
fail("should create a new trigger");
|
||||
}).then( () => {
|
||||
fail("should not be able to create the same trigger");
|
||||
}, (err) => {
|
||||
expect(err.message).toBe("class MyClass does not exist")
|
||||
} else {
|
||||
fail('should have errored');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail to register hooks without Master Key", (done) => {
|
||||
request.post(Parse.serverURL + "/hooks/functions", {
|
||||
headers: {
|
||||
"X-Parse-Application-Id": Parse.applicationId,
|
||||
"X-Parse-REST-API-Key": Parse.restKey,
|
||||
},
|
||||
body: JSON.stringify({ url: "http://hello.word", functionName: "SomeFunction"})
|
||||
}, (err, res, body) => {
|
||||
body = JSON.parse(body);
|
||||
expect(body.error).toBe("unauthorized");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail trying to create two times the same function", (done) => {
|
||||
Parse.Hooks.createFunction("my_new_function", "http://url.com")
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
|
||||
.then(() => {
|
||||
return Parse.Hooks.createFunction("my_new_function", "http://url.com")
|
||||
}, () => {
|
||||
fail("should create a new function");
|
||||
}).then(() => {
|
||||
fail("should not be able to create the same function");
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe('class MyClass already has trigger beforeSave')
|
||||
return Parse.Hooks.deleteTrigger("MyClass", "beforeSave");
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail trying to update a function that don't exist", (done) => {
|
||||
Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then( () => {
|
||||
fail("Should not succeed")
|
||||
}, (err) => {
|
||||
expect(err.message).toBe('function name: my_new_function already exits')
|
||||
}
|
||||
return Parse.Hooks.removeFunction("my_new_function");
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail trying to create two times the same trigger", (done) => {
|
||||
Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then(() => {
|
||||
return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com")
|
||||
}, () => {
|
||||
fail("should create a new trigger");
|
||||
}).then(() => {
|
||||
fail("should not be able to create the same trigger");
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||
return Parse.Hooks.getFunction("A_COOL_FUNCTION")
|
||||
}).then( (res) => {
|
||||
fail("the function should not exist");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.message).toBe('class MyClass already has trigger beforeSave')
|
||||
}
|
||||
return Parse.Hooks.removeTrigger("MyClass", "beforeSave");
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail trying to update a function that don't exist", (done) => {
|
||||
Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then(() => {
|
||||
fail("Should not succeed")
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail trying to update a trigger that don't exist", (done) => {
|
||||
Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then( () => {
|
||||
fail("Should not succeed")
|
||||
}, (err) => {
|
||||
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||
}
|
||||
return Parse.Hooks.getFunction("A_COOL_FUNCTION")
|
||||
}).then(() => {
|
||||
fail("the function should not exist");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe('class AClassName does not exist');
|
||||
return Parse.Hooks.getTrigger("AClassName","beforeSave")
|
||||
}).then( (res) => {
|
||||
fail("the function should not exist");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.message).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail trying to update a trigger that don't exist", (done) => {
|
||||
Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then(() => {
|
||||
fail("Should not succeed")
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe('class AClassName does not exist');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should fail trying to create a malformed function", (done) => {
|
||||
Parse.Hooks.createFunction("MyFunction").then( (res) => {
|
||||
fail(res);
|
||||
}, (err) => {
|
||||
expect(err.message).toBe('class AClassName does not exist');
|
||||
}
|
||||
return Parse.Hooks.getTrigger("AClassName","beforeSave")
|
||||
}).then(() => {
|
||||
fail("the function should not exist");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.message).toBe('class AClassName does not exist');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should fail trying to create a malformed function", (done) => {
|
||||
Parse.Hooks.createFunction("MyFunction").then((res) => {
|
||||
fail(res);
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(143);
|
||||
expect(err.error).toBe("invalid hook declaration");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail trying to create a malformed function (REST)", (done) => {
|
||||
request.post(Parse.serverURL+"/hooks/functions", {
|
||||
headers: {
|
||||
"X-Parse-Application-Id": Parse.applicationId,
|
||||
"X-Parse-Master-Key": Parse.masterKey,
|
||||
},
|
||||
body: JSON.stringify({ functionName: "SomeFunction"})
|
||||
}, (err, res, body) => {
|
||||
body = JSON.parse(body);
|
||||
expect(body.error).toBe("invalid hook declaration");
|
||||
expect(body.code).toBe(143);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should create hooks and properly preload them", (done) => {
|
||||
|
||||
var promises = [];
|
||||
for (var i = 0; i<5; i++) {
|
||||
promises.push(Parse.Hooks.createTrigger("MyClass"+i, "beforeSave", "http://url.com/beforeSave/"+i));
|
||||
promises.push(Parse.Hooks.createFunction("AFunction"+i, "http://url.com/function"+i));
|
||||
}
|
||||
|
||||
Parse.Promise.when(promises).then(function(results){
|
||||
for (var i=0; i<5; i++) {
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail trying to create a malformed function (REST)", (done) => {
|
||||
request.post(Parse.serverURL + "/hooks/functions", {
|
||||
headers: {
|
||||
"X-Parse-Application-Id": Parse.applicationId,
|
||||
"X-Parse-Master-Key": Parse.masterKey,
|
||||
},
|
||||
body: JSON.stringify({ functionName: "SomeFunction"})
|
||||
}, (err, res, body) => {
|
||||
body = JSON.parse(body);
|
||||
expect(body.error).toBe("invalid hook declaration");
|
||||
expect(body.code).toBe(143);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should create hooks and properly preload them", (done) => {
|
||||
|
||||
var promises = [];
|
||||
for (var i = 0; i < 5; i++) {
|
||||
promises.push(Parse.Hooks.createTrigger("MyClass" + i, "beforeSave", "http://url.com/beforeSave/" + i));
|
||||
promises.push(Parse.Hooks.createFunction("AFunction" + i, "http://url.com/function" + i));
|
||||
}
|
||||
|
||||
Parse.Promise.when(promises).then(function(){
|
||||
for (var i = 0; i < 5; i++) {
|
||||
// Delete everything from memory, as the server just started
|
||||
triggers.removeTrigger("beforeSave", "MyClass"+i, Parse.applicationId);
|
||||
triggers.removeFunction("AFunction"+i, Parse.applicationId);
|
||||
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).toBeUndefined();
|
||||
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).toBeUndefined();
|
||||
}
|
||||
const hooksController = new HooksController(Parse.applicationId);
|
||||
return hooksController.load()
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail();
|
||||
done();
|
||||
}).then(function() {
|
||||
for (var i=0; i<5; i++) {
|
||||
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).not.toBeUndefined();
|
||||
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).not.toBeUndefined();
|
||||
}
|
||||
done();
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail();
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should run the function on the test server", (done) => {
|
||||
|
||||
app.post("/SomeFunction", function(req, res) {
|
||||
res.json({success:"OK!"});
|
||||
triggers.removeTrigger("beforeSave", "MyClass" + i, Parse.applicationId);
|
||||
triggers.removeFunction("AFunction" + i, Parse.applicationId);
|
||||
expect(triggers.getTrigger("MyClass" + i, "beforeSave", Parse.applicationId)).toBeUndefined();
|
||||
expect(triggers.getFunction("AFunction" + i, Parse.applicationId)).toBeUndefined();
|
||||
}
|
||||
const hooksController = new HooksController(Parse.applicationId, AppCache.get('test').databaseController);
|
||||
return hooksController.load()
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail('Should properly create all hooks');
|
||||
done();
|
||||
}).then(function() {
|
||||
for (var i = 0; i < 5; i++) {
|
||||
expect(triggers.getTrigger("MyClass" + i, "beforeSave", Parse.applicationId)).not.toBeUndefined();
|
||||
expect(triggers.getFunction("AFunction" + i, Parse.applicationId)).not.toBeUndefined();
|
||||
}
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail('should properly load all hooks');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should run the function on the test server", (done) => {
|
||||
|
||||
app.post("/SomeFunction", function(req, res) {
|
||||
res.json({success:"OK!"});
|
||||
});
|
||||
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/SomeFunction").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
expect(res).toBe("OK!");
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail calling a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should run the function on the test server", (done) => {
|
||||
|
||||
app.post("/SomeFunctionError", function(req, res) {
|
||||
res.json({error: {code: 1337, error: "hacking that one!"}});
|
||||
});
|
||||
// The function is deleted as the DB is dropped between calls
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/SomeFunctionError").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function() {
|
||||
fail("Should not succeed calling that function");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(141);
|
||||
expect(err.message.code).toEqual(1337)
|
||||
expect(err.message.error).toEqual("hacking that one!");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should provide X-Parse-Webhook-Key when defined", (done) => {
|
||||
app.post("/ExpectingKey", function(req, res) {
|
||||
if (req.get('X-Parse-Webhook-Key') === 'hook') {
|
||||
res.json({success: "correct key provided"});
|
||||
} else {
|
||||
res.json({error: "incorrect key provided"});
|
||||
}
|
||||
});
|
||||
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/ExpectingKey").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
expect(res).toBe("correct key provided");
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail calling a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not pass X-Parse-Webhook-Key if not provided", (done) => {
|
||||
reconfigureServer({ webhookKey: undefined })
|
||||
.then(() => {
|
||||
app.post("/ExpectingKeyAlso", function(req, res) {
|
||||
if (req.get('X-Parse-Webhook-Key') === 'hook') {
|
||||
res.json({success: "correct key provided"});
|
||||
} else {
|
||||
res.json({error: "incorrect key provided"});
|
||||
}
|
||||
});
|
||||
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL + "/ExpectingKeyAlso").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(){
|
||||
fail("Should not succeed calling that function");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err).not.toBe(undefined);
|
||||
expect(err).not.toBe(null);
|
||||
if (err) {
|
||||
expect(err.code).toBe(141);
|
||||
expect(err.message).toEqual("incorrect key provided");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunction").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
expect(res).toBe("OK!");
|
||||
done();
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail("Should not fail calling a function");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("should run the function on the test server", (done) => {
|
||||
|
||||
app.post("/SomeFunctionError", function(req, res) {
|
||||
res.json({error: {code: 1337, error: "hacking that one!"}});
|
||||
});
|
||||
// The function is delete as the DB is dropped between calls
|
||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunctionError").then(function(){
|
||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
}).then(function(res){
|
||||
fail("Should not succeed calling that function");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(141);
|
||||
expect(err.message.code).toEqual(1337)
|
||||
expect(err.message.error).toEqual("hacking that one!");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should run the beforeSave hook on the test server", (done) => {
|
||||
var triggerCount = 0;
|
||||
app.post("/BeforeSaveSome", function(req, res) {
|
||||
triggerCount++;
|
||||
var object = req.body.object;
|
||||
object.hello = "world";
|
||||
});
|
||||
|
||||
|
||||
it("should run the beforeSave hook on the test server", (done) => {
|
||||
var triggerCount = 0;
|
||||
app.post("/BeforeSaveSome", function(req, res) {
|
||||
triggerCount++;
|
||||
var object = req.body.object;
|
||||
object.hello = "world";
|
||||
// Would need parse cloud express to set much more
|
||||
// But this should override the key upon return
|
||||
res.json({success: {object: object}});
|
||||
});
|
||||
// The function is delete as the DB is dropped between calls
|
||||
Parse.Hooks.createTrigger("SomeRandomObject", "beforeSave" ,hookServerURL+"/BeforeSaveSome").then(function(){
|
||||
const obj = new Parse.Object("SomeRandomObject");
|
||||
return obj.save();
|
||||
}).then(function(res){
|
||||
expect(triggerCount).toBe(1);
|
||||
return res.fetch();
|
||||
}).then(function(res){
|
||||
expect(res.get("hello")).toEqual("world");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
console.error(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should run the afterSave hook on the test server", (done) => {
|
||||
var triggerCount = 0;
|
||||
var newObjectId;
|
||||
app.post("/AfterSaveSome", function(req, res) {
|
||||
triggerCount++;
|
||||
var obj = new Parse.Object("AnotherObject");
|
||||
obj.set("foo", "bar");
|
||||
obj.save().then(function(obj){
|
||||
newObjectId = obj.id;
|
||||
res.json({success: {}});
|
||||
})
|
||||
});
|
||||
// The function is delete as the DB is dropped between calls
|
||||
Parse.Hooks.createTrigger("SomeRandomObject", "afterSave" ,hookServerURL+"/AfterSaveSome").then(function(){
|
||||
const obj = new Parse.Object("SomeRandomObject");
|
||||
return obj.save();
|
||||
}).then(function(res){
|
||||
var promise = new Parse.Promise();
|
||||
// Wait a bit here as it's an after save
|
||||
setTimeout(function(){
|
||||
res.json({success: object});
|
||||
});
|
||||
// The function is deleted as the DB is dropped between calls
|
||||
Parse.Hooks.createTrigger("SomeRandomObject", "beforeSave", hookServerURL + "/BeforeSaveSome").then(function () {
|
||||
const obj = new Parse.Object("SomeRandomObject");
|
||||
return obj.save();
|
||||
}).then(function(res) {
|
||||
expect(triggerCount).toBe(1);
|
||||
return res.fetch();
|
||||
}).then(function(res) {
|
||||
expect(res.get("hello")).toEqual("world");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("beforeSave hooks should correctly handle responses containing entire object", (done) => {
|
||||
app.post("/BeforeSaveSome2", function(req, res) {
|
||||
var object = Parse.Object.fromJSON(req.body.object);
|
||||
object.set('hello', "world");
|
||||
res.json({success: object});
|
||||
});
|
||||
Parse.Hooks.createTrigger("SomeRandomObject2", "beforeSave", hookServerURL + "/BeforeSaveSome2").then(function(){
|
||||
const obj = new Parse.Object("SomeRandomObject2");
|
||||
return obj.save();
|
||||
}).then(function(res) {
|
||||
return res.save();
|
||||
}).then(function(res) {
|
||||
expect(res.get("hello")).toEqual("world");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
fail(`Should not fail: ${JSON.stringify(err)}`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should run the afterSave hook on the test server", (done) => {
|
||||
var triggerCount = 0;
|
||||
var newObjectId;
|
||||
app.post("/AfterSaveSome", function(req, res) {
|
||||
triggerCount++;
|
||||
var obj = new Parse.Object("AnotherObject");
|
||||
obj.set("foo", "bar");
|
||||
obj.save().then(function(obj){
|
||||
newObjectId = obj.id;
|
||||
res.json({success: {}});
|
||||
})
|
||||
});
|
||||
// The function is deleted as the DB is dropped between calls
|
||||
Parse.Hooks.createTrigger("SomeRandomObject", "afterSave", hookServerURL + "/AfterSaveSome").then(function(){
|
||||
const obj = new Parse.Object("SomeRandomObject");
|
||||
return obj.save();
|
||||
}).then(function() {
|
||||
var promise = new Parse.Promise();
|
||||
// Wait a bit here as it's an after save
|
||||
setTimeout(() => {
|
||||
expect(triggerCount).toBe(1);
|
||||
var q = new Parse.Query("AnotherObject");
|
||||
q.get(newObjectId).then(function(r){
|
||||
promise.resolve(r);
|
||||
});
|
||||
}, 300)
|
||||
return promise;
|
||||
}).then(function(res){
|
||||
expect(res.get("foo")).toEqual("bar");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
console.error(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
new Parse.Query("AnotherObject")
|
||||
.get(newObjectId)
|
||||
.then((r) => promise.resolve(r));
|
||||
}, 500);
|
||||
return promise;
|
||||
}).then(function(res){
|
||||
expect(res.get("foo")).toEqual("bar");
|
||||
done();
|
||||
}).fail((err) => {
|
||||
jfail(err);
|
||||
fail("Should not fail creating a function");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+391
-206
@@ -2,18 +2,29 @@
|
||||
// These tests check the Installations functionality of the REST API.
|
||||
// Ported from installation_collection_test.go
|
||||
|
||||
var auth = require('../src/Auth');
|
||||
var cache = require('../src/cache');
|
||||
var Config = require('../src/Config');
|
||||
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||
var Parse = require('parse/node').Parse;
|
||||
var rest = require('../src/rest');
|
||||
const auth = require('../src/Auth');
|
||||
const Config = require('../src/Config');
|
||||
const Parse = require('parse/node').Parse;
|
||||
const rest = require('../src/rest');
|
||||
const request = require("request");
|
||||
|
||||
var config = new Config('test');
|
||||
let database = DatabaseAdapter.getDatabaseConnection('test', 'test_');
|
||||
let config;
|
||||
let database;
|
||||
const defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
|
||||
|
||||
const delay = function delay(delay) {
|
||||
return new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
|
||||
const installationSchema = { fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation) };
|
||||
|
||||
describe('Installations', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config('test');
|
||||
database = config.database;
|
||||
});
|
||||
|
||||
it('creates an android installation with ids', (done) => {
|
||||
var installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
var device = 'android';
|
||||
@@ -22,15 +33,14 @@ describe('Installations', () => {
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(obj.installationId).toEqual(installId);
|
||||
expect(obj.deviceType).toEqual(device);
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch((error) => { console.log(error); jfail(error); done(); });
|
||||
});
|
||||
|
||||
it('creates an ios installation with ids', (done) => {
|
||||
@@ -41,15 +51,14 @@ describe('Installations', () => {
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(obj.deviceToken).toEqual(t);
|
||||
expect(obj.deviceType).toEqual(device);
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch((error) => { console.log(error); jfail(error); done(); });
|
||||
});
|
||||
|
||||
it('creates an embedded installation with ids', (done) => {
|
||||
@@ -60,15 +69,14 @@ describe('Installations', () => {
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(obj.installationId).toEqual(installId);
|
||||
expect(obj.deviceType).toEqual(device);
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch((error) => { console.log(error); jfail(error); done(); });
|
||||
});
|
||||
|
||||
it('creates an android installation with all fields', (done) => {
|
||||
@@ -80,9 +88,8 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(obj.installationId).toEqual(installId);
|
||||
@@ -92,7 +99,7 @@ describe('Installations', () => {
|
||||
expect(obj.channels[0]).toEqual('foo');
|
||||
expect(obj.channels[1]).toEqual('bar');
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch((error) => { console.log(error); jfail(error); done(); });
|
||||
});
|
||||
|
||||
it('creates an ios installation with all fields', (done) => {
|
||||
@@ -104,9 +111,8 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(obj.deviceToken).toEqual(t);
|
||||
@@ -116,7 +122,51 @@ describe('Installations', () => {
|
||||
expect(obj.channels[0]).toEqual('foo');
|
||||
expect(obj.channels[1]).toEqual('bar');
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch((error) => { console.log(error); jfail(error); done(); });
|
||||
});
|
||||
|
||||
it('should properly fail queying installations', (done) => {
|
||||
var installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
var device = 'android';
|
||||
var input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
const query = new Parse.Query(Parse.Installation);
|
||||
return query.find()
|
||||
}).then(() => {
|
||||
fail('Should not succeed!');
|
||||
done();
|
||||
}).catch((error) => {
|
||||
expect(error.code).toBe(119);
|
||||
expect(error.message).toBe('Clients aren\'t allowed to perform the find operation on the installation collection.')
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly queying installations with masterKey', (done) => {
|
||||
var installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
var device = 'android';
|
||||
var input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
const query = new Parse.Query(Parse.Installation);
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0].toJSON();
|
||||
expect(obj.installationId).toEqual(installId);
|
||||
expect(obj.deviceType).toEqual(device);
|
||||
done();
|
||||
}).catch(() => {
|
||||
fail('Should not fail');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with missing ids', (done) => {
|
||||
@@ -158,10 +208,9 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar'],
|
||||
'custom': 'allowed'
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(obj.custom).toEqual('allowed');
|
||||
@@ -174,7 +223,6 @@ describe('Installations', () => {
|
||||
it('merging when installationId already exists', (done) => {
|
||||
var installId1 = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
var t = '11433856eed2f1285fb3aa11136718c1198ed5647875096952c66bf8cb976306';
|
||||
var installId2 = '12345678-abcd-abcd-abcd-123456789abd';
|
||||
var input = {
|
||||
'deviceToken': t,
|
||||
'deviceType': 'ios',
|
||||
@@ -184,18 +232,17 @@ describe('Installations', () => {
|
||||
var firstObject;
|
||||
var secondObject;
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
firstObject = results[0];
|
||||
delete input.deviceToken;
|
||||
delete input.channels;
|
||||
input['foo'] = 'bar';
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
secondObject = results[0];
|
||||
expect(firstObject._id).toEqual(secondObject._id);
|
||||
@@ -224,15 +271,14 @@ describe('Installations', () => {
|
||||
var firstObject;
|
||||
var secondObject;
|
||||
rest.create(config, auth.nobody(config), '_Installation', input1)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
firstObject = results[0];
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input2);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(2);
|
||||
if (results[0]['_id'] == firstObject._id) {
|
||||
secondObject = results[1];
|
||||
@@ -240,16 +286,19 @@ describe('Installations', () => {
|
||||
secondObject = results[0];
|
||||
}
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input3);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]['_id']).toEqual(secondObject._id);
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
notWorking('creating multiple devices with same device token works', (done) => {
|
||||
xit('creating multiple devices with same device token works', (done) => {
|
||||
var installId1 = '11111111-abcd-abcd-abcd-123456789abc';
|
||||
var installId2 = '22222222-abcd-abcd-abcd-123456789abc';
|
||||
var installId3 = '33333333-abcd-abcd-abcd-123456789abc';
|
||||
@@ -266,17 +315,14 @@ describe('Installations', () => {
|
||||
}).then(() => {
|
||||
input.installationId = installId3;
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation',
|
||||
{installationId: installId1}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {installationId: installId1}, installationSchema, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
return database.mongoFind('_Installation',
|
||||
{installationId: installId2}, {});
|
||||
}).then((results) => {
|
||||
return database.adapter.find('_Installation', {installationId: installId2}, installationSchema, {});
|
||||
}).then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
return database.mongoFind('_Installation',
|
||||
{installationId: installId3}, {});
|
||||
return database.adapter.find('_Installation', {installationId: installId3}, installationSchema, {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
@@ -285,29 +331,30 @@ describe('Installations', () => {
|
||||
|
||||
it('updating with new channels', (done) => {
|
||||
var input = {
|
||||
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
|
||||
'deviceType': 'android',
|
||||
'channels': ['foo', 'bar']
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
channels: ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var id = results[0]['_id'];
|
||||
var id = results[0].objectId;
|
||||
var update = {
|
||||
'channels': ['baz']
|
||||
};
|
||||
return rest.update(config, auth.nobody(config),
|
||||
'_Installation', id, update);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', id, update);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].channels.length).toEqual(1);
|
||||
expect(results[0].channels[0]).toEqual('baz');
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('update android fails with new installation id', (done) => {
|
||||
@@ -319,15 +366,11 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
'installationId': installId2
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
results[0]['_id'], input);
|
||||
input = { 'installationId': installId2 };
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
}).then(() => {
|
||||
fail('Updating the installation should have failed.');
|
||||
done();
|
||||
@@ -346,15 +389,11 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
'deviceToken': b
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
results[0]['_id'], input);
|
||||
input = { 'deviceToken': b };
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
}).then(() => {
|
||||
fail('Updating the installation should have failed.');
|
||||
}).catch((error) => {
|
||||
@@ -374,24 +413,25 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
'installationId': installId,
|
||||
'deviceToken': u,
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
results[0]['_id'], input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].deviceToken).toEqual(u);
|
||||
done();
|
||||
});
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('update fails to change deviceType', (done) => {
|
||||
@@ -402,15 +442,13 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
results[0]['_id'], input);
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
}).then(() => {
|
||||
fail('Should not have been able to update Installation.');
|
||||
done();
|
||||
@@ -428,18 +466,16 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
'custom': 'allowed'
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
results[0]['_id'], input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]['custom']).toEqual('allowed');
|
||||
done();
|
||||
@@ -458,40 +494,39 @@ describe('Installations', () => {
|
||||
var firstObject;
|
||||
var secondObject;
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
input = {
|
||||
'installationId': installId2,
|
||||
'deviceType': 'android'
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation',
|
||||
{installationId: installId1}, {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
.then(() => {
|
||||
input = {
|
||||
'installationId': installId2,
|
||||
'deviceType': 'android'
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {installationId: installId1}, {}))
|
||||
.then(results => {
|
||||
firstObject = results[0];
|
||||
return database.mongoFind('_Installation',
|
||||
{installationId: installId2}, {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
return database.adapter.find('_Installation', installationSchema, {installationId: installId2}, {});
|
||||
}).then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
secondObject = results[0];
|
||||
// Update second installation to conflict with first installation
|
||||
input = {
|
||||
'objectId': secondObject._id,
|
||||
'objectId': secondObject.objectId,
|
||||
'deviceToken': t
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
secondObject._id, input);
|
||||
}).then(() => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', secondObject.objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {objectId: firstObject.objectId}, {}))
|
||||
.then(results => {
|
||||
// The first object should have been deleted
|
||||
return database.mongoFind('_Installation', {_id: firstObject._id}, {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(0);
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('update ios device token with duplicate device token', (done) => {
|
||||
var installId1 = '11111111-abcd-abcd-abcd-123456789abc';
|
||||
var installId2 = '22222222-abcd-abcd-abcd-123456789abc';
|
||||
@@ -510,15 +545,16 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation',
|
||||
{installationId: installId1}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => delay(100))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {installationId: installId1}, {}))
|
||||
.then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
firstObject = results[0];
|
||||
return database.mongoFind('_Installation',
|
||||
{installationId: installId2}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => delay(100))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {installationId: installId2}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
secondObject = results[0];
|
||||
// Update second installation to conflict with first installation id
|
||||
@@ -526,18 +562,21 @@ describe('Installations', () => {
|
||||
'installationId': installId2,
|
||||
'deviceToken': t
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
secondObject._id, input);
|
||||
}).then(() => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', secondObject.objectId, input);
|
||||
})
|
||||
.then(() => delay(100))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {objectId: firstObject.objectId}, {}))
|
||||
.then(results => {
|
||||
// The first object should have been deleted
|
||||
return database.mongoFind('_Installation', {_id: firstObject._id}, {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(0);
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
notWorking('update ios device token with duplicate token different app', (done) => {
|
||||
xit('update ios device token with duplicate token different app', (done) => {
|
||||
var installId1 = '11111111-abcd-abcd-abcd-123456789abc';
|
||||
var installId2 = '22222222-abcd-abcd-abcd-123456789abc';
|
||||
var t = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
|
||||
@@ -552,13 +591,16 @@ describe('Installations', () => {
|
||||
input.installationId = installId2;
|
||||
input.appIdentifier = 'bar';
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
// The first object should have been deleted during merge
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId2);
|
||||
done();
|
||||
}).catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -570,24 +612,25 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
'deviceToken': t,
|
||||
'channels': []
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
results[0]['_id'], input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
expect(results[0].deviceToken).toEqual(t);
|
||||
expect(results[0].channels.length).toEqual(0);
|
||||
done();
|
||||
}).catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -605,26 +648,27 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation',
|
||||
{deviceToken: t}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
'deviceToken': t,
|
||||
'installationId': installId,
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
results[0]['_id'], input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
expect(results[0].deviceToken).toEqual(t);
|
||||
expect(results[0].deviceType).toEqual('ios');
|
||||
done();
|
||||
}).catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -642,10 +686,9 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation',
|
||||
{deviceToken: t}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
'deviceToken': t,
|
||||
@@ -656,17 +699,19 @@ describe('Installations', () => {
|
||||
'amount': 1
|
||||
}
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
results[0]['_id'], input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
expect(results[0].deviceToken).toEqual(t);
|
||||
expect(results[0].deviceType).toEqual('ios');
|
||||
expect(results[0].score).toEqual(1);
|
||||
done();
|
||||
}).catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -680,9 +725,8 @@ describe('Installations', () => {
|
||||
var installObj;
|
||||
var tokenObj;
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
installObj = results[0];
|
||||
input = {
|
||||
@@ -690,9 +734,9 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {deviceToken: t}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
tokenObj = results[0];
|
||||
input = {
|
||||
@@ -700,16 +744,18 @@ describe('Installations', () => {
|
||||
'deviceToken': t,
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
installObj._id, input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {_id: tokenObj._id}, {});
|
||||
}).then((results) => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', installObj.objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { objectId: tokenObj.objectId }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
expect(results[0].deviceToken).toEqual(t);
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('update is linking two existing with installation id w/ op', (done) => {
|
||||
@@ -722,9 +768,8 @@ describe('Installations', () => {
|
||||
var installObj;
|
||||
var tokenObj;
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
installObj = results[0];
|
||||
input = {
|
||||
@@ -732,9 +777,9 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {deviceToken: t}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
tokenObj = results[0];
|
||||
input = {
|
||||
@@ -746,17 +791,19 @@ describe('Installations', () => {
|
||||
'amount': 1
|
||||
}
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation',
|
||||
installObj._id, input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {_id: tokenObj._id}, {});
|
||||
}).then((results) => {
|
||||
return rest.update(config, auth.nobody(config), '_Installation', installObj.objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { objectId: tokenObj.objectId }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
expect(results[0].deviceToken).toEqual(t);
|
||||
expect(results[0].score).toEqual(1);
|
||||
done();
|
||||
}).catch((error) => { console.log(error); });
|
||||
}).catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ios merge existing same token no installation id', (done) => {
|
||||
@@ -777,9 +824,8 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
'installationId': installId,
|
||||
@@ -787,18 +833,157 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
}).then(() => {
|
||||
return database.mongoFind('_Installation', {}, {});
|
||||
}).then((results) => {
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].deviceToken).toEqual(t);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
fail();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows you to get your own installation (regression test for #1718)', done => {
|
||||
const installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
const device = 'android';
|
||||
const input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(createResult => {
|
||||
const headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
request.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/installations/' + createResult.response.objectId,
|
||||
json: true,
|
||||
}, (error, response, body) => {
|
||||
expect(body.objectId).toEqual(createResult.response.objectId);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
fail('failed');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows you to update installation from header (#2090)', done => {
|
||||
const installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
const device = 'android';
|
||||
const input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
const headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Installation-Id': installId
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/_Installation',
|
||||
json: true,
|
||||
body: {
|
||||
date: new Date()
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(body.updatedAt).not.toBeUndefined();
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
fail('failed');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows you to update installation with masterKey', done => {
|
||||
const installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
const device = 'android';
|
||||
const input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(createResult => {
|
||||
const installationObj = Parse.Installation.createWithoutData(createResult.response.objectId);
|
||||
installationObj.set('customField', 'custom value');
|
||||
return installationObj.save(null, {useMasterKey: true});
|
||||
}).then(updateResult => {
|
||||
expect(updateResult).not.toBeUndefined();
|
||||
expect(updateResult.get('customField')).toEqual('custom value');
|
||||
done();
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
fail('failed');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly handle installation save #2780', done => {
|
||||
const installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
const device = 'android';
|
||||
const input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input).then(() => {
|
||||
const query = new Parse.Query(Parse.Installation);
|
||||
query.equalTo('installationId', installId);
|
||||
query.first({useMasterKey: true}).then((installation) => {
|
||||
return installation.save({
|
||||
key: 'value'
|
||||
}, {useMasterKey: true});
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err)
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly reject updating installationId', done => {
|
||||
const installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
const device = 'android';
|
||||
const input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input).then(() => {
|
||||
const query = new Parse.Query(Parse.Installation);
|
||||
query.equalTo('installationId', installId);
|
||||
query.first({useMasterKey: true}).then((installation) => {
|
||||
return installation.save({
|
||||
key: 'value',
|
||||
installationId: '22222222-abcd-abcd-abcd-123456789abc'
|
||||
}, {useMasterKey: true});
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(136);
|
||||
expect(err.message).toBe('installationId may not be changed in this operation');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Look at additional tests from installation_collection_test.go:882
|
||||
// TODO: Do we need to support _tombstone disabling of installations?
|
||||
// TODO: Test deletion, badge increments
|
||||
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ var testUserId = 'userId';
|
||||
var testClassName = 'TestObject';
|
||||
|
||||
describe('ParseLiveQueryServer', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
// Mock ParseWebSocketServer
|
||||
var mockParseWebSocketServer = jasmine.createSpy('ParseWebSocketServer');
|
||||
@@ -160,7 +159,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
// Add two mock clients
|
||||
var clientId = 1;
|
||||
var client = addMockClient(parseLiveQueryServer, clientId);
|
||||
addMockClient(parseLiveQueryServer, clientId);
|
||||
var clientIdAgain = 2;
|
||||
var clientAgain = addMockClient(parseLiveQueryServer, clientIdAgain);
|
||||
// Add subscription for mock client 1
|
||||
@@ -233,7 +232,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
// Add mock client
|
||||
var clientId = 1;
|
||||
var client = addMockClient(parseLiveQueryServer, clientId);
|
||||
addMockClient(parseLiveQueryServer, clientId);
|
||||
// Handle unsubscribe command
|
||||
var parseWebSocket = {
|
||||
clientId: 1
|
||||
@@ -275,7 +274,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
expect(subscriptions.size).toBe(0);
|
||||
});
|
||||
|
||||
it('can set connect command message handler for a parseWebSocket', function() {
|
||||
it('can set connect command message handler for a parseWebSocket', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
// Register mock connect/subscribe/unsubscribe handler for the server
|
||||
parseLiveQueryServer._handleConnect = jasmine.createSpy('_handleSubscribe');
|
||||
@@ -336,6 +335,35 @@ describe('ParseLiveQueryServer', function() {
|
||||
expect(JSON.stringify(args[1])).toBe(unsubscribeRequest);
|
||||
});
|
||||
|
||||
it('can set update command message handler for a parseWebSocket', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
// Register mock connect/subscribe/unsubscribe handler for the server
|
||||
spyOn(parseLiveQueryServer, '_handleUpdateSubscription').and.callThrough();
|
||||
spyOn(parseLiveQueryServer, '_handleUnsubscribe').and.callThrough();
|
||||
spyOn(parseLiveQueryServer, '_handleSubscribe').and.callThrough();
|
||||
|
||||
// Make mock parseWebsocket
|
||||
var EventEmitter = require('events');
|
||||
var parseWebSocket = new EventEmitter();
|
||||
|
||||
// Register message handlers for the parseWebSocket
|
||||
parseLiveQueryServer._onConnect(parseWebSocket);
|
||||
|
||||
// Check updateRequest request
|
||||
var updateRequest = '{"op":"update"}';
|
||||
// Trigger message event
|
||||
parseWebSocket.emit('message', updateRequest);
|
||||
// Make sure _handleUnsubscribe is called
|
||||
var args = parseLiveQueryServer._handleUpdateSubscription.calls.mostRecent().args;
|
||||
expect(args[0]).toBe(parseWebSocket);
|
||||
expect(JSON.stringify(args[1])).toBe(updateRequest);
|
||||
expect(parseLiveQueryServer._handleUnsubscribe).toHaveBeenCalled();
|
||||
const unsubArgs = parseLiveQueryServer._handleUnsubscribe.calls.mostRecent().args;
|
||||
expect(unsubArgs.length).toBe(3);
|
||||
expect(unsubArgs[2]).toBe(false);
|
||||
expect(parseLiveQueryServer._handleSubscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can set unknown command message handler for a parseWebSocket', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
// Make mock parseWebsocket
|
||||
@@ -505,7 +533,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
// In order to mimic a enter, we need original match return false
|
||||
// and the current match return true
|
||||
var counter = 0;
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject){
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
@@ -539,7 +567,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var requestId = 2;
|
||||
addMockSubscription(parseLiveQueryServer, clientId, requestId);
|
||||
// Mock _matchesSubscription to return matching
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject){
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
@@ -575,7 +603,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
// In order to mimic a leave, we need original match return true
|
||||
// and the current match return false
|
||||
var counter = 0;
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject){
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
@@ -609,7 +637,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
var requestId = 2;
|
||||
addMockSubscription(parseLiveQueryServer, clientId, requestId);
|
||||
// Mock _matchesSubscription to return matching
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject, subscription){
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject){
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
@@ -718,7 +746,6 @@ describe('ParseLiveQueryServer', function() {
|
||||
};
|
||||
var requestId = 0;
|
||||
|
||||
var isChecked = false;
|
||||
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
|
||||
expect(isMatched).toBe(false);
|
||||
done();
|
||||
@@ -835,6 +862,116 @@ describe('ParseLiveQueryServer', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('won\'t match ACL that doesn\'t have public read or any roles', function(done){
|
||||
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
var acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(false);
|
||||
var client = {
|
||||
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
|
||||
sessionToken: 'sessionToken'
|
||||
})
|
||||
};
|
||||
var requestId = 0;
|
||||
|
||||
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
|
||||
expect(isMatched).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('won\'t match non-public ACL with role when there is no user', function(done){
|
||||
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
var acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(false);
|
||||
acl.setRoleReadAccess("livequery", true);
|
||||
var client = {
|
||||
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
|
||||
})
|
||||
};
|
||||
var requestId = 0;
|
||||
|
||||
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
|
||||
expect(isMatched).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('won\'t match ACL with role based read access set to false', function(done){
|
||||
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
var acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(false);
|
||||
acl.setRoleReadAccess("liveQueryRead", false);
|
||||
var client = {
|
||||
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
|
||||
sessionToken: 'sessionToken'
|
||||
})
|
||||
};
|
||||
var requestId = 0;
|
||||
|
||||
spyOn(Parse, "Query").and.callFake(function(){
|
||||
return {
|
||||
equalTo() {
|
||||
// Nothing to do here
|
||||
},
|
||||
find() {
|
||||
//Return a role with the name "liveQueryRead" as that is what was set on the ACL
|
||||
var liveQueryRole = new Parse.Role();
|
||||
liveQueryRole.set('name', 'liveQueryRead');
|
||||
return [
|
||||
liveQueryRole
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
|
||||
expect(isMatched).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('will match ACL with role based read access set to true', function(done){
|
||||
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
|
||||
var acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(false);
|
||||
acl.setRoleReadAccess("liveQueryRead", true);
|
||||
var client = {
|
||||
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
|
||||
sessionToken: 'sessionToken'
|
||||
})
|
||||
};
|
||||
var requestId = 0;
|
||||
|
||||
spyOn(Parse, "Query").and.callFake(function(){
|
||||
return {
|
||||
equalTo() {
|
||||
// Nothing to do here
|
||||
},
|
||||
find() {
|
||||
//Return a role with the name "liveQueryRead" as that is what was set on the ACL
|
||||
var liveQueryRole = new Parse.Role();
|
||||
liveQueryRole.set('name', 'liveQueryRead');
|
||||
return [
|
||||
liveQueryRole
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
|
||||
expect(isMatched).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('can validate key when valid key is provided', function() {
|
||||
var parseLiveQueryServer = new ParseLiveQueryServer({}, {
|
||||
keyPairs: {
|
||||
|
||||
+151
-57
@@ -14,7 +14,7 @@
|
||||
|
||||
describe('Parse.Object testing', () => {
|
||||
it("create", function(done) {
|
||||
create({ "test" : "test" }, function(model, response) {
|
||||
create({ "test" : "test" }, function(model) {
|
||||
ok(model.id, "Should have an objectId set");
|
||||
equal(model.get("test"), "test", "Should have the right attribute");
|
||||
done();
|
||||
@@ -22,11 +22,11 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it("update", function(done) {
|
||||
create({ "test" : "test" }, function(model, response) {
|
||||
create({ "test" : "test" }, function(model) {
|
||||
var t2 = new TestObject({ objectId: model.id });
|
||||
t2.set("test", "changed");
|
||||
t2.save(null, {
|
||||
success: function(model, response) {
|
||||
success: function(model) {
|
||||
equal(model.get("test"), "changed", "Update should have succeeded");
|
||||
done();
|
||||
}
|
||||
@@ -49,7 +49,7 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("save cycle", function(done) {
|
||||
it("save cycle", done => {
|
||||
var a = new Parse.Object("TestObject");
|
||||
var b = new Parse.Object("TestObject");
|
||||
a.set("b", b);
|
||||
@@ -72,10 +72,10 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it("get", function(done) {
|
||||
create({ "test" : "test" }, function(model, response) {
|
||||
create({ "test" : "test" }, function(model) {
|
||||
var t2 = new TestObject({ objectId: model.id });
|
||||
t2.fetch({
|
||||
success: function(model2, response) {
|
||||
success: function(model2) {
|
||||
equal(model2.get("test"), "test", "Update should have succeeded");
|
||||
ok(model2.id);
|
||||
equal(model2.id, model.id, "Ids should match");
|
||||
@@ -268,16 +268,21 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("can set null", function(done) {
|
||||
it_exclude_dbs(['postgres'])("can set null", function(done) {
|
||||
var obj = new Parse.Object("TestObject");
|
||||
obj.set("foo", null);
|
||||
obj.save(null, {
|
||||
success: function(obj) {
|
||||
equal(obj.get("foo"), null);
|
||||
on_db('mongo', () => {
|
||||
equal(obj.get("foo"), null);
|
||||
});
|
||||
on_db('postgres', () => {
|
||||
fail('should not succeed');
|
||||
});
|
||||
done();
|
||||
},
|
||||
error: function(obj, error) {
|
||||
ok(false, error.message);
|
||||
error: function() {
|
||||
fail('should not fail');
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -317,11 +322,11 @@ describe('Parse.Object testing', () => {
|
||||
it("invalid class name", function(done) {
|
||||
var item = new Parse.Object("Foo^bar");
|
||||
item.save(null, {
|
||||
success: function(item) {
|
||||
success: function() {
|
||||
ok(false, "The name should have been invalid.");
|
||||
done();
|
||||
},
|
||||
error: function(item, error) {
|
||||
error: function() {
|
||||
// Because the class name is invalid, the router will not be able to route
|
||||
// it, so it will actually return a -1 error code.
|
||||
// equal(error.code, Parse.Error.INVALID_CLASS_NAME);
|
||||
@@ -340,7 +345,6 @@ describe('Parse.Object testing', () => {
|
||||
it("invalid __type", function(done) {
|
||||
var item = new Parse.Object("Item");
|
||||
var types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes'];
|
||||
var Error = Parse.Error;
|
||||
var tests = types.map(type => {
|
||||
var test = new Parse.Object("Item");
|
||||
test.set('foo', {
|
||||
@@ -362,7 +366,7 @@ describe('Parse.Object testing', () => {
|
||||
"foo": {
|
||||
__type: "IvalidName"
|
||||
}
|
||||
}).then(fail, err => next(0));
|
||||
}).then(fail, () => next(0));
|
||||
});
|
||||
|
||||
it("simple field deletion", function(done) {
|
||||
@@ -585,17 +589,27 @@ describe('Parse.Object testing', () => {
|
||||
var objectId = x1.id;
|
||||
var x2 = new Parse.Object('X', {objectId: objectId});
|
||||
x2.addUnique('stuff', 2);
|
||||
x2.addUnique('stuff', 3);
|
||||
expect(x2.get('stuff')).toEqual([2, 3]);
|
||||
x2.addUnique('stuff', 4);
|
||||
expect(x2.get('stuff')).toEqual([2, 4]);
|
||||
return x2.save();
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('X');
|
||||
return query.get(x1.id);
|
||||
}).then((x3) => {
|
||||
expect(x3.get('stuff')).toEqual([1, 2, 3]);
|
||||
const stuff = x3.get('stuff');
|
||||
const expected = [1, 2, 4];
|
||||
expect(stuff.length).toBe(expected.length);
|
||||
for (var i of stuff) {
|
||||
expect(expected.indexOf(i) >= 0).toBe(true);
|
||||
}
|
||||
done();
|
||||
}, (error) => {
|
||||
fail(error);
|
||||
on_db('mongo', () => {
|
||||
jfail(error);
|
||||
});
|
||||
on_db('postgres', () => {
|
||||
expect(error.message).toEqual("Postgres does not support AddUnique operator.");
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -614,10 +628,21 @@ describe('Parse.Object testing', () => {
|
||||
var query = new Parse.Query('X');
|
||||
return query.get(x1.id);
|
||||
}).then((x3) => {
|
||||
expect(x3.get('stuff')).toEqual([1, {'hello': 'world'}, {'foo': 'bar'}, {'bar': 'baz'}]);
|
||||
const stuff = x3.get('stuff');
|
||||
const target = [1, {'hello': 'world'}, {'foo': 'bar'}, {'bar': 'baz'}];
|
||||
expect(stuff.length).toEqual(target.length);
|
||||
let found = 0;
|
||||
for (const thing in target) {
|
||||
for (const st in stuff) {
|
||||
if (st == thing) {
|
||||
found++;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(found).toBe(target.length);
|
||||
done();
|
||||
}, (error) => {
|
||||
fail(error);
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -638,7 +663,7 @@ describe('Parse.Object testing', () => {
|
||||
expect(x3.get('stuff')).toEqual([1, {'foo': 'bar'}]);
|
||||
done();
|
||||
}, (error) => {
|
||||
fail(error);
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -661,7 +686,7 @@ describe('Parse.Object testing', () => {
|
||||
|
||||
done();
|
||||
},
|
||||
error: function(object, error) {
|
||||
error: function() {
|
||||
ok(false, "This should have saved.");
|
||||
done();
|
||||
}
|
||||
@@ -1052,7 +1077,7 @@ describe('Parse.Object testing', () => {
|
||||
parent.set('children', [child1, child2]);
|
||||
|
||||
parent.save(null, {
|
||||
success: function(parent) {
|
||||
success: function() {
|
||||
var query = new Parse.Query(Child);
|
||||
query.ascending('name');
|
||||
query.find({
|
||||
@@ -1172,8 +1197,7 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it("toJSON saved object", function(done) {
|
||||
var _ = Parse._;
|
||||
create({ "foo" : "bar" }, function(model, response) {
|
||||
create({ "foo" : "bar" }, function(model) {
|
||||
var objJSON = model.toJSON();
|
||||
ok(objJSON.foo, "expected json to contain key 'foo'");
|
||||
ok(objJSON.objectId, "expected json to contain key 'objectId'");
|
||||
@@ -1247,7 +1271,7 @@ describe('Parse.Object testing', () => {
|
||||
return bryan.save({
|
||||
meal: "tomatoes"
|
||||
});
|
||||
}, function(error) {
|
||||
}, function() {
|
||||
ok(false, "Save should have succeeded.");
|
||||
}).then(function() {
|
||||
ok(false, "Save should have failed.");
|
||||
@@ -1304,7 +1328,7 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("bytes work", function(done) {
|
||||
it_exclude_dbs(['postgres'])("bytes work", function(done) {
|
||||
Parse.Promise.as().then(function() {
|
||||
var obj = new TestObject();
|
||||
obj.set("bytes", { __type: "Bytes", base64: "ZnJveW8=" });
|
||||
@@ -1366,7 +1390,7 @@ describe('Parse.Object testing', () => {
|
||||
}
|
||||
equal(itemsAgain.length, numItems, "Should get the array back");
|
||||
itemsAgain.forEach(function(item, i) {
|
||||
var newValue = i*2;
|
||||
var newValue = i * 2;
|
||||
item.set("x", newValue);
|
||||
});
|
||||
return Parse.Object.saveAll(itemsAgain);
|
||||
@@ -1376,7 +1400,7 @@ describe('Parse.Object testing', () => {
|
||||
equal(fetchedItemsAgain.length, numItems,
|
||||
"Number of items fetched should not change");
|
||||
fetchedItemsAgain.forEach(function(item, i) {
|
||||
equal(item.get("x"), i*2);
|
||||
equal(item.get("x"), i * 2);
|
||||
});
|
||||
done();
|
||||
});
|
||||
@@ -1433,7 +1457,7 @@ describe('Parse.Object testing', () => {
|
||||
}
|
||||
equal(itemsAgain.length, numItems, "Should get the array back");
|
||||
itemsAgain.forEach(function(item, i) {
|
||||
var newValue = i*2;
|
||||
var newValue = i * 2;
|
||||
item.set("x", newValue);
|
||||
});
|
||||
return Parse.Object.saveAll(itemsAgain);
|
||||
@@ -1443,11 +1467,11 @@ describe('Parse.Object testing', () => {
|
||||
equal(fetchedItemsAgain.length, numItems,
|
||||
"Number of items fetched should not change");
|
||||
fetchedItemsAgain.forEach(function(item, i) {
|
||||
equal(item.get("x"), i*2);
|
||||
equal(item.get("x"), i * 2);
|
||||
});
|
||||
done();
|
||||
},
|
||||
error: function(error) {
|
||||
error: function() {
|
||||
ok(false, "Failed to fetchAll");
|
||||
done();
|
||||
}
|
||||
@@ -1480,8 +1504,6 @@ describe('Parse.Object testing', () => {
|
||||
|
||||
it("fetchAll error on deleted object", function(done) {
|
||||
var numItems = 11;
|
||||
var container = new Container();
|
||||
var subContainer = new Container();
|
||||
var items = [];
|
||||
for (var i = 0; i < numItems; i++) {
|
||||
var item = new Item();
|
||||
@@ -1504,7 +1526,7 @@ describe('Parse.Object testing', () => {
|
||||
|
||||
// TODO: Verify that with Sessions, this test is wrong... A fetch on
|
||||
// user should not bring down a session token.
|
||||
notWorking("fetchAll User attributes get merged", function(done) {
|
||||
xit("fetchAll User attributes get merged", function(done) {
|
||||
var sameUser;
|
||||
var user = new Parse.User();
|
||||
user.set("username", "asdf");
|
||||
@@ -1559,7 +1581,7 @@ describe('Parse.Object testing', () => {
|
||||
return;
|
||||
}
|
||||
itemsAgain.forEach(function(item, i) {
|
||||
item.set("x", i*2);
|
||||
item.set("x", i * 2);
|
||||
});
|
||||
return Parse.Object.saveAll(itemsAgain);
|
||||
}).then(function() {
|
||||
@@ -1597,7 +1619,7 @@ describe('Parse.Object testing', () => {
|
||||
return;
|
||||
}
|
||||
itemsAgain.forEach(function(item, i) {
|
||||
item.set("x", i*2);
|
||||
item.set("x", i * 2);
|
||||
});
|
||||
return Parse.Object.saveAll(itemsAgain);
|
||||
}).then(function() {
|
||||
@@ -1612,7 +1634,7 @@ describe('Parse.Object testing', () => {
|
||||
done();
|
||||
},
|
||||
|
||||
error: function(error) {
|
||||
error: function() {
|
||||
ok(false, "Failed to fetchAll");
|
||||
done();
|
||||
}
|
||||
@@ -1736,7 +1758,7 @@ describe('Parse.Object testing', () => {
|
||||
var obj2 = new TestObject();
|
||||
obj2.increment('astring');
|
||||
return obj2.save();
|
||||
}).then((obj2) => {
|
||||
}).then(() => {
|
||||
fail('Should not have saved.');
|
||||
done();
|
||||
}, (error) => {
|
||||
@@ -1820,10 +1842,10 @@ describe('Parse.Object testing', () => {
|
||||
"_nested": "key"
|
||||
}
|
||||
});
|
||||
object.save().then( res => {
|
||||
object.save().then(res => {
|
||||
ok(res);
|
||||
return res.fetch();
|
||||
}).then( res => {
|
||||
}).then(res => {
|
||||
const foo = res.get("foo");
|
||||
expect(foo["_bar"]).toEqual("_");
|
||||
expect(foo["baz_bar"]).toEqual(1);
|
||||
@@ -1831,34 +1853,40 @@ describe('Parse.Object testing', () => {
|
||||
expect(foo["_0"]).toEqual("underscore_zero");
|
||||
expect(foo["_more"]["_nested"]).toEqual("key");
|
||||
done();
|
||||
}).fail( err => {
|
||||
console.error(err);
|
||||
}).fail(err => {
|
||||
jfail(err);
|
||||
fail("should not fail");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have undefined includes when object is missing', (done) => {
|
||||
let obj1 = new Parse.Object("AnObject");
|
||||
let obj2 = new Parse.Object("AnObject");
|
||||
const obj1 = new Parse.Object("AnObject");
|
||||
const obj2 = new Parse.Object("AnObject");
|
||||
|
||||
Parse.Object.saveAll([obj1, obj2]).then(() => {
|
||||
Parse.Object.saveAll([obj1, obj2]).then(() => {
|
||||
obj1.set("obj", obj2);
|
||||
// Save the pointer, delete the pointee
|
||||
return obj1.save().then(() => { return obj2.destroy() });
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("AnObject");
|
||||
const query = new Parse.Query("AnObject");
|
||||
query.include("obj");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0].get("obj")).toBe(undefined);
|
||||
let query = new Parse.Query("AnObject");
|
||||
if (res[0]) {
|
||||
expect(res[0].get("obj")).toBe(undefined);
|
||||
}
|
||||
const query = new Parse.Query("AnObject");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0].get("obj")).not.toBe(undefined);
|
||||
return res[0].get("obj").fetch();
|
||||
if (res[0]) {
|
||||
expect(res[0].get("obj")).not.toBe(undefined);
|
||||
return res[0].get("obj").fetch();
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}).then(() => {
|
||||
fail("Should not fetch a deleted object");
|
||||
}, (err) => {
|
||||
@@ -1868,22 +1896,88 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
|
||||
it('should have undefined includes when object is missing on deeper path', (done) => {
|
||||
let obj1 = new Parse.Object("AnObject");
|
||||
let obj2 = new Parse.Object("AnObject");
|
||||
let obj3 = new Parse.Object("AnObject");
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
const obj1 = new Parse.Object("AnObject");
|
||||
const obj2 = new Parse.Object("AnObject");
|
||||
const obj3 = new Parse.Object("AnObject");
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
obj1.set("obj", obj2);
|
||||
obj2.set("obj", obj3);
|
||||
// Save the pointer, delete the pointee
|
||||
return Parse.Object.saveAll([obj1, obj2]).then(() => { return obj3.destroy() });
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("AnObject");
|
||||
const query = new Parse.Query("AnObject");
|
||||
query.include("obj.obj");
|
||||
return query.get(obj1.id);
|
||||
}).then((res) => {
|
||||
expect(res.get("obj")).not.toBe(undefined);
|
||||
expect(res.get("obj").get("obj")).toBe(undefined);
|
||||
done();
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should handle includes on null arrays #2752', (done) => {
|
||||
const obj1 = new Parse.Object("AnObject");
|
||||
const obj2 = new Parse.Object("AnotherObject");
|
||||
const obj3 = new Parse.Object("NestedObject");
|
||||
obj3.set({
|
||||
"foo": "bar"
|
||||
})
|
||||
obj2.set({
|
||||
"key": obj3
|
||||
})
|
||||
|
||||
Parse.Object.saveAll([obj1, obj2]).then(() => {
|
||||
obj1.set("objects", [null, null, obj2]);
|
||||
return obj1.save();
|
||||
}).then(() => {
|
||||
const query = new Parse.Query("AnObject");
|
||||
query.include("objects.key");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
const obj = res[0];
|
||||
expect(obj.get("objects")).not.toBe(undefined);
|
||||
const array = obj.get("objects");
|
||||
expect(Array.isArray(array)).toBe(true);
|
||||
expect(array[0]).toBe(null);
|
||||
expect(array[1]).toBe(null);
|
||||
expect(array[2].get("key").get("foo")).toEqual("bar");
|
||||
done();
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should handle select and include #2786', (done) => {
|
||||
const score = new Parse.Object("GameScore");
|
||||
const player = new Parse.Object("Player");
|
||||
score.set({
|
||||
"score": 1234
|
||||
});
|
||||
|
||||
score.save().then(() => {
|
||||
player.set("gameScore", score);
|
||||
player.set("other", "value");
|
||||
return player.save();
|
||||
}).then(() => {
|
||||
const query = new Parse.Query("Player");
|
||||
query.include("gameScore");
|
||||
query.select("gameScore");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
const obj = res[0];
|
||||
const gameScore = obj.get("gameScore");
|
||||
const other = obj.get("other");
|
||||
expect(other).toBeUndefined();
|
||||
expect(gameScore).not.toBeUndefined();
|
||||
expect(gameScore.get("score")).toBe(1234);
|
||||
done();
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
+68
-18
@@ -8,58 +8,108 @@ describe('ParsePubSub', function() {
|
||||
createPublisher: jasmine.createSpy('createPublisherRedis'),
|
||||
createSubscriber: jasmine.createSpy('createSubscriberRedis')
|
||||
};
|
||||
jasmine.mockLibrary('../src/LiveQuery/RedisPubSub', 'RedisPubSub', mockRedisPubSub);
|
||||
jasmine.mockLibrary('../src/Adapters/PubSub/RedisPubSub', 'RedisPubSub', mockRedisPubSub);
|
||||
// Mock EventEmitterPubSub
|
||||
var mockEventEmitterPubSub = {
|
||||
createPublisher: jasmine.createSpy('createPublisherEventEmitter'),
|
||||
createSubscriber: jasmine.createSpy('createSubscriberEventEmitter')
|
||||
};
|
||||
jasmine.mockLibrary('../src/LiveQuery/EventEmitterPubSub', 'EventEmitterPubSub', mockEventEmitterPubSub);
|
||||
jasmine.mockLibrary('../src/Adapters/PubSub/EventEmitterPubSub', 'EventEmitterPubSub', mockEventEmitterPubSub);
|
||||
done();
|
||||
});
|
||||
|
||||
it('can create redis publisher', function() {
|
||||
var publisher = ParsePubSub.createPublisher({
|
||||
ParsePubSub.createPublisher({
|
||||
redisURL: 'redisURL'
|
||||
});
|
||||
|
||||
var RedisPubSub = require('../src/LiveQuery/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/LiveQuery/EventEmitterPubSub').EventEmitterPubSub;
|
||||
expect(RedisPubSub.createPublisher).toHaveBeenCalledWith('redisURL');
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
expect(RedisPubSub.createPublisher).toHaveBeenCalledWith({redisURL: 'redisURL'});
|
||||
expect(EventEmitterPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create event emitter publisher', function() {
|
||||
var publisher = ParsePubSub.createPublisher({});
|
||||
ParsePubSub.createPublisher({});
|
||||
|
||||
var RedisPubSub = require('../src/LiveQuery/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/LiveQuery/EventEmitterPubSub').EventEmitterPubSub;
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createPublisher).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create redis subscriber', function() {
|
||||
var subscriber = ParsePubSub.createSubscriber({
|
||||
ParsePubSub.createSubscriber({
|
||||
redisURL: 'redisURL'
|
||||
});
|
||||
|
||||
var RedisPubSub = require('../src/LiveQuery/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/LiveQuery/EventEmitterPubSub').EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).toHaveBeenCalledWith('redisURL');
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).toHaveBeenCalledWith({redisURL: 'redisURL'});
|
||||
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create event emitter subscriber', function() {
|
||||
var subscriptionInfos = ParsePubSub.createSubscriber({});
|
||||
ParsePubSub.createSubscriber({});
|
||||
|
||||
var RedisPubSub = require('../src/LiveQuery/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/LiveQuery/EventEmitterPubSub').EventEmitterPubSub;
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createSubscriber).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create publisher/sub with custom adapter', function() {
|
||||
const adapter = {
|
||||
createPublisher: jasmine.createSpy('createPublisher'),
|
||||
createSubscriber: jasmine.createSpy('createSubscriber')
|
||||
}
|
||||
ParsePubSub.createPublisher({
|
||||
pubSubAdapter: adapter
|
||||
});
|
||||
expect(adapter.createPublisher).toHaveBeenCalled();
|
||||
|
||||
ParsePubSub.createSubscriber({
|
||||
pubSubAdapter: adapter
|
||||
});
|
||||
expect(adapter.createSubscriber).toHaveBeenCalled();
|
||||
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
var EventEmitterPubSub = require('../src/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
expect(RedisPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createSubscriber).not.toHaveBeenCalled();
|
||||
expect(RedisPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
expect(EventEmitterPubSub.createPublisher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create publisher/sub with custom function adapter', function() {
|
||||
const adapter = {
|
||||
createPublisher: jasmine.createSpy('createPublisher'),
|
||||
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;
|
||||
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/LiveQuery/RedisPubSub', 'RedisPubSub');
|
||||
jasmine.restoreLibrary('../src/LiveQuery/EventEmitterPubSub', 'EventEmitterPubSub');
|
||||
jasmine.restoreLibrary('../src/Adapters/PubSub/RedisPubSub', 'RedisPubSub');
|
||||
jasmine.restoreLibrary('../src/Adapters/PubSub/EventEmitterPubSub', 'EventEmitterPubSub');
|
||||
});
|
||||
});
|
||||
|
||||
+766
-225
File diff suppressed because it is too large
Load Diff
+263
-141
@@ -46,10 +46,10 @@ describe('Parse.Relation testing', () => {
|
||||
var childObjects = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
childObjects.push(new ChildObject({x:i}));
|
||||
};
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects, expectSuccess({
|
||||
success: function(list) {
|
||||
success: function() {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
@@ -84,7 +84,7 @@ describe('Parse.Relation testing', () => {
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects, {
|
||||
success: function(list) {
|
||||
success: function() {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
@@ -106,7 +106,7 @@ describe('Parse.Relation testing', () => {
|
||||
"The relation should not be dirty");
|
||||
done();
|
||||
},
|
||||
error: function(list) {
|
||||
error: function() {
|
||||
ok(false, "This shouldn't have failed");
|
||||
done();
|
||||
}
|
||||
@@ -131,7 +131,7 @@ describe('Parse.Relation testing', () => {
|
||||
var parent;
|
||||
var relation;
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(function(list) {
|
||||
Parse.Object.saveAll(childObjects).then(function() {
|
||||
var ParentObject = Parse.Object.extend('ParentObject');
|
||||
parent = new ParentObject();
|
||||
parent.set('x', 4);
|
||||
@@ -164,7 +164,7 @@ describe('Parse.Relation testing', () => {
|
||||
});
|
||||
|
||||
|
||||
it("queries with relations", (done) => {
|
||||
it_exclude_dbs(['postgres'])("queries with relations", (done) => {
|
||||
|
||||
var ChildObject = Parse.Object.extend("ChildObject");
|
||||
var childObjects = [];
|
||||
@@ -248,7 +248,55 @@ describe('Parse.Relation testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("queries on relation fields with multiple ins", (done) => {
|
||||
it("queries on relation fields with multiple containedIn (regression test for #1271)", (done) => {
|
||||
const ChildObject = Parse.Object.extend("ChildObject");
|
||||
const childObjects = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
childObjects.push(new ChildObject({x: i}));
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(() => {
|
||||
const ParentObject = Parse.Object.extend("ParentObject");
|
||||
const parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
const parent1Children = parent.relation("child");
|
||||
parent1Children.add(childObjects[0]);
|
||||
parent1Children.add(childObjects[1]);
|
||||
parent1Children.add(childObjects[2]);
|
||||
const parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
const parent2Children = parent2.relation("child");
|
||||
parent2Children.add(childObjects[4]);
|
||||
parent2Children.add(childObjects[5]);
|
||||
parent2Children.add(childObjects[6]);
|
||||
|
||||
const parent2OtherChildren = parent2.relation("otherChild");
|
||||
parent2OtherChildren.add(childObjects[0]);
|
||||
parent2OtherChildren.add(childObjects[1]);
|
||||
parent2OtherChildren.add(childObjects[2]);
|
||||
|
||||
return Parse.Object.saveAll([parent, parent2]);
|
||||
}).then(() => {
|
||||
const objectsWithChild0InBothChildren = new Parse.Query(ParentObject);
|
||||
objectsWithChild0InBothChildren.containedIn("child", [childObjects[0]]);
|
||||
objectsWithChild0InBothChildren.containedIn("otherChild", [childObjects[0]]);
|
||||
return objectsWithChild0InBothChildren.find();
|
||||
}).then(objectsWithChild0InBothChildren => {
|
||||
//No parent has child 0 in both it's "child" and "otherChild" field;
|
||||
expect(objectsWithChild0InBothChildren.length).toEqual(0);
|
||||
}).then(() => {
|
||||
const objectsWithChild4andOtherChild1 = new Parse.Query(ParentObject);
|
||||
objectsWithChild4andOtherChild1.containedIn("child", [childObjects[4]]);
|
||||
objectsWithChild4andOtherChild1.containedIn("otherChild", [childObjects[1]]);
|
||||
return objectsWithChild4andOtherChild1.find();
|
||||
}).then(objects => {
|
||||
// parent2 has child 4 and otherChild 1
|
||||
expect(objects.length).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])("query on pointer and relation fields with equal", (done) => {
|
||||
var ChildObject = Parse.Object.extend("ChildObject");
|
||||
var childObjects = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
@@ -259,74 +307,33 @@ describe('Parse.Relation testing', () => {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("child");
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
var relation2 = parent2.relation("child");
|
||||
relation2.add(childObjects[4]);
|
||||
relation2.add(childObjects[5]);
|
||||
relation2.add(childObjects[6]);
|
||||
|
||||
var otherChild2 = parent2.relation("otherChild");
|
||||
otherChild2.add(childObjects[0]);
|
||||
otherChild2.add(childObjects[1]);
|
||||
otherChild2.add(childObjects[2]);
|
||||
parent2.set("toChild", childObjects[2]);
|
||||
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
return Parse.Object.saveAll(parents);
|
||||
}).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
var objects = [];
|
||||
objects.push(childObjects[0]);
|
||||
query.containedIn("child", objects);
|
||||
query.containedIn("otherChild", [childObjects[0]]);
|
||||
return query.find();
|
||||
}).then((list) => {
|
||||
equal(list.length, 2, "There should be 2 results");
|
||||
done();
|
||||
});
|
||||
});
|
||||
parents.push(new ParentObject());
|
||||
|
||||
it("query on pointer and relation fields with equal", (done) => {
|
||||
var ChildObject = Parse.Object.extend("ChildObject");
|
||||
var childObjects = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
childObjects.push(new ChildObject({x: i}));
|
||||
}
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
query.equalTo("objectId", parent.id);
|
||||
query.equalTo("toChilds", childObjects[2]);
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(() => {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.set("toChild", childObjects[2]);
|
||||
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
query.equalTo("objectId", parent.id);
|
||||
query.equalTo("toChilds", childObjects[2]);
|
||||
|
||||
return query.find().then((list) => {
|
||||
equal(list.length, 1, "There should be 1 result");
|
||||
done();
|
||||
});
|
||||
return query.find().then((list) => {
|
||||
equal(list.length, 1, "There should be 1 result");
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -338,39 +345,39 @@ describe('Parse.Relation testing', () => {
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(() => {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.relation("toChilds").add(childObjects[2]);
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.relation("toChilds").add(childObjects[2]);
|
||||
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
query.equalTo("objectId", parent2.id);
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
query.equalTo("objectId", parent2.id);
|
||||
// childObjects[2] is in 2 relations
|
||||
// before the fix, that woul yield 2 results
|
||||
query.equalTo("toChilds", childObjects[2]);
|
||||
query.equalTo("toChilds", childObjects[2]);
|
||||
|
||||
return query.find().then((list) => {
|
||||
equal(list.length, 1, "There should be 1 result");
|
||||
done();
|
||||
});
|
||||
return query.find().then((list) => {
|
||||
equal(list.length, 1, "There should be 1 result");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("or queries on pointer and relation fields", (done) => {
|
||||
it_exclude_dbs(['postgres'])("or queries on pointer and relation fields", (done) => {
|
||||
var ChildObject = Parse.Object.extend("ChildObject");
|
||||
var childObjects = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
@@ -378,39 +385,39 @@ describe('Parse.Relation testing', () => {
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(() => {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("toChilds");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.set("toChild", childObjects[2]);
|
||||
var parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
parent2.set("toChild", childObjects[2]);
|
||||
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
parents.push(new ParentObject());
|
||||
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query1 = new Parse.Query(ParentObject);
|
||||
query1.containedIn("toChilds", [childObjects[2]]);
|
||||
var query2 = new Parse.Query(ParentObject);
|
||||
query2.equalTo("toChild", childObjects[2]);
|
||||
var query = Parse.Query.or(query1, query2);
|
||||
return query.find().then((list) => {
|
||||
var objectIds = list.map(function(item){
|
||||
return item.id;
|
||||
});
|
||||
expect(objectIds.indexOf(parent.id)).not.toBe(-1);
|
||||
expect(objectIds.indexOf(parent2.id)).not.toBe(-1);
|
||||
equal(list.length, 2, "There should be 2 results");
|
||||
done();
|
||||
return Parse.Object.saveAll(parents).then(() => {
|
||||
var query1 = new Parse.Query(ParentObject);
|
||||
query1.containedIn("toChilds", [childObjects[2]]);
|
||||
var query2 = new Parse.Query(ParentObject);
|
||||
query2.equalTo("toChild", childObjects[2]);
|
||||
var query = Parse.Query.or(query1, query2);
|
||||
return query.find().then((list) => {
|
||||
var objectIds = list.map(function(item){
|
||||
return item.id;
|
||||
});
|
||||
expect(objectIds.indexOf(parent.id)).not.toBe(-1);
|
||||
expect(objectIds.indexOf(parent2.id)).not.toBe(-1);
|
||||
equal(list.length, 2, "There should be 2 results");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -503,24 +510,24 @@ describe('Parse.Relation testing', () => {
|
||||
});
|
||||
|
||||
it('should properly get related objects with unfetched queries', (done) => {
|
||||
let objects = [];
|
||||
let owners = [];
|
||||
let allObjects = [];
|
||||
const objects = [];
|
||||
const owners = [];
|
||||
const allObjects = [];
|
||||
// Build 10 Objects and 10 owners
|
||||
while (objects.length != 10) {
|
||||
let object = new Parse.Object('AnObject');
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set({
|
||||
index: objects.length,
|
||||
even: objects.length % 2 == 0
|
||||
});
|
||||
objects.push(object);
|
||||
let owner = new Parse.Object('AnOwner');
|
||||
const owner = new Parse.Object('AnOwner');
|
||||
owners.push(owner);
|
||||
allObjects.push(object);
|
||||
allObjects.push(owner);
|
||||
}
|
||||
|
||||
let anotherOwner = new Parse.Object('AnotherOwner');
|
||||
const anotherOwner = new Parse.Object('AnotherOwner');
|
||||
|
||||
return Parse.Object.saveAll(allObjects.concat([anotherOwner])).then(() => {
|
||||
// put all the AnObject into the anotherOwner relationKey
|
||||
@@ -532,42 +539,45 @@ describe('Parse.Relation testing', () => {
|
||||
return Parse.Object.saveAll(owners.concat([anotherOwner]));
|
||||
}).then(() => {
|
||||
// Query on the relation of another owner
|
||||
let object = new Parse.Object('AnotherOwner');
|
||||
const object = new Parse.Object('AnotherOwner');
|
||||
object.id = anotherOwner.id;
|
||||
let relationQuery = object.relation('relationKey').query();
|
||||
const relationQuery = object.relation('relationKey').query();
|
||||
// Just get the even ones
|
||||
relationQuery.equalTo('even', true);
|
||||
// Make the query on anOwner
|
||||
let query = new Parse.Query('AnOwner');
|
||||
const query = new Parse.Query('AnOwner');
|
||||
// where key match the relation query.
|
||||
query.matchesQuery('key', relationQuery);
|
||||
query.include('key');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(5);
|
||||
results.forEach((result) => {
|
||||
results.forEach((result) => {
|
||||
expect(result.get('key').get('even')).toBe(true);
|
||||
});
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
// Query on the relation of another owner
|
||||
let object = new Parse.Object('AnotherOwner');
|
||||
const object = new Parse.Object('AnotherOwner');
|
||||
object.id = anotherOwner.id;
|
||||
let relationQuery = object.relation('relationKey').query();
|
||||
const relationQuery = object.relation('relationKey').query();
|
||||
// Just get the even ones
|
||||
relationQuery.equalTo('even', true);
|
||||
// Make the query on anOwner
|
||||
let query = new Parse.Query('AnOwner');
|
||||
const query = new Parse.Query('AnOwner');
|
||||
// where key match the relation query.
|
||||
query.doesNotMatchQuery('key', relationQuery);
|
||||
query.include('key');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(5);
|
||||
results.forEach((result) => {
|
||||
results.forEach((result) => {
|
||||
expect(result.get('key').get('even')).toBe(false);
|
||||
});
|
||||
done();
|
||||
}, (e) => {
|
||||
fail(JSON.stringify(e));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
@@ -579,21 +589,19 @@ describe('Parse.Relation testing', () => {
|
||||
new RestaurantObject({ ratings: 5, location: "Djibouti" }),
|
||||
new RestaurantObject({ ratings: 3, location: "Ouagadougou" }),
|
||||
];
|
||||
let persons = [
|
||||
const persons = [
|
||||
new PersonObject({ name: "Bob", hometown: "Djibouti" }),
|
||||
new PersonObject({ name: "Tom", hometown: "Ouagadougou" }),
|
||||
new PersonObject({ name: "Billy", hometown: "Detroit" }),
|
||||
];
|
||||
let owner = new OwnerObject({name: 'Joe'});
|
||||
let ownerId;
|
||||
let allObjects = [owner].concat(restaurants).concat(persons);
|
||||
const owner = new OwnerObject({name: 'Joe'});
|
||||
const allObjects = [owner].concat(restaurants).concat(persons);
|
||||
expect(allObjects.length).toEqual(6);
|
||||
Parse.Object.saveAll([owner].concat(restaurants).concat(persons)).then(function() {
|
||||
ownerId = owner.id;
|
||||
owner.relation('restaurants').add(restaurants);
|
||||
return owner.save()
|
||||
}).then(() => {
|
||||
let unfetchedOwner = new OwnerObject();
|
||||
const unfetchedOwner = new OwnerObject();
|
||||
unfetchedOwner.id = owner.id;
|
||||
var query = unfetchedOwner.relation('restaurants').query();
|
||||
query.greaterThan("ratings", 4);
|
||||
@@ -608,6 +616,9 @@ describe('Parse.Relation testing', () => {
|
||||
done();
|
||||
}
|
||||
}));
|
||||
}, (e) => {
|
||||
fail(JSON.stringify(e));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -619,21 +630,19 @@ describe('Parse.Relation testing', () => {
|
||||
new RestaurantObject({ ratings: 5, location: "Djibouti" }),
|
||||
new RestaurantObject({ ratings: 3, location: "Ouagadougou" }),
|
||||
];
|
||||
let persons = [
|
||||
const persons = [
|
||||
new PersonObject({ name: "Bob", hometown: "Djibouti" }),
|
||||
new PersonObject({ name: "Tom", hometown: "Ouagadougou" }),
|
||||
new PersonObject({ name: "Billy", hometown: "Detroit" }),
|
||||
];
|
||||
let owner = new OwnerObject({name: 'Joe'});
|
||||
let ownerId;
|
||||
let allObjects = [owner].concat(restaurants).concat(persons);
|
||||
const owner = new OwnerObject({name: 'Joe'});
|
||||
const allObjects = [owner].concat(restaurants).concat(persons);
|
||||
expect(allObjects.length).toEqual(6);
|
||||
Parse.Object.saveAll([owner].concat(restaurants).concat(persons)).then(function() {
|
||||
ownerId = owner.id;
|
||||
owner.relation('restaurants').add(restaurants);
|
||||
return owner.save()
|
||||
}).then(() => {
|
||||
let unfetchedOwner = new OwnerObject();
|
||||
const unfetchedOwner = new OwnerObject();
|
||||
unfetchedOwner.id = owner.id;
|
||||
var query = unfetchedOwner.relation('restaurants').query();
|
||||
query.greaterThan("ratings", 4);
|
||||
@@ -650,6 +659,119 @@ describe('Parse.Relation testing', () => {
|
||||
done();
|
||||
}
|
||||
}));
|
||||
}, (e) => {
|
||||
fail(JSON.stringify(e));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('relations are not bidirectional (regression test for #871)', done => {
|
||||
const PersonObject = Parse.Object.extend("Person");
|
||||
const p1 = new PersonObject();
|
||||
const p2 = new PersonObject();
|
||||
Parse.Object.saveAll([p1, p2]).then(results => {
|
||||
const p1 = results[0];
|
||||
const p2 = results[1];
|
||||
const relation = p1.relation('relation');
|
||||
relation.add(p2);
|
||||
p1.save().then(() => {
|
||||
const query = new Parse.Query(PersonObject);
|
||||
query.equalTo('relation', p1);
|
||||
query.find().then(results => {
|
||||
expect(results.length).toEqual(0);
|
||||
|
||||
const query = new Parse.Query(PersonObject);
|
||||
query.equalTo('relation', p2);
|
||||
query.find().then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].objectId).toEqual(p1.objectId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('can query roles in Cloud Code (regession test #1489)', done => {
|
||||
Parse.Cloud.define('isAdmin', (request, response) => {
|
||||
const query = new Parse.Query(Parse.Role);
|
||||
query.equalTo('name', 'admin');
|
||||
query.first({ useMasterKey: true })
|
||||
.then(role => {
|
||||
const relation = new Parse.Relation(role, 'users');
|
||||
const admins = relation.query();
|
||||
admins.equalTo('username', request.user.get('username'));
|
||||
admins.first({ useMasterKey: true })
|
||||
.then(user => {
|
||||
if (user) {
|
||||
response.success(user);
|
||||
done();
|
||||
} else {
|
||||
fail('Should have found admin user, found nothing instead');
|
||||
done();
|
||||
}
|
||||
}, () => {
|
||||
fail('User not admin');
|
||||
done();
|
||||
})
|
||||
}, error => {
|
||||
fail('Should have found admin user, errored instead');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
const adminUser = new Parse.User();
|
||||
adminUser.set('username', 'name');
|
||||
adminUser.set('password', 'pass');
|
||||
adminUser.signUp()
|
||||
.then(adminUser => {
|
||||
const adminACL = new Parse.ACL();
|
||||
adminACL.setPublicReadAccess(true);
|
||||
|
||||
// Create admin role
|
||||
const adminRole = new Parse.Role('admin', adminACL);
|
||||
adminRole.getUsers().add(adminUser);
|
||||
adminRole.save()
|
||||
.then(() => {
|
||||
Parse.Cloud.run('isAdmin');
|
||||
}, error => {
|
||||
fail('failed to save role');
|
||||
fail(error);
|
||||
done()
|
||||
});
|
||||
}, error => {
|
||||
fail('failed to sign up');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can be saved without error', done => {
|
||||
const obj1 = new Parse.Object('PPAP');
|
||||
obj1.save()
|
||||
.then(() => {
|
||||
const newRelation = obj1.relation('aRelation');
|
||||
newRelation.add(obj1);
|
||||
obj1.save().then(() => {
|
||||
const relation = obj1.get('aRelation');
|
||||
obj1.set('aRelation', relation);
|
||||
obj1.save().then(() => {
|
||||
done();
|
||||
}, error => {
|
||||
fail('failed to save ParseRelation object');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
}, error => {
|
||||
fail('failed to create relation field');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
}, error => {
|
||||
fail('failed to save obj');
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+232
-81
@@ -2,24 +2,27 @@
|
||||
|
||||
// Roles are not accessible without the master key, so they are not intended
|
||||
// for use by clients. We can manually test them using the master key.
|
||||
var RestQuery = require("../src/RestQuery");
|
||||
var Auth = require("../src/Auth").Auth;
|
||||
var Config = require("../src/Config");
|
||||
|
||||
describe('Parse Role testing', () => {
|
||||
|
||||
it('Do a bunch of basic role testing', (done) => {
|
||||
|
||||
it('Do a bunch of basic role testing', done => {
|
||||
var user;
|
||||
var role;
|
||||
|
||||
createTestUser().then((x) => {
|
||||
user = x;
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(false);
|
||||
role = new Parse.Object('_Role');
|
||||
role.set('name', 'Foos');
|
||||
role.setACL(acl);
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
return role.save({}, { useMasterKey: true });
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('_Role');
|
||||
return query.find({ useMasterKey: true });
|
||||
}).then((x) => {
|
||||
@@ -39,7 +42,7 @@ describe('Parse Role testing', () => {
|
||||
acl.setRoleWriteAccess('Foos', true);
|
||||
obj.setACL(acl);
|
||||
return obj.save();
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('TestObject');
|
||||
return query.find({ sessionToken: user.getSessionToken() });
|
||||
}).then((x) => {
|
||||
@@ -52,69 +55,135 @@ describe('Parse Role testing', () => {
|
||||
x.set('foo', 'baz');
|
||||
// This should fail:
|
||||
return x.save({},{sessionToken: ""});
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
fail('Should not have been able to save.');
|
||||
}, (e) => {
|
||||
expect(e.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var createRole = function(name, sibling, user) {
|
||||
var role = new Parse.Role(name, new Parse.ACL());
|
||||
if (user) {
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
}
|
||||
if (sibling) {
|
||||
role.relation('roles').add(sibling);
|
||||
}
|
||||
return role.save({}, { useMasterKey: true });
|
||||
};
|
||||
|
||||
it("should not recursively load the same role multiple times", (done) => {
|
||||
var rootRole = "RootRole";
|
||||
var roleNames = ["FooRole", "BarRole", "BazRole"];
|
||||
var allRoles = [rootRole].concat(roleNames);
|
||||
|
||||
var roleObjs = {};
|
||||
var createAllRoles = function(user) {
|
||||
var promises = allRoles.map(function(roleName) {
|
||||
return createRole(roleName, null, user)
|
||||
.then(function(roleObj) {
|
||||
roleObjs[roleName] = roleObj;
|
||||
return roleObj;
|
||||
});
|
||||
});
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
var restExecute = spyOn(RestQuery.prototype, "execute").and.callThrough();
|
||||
|
||||
var user,
|
||||
auth,
|
||||
getAllRolesSpy;
|
||||
createTestUser().then((newUser) => {
|
||||
user = newUser;
|
||||
return createAllRoles(user);
|
||||
}).then ((roles) => {
|
||||
var rootRoleObj = roleObjs[rootRole];
|
||||
roles.forEach(function(role, i) {
|
||||
// Add all roles to the RootRole
|
||||
if (role.id !== rootRoleObj.id) {
|
||||
role.relation("roles").add(rootRoleObj);
|
||||
}
|
||||
// Add all "roleNames" roles to the previous role
|
||||
if (i > 0) {
|
||||
role.relation("roles").add(roles[i - 1]);
|
||||
}
|
||||
});
|
||||
|
||||
return Parse.Object.saveAll(roles, { useMasterKey: true });
|
||||
}).then(() => {
|
||||
auth = new Auth({config: new Config("test"), isMaster: true, user: user});
|
||||
getAllRolesSpy = spyOn(auth, "_getAllRolesNamesForRoleIds").and.callThrough();
|
||||
|
||||
return auth._loadRoles();
|
||||
}).then ((roles) => {
|
||||
expect(roles.length).toEqual(4);
|
||||
|
||||
allRoles.forEach(function(name) {
|
||||
expect(roles.indexOf("role:" + name)).not.toBe(-1);
|
||||
});
|
||||
|
||||
// 1 Query for the initial setup
|
||||
// 1 query for the parent roles
|
||||
expect(restExecute.calls.count()).toEqual(2);
|
||||
|
||||
// 1 call for the 1st layer of roles
|
||||
// 1 call for the 2nd layer
|
||||
expect(getAllRolesSpy.calls.count()).toEqual(2);
|
||||
done()
|
||||
}).catch(() => {
|
||||
fail("should succeed");
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("should recursively load roles", (done) => {
|
||||
|
||||
var rolesNames = ["FooRole", "BarRole", "BazRole"];
|
||||
|
||||
var createRole = function(name, sibling, user) {
|
||||
var role = new Parse.Role(name, new Parse.ACL());
|
||||
if (user) {
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
}
|
||||
if (sibling) {
|
||||
role.relation('roles').add(sibling);
|
||||
}
|
||||
return role.save({}, { useMasterKey: true });
|
||||
}
|
||||
var roleIds = {};
|
||||
createTestUser().then( (user) => {
|
||||
createTestUser().then((user) => {
|
||||
// Put the user on the 1st role
|
||||
return createRole(rolesNames[0], null, user).then( (aRole) => {
|
||||
roleIds[aRole.get("name")] = aRole.id;
|
||||
return createRole(rolesNames[0], null, user).then((aRole) => {
|
||||
roleIds[aRole.get("name")] = aRole.id;
|
||||
// set the 1st role as a sibling of the second
|
||||
// user will should have 2 role now
|
||||
return createRole(rolesNames[1], aRole, null);
|
||||
}).then( (anotherRole) => {
|
||||
roleIds[anotherRole.get("name")] = anotherRole.id;
|
||||
return createRole(rolesNames[1], aRole, null);
|
||||
}).then((anotherRole) => {
|
||||
roleIds[anotherRole.get("name")] = anotherRole.id;
|
||||
// set this role as a sibling of the last
|
||||
// the user should now have 3 roles
|
||||
return createRole(rolesNames[2], anotherRole, null);
|
||||
}).then( (lastRole) => {
|
||||
roleIds[lastRole.get("name")] = lastRole.id;
|
||||
var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
|
||||
return auth._loadRoles();
|
||||
})
|
||||
}).then( (roles) => {
|
||||
expect(roles.length).toEqual(3);
|
||||
rolesNames.forEach( (name) => {
|
||||
expect(roles.indexOf('role:'+name)).not.toBe(-1);
|
||||
})
|
||||
done();
|
||||
}, function(err){
|
||||
fail("should succeed")
|
||||
done();
|
||||
});
|
||||
return createRole(rolesNames[2], anotherRole, null);
|
||||
}).then((lastRole) => {
|
||||
roleIds[lastRole.get("name")] = lastRole.id;
|
||||
var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
|
||||
return auth._loadRoles();
|
||||
})
|
||||
}).then((roles) => {
|
||||
expect(roles.length).toEqual(3);
|
||||
rolesNames.forEach((name) => {
|
||||
expect(roles.indexOf('role:' + name)).not.toBe(-1);
|
||||
});
|
||||
done();
|
||||
}, function(){
|
||||
fail("should succeed")
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("_Role object should not save without name.", (done) => {
|
||||
var role = new Parse.Role();
|
||||
role.save(null,{useMasterKey:true})
|
||||
.then((r) => {
|
||||
.then(() => {
|
||||
fail("_Role object should not save without name.");
|
||||
}, (error) => {
|
||||
expect(error.code).toEqual(111);
|
||||
role.set('name','testRole');
|
||||
role.save(null,{useMasterKey:true})
|
||||
.then((r2)=>{
|
||||
.then(()=>{
|
||||
fail("_Role object should not save without ACL.");
|
||||
}, (error2) =>{
|
||||
expect(error2.code).toEqual(111);
|
||||
@@ -122,59 +191,76 @@ describe('Parse Role testing', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Should properly resolve roles", (done) => {
|
||||
let admin = new Parse.Role("Admin", new Parse.ACL());
|
||||
let moderator = new Parse.Role("Moderator", new Parse.ACL());
|
||||
let superModerator = new Parse.Role("SuperModerator", new Parse.ACL());
|
||||
let contentManager = new Parse.Role('ContentManager', new Parse.ACL());
|
||||
let superContentManager = new Parse.Role('SuperContentManager', new Parse.ACL());
|
||||
Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true}).then(() => {
|
||||
|
||||
it("Different _Role objects cannot have the same name.", (done) => {
|
||||
const roleName = "MyRole";
|
||||
let aUser;
|
||||
createTestUser().then((user) => {
|
||||
aUser = user;
|
||||
return createRole(roleName, null, aUser);
|
||||
}).then((firstRole) => {
|
||||
expect(firstRole.getName()).toEqual(roleName);
|
||||
return createRole(roleName, null, aUser);
|
||||
}).then(() => {
|
||||
fail("_Role cannot have the same name as another role");
|
||||
done();
|
||||
}, (error) => {
|
||||
expect(error.code).toEqual(137);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("Should properly resolve roles", (done) => {
|
||||
const admin = new Parse.Role("Admin", new Parse.ACL());
|
||||
const moderator = new Parse.Role("Moderator", new Parse.ACL());
|
||||
const superModerator = new Parse.Role("SuperModerator", new Parse.ACL());
|
||||
const contentManager = new Parse.Role('ContentManager', new Parse.ACL());
|
||||
const superContentManager = new Parse.Role('SuperContentManager', new Parse.ACL());
|
||||
Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true}).then(() => {
|
||||
contentManager.getRoles().add([moderator, superContentManager]);
|
||||
moderator.getRoles().add([admin, superModerator]);
|
||||
superContentManager.getRoles().add(superModerator);
|
||||
return Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
var auth = new Auth({ config: new Config("test"), isMaster: true });
|
||||
// For each role, fetch their sibling, what they inherit
|
||||
// return with result and roleId for later comparison
|
||||
let promises = [admin, moderator, contentManager, superModerator].map((role) => {
|
||||
return auth._getAllRoleNamesForId(role.id).then((result) => {
|
||||
const promises = [admin, moderator, contentManager, superModerator].map((role) => {
|
||||
return auth._getAllRolesNamesForRoleIds([role.id]).then((result) => {
|
||||
return Parse.Promise.as({
|
||||
id: role.id,
|
||||
name: role.get('name'),
|
||||
roleIds: result
|
||||
roleNames: result
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
return Parse.Promise.when(promises);
|
||||
}).then((results) => {
|
||||
results.forEach((result) => {
|
||||
let id = result.id;
|
||||
let roleIds = result.roleIds;
|
||||
const id = result.id;
|
||||
const roleNames = result.roleNames;
|
||||
if (id == admin.id) {
|
||||
expect(roleIds.length).toBe(2);
|
||||
expect(roleIds.indexOf(moderator.id)).not.toBe(-1);
|
||||
expect(roleIds.indexOf(contentManager.id)).not.toBe(-1);
|
||||
expect(roleNames.length).toBe(2);
|
||||
expect(roleNames.indexOf("Moderator")).not.toBe(-1);
|
||||
expect(roleNames.indexOf("ContentManager")).not.toBe(-1);
|
||||
} else if (id == moderator.id) {
|
||||
expect(roleIds.length).toBe(1);
|
||||
expect(roleIds.indexOf(contentManager.id)).toBe(0);
|
||||
expect(roleNames.length).toBe(1);
|
||||
expect(roleNames.indexOf("ContentManager")).toBe(0);
|
||||
} else if (id == contentManager.id) {
|
||||
expect(roleIds.length).toBe(0);
|
||||
expect(roleNames.length).toBe(0);
|
||||
} else if (id == superModerator.id) {
|
||||
expect(roleIds.length).toBe(3);
|
||||
expect(roleIds.indexOf(moderator.id)).not.toBe(-1);
|
||||
expect(roleIds.indexOf(contentManager.id)).not.toBe(-1);
|
||||
expect(roleIds.indexOf(superContentManager.id)).not.toBe(-1);
|
||||
expect(roleNames.length).toBe(3);
|
||||
expect(roleNames.indexOf("Moderator")).not.toBe(-1);
|
||||
expect(roleNames.indexOf("ContentManager")).not.toBe(-1);
|
||||
expect(roleNames.indexOf("SuperContentManager")).not.toBe(-1);
|
||||
}
|
||||
});
|
||||
done();
|
||||
}).fail((err) => {
|
||||
console.error(err);
|
||||
}).fail(() => {
|
||||
done();
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('can create role and query empty users', (done)=> {
|
||||
@@ -182,17 +268,16 @@ describe('Parse Role testing', () => {
|
||||
roleACL.setPublicReadAccess(true);
|
||||
var role = new Parse.Role('subscribers', roleACL);
|
||||
role.save({}, {useMasterKey : true})
|
||||
.then((x)=>{
|
||||
.then(()=>{
|
||||
var query = role.relation('users').query();
|
||||
query.find({useMasterKey : true})
|
||||
.then((users)=>{
|
||||
.then(()=>{
|
||||
done();
|
||||
}, (e)=>{
|
||||
}, ()=>{
|
||||
fail('should not have errors');
|
||||
done();
|
||||
});
|
||||
}, (e) => {
|
||||
console.log(e);
|
||||
}, () => {
|
||||
fail('should not have errored');
|
||||
});
|
||||
});
|
||||
@@ -211,10 +296,10 @@ describe('Parse Role testing', () => {
|
||||
user = x;
|
||||
user2 = new Parse.User();
|
||||
return user2.save({ username: 'user2', password: 'omgbbq' });
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
user3 = new Parse.User();
|
||||
return user3.save({ username: 'user3', password: 'omgbbq' });
|
||||
}).then((x) => {
|
||||
}).then(() => {
|
||||
role = new Parse.Role('Admin', prACL);
|
||||
role.getUsers().add(user);
|
||||
return role.save({}, { useMasterKey: true });
|
||||
@@ -260,7 +345,7 @@ describe('Parse Role testing', () => {
|
||||
obj2 = new Parse.Object('TestObjectRoles');
|
||||
obj2.set('ACL', adminACL);
|
||||
return obj2.save(null, { useMasterKey: true });
|
||||
}, (e) => {
|
||||
}, () => {
|
||||
fail('Admin user should have been able to save.');
|
||||
done();
|
||||
}).then(() => {
|
||||
@@ -271,10 +356,76 @@ describe('Parse Role testing', () => {
|
||||
fail('Customer user should not have been able to save.');
|
||||
done();
|
||||
}, (e) => {
|
||||
expect(e.code).toEqual(101);
|
||||
if (e) {
|
||||
expect(e.code).toEqual(101);
|
||||
} else {
|
||||
fail('should return an error');
|
||||
}
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
it('should add multiple users to a role and remove users', (done) => {
|
||||
var user, user2, user3;
|
||||
var role;
|
||||
var obj;
|
||||
|
||||
var prACL = new Parse.ACL();
|
||||
prACL.setPublicReadAccess(true);
|
||||
prACL.setPublicWriteAccess(true);
|
||||
|
||||
createTestUser().then((x) => {
|
||||
user = x;
|
||||
user2 = new Parse.User();
|
||||
return user2.save({ username: 'user2', password: 'omgbbq' });
|
||||
}).then(() => {
|
||||
user3 = new Parse.User();
|
||||
return user3.save({ username: 'user3', password: 'omgbbq' });
|
||||
}).then(() => {
|
||||
role = new Parse.Role('sharedRole', prACL);
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
users.add(user2);
|
||||
users.add(user3);
|
||||
return role.save({}, { useMasterKey: true });
|
||||
}).then(() => {
|
||||
// query for saved role and get 3 users
|
||||
var query = new Parse.Query('_Role');
|
||||
query.equalTo('name', 'sharedRole');
|
||||
return query.find({ useMasterKey: true });
|
||||
}).then((role) => {
|
||||
expect(role.length).toEqual(1);
|
||||
var users = role[0].relation('users').query();
|
||||
return users.find({ useMasterKey: true });
|
||||
}).then((users) => {
|
||||
expect(users.length).toEqual(3);
|
||||
obj = new Parse.Object('TestObjectRoles');
|
||||
obj.set('ACL', prACL);
|
||||
return obj.save(null, { useMasterKey: true });
|
||||
}).then(() => {
|
||||
// Above, the Admin role was added to the Customer role.
|
||||
// An object secured by the Customer ACL should be able to be edited by the Admin user.
|
||||
obj.set('changedByUsers', true);
|
||||
return obj.save(null, { sessionToken: user.getSessionToken() });
|
||||
}).then(() => {
|
||||
// query for saved role and get 3 users
|
||||
var query = new Parse.Query('_Role');
|
||||
query.equalTo('name', 'sharedRole');
|
||||
return query.find({ useMasterKey: true });
|
||||
}).then((role) => {
|
||||
expect(role.length).toEqual(1);
|
||||
var users = role[0].relation('users');
|
||||
users.remove(user);
|
||||
users.remove(user3);
|
||||
return role[0].save({}, { useMasterKey: true });
|
||||
}).then((role) =>{
|
||||
var users = role.relation('users').query();
|
||||
return users.find({ useMasterKey: true });
|
||||
}).then((users) => {
|
||||
expect(users.length).toEqual(1);
|
||||
expect(users[0].get('username')).toEqual('user2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
const ParseServerRESTController = require('../src/ParseServerRESTController').ParseServerRESTController;
|
||||
const ParseServer = require('../src/ParseServer').default;
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
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'
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/classes/MyObject',
|
||||
body: {"key": "value"}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
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});
|
||||
});
|
||||
}).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();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
+829
-146
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,9 @@ describe('ParseWebSocketServer', function() {
|
||||
|
||||
it('can handle connect event when ws is open', function(done) {
|
||||
var onConnectCallback = jasmine.createSpy('onConnectCallback');
|
||||
var parseWebSocketServer = new ParseWebSocketServer({}, onConnectCallback, 5).server;
|
||||
var http = require('http');
|
||||
var server = http.createServer();
|
||||
var parseWebSocketServer = new ParseWebSocketServer(server, onConnectCallback, 5).server;
|
||||
var ws = {
|
||||
readyState: 0,
|
||||
OPEN: 0,
|
||||
@@ -27,6 +29,7 @@ describe('ParseWebSocketServer', function() {
|
||||
// Make sure we ping to the client
|
||||
setTimeout(function() {
|
||||
expect(ws.ping).toHaveBeenCalled();
|
||||
server.close();
|
||||
done();
|
||||
}, 10)
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,692 @@
|
||||
'use strict';
|
||||
var Config = require('../src/Config');
|
||||
|
||||
describe('Pointer Permissions', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
new Config(Parse.applicationId).database.schemaCache.clear();
|
||||
});
|
||||
|
||||
it('should work with find', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
const obj2 = new Parse.Object('AnObject');
|
||||
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
obj.set('owner', user);
|
||||
obj2.set('owner', user2);
|
||||
return Parse.Object.saveAll([obj, obj2]);
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.updateClass('AnObject', {}, {readUserFields: ['owner']})
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0].id).toBe(obj.id);
|
||||
done();
|
||||
}).catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should work with write', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
const obj2 = new Parse.Object('AnObject');
|
||||
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
obj.set('owner', user);
|
||||
obj.set('reader', user2);
|
||||
obj2.set('owner', user2);
|
||||
obj2.set('reader', user);
|
||||
return Parse.Object.saveAll([obj, obj2]);
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.updateClass('AnObject', {}, {writeUserFields: ['owner'], readUserFields: ['reader', 'owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
obj2.set('hello', 'world');
|
||||
return obj2.save();
|
||||
}).then(() => {
|
||||
fail('User should not be able to update obj2');
|
||||
}, (err) => {
|
||||
// User 1 should not be able to update obj2
|
||||
expect(err.code).toBe(101);
|
||||
return Promise.resolve();
|
||||
}).then(()=> {
|
||||
obj.set('hello', 'world');
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}, () => {
|
||||
fail('User should be able to update');
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}, () => {
|
||||
fail('should login with user 2');
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(2);
|
||||
res.forEach((result) => {
|
||||
if (result.id == obj.id) {
|
||||
expect(result.get('hello')).toBe('world');
|
||||
} else {
|
||||
expect(result.id).toBe(obj2.id);
|
||||
}
|
||||
})
|
||||
done();
|
||||
}, () => {
|
||||
fail("failed");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should let a proper user find', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
const obj2 = new Parse.Object('AnObject');
|
||||
user.signUp().then(() => {
|
||||
return user2.signUp()
|
||||
}).then(() => {
|
||||
Parse.User.logOut();
|
||||
}).then(() => {
|
||||
obj.set('owner', user);
|
||||
return Parse.Object.saveAll([obj, obj2]);
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.updateClass('AnObject', {}, {find: {}, get:{}, readUserFields: ['owner']})
|
||||
});
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(0);
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(0);
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.get(obj.id);
|
||||
}).then(() => {
|
||||
fail('User 2 should not get the obj1 object');
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
expect(err.message).toBe('Object not found.');
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then((res) => {
|
||||
expect(res.length).toBe(1);
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should not allow creating objects', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
user.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type:'Pointer', targetClass: '_User'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
obj.set('owner', user);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(119);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should handle multiple writeUserFields', done => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2])
|
||||
.then(() => {
|
||||
obj.set('owner', user);
|
||||
obj.set('otherOwner', user2);
|
||||
return obj.save();
|
||||
})
|
||||
.then(() => config.database.loadSchema())
|
||||
.then(schema => schema.updateClass('AnObject', {}, {find: {"*": true},writeUserFields: ['owner', 'otherOwner']}))
|
||||
.then(() => Parse.User.logIn('user1', 'password'))
|
||||
.then(() => obj.save({hello: 'fromUser1'}))
|
||||
.then(() => Parse.User.logIn('user2', 'password'))
|
||||
.then(() => obj.save({hello: 'fromUser2'}))
|
||||
.then(() => Parse.User.logOut())
|
||||
.then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.first();
|
||||
})
|
||||
.then(result => {
|
||||
expect(result.get('hello')).toBe('fromUser2');
|
||||
done();
|
||||
}).catch(() => {
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should prevent creating pointer permission on missing field', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
config.database.loadSchema().then((schema) => {
|
||||
return schema.addClassIfNotExists('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(107);
|
||||
expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should prevent creating pointer permission on bad field', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
config.database.loadSchema().then((schema) => {
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type: 'String'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(107);
|
||||
expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should prevent creating pointer permission on bad field', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('owner', 'value');
|
||||
object.save().then(() => {
|
||||
return config.database.loadSchema();
|
||||
}).then((schema) => {
|
||||
return schema.updateClass('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(107);
|
||||
expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL write (PP Locked)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: update closed ({})
|
||||
PointerPerm: "owner"
|
||||
ACL: logged in user has access
|
||||
|
||||
The owner is another user than the ACL
|
||||
*/
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
obj.setACL(ACL);
|
||||
obj.set('owner', user2);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be blocked by PP
|
||||
return obj.save({key: 'value'});
|
||||
}).then(() => {
|
||||
fail('Should not succeed saving');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL write (ACL Locked)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: update closed ({})
|
||||
PointerPerm: "owner"
|
||||
ACL: logged in user has access
|
||||
*/
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
obj.setACL(ACL);
|
||||
obj.set('owner', user2);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be blocked by ACL
|
||||
return obj.save({key: 'value'});
|
||||
}).then(() => {
|
||||
fail('Should not succeed saving');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL write (ACL/PP OK)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: update closed ({})
|
||||
PointerPerm: "owner"
|
||||
ACL: logged in user has access
|
||||
*/
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setWriteAccess(user, true);
|
||||
ACL.setWriteAccess(user2, true);
|
||||
obj.setACL(ACL);
|
||||
obj.set('owner', user2);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be blocked by ACL
|
||||
return obj.save({key: 'value'});
|
||||
}).then((objAgain) => {
|
||||
expect(objAgain.get('key')).toBe('value');
|
||||
done();
|
||||
}, () => {
|
||||
fail('Should not fail saving');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL read (PP locked)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: find/get open ({})
|
||||
PointerPerm: "owner" : read
|
||||
ACL: logged in user has access
|
||||
|
||||
The owner is another user than the ACL
|
||||
*/
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
obj.setACL(ACL);
|
||||
obj.set('owner', user2);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {find: {}, get: {}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user1', 'password');
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be block
|
||||
return obj.fetch();
|
||||
}).then(() => {
|
||||
fail('Should not succeed saving');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL read (PP/ACL OK)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: find/get open ({"*": true})
|
||||
PointerPerm: "owner" : read
|
||||
ACL: logged in user has access
|
||||
*/
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
ACL.setReadAccess(user2, true);
|
||||
ACL.setWriteAccess(user2, true);
|
||||
obj.setACL(ACL);
|
||||
obj.set('owner', user2);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {find: {"*": true}, get: {"*": true}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
// user1 has ACL read/write but should be block
|
||||
return obj.fetch();
|
||||
}).then((objAgain) => {
|
||||
expect(objAgain.id).toBe(obj.id);
|
||||
done();
|
||||
}, () => {
|
||||
fail('Should not fail fetching');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('tests CLP / Pointer Perms / ACL read (ACL locked)', (done) => {
|
||||
/*
|
||||
tests:
|
||||
CLP: find/get open ({"*": true})
|
||||
PointerPerm: "owner" : read // proper owner
|
||||
ACL: logged in user has not access
|
||||
*/
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = new Parse.User();
|
||||
const user2 = new Parse.User();
|
||||
user.set({
|
||||
username: 'user1',
|
||||
password: 'password'
|
||||
});
|
||||
user2.set({
|
||||
username: 'user2',
|
||||
password: 'password'
|
||||
});
|
||||
const obj = new Parse.Object('AnObject');
|
||||
Parse.Object.saveAll([user, user2]).then(() => {
|
||||
const ACL = new Parse.ACL();
|
||||
ACL.setReadAccess(user, true);
|
||||
ACL.setWriteAccess(user, true);
|
||||
obj.setACL(ACL);
|
||||
obj.set('owner', user2);
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {}, {find: {"*": true}, get: {"*": true}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return Parse.User.logIn('user2', 'password');
|
||||
}).then(() => {
|
||||
// user2 has ACL read/write but should be block by ACL
|
||||
return obj.fetch();
|
||||
}).then(() => {
|
||||
fail('Should not succeed saving');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should let master key find objects', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('hello', 'world');
|
||||
return object.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {find: {}, get: {}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find();
|
||||
}).then(() => {
|
||||
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.find({useMasterKey: true});
|
||||
}).then((objects) => {
|
||||
expect(objects.length).toBe(1);
|
||||
done();
|
||||
}, () => {
|
||||
fail('master key should find the object');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should let master key get objects', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('hello', 'world');
|
||||
return object.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {find: {}, get: {}, readUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.get(object.id);
|
||||
}).then(() => {
|
||||
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
const q = new Parse.Query('AnObject');
|
||||
return q.get(object.id, {useMasterKey: true});
|
||||
}).then((objectAgain) => {
|
||||
expect(objectAgain).not.toBeUndefined();
|
||||
expect(objectAgain.id).toBe(object.id);
|
||||
done();
|
||||
}, () => {
|
||||
fail('master key should find the object');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should let master key update objects', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('hello', 'world');
|
||||
return object.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {update: {}, writeUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return object.save({'hello': 'bar'});
|
||||
}).then(() => {
|
||||
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
return object.save({'hello': 'baz'}, {useMasterKey: true});
|
||||
}).then((objectAgain) => {
|
||||
expect(objectAgain.get('hello')).toBe('baz');
|
||||
done();
|
||||
}, () => {
|
||||
fail('master key should save the object');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should let master key delete objects', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const object = new Parse.Object('AnObject');
|
||||
object.set('hello', 'world');
|
||||
return object.save().then(() => {
|
||||
return config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner']});
|
||||
});
|
||||
}).then(() => {
|
||||
return object.destroy();
|
||||
}).then(() => {
|
||||
fail();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(101);
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
return object.destroy({useMasterKey: true});
|
||||
}).then(() => {
|
||||
done();
|
||||
}, () => {
|
||||
fail('master key should destroy the object');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should fail with invalid pointer perms', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: 'owner'});
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with invalid pointer perms', (done) => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Lock the update, and let only owner write
|
||||
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner', 'invalid']});
|
||||
}).catch((err) => {
|
||||
expect(err.code).toBe(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
const parser = require('../src/Adapters/Storage/Postgres/PostgresConfigParser');
|
||||
|
||||
const queryParamTests = {
|
||||
'a=1&b=2': { a: '1', b: '2' },
|
||||
'a=abcd%20efgh&b=abcd%3Defgh': { a: 'abcd efgh', b: 'abcd=efgh' },
|
||||
'a=1&b&c=true': { a: '1', b: '', c: 'true' }
|
||||
}
|
||||
|
||||
describe('PostgresConfigParser.parseQueryParams', () => {
|
||||
it('creates a map from a query string', () => {
|
||||
|
||||
for (const key in queryParamTests) {
|
||||
const result = parser.parseQueryParams(key);
|
||||
|
||||
const testObj = queryParamTests[key];
|
||||
|
||||
expect(Object.keys(result).length)
|
||||
.toEqual(Object.keys(testObj).length);
|
||||
|
||||
for (const k in result) {
|
||||
expect(result[k]).toEqual(testObj[k]);
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
const baseURI = 'postgres://username:password@localhost:5432/db-name'
|
||||
|
||||
const dbOptionsTest = {};
|
||||
dbOptionsTest[`${baseURI}?ssl=true&binary=true&application_name=app_name&fallback_application_name=f_app_name&poolSize=10`] = {
|
||||
ssl: true,
|
||||
binary: true,
|
||||
application_name: 'app_name',
|
||||
fallback_application_name: 'f_app_name',
|
||||
poolSize: 10
|
||||
};
|
||||
dbOptionsTest[`${baseURI}?ssl=&binary=aa`] = {
|
||||
ssl: false,
|
||||
binary: false
|
||||
}
|
||||
|
||||
describe('PostgresConfigParser.getDatabaseOptionsFromURI', () => {
|
||||
it('creates a db options map from a query string', () => {
|
||||
|
||||
for (const key in dbOptionsTest) {
|
||||
const result = parser.getDatabaseOptionsFromURI(key);
|
||||
|
||||
const testObj = dbOptionsTest[key];
|
||||
|
||||
for (const k in testObj) {
|
||||
expect(result[k]).toEqual(testObj[k]);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
it('sets the poolSize to 10 if the it is not a number', () => {
|
||||
|
||||
const result = parser.getDatabaseOptionsFromURI(`${baseURI}?poolSize=sdf`);
|
||||
|
||||
expect(result.poolSize).toEqual(10);
|
||||
|
||||
});
|
||||
});
|
||||
@@ -1,20 +1,19 @@
|
||||
var PromiseRouter = require("../src/PromiseRouter").default;
|
||||
|
||||
describe("PromiseRouter", () => {
|
||||
|
||||
it("should properly handle rejects", (done) => {
|
||||
describe("PromiseRouter", () => {
|
||||
it("should properly handle rejects", (done) => {
|
||||
var router = new PromiseRouter();
|
||||
router.route("GET", "/dummy", (req)=> {
|
||||
router.route("GET", "/dummy", ()=> {
|
||||
return Promise.reject({
|
||||
error: "an error",
|
||||
code: -1
|
||||
})
|
||||
}, (req) => {
|
||||
}, () => {
|
||||
fail("this should not be called");
|
||||
});
|
||||
|
||||
|
||||
router.routes[0].handler({}).then((result) => {
|
||||
console.error(result);
|
||||
jfail(result);
|
||||
fail("this should not be called");
|
||||
done();
|
||||
}, (error)=> {
|
||||
@@ -23,4 +22,4 @@ describe("PromiseRouter", () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
+25
-46
@@ -2,45 +2,35 @@
|
||||
var request = require('request');
|
||||
|
||||
describe("public API", () => {
|
||||
beforeEach(done => {
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
appName: 'unused',
|
||||
javascriptKey: 'test',
|
||||
dotNetKey: 'windows',
|
||||
clientKey: 'client',
|
||||
restAPIKey: 'rest',
|
||||
masterKey: 'test',
|
||||
collectionPrefix: 'test_',
|
||||
fileKey: 'test',
|
||||
publicServerURL: 'http://localhost:8378/1'
|
||||
});
|
||||
done();
|
||||
})
|
||||
it("should get invalid_link.html", (done) => {
|
||||
request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should get choose_password", (done) => {
|
||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
reconfigureServer({
|
||||
appName: 'unused',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should get verify_email_success.html", (done) => {
|
||||
request('http://localhost:8378/1/apps/verify_email_success.html', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/verify_email_success.html', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should get password_reset_success.html", (done) => {
|
||||
request('http://localhost:8378/1/apps/password_reset_success.html', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/password_reset_success.html', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
@@ -48,37 +38,26 @@ describe("public API", () => {
|
||||
});
|
||||
|
||||
describe("public API without publicServerURL", () => {
|
||||
beforeEach(done => {
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
appName: 'unused',
|
||||
javascriptKey: 'test',
|
||||
dotNetKey: 'windows',
|
||||
clientKey: 'client',
|
||||
restAPIKey: 'rest',
|
||||
masterKey: 'test',
|
||||
collectionPrefix: 'test_',
|
||||
fileKey: 'test',
|
||||
});
|
||||
done();
|
||||
})
|
||||
beforeEach(done => {
|
||||
reconfigureServer({ appName: 'unused' })
|
||||
.then(done, fail);
|
||||
});
|
||||
it("should get 404 on verify_email", (done) => {
|
||||
request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should get 404 choose_password", (done) => {
|
||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should get 404 on request_password_reset", (done) => {
|
||||
request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse, body) => {
|
||||
request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse) => {
|
||||
expect(httpResponse.statusCode).toBe(404);
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
var request = require("request");
|
||||
|
||||
|
||||
|
||||
function createProduct() {
|
||||
const file = new Parse.File("name", {
|
||||
base64: new Buffer("download_file", "utf-8").toString("base64")
|
||||
@@ -9,7 +7,7 @@ function createProduct() {
|
||||
return file.save().then(function(){
|
||||
var product = new Parse.Object("_Product");
|
||||
product.set({
|
||||
download: file,
|
||||
download: file,
|
||||
icon: file,
|
||||
title: "a product",
|
||||
subtitle: "a product",
|
||||
@@ -18,29 +16,26 @@ function createProduct() {
|
||||
})
|
||||
return product.save();
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
describe("test validate_receipt endpoint", () => {
|
||||
|
||||
beforeEach( done => {
|
||||
createProduct().then(done).fail(function(err){
|
||||
console.error(err);
|
||||
beforeEach(done => {
|
||||
createProduct().then(done).fail(function(){
|
||||
done();
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
it("should bypass appstore validation", (done) => {
|
||||
|
||||
request.post({
|
||||
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
url: 'http://localhost:8378/1/validate_purchase',
|
||||
json: true,
|
||||
body: {
|
||||
productIdentifier: "a-product",
|
||||
productIdentifier: "a-product",
|
||||
receipt: {
|
||||
__type: "Bytes",
|
||||
base64: new Buffer("receipt", "utf-8").toString("base64")
|
||||
@@ -50,7 +45,7 @@ describe("test validate_receipt endpoint", () => {
|
||||
}, function(err, res, body){
|
||||
if (typeof body != "object") {
|
||||
fail("Body is not an object");
|
||||
done();
|
||||
done();
|
||||
} else {
|
||||
expect(body.__type).toEqual("File");
|
||||
const url = body.url;
|
||||
@@ -63,31 +58,31 @@ describe("test validate_receipt endpoint", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should fail for missing receipt", (done) => {
|
||||
request.post({
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
url: 'http://localhost:8378/1/validate_purchase',
|
||||
json: true,
|
||||
body: {
|
||||
productIdentifier: "a-product",
|
||||
productIdentifier: "a-product",
|
||||
bypassAppStoreValidation: true
|
||||
}
|
||||
}, function(err, res, body){
|
||||
if (typeof body != "object") {
|
||||
fail("Body is not an object");
|
||||
done();
|
||||
done();
|
||||
} else {
|
||||
expect(body.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should fail for missing product identifier", (done) => {
|
||||
request.post({
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
@@ -103,24 +98,24 @@ describe("test validate_receipt endpoint", () => {
|
||||
}, function(err, res, body){
|
||||
if (typeof body != "object") {
|
||||
fail("Body is not an object");
|
||||
done();
|
||||
done();
|
||||
} else {
|
||||
expect(body.code).toEqual(Parse.Error.INVALID_JSON);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should bypass appstore validation and not find product", (done) => {
|
||||
|
||||
request.post({
|
||||
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
url: 'http://localhost:8378/1/validate_purchase',
|
||||
json: true,
|
||||
body: {
|
||||
productIdentifier: "another-product",
|
||||
productIdentifier: "another-product",
|
||||
receipt: {
|
||||
__type: "Bytes",
|
||||
base64: new Buffer("receipt", "utf-8").toString("base64")
|
||||
@@ -138,17 +133,16 @@ describe("test validate_receipt endpoint", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail at appstore validation", (done) => {
|
||||
|
||||
request.post({
|
||||
|
||||
it("should fail at appstore validation", done => {
|
||||
request.post({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'},
|
||||
url: 'http://localhost:8378/1/validate_purchase',
|
||||
json: true,
|
||||
body: {
|
||||
productIdentifier: "a-product",
|
||||
productIdentifier: "a-product",
|
||||
receipt: {
|
||||
__type: "Bytes",
|
||||
base64: new Buffer("receipt", "utf-8").toString("base64")
|
||||
@@ -164,46 +158,51 @@ describe("test validate_receipt endpoint", () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should not create a _Product", (done) => {
|
||||
var product = new Parse.Object("_Product");
|
||||
product.save().then(function(){
|
||||
fail("Should not be able to save");
|
||||
done();
|
||||
}, function(err){
|
||||
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
done();
|
||||
})
|
||||
var product = new Parse.Object("_Product");
|
||||
product.save().then(function(){
|
||||
fail("Should not be able to save");
|
||||
done();
|
||||
}, function(err){
|
||||
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("should be able to update a _Product", (done) => {
|
||||
var query = new Parse.Query("_Product");
|
||||
query.first().then(function(product){
|
||||
product.set("title", "a new title");
|
||||
return product.save();
|
||||
}).then(function(productAgain){
|
||||
expect(productAgain.get('downloadName')).toEqual(productAgain.get('download').name());
|
||||
expect(productAgain.get("title")).toEqual("a new title");
|
||||
done();
|
||||
}).fail(function(err){
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
var query = new Parse.Query("_Product");
|
||||
query.first().then(function(product) {
|
||||
if (!product) {
|
||||
return Promise.reject(new Error('Product should be found'));
|
||||
}
|
||||
product.set("title", "a new title");
|
||||
return product.save();
|
||||
}).then(function(productAgain){
|
||||
expect(productAgain.get('downloadName')).toEqual(productAgain.get('download').name());
|
||||
expect(productAgain.get("title")).toEqual("a new title");
|
||||
done();
|
||||
}).fail(function(err){
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should not be able to remove a require key in a _Product", (done) => {
|
||||
var query = new Parse.Query("_Product");
|
||||
query.first().then(function(product){
|
||||
product.unset("title");
|
||||
return product.save();
|
||||
}).then(function(productAgain){
|
||||
fail("Should not succeed");
|
||||
done();
|
||||
}).fail(function(err){
|
||||
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
expect(err.message).toEqual("title is required.");
|
||||
done();
|
||||
});
|
||||
var query = new Parse.Query("_Product");
|
||||
query.first().then(function(product){
|
||||
if (!product) {
|
||||
return Promise.reject(new Error('Product should be found'));
|
||||
}
|
||||
product.unset("title");
|
||||
return product.save();
|
||||
}).then(function(){
|
||||
fail("Should not succeed");
|
||||
done();
|
||||
}).fail(function(err){
|
||||
expect(err.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
expect(err.message).toEqual("title is required.");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+357
-186
@@ -1,11 +1,12 @@
|
||||
"use strict";
|
||||
var PushController = require('../src/Controllers/PushController').PushController;
|
||||
var pushStatusHandler = require('../src/pushStatusHandler');
|
||||
var StatusHandler = require('../src/StatusHandler');
|
||||
var Config = require('../src/Config');
|
||||
var validatePushType = require('../src/Push/utils').validatePushType;
|
||||
|
||||
const successfulTransmissions = function(body, installations) {
|
||||
|
||||
let promises = installations.map((device) => {
|
||||
const promises = installations.map((device) => {
|
||||
return Promise.resolve({
|
||||
transmitted: true,
|
||||
device: device,
|
||||
@@ -17,7 +18,7 @@ const successfulTransmissions = function(body, installations) {
|
||||
|
||||
const successfulIOS = function(body, installations) {
|
||||
|
||||
let promises = installations.map((device) => {
|
||||
const promises = installations.map((device) => {
|
||||
return Promise.resolve({
|
||||
transmitted: device.deviceType == "ios",
|
||||
device: device,
|
||||
@@ -35,7 +36,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -48,7 +49,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -63,7 +64,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -76,7 +77,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -89,7 +90,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -98,8 +99,8 @@ describe('PushController', () => {
|
||||
// Make mock request
|
||||
var timeStr = '2015-03-19T22:05:08Z';
|
||||
var body = {
|
||||
'expiration_time': timeStr
|
||||
}
|
||||
'expiration_time': timeStr
|
||||
}
|
||||
|
||||
var time = PushController.getExpirationTime(body);
|
||||
expect(time).toEqual(new Date(timeStr).valueOf());
|
||||
@@ -131,122 +132,32 @@ describe('PushController', () => {
|
||||
});
|
||||
|
||||
it('properly increment badges', (done) => {
|
||||
|
||||
var payload = {data:{
|
||||
alert: "Hello World!",
|
||||
badge: "Increment",
|
||||
}}
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_"+installations.length);
|
||||
installation.set("deviceToken","device_token_"+installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
|
||||
while(installations.length != 15) {
|
||||
var 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);
|
||||
}
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
installations.forEach((installation) => {
|
||||
if (installation.deviceType == "ios") {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(installation.originalBadge+1).toEqual(installation.badge);
|
||||
} else {
|
||||
expect(installation.badge).toBeUndefined();
|
||||
}
|
||||
})
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios", "android"];
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
installations.forEach((installation) => {
|
||||
if (installation.deviceType == "ios") {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(installation.originalBadge + 1).toEqual(installation.badge);
|
||||
} else {
|
||||
expect(installation.badge).toBeUndefined();
|
||||
}
|
||||
})
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios", "android"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId);
|
||||
Parse.Object.saveAll(installations).then((installations) => {
|
||||
return pushController.sendPush(payload, {}, config, auth);
|
||||
}).then((result) => {
|
||||
done();
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail("should not fail");
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('properly set badges to 1', (done) => {
|
||||
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
badge: 1,
|
||||
}}
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_"+installations.length);
|
||||
installation.set("deviceToken","device_token_"+installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
installations.forEach((installation) => {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(1).toEqual(installation.badge);
|
||||
})
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId);
|
||||
Parse.Object.saveAll(installations).then((installations) => {
|
||||
return pushController.sendPush(payload, {}, config, auth);
|
||||
}).then((result) => {
|
||||
done();
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail("should not fail");
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('properly creates _PushStatus', (done) => {
|
||||
|
||||
var payload = {data:{
|
||||
alert: "Hello World!",
|
||||
badge: "Increment",
|
||||
}}
|
||||
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)
|
||||
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");
|
||||
@@ -254,50 +165,220 @@ describe('PushController', () => {
|
||||
}
|
||||
|
||||
while(installations.length != 15) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_"+installations.length);
|
||||
installation.set("deviceToken","device_token_"+installations.length)
|
||||
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);
|
||||
}
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
badge: 1,
|
||||
}}
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
return successfulIOS(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations)
|
||||
}).then(() => {
|
||||
return pushController.sendPush(payload, {}, config, auth);
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId);
|
||||
Parse.Object.saveAll(installations).then(() => {
|
||||
it('properly set badges to 1', (done) => {
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
installations.forEach((installation) => {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(1).toEqual(installation.badge);
|
||||
})
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
badge: 1,
|
||||
}}
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations)
|
||||
}).then(() => {
|
||||
return pushController.sendPush(payload, {}, config, auth);
|
||||
}).then(() => {
|
||||
done();
|
||||
}, () => {
|
||||
fail("should not fail");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('properly set badges to 1 with complex query #2903 #3022', (done) => {
|
||||
|
||||
var payload = {
|
||||
data: {
|
||||
alert: "Hello World!",
|
||||
badge: 1,
|
||||
}
|
||||
}
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length)
|
||||
installation.set("badge", installations.length);
|
||||
installation.set("originalBadge", installations.length);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
let matchedInstallationsCount = 0;
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
matchedInstallationsCount += installations.length;
|
||||
var badge = body.data.badge;
|
||||
installations.forEach((installation) => {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(1).toEqual(installation.badge);
|
||||
})
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: {
|
||||
adapter: pushAdapter
|
||||
}
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations)
|
||||
}).then((installations) => {
|
||||
const objectIds = installations.map(installation => {
|
||||
return installation.id;
|
||||
})
|
||||
const where = {
|
||||
objectId: {'$in': objectIds.slice(0, 5)}
|
||||
}
|
||||
return pushController.sendPush(payload, where, config, auth);
|
||||
}).then(() => {
|
||||
return new Promise((res) => {
|
||||
setTimeout(res, 300);
|
||||
});
|
||||
}).then(() => {
|
||||
expect(matchedInstallationsCount).toBe(5);
|
||||
const query = new Parse.Query(Parse.Installation);
|
||||
query.equalTo('badge', 1);
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((installations) => {
|
||||
expect(installations.length).toBe(5);
|
||||
done();
|
||||
}).catch(() => {
|
||||
fail("should not fail");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('properly creates _PushStatus', (done) => {
|
||||
|
||||
var 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);
|
||||
}
|
||||
|
||||
while(installations.length != 15) {
|
||||
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);
|
||||
}
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
badge: 1,
|
||||
}}
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
return successfulIOS(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations)
|
||||
})
|
||||
.then(() => {
|
||||
return pushController.sendPush(payload, {}, config, auth);
|
||||
}).then((result) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
}).then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
}).then(() => {
|
||||
let query = new Parse.Query('_PushStatus');
|
||||
}).then(() => {
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
let result = results[0];
|
||||
const result = results[0];
|
||||
expect(result.createdAt instanceof Date).toBe(true);
|
||||
expect(result.updatedAt instanceof Date).toBe(true);
|
||||
expect(result.id.length).toBe(10);
|
||||
expect(result.get('source')).toEqual('rest');
|
||||
expect(result.get('query')).toEqual(JSON.stringify({}));
|
||||
expect(result.get('payload')).toEqual(payload.data);
|
||||
expect(typeof result.get('payload')).toEqual("string");
|
||||
expect(JSON.parse(result.get('payload'))).toEqual(payload.data);
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
expect(result.get('numSent')).toEqual(10);
|
||||
expect(result.get('sentPerType')).toEqual({
|
||||
@@ -308,57 +389,147 @@ describe('PushController', () => {
|
||||
'android': 5 // android
|
||||
});
|
||||
// Try to get it without masterKey
|
||||
let query = new Parse.Query('_PushStatus');
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should support full RESTQuery for increment', (done) => {
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
badge: 'Increment',
|
||||
}}
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
it('should properly report failures in _PushStatus', (done) => {
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
return installations.map((installation) => {
|
||||
return Promise.resolve({
|
||||
deviceType: installation.deviceType
|
||||
})
|
||||
})
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
let where = {
|
||||
'deviceToken': {
|
||||
'$inQuery': {
|
||||
'where': {
|
||||
'deviceType': 'ios'
|
||||
},
|
||||
className: '_Installation'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId);
|
||||
pushController.sendPush(payload, where, config, auth).then((result) => {
|
||||
const where = { 'channels': {
|
||||
'$ins': ['Giants', 'Mets']
|
||||
}};
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
badge: 1,
|
||||
}}
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return pushController.sendPush(payload, where, config, auth)
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
done();
|
||||
}).catch((err) => {
|
||||
}).catch(() => {
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
query.find({useMasterKey: true}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
const pushStatus = results[0];
|
||||
expect(pushStatus.get('status')).toBe('failed');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should support full RESTQuery for increment', (done) => {
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
badge: 'Increment',
|
||||
}}
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
const where = {
|
||||
'deviceToken': {
|
||||
'$inQuery': {
|
||||
'where': {
|
||||
'deviceType': 'ios'
|
||||
},
|
||||
className: '_Installation'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return pushController.sendPush(payload, where, config, auth);
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should support object type for alert', (done) => {
|
||||
var payload = {data: {
|
||||
alert: {
|
||||
'loc-key': 'hello_world',
|
||||
},
|
||||
}}
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
const where = {
|
||||
'deviceToken': {
|
||||
'$inQuery': {
|
||||
'where': {
|
||||
'deviceType': 'ios'
|
||||
},
|
||||
className: '_Installation'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
pushController.sendPush(payload, where, config, auth)
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch(() => {
|
||||
fail('should not fail');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should flatten', () => {
|
||||
var res = pushStatusHandler.flatten([1, [2], [[3, 4], 5], [[[6]]]])
|
||||
var res = StatusHandler.flatten([1, [2], [[3, 4], 5], [[[6]]]])
|
||||
expect(res).toEqual([1,2,3,4,5,6]);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -67,10 +67,10 @@ describe('PushRouter', () => {
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it('sends a push through REST', (done) => {
|
||||
request.post({
|
||||
url: Parse.serverURL+"/push",
|
||||
url: Parse.serverURL + "/push",
|
||||
json: true,
|
||||
body: {
|
||||
'channels': {
|
||||
@@ -82,8 +82,11 @@ describe('PushRouter', () => {
|
||||
'X-Parse-Master-Key': Parse.masterKey
|
||||
}
|
||||
}, function(err, res, body){
|
||||
expect(res.headers['x-parse-push-status-id']).not.toBe(undefined);
|
||||
expect(res.headers['x-parse-push-status-id'].length).toBe(10);
|
||||
expect(res.headers[''])
|
||||
expect(body.result).toBe(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
var PushWorker = require('../src').PushWorker;
|
||||
var Config = require('../src/Config');
|
||||
|
||||
describe('PushWorker', () => {
|
||||
it('should run with small batch', (done) => {
|
||||
const batchSize = 3;
|
||||
var sendCount = 0;
|
||||
reconfigureServer({
|
||||
push: {
|
||||
queueOptions: {
|
||||
disablePushWorker: true,
|
||||
batchSize
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
expect(new Config('test').pushWorker).toBeUndefined();
|
||||
new PushWorker({
|
||||
send: (body, installations) => {
|
||||
expect(installations.length <= batchSize).toBe(true);
|
||||
sendCount += installations.length;
|
||||
return Promise.resolve();
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ['ios', 'android']
|
||||
}
|
||||
});
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length)
|
||||
installation.set("badge", 1);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
return Parse.Object.saveAll(installations);
|
||||
}).then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
}).then(() => {
|
||||
expect(sendCount).toBe(10);
|
||||
done();
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -112,6 +112,20 @@ describe('matchesQuery', function() {
|
||||
expect(matchesQuery(obj, q)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches queries with doesNotExist constraint', function() {
|
||||
var obj = {
|
||||
id: new Id('Item', 'O1'),
|
||||
count: 15
|
||||
};
|
||||
var q = new Parse.Query('Item');
|
||||
q.doesNotExist('name');
|
||||
expect(matchesQuery(obj, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Item');
|
||||
q.doesNotExist('count');
|
||||
expect(matchesQuery(obj, q)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches on equality queries', function() {
|
||||
var day = new Date();
|
||||
var location = new Parse.GeoPoint({
|
||||
@@ -212,6 +226,23 @@ describe('matchesQuery', function() {
|
||||
|
||||
img.owner.objectId = 'U3';
|
||||
expect(matchesQuery(img, q)).toBe(false);
|
||||
|
||||
// pointers in arrays
|
||||
q = new Parse.Query('Image');
|
||||
q.equalTo('owners', u);
|
||||
|
||||
img = {
|
||||
className: 'Image',
|
||||
objectId: 'I1',
|
||||
owners: [{
|
||||
className: '_User',
|
||||
objectId: 'U2'
|
||||
}]
|
||||
};
|
||||
expect(matchesQuery(img, q)).toBe(true);
|
||||
|
||||
img.owners[0].objectId = 'U3';
|
||||
expect(matchesQuery(img, q)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches on inequalities', function() {
|
||||
@@ -381,4 +412,66 @@ describe('matchesQuery', function() {
|
||||
expect(matchesQuery(caltrainStation, q)).toBe(false);
|
||||
expect(matchesQuery(santaClara, q)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches on subobjects with dot notation', function() {
|
||||
var message = {
|
||||
id: new Id('Message', 'O1'),
|
||||
text: "content",
|
||||
status: {x: "read", y: "delivered"}
|
||||
};
|
||||
|
||||
var q = new Parse.Query('Message');
|
||||
q.equalTo("status.x", "read");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.equalTo("status.z", "read");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.equalTo("status.x", "delivered");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.notEqualTo("status.x", "read");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.notEqualTo("status.z", "read");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.notEqualTo("status.x", "delivered");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.exists("status.x");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.exists("status.z");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.exists("nonexistent.x");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.doesNotExist("status.x");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.doesNotExist("status.z");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.doesNotExist("nonexistent.z");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.equalTo("status.x", "read");
|
||||
q.doesNotExist("status.y");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
var RedisCacheAdapter = require('../src/Adapters/Cache/RedisCacheAdapter').default;
|
||||
|
||||
describe('RedisCacheAdapter', function() {
|
||||
var KEY = 'hello';
|
||||
var VALUE = 'world';
|
||||
|
||||
function wait(sleep) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, sleep);
|
||||
})
|
||||
}
|
||||
|
||||
it('should get/set/clear', (done) => {
|
||||
var cache = new RedisCacheAdapter({
|
||||
ttl: NaN
|
||||
});
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(() => cache.clear())
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('should expire after ttl', (done) => {
|
||||
var cache = new RedisCacheAdapter(null, 1);
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(wait.bind(null, 2))
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('should find un-expired records', (done) => {
|
||||
var cache = new RedisCacheAdapter(null, 5);
|
||||
|
||||
cache.put(KEY, VALUE)
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).toEqual(VALUE))
|
||||
.then(wait.bind(null, 1))
|
||||
.then(() => cache.get(KEY))
|
||||
.then((value) => expect(value).not.toEqual(null))
|
||||
.then(done);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
var RedisPubSub = require('../src/LiveQuery/RedisPubSub').RedisPubSub;
|
||||
var RedisPubSub = require('../src/Adapters/PubSub/RedisPubSub').RedisPubSub;
|
||||
|
||||
describe('RedisPubSub', function() {
|
||||
|
||||
@@ -10,14 +10,14 @@ describe('RedisPubSub', function() {
|
||||
});
|
||||
|
||||
it('can create publisher', function() {
|
||||
var publisher = RedisPubSub.createPublisher('redisAddress');
|
||||
RedisPubSub.createPublisher({redisURL: 'redisAddress'});
|
||||
|
||||
var redis = require('redis');
|
||||
expect(redis.createClient).toHaveBeenCalledWith('redisAddress', { no_ready_check: true });
|
||||
});
|
||||
|
||||
it('can create subscriber', function() {
|
||||
var subscriber = RedisPubSub.createSubscriber('redisAddress');
|
||||
RedisPubSub.createSubscriber({redisURL: 'redisAddress'});
|
||||
|
||||
var redis = require('redis');
|
||||
expect(redis.createClient).toHaveBeenCalledWith('redisAddress', { no_ready_check: true });
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
// These tests check the "create" / "update" functionality of the REST API.
|
||||
var auth = require('../src/Auth');
|
||||
var cache = require('../src/cache');
|
||||
var Config = require('../src/Config');
|
||||
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||
var Parse = require('parse/node').Parse;
|
||||
var rest = require('../src/rest');
|
||||
var request = require('request');
|
||||
|
||||
var config = new Config('test');
|
||||
var database = DatabaseAdapter.getDatabaseConnection('test', 'test_');
|
||||
|
||||
describe('rest create', () => {
|
||||
it('handles _id', (done) => {
|
||||
rest.create(config, auth.nobody(config), 'Foo', {}).then(() => {
|
||||
return database.mongoFind('Foo', {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
expect(typeof obj._id).toEqual('string');
|
||||
expect(obj.objectId).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles array, object, date', (done) => {
|
||||
var obj = {
|
||||
array: [1, 2, 3],
|
||||
object: {foo: 'bar'},
|
||||
date: Parse._encode(new Date()),
|
||||
};
|
||||
rest.create(config, auth.nobody(config), 'MyClass', obj).then(() => {
|
||||
return database.mongoFind('MyClass', {}, {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
var mob = results[0];
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date instanceof Date).toBe(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles object and subdocument', (done) => {
|
||||
var obj = {
|
||||
subdoc: {foo: 'bar', wu: 'tan'},
|
||||
};
|
||||
rest.create(config, auth.nobody(config), 'MyClass', obj).then(() => {
|
||||
return database.mongoFind('MyClass', {}, {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
var mob = results[0];
|
||||
expect(typeof mob.subdoc).toBe('object');
|
||||
expect(mob.subdoc.foo).toBe('bar');
|
||||
expect(mob.subdoc.wu).toBe('tan');
|
||||
expect(typeof mob._id).toEqual('string');
|
||||
|
||||
var obj = {
|
||||
'subdoc.wu': 'clan',
|
||||
};
|
||||
|
||||
rest.update(config, auth.nobody(config), 'MyClass', mob._id, obj).then(() => {
|
||||
return database.mongoFind('MyClass', {}, {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
var mob = results[0];
|
||||
expect(typeof mob.subdoc).toBe('object');
|
||||
expect(mob.subdoc.foo).toBe('bar');
|
||||
expect(mob.subdoc.wu).toBe('clan');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('handles create on non-existent class when disabled client class creation', (done) => {
|
||||
var customConfig = Object.assign({}, config, {allowClientClassCreation: false});
|
||||
rest.create(customConfig, auth.nobody(customConfig), 'ClientClassCreation', {})
|
||||
.then(() => {
|
||||
fail('Should throw an error');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(err.message).toEqual('This user is not allowed to access ' +
|
||||
'non-existent class: ClientClassCreation');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles user signup', (done) => {
|
||||
var user = {
|
||||
username: 'asdf',
|
||||
password: 'zxcv',
|
||||
foo: 'bar',
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_User', user)
|
||||
.then((r) => {
|
||||
expect(Object.keys(r.response).length).toEqual(3);
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.sessionToken).toEqual('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles anonymous user signup', (done) => {
|
||||
var data1 = {
|
||||
authData: {
|
||||
anonymous: {
|
||||
id: '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
}
|
||||
};
|
||||
var data2 = {
|
||||
authData: {
|
||||
anonymous: {
|
||||
id: '00000000-0000-0000-0000-000000000002'
|
||||
}
|
||||
}
|
||||
};
|
||||
var username1;
|
||||
rest.create(config, auth.nobody(config), '_User', data1)
|
||||
.then((r) => {
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.sessionToken).toEqual('string');
|
||||
return rest.create(config, auth.nobody(config), '_User', data1);
|
||||
}).then((r) => {
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.username).toEqual('string');
|
||||
expect(typeof r.response.updatedAt).toEqual('string');
|
||||
username1 = r.response.username;
|
||||
return rest.create(config, auth.nobody(config), '_User', data2);
|
||||
}).then((r) => {
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.sessionToken).toEqual('string');
|
||||
return rest.create(config, auth.nobody(config), '_User', data2);
|
||||
}).then((r) => {
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.username).toEqual('string');
|
||||
expect(typeof r.response.updatedAt).toEqual('string');
|
||||
expect(r.response.username).not.toEqual(username1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles anonymous user signup and upgrade to new user', (done) => {
|
||||
var data1 = {
|
||||
authData: {
|
||||
anonymous: {
|
||||
id: '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var updatedData = {
|
||||
authData: { anonymous: null },
|
||||
username: 'hello',
|
||||
password: 'world'
|
||||
}
|
||||
var username1;
|
||||
var objectId;
|
||||
rest.create(config, auth.nobody(config), '_User', data1)
|
||||
.then((r) => {
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.sessionToken).toEqual('string');
|
||||
objectId = r.response.objectId;
|
||||
return auth.getAuthForSessionToken({config, sessionToken: r.response.sessionToken })
|
||||
}).then((sessionAuth) => {
|
||||
return rest.update(config, sessionAuth, '_User', objectId, updatedData);
|
||||
}).then((r) => {
|
||||
return Parse.User.logOut().then(() => {
|
||||
return Parse.User.logIn('hello', 'world');
|
||||
})
|
||||
}).then((r) => {
|
||||
expect(r.id).toEqual(objectId);
|
||||
expect(r.get('username')).toEqual('hello');
|
||||
done();
|
||||
}).catch((err) => {
|
||||
fail('should not fail')
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('handles no anonymous users config', (done) => {
|
||||
var NoAnnonConfig = Object.assign({}, config);
|
||||
NoAnnonConfig.authDataManager.setEnableAnonymousUsers(false);
|
||||
var data1 = {
|
||||
authData: {
|
||||
anonymous: {
|
||||
id: '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
}
|
||||
};
|
||||
rest.create(NoAnnonConfig, auth.nobody(NoAnnonConfig), '_User', data1).then(() => {
|
||||
fail("Should throw an error");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toEqual(Parse.Error.UNSUPPORTED_SERVICE);
|
||||
expect(err.message).toEqual('This authentication method is unsupported.');
|
||||
NoAnnonConfig.authDataManager.setEnableAnonymousUsers(true);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('test facebook signup and login', (done) => {
|
||||
var data = {
|
||||
authData: {
|
||||
facebook: {
|
||||
id: '8675309',
|
||||
access_token: 'jenny'
|
||||
}
|
||||
}
|
||||
};
|
||||
var newUserSignedUpByFacebookObjectId;
|
||||
rest.create(config, auth.nobody(config), '_User', data)
|
||||
.then((r) => {
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.sessionToken).toEqual('string');
|
||||
newUserSignedUpByFacebookObjectId = r.response.objectId;
|
||||
return rest.create(config, auth.nobody(config), '_User', data);
|
||||
}).then((r) => {
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.username).toEqual('string');
|
||||
expect(typeof r.response.updatedAt).toEqual('string');
|
||||
expect(r.response.objectId).toEqual(newUserSignedUpByFacebookObjectId);
|
||||
return rest.find(config, auth.master(config),
|
||||
'_Session', {sessionToken: r.response.sessionToken});
|
||||
}).then((response) => {
|
||||
expect(response.results.length).toEqual(1);
|
||||
var output = response.results[0];
|
||||
expect(output.user.objectId).toEqual(newUserSignedUpByFacebookObjectId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('stores pointers with a _p_ prefix', (done) => {
|
||||
var obj = {
|
||||
foo: 'bar',
|
||||
aPointer: {
|
||||
__type: 'Pointer',
|
||||
className: 'JustThePointer',
|
||||
objectId: 'qwerty'
|
||||
}
|
||||
};
|
||||
rest.create(config, auth.nobody(config), 'APointerDarkly', obj)
|
||||
.then((r) => {
|
||||
return database.mongoFind('APointerDarkly', {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
var output = results[0];
|
||||
expect(typeof output._id).toEqual('string');
|
||||
expect(typeof output._p_aPointer).toEqual('string');
|
||||
expect(output._p_aPointer).toEqual('JustThePointer$qwerty');
|
||||
expect(output.aPointer).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("cannot set objectId", (done) => {
|
||||
var headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.post({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/TestObject',
|
||||
body: JSON.stringify({
|
||||
'foo': 'bar',
|
||||
'objectId': 'hello'
|
||||
})
|
||||
}, (error, response, body) => {
|
||||
var b = JSON.parse(body);
|
||||
expect(b.code).toEqual(105);
|
||||
expect(b.error).toEqual('objectId is an invalid field name.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+140
-29
@@ -1,16 +1,23 @@
|
||||
'use strict'
|
||||
// These tests check the "find" functionality of the REST API.
|
||||
var auth = require('../src/Auth');
|
||||
var cache = require('../src/cache');
|
||||
var Config = require('../src/Config');
|
||||
var rest = require('../src/rest');
|
||||
|
||||
var querystring = require('querystring');
|
||||
var request = require('request');
|
||||
var rp = require('request-promise');
|
||||
|
||||
var config = new Config('test');
|
||||
var config;
|
||||
let database;
|
||||
var nobody = auth.nobody(config);
|
||||
|
||||
describe('rest query', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config('test');
|
||||
database = config.database;
|
||||
});
|
||||
|
||||
it('basic query', (done) => {
|
||||
rest.create(config, nobody, 'TestObject', {}).then(() => {
|
||||
return rest.find(config, nobody, 'TestObject', {});
|
||||
@@ -35,8 +42,38 @@ describe('rest query', () => {
|
||||
});
|
||||
});
|
||||
|
||||
var data = {
|
||||
username: 'blah',
|
||||
password: 'pass',
|
||||
sessionToken: 'abc123',
|
||||
}
|
||||
|
||||
it_exclude_dbs(['postgres'])('query for user w/ legacy credentials without masterKey has them stripped from results', done => {
|
||||
database.create('_User', data).then(() => {
|
||||
return rest.find(config, nobody, '_User')
|
||||
}).then((result) => {
|
||||
var user = result.results[0];
|
||||
expect(user.username).toEqual('blah');
|
||||
expect(user.sessionToken).toBeUndefined();
|
||||
expect(user.password).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])('query for user w/ legacy credentials with masterKey has them stripped from results', done => {
|
||||
database.create('_User', data).then(() => {
|
||||
return rest.find(config, {isMaster: true}, '_User')
|
||||
}).then((result) => {
|
||||
var user = result.results[0];
|
||||
expect(user.username).toEqual('blah');
|
||||
expect(user.sessionToken).toBeUndefined();
|
||||
expect(user.password).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Created to test a scenario in AnyPic
|
||||
it('query with include', (done) => {
|
||||
it_exclude_dbs(['postgres'])('query with include', (done) => {
|
||||
var photo = {
|
||||
foo: 'bar'
|
||||
};
|
||||
@@ -106,6 +143,22 @@ describe('rest query', () => {
|
||||
expect(err.message).toEqual('This user is not allowed to access ' +
|
||||
'non-existent class: ClientClassCreation');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query existent class when disabled client class creation', (done) => {
|
||||
var customConfig = Object.assign({}, config, {allowClientClassCreation: false});
|
||||
config.database.loadSchema()
|
||||
.then(schema => schema.addClassIfNotExists('ClientClassCreation', {}))
|
||||
.then(actualSchema => {
|
||||
expect(actualSchema.className).toEqual('ClientClassCreation');
|
||||
return rest.find(customConfig, auth.nobody(customConfig), 'ClientClassCreation', {});
|
||||
})
|
||||
.then((result) => {
|
||||
expect(result.results.length).toEqual(0);
|
||||
done();
|
||||
}, () => {
|
||||
fail('Should not throw error')
|
||||
});
|
||||
});
|
||||
|
||||
@@ -119,39 +172,97 @@ describe('rest query', () => {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.get({
|
||||
|
||||
const p0 = rp.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/TestParameterEncode?'
|
||||
+ querystring.stringify({
|
||||
where: '{"foo":{"$ne": "baz"}}',
|
||||
limit: 1
|
||||
where: '{"foo":{"$ne": "baz"}}',
|
||||
limit: 1
|
||||
}).replace('=', '%3D'),
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
}).then(fail, (response) => {
|
||||
const error = response.error;
|
||||
var b = JSON.parse(error);
|
||||
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
expect(b.error).toEqual('Improper encode of parameter');
|
||||
done();
|
||||
});
|
||||
|
||||
const p1 = rp.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/TestParameterEncode?'
|
||||
+ querystring.stringify({
|
||||
limit: 1
|
||||
}).replace('=', '%3D'),
|
||||
}).then(fail, (response) => {
|
||||
const error = response.error;
|
||||
var b = JSON.parse(error);
|
||||
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
});
|
||||
return Promise.all([p0, p1]);
|
||||
}).then(done).catch((err) => {
|
||||
jfail(err);
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('query with limit = 0', (done) => {
|
||||
rest.create(config, nobody, 'TestObject', {foo: 'baz'}
|
||||
).then(() => {
|
||||
return rest.create(config, nobody,
|
||||
'TestObject', {foo: 'qux'});
|
||||
}).then(() => {
|
||||
var headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/TestParameterEncode?'
|
||||
+ querystring.stringify({
|
||||
limit: 1
|
||||
}).replace('=', '%3D'),
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
expect(b.error).toEqual('Improper encode of parameter');
|
||||
done();
|
||||
});
|
||||
return rest.find(config, nobody,
|
||||
'TestObject', {}, {limit: 0});
|
||||
}).then((response) => {
|
||||
expect(response.results.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query with limit = 0 and count = 1', (done) => {
|
||||
rest.create(config, nobody, 'TestObject', {foo: 'baz'}
|
||||
).then(() => {
|
||||
return rest.create(config, nobody,
|
||||
'TestObject', {foo: 'qux'});
|
||||
}).then(() => {
|
||||
return rest.find(config, nobody,
|
||||
'TestObject', {}, {limit: 0, count: 1});
|
||||
}).then((response) => {
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('makes sure null pointers are handed correctly #2189', done => {
|
||||
const object = new Parse.Object('AnObject');
|
||||
const anotherObject = new Parse.Object('AnotherObject');
|
||||
anotherObject.save().then(() => {
|
||||
object.set('values', [null, null, anotherObject]);
|
||||
return object.save();
|
||||
}).then(() => {
|
||||
const query = new Parse.Query('AnObject');
|
||||
query.include('values');
|
||||
return query.first();
|
||||
}).then((result) => {
|
||||
const values = result.get('values');
|
||||
expect(values.length).toBe(3);
|
||||
let anotherObjectFound = false;
|
||||
let nullCounts = 0;
|
||||
for(const value of values) {
|
||||
if (value === null) {
|
||||
nullCounts++;
|
||||
} else if (value instanceof Parse.Object) {
|
||||
anotherObjectFound = true;
|
||||
}
|
||||
}
|
||||
expect(nullCounts).toBe(2);
|
||||
expect(anotherObjectFound).toBeTruthy();
|
||||
done();
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
const Config = require('../src/Config');
|
||||
const sessionToken = 'legacySessionToken';
|
||||
const rp = require('request-promise');
|
||||
const Parse = require('parse/node');
|
||||
|
||||
function createUser() {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const user = {
|
||||
objectId: '1234567890',
|
||||
username: 'hello',
|
||||
password: 'pass',
|
||||
_session_token: sessionToken
|
||||
}
|
||||
return config.database.create('_User', user);
|
||||
}
|
||||
|
||||
describe_only_db('mongo')('revocable sessions', () => {
|
||||
|
||||
beforeEach((done) => {
|
||||
// Create 1 user with the legacy
|
||||
createUser().then(done);
|
||||
});
|
||||
|
||||
it('should upgrade legacy session token', done => {
|
||||
const user = Parse.Object.fromJSON({
|
||||
className: '_User',
|
||||
objectId: '1234567890',
|
||||
sessionToken: sessionToken
|
||||
});
|
||||
user._upgradeToRevocableSession().then((res) => {
|
||||
expect(res.getSessionToken().indexOf('r:')).toBe(0);
|
||||
const config = new Config(Parse.applicationId);
|
||||
// use direct access to the DB to make sure we're not
|
||||
// getting the session token stripped
|
||||
return config.database.loadSchema().then(schemaController => {
|
||||
return schemaController.getOneSchema('_User', true)
|
||||
}).then((schema) => {
|
||||
return config.database.adapter.find('_User', schema, {objectId: '1234567890'}, {})
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].sessionToken).toBeUndefined();
|
||||
});
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to become with revocable session token', done => {
|
||||
const user = Parse.Object.fromJSON({
|
||||
className: '_User',
|
||||
objectId: '1234567890',
|
||||
sessionToken: sessionToken
|
||||
});
|
||||
user._upgradeToRevocableSession().then((res) => {
|
||||
expect(res.getSessionToken().indexOf('r:')).toBe(0);
|
||||
return Parse.User.logOut().then(() => {
|
||||
return Parse.User.become(res.getSessionToken())
|
||||
}).then((user) => {
|
||||
expect(user.id).toEqual('1234567890');
|
||||
});
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not upgrade bad legacy session token', done => {
|
||||
rp.post({
|
||||
url: Parse.serverURL + '/upgradeToRevocableSession',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Rest-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': 'badSessionToken'
|
||||
},
|
||||
json: true
|
||||
}).then(() => {
|
||||
fail('should not be able to upgrade a bad token');
|
||||
}, (response) => {
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.error).not.toBeUndefined();
|
||||
expect(response.error.code).toBe(Parse.Error.INVALID_SESSION_TOKEN);
|
||||
expect(response.error.error).toEqual('invalid legacy session token');
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not crash without session token #2720', done => {
|
||||
rp.post({
|
||||
url: Parse.serverURL + '/upgradeToRevocableSession',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Rest-API-Key': 'rest'
|
||||
},
|
||||
json: true
|
||||
}).then(() => {
|
||||
fail('should not be able to upgrade a bad token');
|
||||
}, (response) => {
|
||||
expect(response.statusCode).toBe(404);
|
||||
expect(response.error).not.toBeUndefined();
|
||||
expect(response.error.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
||||
expect(response.error.error).toEqual('invalid session');
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
+584
-176
@@ -1,10 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
var Config = require('../src/Config');
|
||||
var Schema = require('../src/Schema');
|
||||
var SchemaController = require('../src/Controllers/SchemaController');
|
||||
var dd = require('deep-diff');
|
||||
|
||||
var config = new Config('test');
|
||||
var config;
|
||||
|
||||
var hasAllPODobject = () => {
|
||||
var obj = new Parse.Object('HasAllPOD');
|
||||
@@ -19,14 +19,18 @@ var hasAllPODobject = () => {
|
||||
return obj;
|
||||
};
|
||||
|
||||
describe('Schema', () => {
|
||||
describe('SchemaController', () => {
|
||||
beforeEach(() => {
|
||||
config = new Config('test');
|
||||
});
|
||||
|
||||
it('can validate one object', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
return schema.validateObject('TestObject', {a: 1, b: 'yo', c: false});
|
||||
}).then((schema) => {
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (error) => {
|
||||
fail(error);
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -34,10 +38,10 @@ describe('Schema', () => {
|
||||
it('can validate one object with dot notation', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
return schema.validateObject('TestObjectWithSubDoc', {x: false, y: 'YY', z: 1, 'aObject.k1': 'newValue'});
|
||||
}).then((schema) => {
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (error) => {
|
||||
fail(error);
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -47,7 +51,22 @@ describe('Schema', () => {
|
||||
return schema.validateObject('Foo', {x: true, y: 'yyy', z: 0});
|
||||
}).then((schema) => {
|
||||
return schema.validateObject('Foo', {x: false, y: 'YY', z: 1});
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can validate Relation object', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
return schema.validateObject('Stuff', {aRelation: {__type:'Relation',className:'Stuff'}});
|
||||
}).then((schema) => {
|
||||
return schema.validateObject('Stuff', {aRelation: {__type:'Pointer',className:'Stuff'}})
|
||||
.then(() => {
|
||||
fail('expected invalidity');
|
||||
done();
|
||||
}, done);
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -84,13 +103,13 @@ describe('Schema', () => {
|
||||
return schema.setPermissions('Stuff', {
|
||||
'find': {}
|
||||
});
|
||||
}).then((schema) => {
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('Stuff');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
}).then(() => {
|
||||
fail('Class permissions should have rejected this query.');
|
||||
done();
|
||||
}, (e) => {
|
||||
}, () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -109,51 +128,52 @@ describe('Schema', () => {
|
||||
return schema.setPermissions('Stuff', {
|
||||
'find': find
|
||||
});
|
||||
}).then((schema) => {
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('Stuff');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (e) => {
|
||||
}, () => {
|
||||
fail('Class permissions should have allowed this query.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('class-level permissions test get', (done) => {
|
||||
var user;
|
||||
var obj;
|
||||
createTestUser().then((u) => {
|
||||
user = u;
|
||||
return config.database.loadSchema();
|
||||
}).then((schema) => {
|
||||
// Just to create a valid class
|
||||
return schema.validateObject('Stuff', {foo: 'bar'});
|
||||
}).then((schema) => {
|
||||
var find = {};
|
||||
var get = {};
|
||||
get[user.id] = true;
|
||||
return schema.setPermissions('Stuff', {
|
||||
'find': find,
|
||||
'get': get
|
||||
});
|
||||
}).then((schema) => {
|
||||
obj = new Parse.Object('Stuff');
|
||||
obj.set('foo', 'bar');
|
||||
return obj.save();
|
||||
}).then((o) => {
|
||||
obj = o;
|
||||
var query = new Parse.Query('Stuff');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
fail('Class permissions should have rejected this query.');
|
||||
done();
|
||||
}, (e) => {
|
||||
var query = new Parse.Query('Stuff');
|
||||
return query.get(obj.id).then((o) => {
|
||||
createTestUser()
|
||||
.then(user => {
|
||||
return config.database.loadSchema()
|
||||
// Create a valid class
|
||||
.then(schema => schema.validateObject('Stuff', {foo: 'bar'}))
|
||||
.then(schema => {
|
||||
var find = {};
|
||||
var get = {};
|
||||
get[user.id] = true;
|
||||
return schema.setPermissions('Stuff', {
|
||||
'create': {'*': true},
|
||||
'find': find,
|
||||
'get': get
|
||||
});
|
||||
}).then(() => {
|
||||
obj = new Parse.Object('Stuff');
|
||||
obj.set('foo', 'bar');
|
||||
return obj.save();
|
||||
}).then((o) => {
|
||||
obj = o;
|
||||
var query = new Parse.Query('Stuff');
|
||||
return query.find();
|
||||
}).then(() => {
|
||||
fail('Class permissions should have rejected this query.');
|
||||
done();
|
||||
}, (e) => {
|
||||
fail('Class permissions should have allowed this get query');
|
||||
}, () => {
|
||||
var query = new Parse.Query('Stuff');
|
||||
return query.get(obj.id).then(() => {
|
||||
done();
|
||||
}, () => {
|
||||
fail('Class permissions should have allowed this get query');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -163,14 +183,26 @@ describe('Schema', () => {
|
||||
.then(schema => schema.addClassIfNotExists('NewClass', {
|
||||
foo: {type: 'String'}
|
||||
}))
|
||||
.then(result => {
|
||||
expect(result).toEqual({
|
||||
_id: 'NewClass',
|
||||
objectId: 'string',
|
||||
updatedAt: 'string',
|
||||
createdAt: 'string',
|
||||
foo: 'string',
|
||||
})
|
||||
.then(actualSchema => {
|
||||
const expectedSchema = {
|
||||
className: 'NewClass',
|
||||
fields: {
|
||||
objectId: { type: 'String' },
|
||||
updatedAt: { type: 'Date' },
|
||||
createdAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
foo: { type: 'String' },
|
||||
},
|
||||
classLevelPermissions: {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
},
|
||||
}
|
||||
expect(dd(actualSchema, expectedSchema)).toEqual(undefined);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -201,15 +233,27 @@ describe('Schema', () => {
|
||||
.then(schema => {
|
||||
var p1 = schema.addClassIfNotExists('NewClass', {foo: {type: 'String'}});
|
||||
var p2 = schema.addClassIfNotExists('NewClass', {foo: {type: 'String'}});
|
||||
Promise.race([p1, p2]) //Use race because we expect the first completed promise to be the successful one
|
||||
.then(response => {
|
||||
expect(response).toEqual({
|
||||
_id: 'NewClass',
|
||||
objectId: 'string',
|
||||
updatedAt: 'string',
|
||||
createdAt: 'string',
|
||||
foo: 'string',
|
||||
});
|
||||
Promise.race([p1, p2])
|
||||
.then(actualSchema => {
|
||||
const expectedSchema = {
|
||||
className: 'NewClass',
|
||||
fields: {
|
||||
objectId: { type: 'String' },
|
||||
updatedAt: { type: 'Date' },
|
||||
createdAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
foo: { type: 'String' },
|
||||
},
|
||||
classLevelPermissions: {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
},
|
||||
}
|
||||
expect(dd(actualSchema, expectedSchema)).toEqual(undefined);
|
||||
});
|
||||
Promise.all([p1,p2])
|
||||
.catch(error => {
|
||||
@@ -373,23 +417,35 @@ describe('Schema', () => {
|
||||
aPointer: {type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet'},
|
||||
aRelation: {type: 'Relation', targetClass: 'NewClass'},
|
||||
}))
|
||||
.then(mongoObj => {
|
||||
expect(mongoObj).toEqual({
|
||||
_id: 'NewClass',
|
||||
objectId: 'string',
|
||||
createdAt: 'string',
|
||||
updatedAt: 'string',
|
||||
aNumber: 'number',
|
||||
aString: 'string',
|
||||
aBool: 'boolean',
|
||||
aDate: 'date',
|
||||
aObject: 'object',
|
||||
aArray: 'array',
|
||||
aGeoPoint: 'geopoint',
|
||||
aFile: 'file',
|
||||
aPointer: '*ThisClassDoesNotExistYet',
|
||||
aRelation: 'relation<NewClass>',
|
||||
});
|
||||
.then(actualSchema => {
|
||||
const expectedSchema = {
|
||||
className: 'NewClass',
|
||||
fields: {
|
||||
objectId: { type: 'String' },
|
||||
updatedAt: { type: 'Date' },
|
||||
createdAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
aString: { type: 'String' },
|
||||
aNumber: { type: 'Number' },
|
||||
aBool: { type: 'Boolean' },
|
||||
aDate: { type: 'Date' },
|
||||
aObject: { type: 'Object' },
|
||||
aArray: { type: 'Array' },
|
||||
aGeoPoint: { type: 'GeoPoint' },
|
||||
aFile: { type: 'File' },
|
||||
aPointer: { type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet' },
|
||||
aRelation: { type: 'Relation', targetClass: 'NewClass' },
|
||||
},
|
||||
classLevelPermissions: {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
},
|
||||
}
|
||||
expect(dd(actualSchema, expectedSchema)).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -399,40 +455,69 @@ describe('Schema', () => {
|
||||
.then(schema => schema.addClassIfNotExists('_Installation', {
|
||||
foo: {type: 'Number'},
|
||||
}))
|
||||
.then(mongoObj => {
|
||||
expect(mongoObj).toEqual({
|
||||
_id: '_Installation',
|
||||
createdAt: 'string',
|
||||
updatedAt: 'string',
|
||||
objectId: 'string',
|
||||
foo: 'number',
|
||||
installationId: 'string',
|
||||
deviceToken: 'string',
|
||||
channels: 'array',
|
||||
deviceType: 'string',
|
||||
pushType: 'string',
|
||||
GCMSenderId: 'string',
|
||||
timeZone: 'string',
|
||||
localeIdentifier: 'string',
|
||||
badge: 'number',
|
||||
});
|
||||
.then(actualSchema => {
|
||||
const expectedSchema = {
|
||||
className: '_Installation',
|
||||
fields: {
|
||||
objectId: { type: 'String' },
|
||||
updatedAt: { type: 'Date' },
|
||||
createdAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
foo: { type: 'Number' },
|
||||
installationId: { type: 'String' },
|
||||
deviceToken: { type: 'String' },
|
||||
channels: { type: 'Array' },
|
||||
deviceType: { type: 'String' },
|
||||
pushType: { type: 'String' },
|
||||
GCMSenderId: { type: 'String' },
|
||||
timeZone: { type: 'String' },
|
||||
localeIdentifier: { type: 'String' },
|
||||
badge: { type: 'Number' },
|
||||
appVersion: { type: 'String' },
|
||||
appName: { type: 'String' },
|
||||
appIdentifier: { type: 'String' },
|
||||
parseVersion: { type: 'String' },
|
||||
},
|
||||
classLevelPermissions: {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
},
|
||||
}
|
||||
expect(dd(actualSchema, expectedSchema)).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('creates non-custom classes which include relation field', done => {
|
||||
config.database.loadSchema()
|
||||
.then(schema => schema.addClassIfNotExists('_Role', {}))
|
||||
.then(mongoObj => {
|
||||
expect(mongoObj).toEqual({
|
||||
_id: '_Role',
|
||||
createdAt: 'string',
|
||||
updatedAt: 'string',
|
||||
objectId: 'string',
|
||||
name: 'string',
|
||||
users: 'relation<_User>',
|
||||
roles: 'relation<_Role>',
|
||||
});
|
||||
//as `_Role` is always created by default, we only get it here
|
||||
.then(schema => schema.getOneSchema('_Role'))
|
||||
.then(actualSchema => {
|
||||
const expectedSchema = {
|
||||
className: '_Role',
|
||||
fields: {
|
||||
objectId: { type: 'String' },
|
||||
updatedAt: { type: 'Date' },
|
||||
createdAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
name: { type: 'String' },
|
||||
users: { type: 'Relation', targetClass: '_User' },
|
||||
roles: { type: 'Relation', targetClass: '_Role' },
|
||||
},
|
||||
classLevelPermissions: {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
},
|
||||
};
|
||||
expect(dd(actualSchema, expectedSchema)).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -440,19 +525,31 @@ describe('Schema', () => {
|
||||
it('creates non-custom classes which include pointer field', done => {
|
||||
config.database.loadSchema()
|
||||
.then(schema => schema.addClassIfNotExists('_Session', {}))
|
||||
.then(mongoObj => {
|
||||
expect(mongoObj).toEqual({
|
||||
_id: '_Session',
|
||||
createdAt: 'string',
|
||||
updatedAt: 'string',
|
||||
objectId: 'string',
|
||||
restricted: 'boolean',
|
||||
user: '*_User',
|
||||
installationId: 'string',
|
||||
sessionToken: 'string',
|
||||
expiresAt: 'date',
|
||||
createdWith: 'object'
|
||||
});
|
||||
.then(actualSchema => {
|
||||
const expectedSchema = {
|
||||
className: '_Session',
|
||||
fields: {
|
||||
objectId: { type: 'String' },
|
||||
updatedAt: { type: 'Date' },
|
||||
createdAt: { type: 'Date' },
|
||||
restricted: { type: 'Boolean' },
|
||||
user: { type: 'Pointer', targetClass: '_User' },
|
||||
installationId: { type: 'String' },
|
||||
sessionToken: { type: 'String' },
|
||||
expiresAt: { type: 'Date' },
|
||||
createdWith: { type: 'Object' },
|
||||
ACL: { type: 'ACL' },
|
||||
},
|
||||
classLevelPermissions: {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
},
|
||||
};
|
||||
expect(dd(actualSchema, expectedSchema)).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -491,10 +588,10 @@ describe('Schema', () => {
|
||||
})
|
||||
.catch(error => {
|
||||
fail('Couldn\'t create class');
|
||||
fail(error);
|
||||
jfail(error);
|
||||
});
|
||||
})
|
||||
.catch(error => fail('Couldn\'t load schema'));
|
||||
.catch(() => fail('Couldn\'t load schema'));
|
||||
});
|
||||
|
||||
it('refuses to delete fields from invalid class names', done => {
|
||||
@@ -572,7 +669,7 @@ describe('Schema', () => {
|
||||
}
|
||||
done();
|
||||
}, error => {
|
||||
fail(error);
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -583,27 +680,47 @@ describe('Schema', () => {
|
||||
schema.addClassIfNotExists('NewClass', {
|
||||
relationField: {type: 'Relation', targetClass: '_User'}
|
||||
})
|
||||
.then(mongoObj => {
|
||||
expect(mongoObj).toEqual({
|
||||
_id: 'NewClass',
|
||||
objectId: 'string',
|
||||
updatedAt: 'string',
|
||||
createdAt: 'string',
|
||||
relationField: 'relation<_User>',
|
||||
});
|
||||
.then(actualSchema => {
|
||||
const expectedSchema = {
|
||||
className: 'NewClass',
|
||||
fields: {
|
||||
objectId: { type: 'String' },
|
||||
updatedAt: { type: 'Date' },
|
||||
createdAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
relationField: { type: 'Relation', targetClass: '_User' },
|
||||
},
|
||||
classLevelPermissions: {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
},
|
||||
};
|
||||
expect(dd(actualSchema, expectedSchema)).toEqual(undefined);
|
||||
})
|
||||
.then(() => config.database.collectionExists('_Join:relationField:NewClass'))
|
||||
.then(exist => {
|
||||
expect(exist).toEqual(false);
|
||||
on_db('postgres', () => {
|
||||
// We create the table when creating the column
|
||||
expect(exist).toEqual(true);
|
||||
}, () => {
|
||||
expect(exist).toEqual(false);
|
||||
});
|
||||
|
||||
})
|
||||
.then(() => schema.deleteField('relationField', 'NewClass', config.database))
|
||||
.then(() => schema.reloadData())
|
||||
.then(() => {
|
||||
expect(schema['data']['NewClass']).toEqual({
|
||||
objectId: 'string',
|
||||
updatedAt: 'string',
|
||||
createdAt: 'string'
|
||||
});
|
||||
const expectedSchema = {
|
||||
objectId: { type: 'String' },
|
||||
updatedAt: { type: 'Date' },
|
||||
createdAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
};
|
||||
expect(dd(schema.data.NewClass, expectedSchema)).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -613,7 +730,7 @@ describe('Schema', () => {
|
||||
Parse.Object.disableSingleInstance();
|
||||
var obj1 = hasAllPODobject();
|
||||
var obj2 = hasAllPODobject();
|
||||
var p = Parse.Object.saveAll([obj1, obj2])
|
||||
Parse.Object.saveAll([obj1, obj2])
|
||||
.then(() => config.database.loadSchema())
|
||||
.then(schema => schema.deleteField('aString', 'HasAllPOD', config.database))
|
||||
.then(() => new Parse.Query('HasAllPOD').get(obj1.id))
|
||||
@@ -630,6 +747,10 @@ describe('Schema', () => {
|
||||
done();
|
||||
Parse.Object.enableSingleInstance();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
jfail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -660,9 +781,9 @@ describe('Schema', () => {
|
||||
});
|
||||
|
||||
it('can merge schemas', done => {
|
||||
expect(Schema.buildMergedSchemaObject({
|
||||
expect(SchemaController.buildMergedSchemaObject({
|
||||
_id: 'SomeClass',
|
||||
someType: 'number'
|
||||
someType: { type: 'Number' }
|
||||
}, {
|
||||
newType: {type: 'Number'}
|
||||
})).toEqual({
|
||||
@@ -673,10 +794,10 @@ describe('Schema', () => {
|
||||
});
|
||||
|
||||
it('can merge deletions', done => {
|
||||
expect(Schema.buildMergedSchemaObject({
|
||||
expect(SchemaController.buildMergedSchemaObject({
|
||||
_id: 'SomeClass',
|
||||
someType: 'number',
|
||||
outDatedType: 'string',
|
||||
someType: { type: 'Number' },
|
||||
outDatedType: { type: 'String' },
|
||||
},{
|
||||
newType: {type: 'GeoPoint'},
|
||||
outDatedType: {__op: 'Delete'},
|
||||
@@ -688,48 +809,335 @@ describe('Schema', () => {
|
||||
});
|
||||
|
||||
it('ignore default field when merge with system class', done => {
|
||||
expect(Schema.buildMergedSchemaObject({
|
||||
expect(SchemaController.buildMergedSchemaObject({
|
||||
_id: '_User',
|
||||
username: 'string',
|
||||
password: 'string',
|
||||
authData: 'object',
|
||||
email: 'string',
|
||||
emailVerified: 'boolean'
|
||||
username: { type: 'String' },
|
||||
password: { type: 'String' },
|
||||
email: { type: 'String' },
|
||||
emailVerified: { type: 'Boolean' },
|
||||
},{
|
||||
authData: {type: 'string'},
|
||||
customField: {type: 'string'},
|
||||
emailVerified: { type: 'String' },
|
||||
customField: { type: 'String' },
|
||||
})).toEqual({
|
||||
customField: {type: 'string'}
|
||||
customField: { type: 'String' }
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('handles legacy _client_permissions keys without crashing', done => {
|
||||
Schema.mongoSchemaToSchemaAPIResponse({
|
||||
"_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},
|
||||
it('yields a proper schema mismatch error (#2661)', done => {
|
||||
const anObject = new Parse.Object('AnObject');
|
||||
const anotherObject = new Parse.Object('AnotherObject');
|
||||
const someObject = new Parse.Object('SomeObject');
|
||||
Parse.Object.saveAll([anObject, anotherObject, someObject]).then(() => {
|
||||
anObject.set('pointer', anotherObject);
|
||||
return anObject.save();
|
||||
}).then(() => {
|
||||
anObject.set('pointer', someObject);
|
||||
return anObject.save();
|
||||
}).then(() => {
|
||||
fail('shoud not save correctly');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err instanceof Parse.Error).toBeTruthy();
|
||||
expect(err.message).toEqual('schema mismatch for AnObject.pointer; expected Pointer<AnotherObject> but got Pointer<SomeObject>')
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('yields a proper schema mismatch error bis (#2661)', done => {
|
||||
const anObject = new Parse.Object('AnObject');
|
||||
const someObject = new Parse.Object('SomeObject');
|
||||
Parse.Object.saveAll([anObject, someObject]).then(() => {
|
||||
anObject.set('number', 1);
|
||||
return anObject.save();
|
||||
}).then(() => {
|
||||
anObject.set('number', someObject);
|
||||
return anObject.save();
|
||||
}).then(() => {
|
||||
fail('shoud not save correctly');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err instanceof Parse.Error).toBeTruthy();
|
||||
expect(err.message).toEqual('schema mismatch for AnObject.number; expected Number but got Pointer<SomeObject>')
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('yields a proper schema mismatch error ter (#2661)', done => {
|
||||
const anObject = new Parse.Object('AnObject');
|
||||
const someObject = new Parse.Object('SomeObject');
|
||||
Parse.Object.saveAll([anObject, someObject]).then(() => {
|
||||
anObject.set('pointer', someObject);
|
||||
return anObject.save();
|
||||
}).then(() => {
|
||||
anObject.set('pointer', 1);
|
||||
return anObject.save();
|
||||
}).then(() => {
|
||||
fail('shoud not save correctly');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err instanceof Parse.Error).toBeTruthy();
|
||||
expect(err.message).toEqual('schema mismatch for AnObject.pointer; expected Pointer<SomeObject> but got Number')
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('properly handles volatile _Schemas', done => {
|
||||
function validateSchemaStructure(schema) {
|
||||
expect(schema.hasOwnProperty('className')).toBe(true);
|
||||
expect(schema.hasOwnProperty('fields')).toBe(true);
|
||||
expect(schema.hasOwnProperty('classLevelPermissions')).toBe(true);
|
||||
}
|
||||
function validateSchemaDataStructure(schemaData) {
|
||||
Object.keys(schemaData).forEach(className => {
|
||||
const schema = schemaData[className];
|
||||
// Hooks has className...
|
||||
if (className != '_Hooks') {
|
||||
expect(schema.hasOwnProperty('className')).toBe(false);
|
||||
}
|
||||
},
|
||||
"installationId":"string",
|
||||
"deviceToken":"string",
|
||||
"deviceType":"string",
|
||||
"channels":"array",
|
||||
"user":"*_User",
|
||||
expect(schema.hasOwnProperty('fields')).toBe(false);
|
||||
expect(schema.hasOwnProperty('classLevelPermissions')).toBe(false);
|
||||
});
|
||||
}
|
||||
let schema;
|
||||
config.database.loadSchema().then(s => {
|
||||
schema = s;
|
||||
return schema.getOneSchema('_User', false);
|
||||
}).then(userSchema => {
|
||||
validateSchemaStructure(userSchema);
|
||||
validateSchemaDataStructure(schema.data);
|
||||
return schema.getOneSchema('_PushStatus', true);
|
||||
}).then(pushStatusSchema => {
|
||||
validateSchemaStructure(pushStatusSchema);
|
||||
validateSchemaDataStructure(schema.data);
|
||||
done();
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Class Level Permissions for requiredAuth', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config('test');
|
||||
});
|
||||
|
||||
function createUser() {
|
||||
const user = new Parse.User();
|
||||
user.set("username", "hello");
|
||||
user.set("password", "world");
|
||||
return user.signUp(null);
|
||||
}
|
||||
|
||||
it('required auth test find', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Just to create a valid class
|
||||
return schema.validateObject('Stuff', {foo: 'bar'});
|
||||
}).then((schema) => {
|
||||
return schema.setPermissions('Stuff', {
|
||||
'find': {
|
||||
'requiresAuthentication': true
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('Stuff');
|
||||
return query.find();
|
||||
}).then(() => {
|
||||
fail('Class permissions should have rejected this query.');
|
||||
done();
|
||||
}, (e) => {
|
||||
expect(e.message).toEqual('Permission denied, user needs to be authenticated.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('required auth test find authenticated', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Just to create a valid class
|
||||
return schema.validateObject('Stuff', {foo: 'bar'});
|
||||
}).then((schema) => {
|
||||
return schema.setPermissions('Stuff', {
|
||||
'find': {
|
||||
'requiresAuthentication': true
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
return createUser();
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('Stuff');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(0);
|
||||
done();
|
||||
}, (e) => {
|
||||
console.error(e);
|
||||
fail("Should not have failed");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('required auth should allow create authenticated', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Just to create a valid class
|
||||
return schema.validateObject('Stuff', {foo: 'bar'});
|
||||
}).then((schema) => {
|
||||
return schema.setPermissions('Stuff', {
|
||||
'create': {
|
||||
'requiresAuthentication': true
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
return createUser();
|
||||
}).then(() => {
|
||||
const stuff = new Parse.Object('Stuff');
|
||||
stuff.set('foo', 'bar');
|
||||
return stuff.save();
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (e) => {
|
||||
console.error(e);
|
||||
fail("Should not have failed");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('required auth should reject create when not authenticated', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Just to create a valid class
|
||||
return schema.validateObject('Stuff', {foo: 'bar'});
|
||||
}).then((schema) => {
|
||||
return schema.setPermissions('Stuff', {
|
||||
'create': {
|
||||
'requiresAuthentication': true
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
const stuff = new Parse.Object('Stuff');
|
||||
stuff.set('foo', 'bar');
|
||||
return stuff.save();
|
||||
}).then(() => {
|
||||
fail('Class permissions should have rejected this query.');
|
||||
done();
|
||||
}, (e) => {
|
||||
expect(e.message).toEqual('Permission denied, user needs to be authenticated.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('required auth test create/get/update/delete authenticated', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Just to create a valid class
|
||||
return schema.validateObject('Stuff', {foo: 'bar'});
|
||||
}).then((schema) => {
|
||||
return schema.setPermissions('Stuff', {
|
||||
'create': {
|
||||
'requiresAuthentication': true
|
||||
},
|
||||
'get': {
|
||||
'requiresAuthentication': true
|
||||
},
|
||||
'delete': {
|
||||
'requiresAuthentication': true
|
||||
},
|
||||
'update': {
|
||||
'requiresAuthentication': true
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
return createUser();
|
||||
}).then(() => {
|
||||
const stuff = new Parse.Object('Stuff');
|
||||
stuff.set('foo', 'bar');
|
||||
return stuff.save().then(() => {
|
||||
const query = new Parse.Query('Stuff');
|
||||
return query.get(stuff.id);
|
||||
});
|
||||
}).then((gotStuff) => {
|
||||
return gotStuff.save({'foo': 'baz'}).then(() => {
|
||||
return gotStuff.destroy();
|
||||
})
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (e) => {
|
||||
console.error(e);
|
||||
fail("Should not have failed");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('required auth test create/get/update/delete not authenitcated', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Just to create a valid class
|
||||
return schema.validateObject('Stuff', {foo: 'bar'});
|
||||
}).then((schema) => {
|
||||
return schema.setPermissions('Stuff', {
|
||||
'get': {
|
||||
'requiresAuthentication': true
|
||||
},
|
||||
'delete': {
|
||||
'requiresAuthentication': true
|
||||
},
|
||||
'update': {
|
||||
'requiresAuthentication': true
|
||||
},
|
||||
'create': {
|
||||
'*': true
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
const stuff = new Parse.Object('Stuff');
|
||||
stuff.set('foo', 'bar');
|
||||
return stuff.save().then(() => {
|
||||
const query = new Parse.Query('Stuff');
|
||||
return query.get(stuff.id);
|
||||
});
|
||||
}).then(() => {
|
||||
fail("Should not succeed!");
|
||||
done();
|
||||
}, (e) => {
|
||||
expect(e.message).toEqual('Permission denied, user needs to be authenticated.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('required auth test create/get/update/delete not authenitcated', (done) => {
|
||||
config.database.loadSchema().then((schema) => {
|
||||
// Just to create a valid class
|
||||
return schema.validateObject('Stuff', {foo: 'bar'});
|
||||
}).then((schema) => {
|
||||
return schema.setPermissions('Stuff', {
|
||||
'find': {
|
||||
'requiresAuthentication': true
|
||||
},
|
||||
'delete': {
|
||||
'requiresAuthentication': true
|
||||
},
|
||||
'update': {
|
||||
'requiresAuthentication': true
|
||||
},
|
||||
'create': {
|
||||
'*': true
|
||||
},
|
||||
'get': {
|
||||
'*': true
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
const stuff = new Parse.Object('Stuff');
|
||||
stuff.set('foo', 'bar');
|
||||
return stuff.save().then(() => {
|
||||
const query = new Parse.Query('Stuff');
|
||||
return query.get(stuff.id);
|
||||
})
|
||||
}).then((result) => {
|
||||
expect(result.get('foo')).toEqual('bar');
|
||||
const query = new Parse.Query('Stuff');
|
||||
return query.find();
|
||||
}).then(() => {
|
||||
fail("Should not succeed!");
|
||||
done();
|
||||
}, (e) => {
|
||||
expect(e.message).toEqual('Permission denied, user needs to be authenticated.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
var CacheController = require('../src/Controllers/CacheController.js').default;
|
||||
var InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').default;
|
||||
var SchemaCache = require('../src/Controllers/SchemaCache').default;
|
||||
|
||||
describe('SchemaCache', () => {
|
||||
let cacheController;
|
||||
|
||||
beforeEach(() => {
|
||||
const cacheAdapter = new InMemoryCacheAdapter({});
|
||||
cacheController = new CacheController(cacheAdapter, 'appId');
|
||||
});
|
||||
|
||||
it('can retrieve a single schema after all schemas stored', (done) => {
|
||||
const schemaCache = new SchemaCache(cacheController);
|
||||
const allSchemas = [{
|
||||
className: 'Class1'
|
||||
}, {
|
||||
className: 'Class2'
|
||||
}];
|
||||
schemaCache.setAllClasses(allSchemas).then(() => {
|
||||
return schemaCache.getOneSchema('Class2');
|
||||
}).then((schema) => {
|
||||
expect(schema).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not return all schemas after a single schema is stored', (done) => {
|
||||
const schemaCache = new SchemaCache(cacheController);
|
||||
const schema = {
|
||||
className: 'Class1'
|
||||
};
|
||||
schemaCache.setOneSchema(schema.className, schema).then(() => {
|
||||
return schemaCache.getAllClasses();
|
||||
}).then((allSchemas) => {
|
||||
expect(allSchemas).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('doesn\'t persist cached data by default', (done) => {
|
||||
const schemaCache = new SchemaCache(cacheController);
|
||||
const schema = {
|
||||
className: 'Class1'
|
||||
};
|
||||
schemaCache.setOneSchema(schema.className, schema).then(() => {
|
||||
const anotherSchemaCache = new SchemaCache(cacheController);
|
||||
return anotherSchemaCache.getOneSchema(schema.className).then((schema) => {
|
||||
expect(schema).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can persist cached data', (done) => {
|
||||
const schemaCache = new SchemaCache(cacheController, 5000, true);
|
||||
const schema = {
|
||||
className: 'Class1'
|
||||
};
|
||||
schemaCache.setOneSchema(schema.className, schema).then(() => {
|
||||
const anotherSchemaCache = new SchemaCache(cacheController, 5000, true);
|
||||
return anotherSchemaCache.getOneSchema(schema.className).then((schema) => {
|
||||
expect(schema).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,20 +4,21 @@ describe('SessionTokenCache', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
var Parse = require('parse/node');
|
||||
// Mock parse
|
||||
var mockUser = {
|
||||
become: jasmine.createSpy('become').and.returnValue(Parse.Promise.as({
|
||||
id: 'userId'
|
||||
}))
|
||||
}
|
||||
jasmine.mockLibrary('parse/node', 'User', mockUser);
|
||||
|
||||
spyOn(Parse, "Query").and.returnValue({
|
||||
first: jasmine.createSpy("first").and.returnValue(Parse.Promise.as(new Parse.Object("_Session", {
|
||||
user: new Parse.User({id:"userId"})
|
||||
}))),
|
||||
equalTo: function(){}
|
||||
})
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('can get undefined userId', function(done) {
|
||||
var sessionTokenCache = new SessionTokenCache();
|
||||
|
||||
sessionTokenCache.getUserId(undefined).then((userIdFromCache) => {
|
||||
sessionTokenCache.getUserId(undefined).then(() => {
|
||||
}, (error) => {
|
||||
expect(error).not.toBeNull();
|
||||
done();
|
||||
@@ -46,7 +47,4 @@ describe('SessionTokenCache', function() {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.restoreLibrary('parse/node', 'User');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
var Subscription = require('../src/LiveQuery/Subscription').Subscription;
|
||||
|
||||
let logger;
|
||||
describe('Subscription', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
var mockError = jasmine.createSpy('error');
|
||||
jasmine.mockLibrary('../src/LiveQuery/PLog', 'error', mockError);
|
||||
logger = require('../src/logger').logger;
|
||||
spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
it('can be initialized', function() {
|
||||
@@ -62,8 +62,7 @@ describe('Subscription', function() {
|
||||
var subscription = new Subscription('className', { key : 'value' }, 'hash');
|
||||
subscription.deleteClientSubscription(1, 1);
|
||||
|
||||
var PLog =require('../src/LiveQuery/PLog');
|
||||
expect(PLog.error).toHaveBeenCalled();
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can delete nonexistent request for one client', function() {
|
||||
@@ -71,8 +70,7 @@ describe('Subscription', function() {
|
||||
subscription.addClientSubscription(1, 1);
|
||||
subscription.deleteClientSubscription(1, 2);
|
||||
|
||||
var PLog =require('../src/LiveQuery/PLog');
|
||||
expect(PLog.error).toHaveBeenCalled();
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
expect(subscription.clientRequestIds.size).toBe(1);
|
||||
expect(subscription.clientRequestIds.get(1)).toEqual([1]);
|
||||
});
|
||||
@@ -83,8 +81,7 @@ describe('Subscription', function() {
|
||||
subscription.addClientSubscription(1, 2);
|
||||
subscription.deleteClientSubscription(1, 2);
|
||||
|
||||
var PLog =require('../src/LiveQuery/PLog');
|
||||
expect(PLog.error).not.toHaveBeenCalled();
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
expect(subscription.clientRequestIds.size).toBe(1);
|
||||
expect(subscription.clientRequestIds.get(1)).toEqual([1]);
|
||||
});
|
||||
@@ -96,8 +93,7 @@ describe('Subscription', function() {
|
||||
subscription.deleteClientSubscription(1, 1);
|
||||
subscription.deleteClientSubscription(1, 2);
|
||||
|
||||
var PLog =require('../src/LiveQuery/PLog');
|
||||
expect(PLog.error).not.toHaveBeenCalled();
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
expect(subscription.clientRequestIds.size).toBe(0);
|
||||
});
|
||||
|
||||
@@ -111,13 +107,8 @@ describe('Subscription', function() {
|
||||
subscription.deleteClientSubscription(2, 1);
|
||||
subscription.deleteClientSubscription(2, 2);
|
||||
|
||||
var PLog =require('../src/LiveQuery/PLog');
|
||||
expect(PLog.error).not.toHaveBeenCalled();
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
expect(subscription.clientRequestIds.size).toBe(1);
|
||||
expect(subscription.clientRequestIds.get(1)).toEqual([1]);
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
jasmine.restoreLibrary('../src/LiveQuery/PLog', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
const twitter = require('../src/Adapters/Auth/twitter');
|
||||
|
||||
describe('Twitter Auth', () => {
|
||||
it('should use the proper configuration', () => {
|
||||
// Multiple options, consumer_key found
|
||||
expect(twitter.handleMultipleConfigurations({
|
||||
consumer_key: 'hello',
|
||||
}, [{
|
||||
consumer_key: 'hello'
|
||||
}, {
|
||||
consumer_key: 'world'
|
||||
}]).consumer_key).toEqual('hello')
|
||||
|
||||
// Multiple options, consumer_key not found
|
||||
expect(function(){
|
||||
twitter.handleMultipleConfigurations({
|
||||
consumer_key: 'some',
|
||||
}, [{
|
||||
consumer_key: 'hello'
|
||||
}, {
|
||||
consumer_key: 'world'
|
||||
}]);
|
||||
}).toThrow();
|
||||
|
||||
// Multiple options, consumer_key not found
|
||||
expect(function(){
|
||||
twitter.handleMultipleConfigurations({
|
||||
auth_token: 'token',
|
||||
}, [{
|
||||
consumer_key: 'hello'
|
||||
}, {
|
||||
consumer_key: 'world'
|
||||
}]);
|
||||
}).toThrow();
|
||||
|
||||
// Single configuration and consumer_key set
|
||||
expect(twitter.handleMultipleConfigurations({
|
||||
consumer_key: 'hello',
|
||||
}, {
|
||||
consumer_key: 'hello'
|
||||
}).consumer_key).toEqual('hello');
|
||||
|
||||
// General case, only 1 config, no consumer_key set
|
||||
expect(twitter.handleMultipleConfigurations({
|
||||
auth_token: 'token',
|
||||
}, {
|
||||
consumer_key: 'hello'
|
||||
}).consumer_key).toEqual('hello');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
const Parse = require("parse/node");
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe('Uniqueness', function() {
|
||||
it('fail when create duplicate value in unique field', done => {
|
||||
const obj = new Parse.Object('UniqueField');
|
||||
obj.set('unique', 'value');
|
||||
obj.save().then(() => {
|
||||
expect(obj.id).not.toBeUndefined();
|
||||
const config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniqueField', { fields: { unique: { __type: 'String' } } }, ['unique'])
|
||||
})
|
||||
.then(() => {
|
||||
const obj = new Parse.Object('UniqueField');
|
||||
obj.set('unique', 'value');
|
||||
return obj.save()
|
||||
}).then(() => {
|
||||
fail('Saving duplicate field should have failed');
|
||||
done();
|
||||
}, error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('unique indexing works on pointer fields', done => {
|
||||
const obj = new Parse.Object('UniquePointer');
|
||||
obj.save({ string: 'who cares' })
|
||||
.then(() => obj.save({ ptr: obj }))
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniquePointer', { fields: {
|
||||
string: { __type: 'String' },
|
||||
ptr: { __type: 'Pointer', targetClass: 'UniquePointer' }
|
||||
} }, ['ptr']);
|
||||
})
|
||||
.then(() => {
|
||||
const newObj = new Parse.Object('UniquePointer')
|
||||
newObj.set('ptr', obj)
|
||||
return newObj.save()
|
||||
})
|
||||
.then(() => {
|
||||
fail('save should have failed due to duplicate value');
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails when attempting to ensure uniqueness of fields that are not currently unique', done => {
|
||||
const o1 = new Parse.Object('UniqueFail');
|
||||
o1.set('key', 'val');
|
||||
const o2 = new Parse.Object('UniqueFail');
|
||||
o2.set('key', 'val');
|
||||
Parse.Object.saveAll([o1, o2])
|
||||
.then(() => {
|
||||
const config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniqueFail', { fields: { key: { __type: 'String' } } }, ['key']);
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])('can do compound uniqueness', done => {
|
||||
const config = new Config('test');
|
||||
config.database.adapter.ensureUniqueness('CompoundUnique', { fields: { k1: { __type: 'String' }, k2: { __type: 'String' } } }, ['k1', 'k2'])
|
||||
.then(() => {
|
||||
const o1 = new Parse.Object('CompoundUnique');
|
||||
o1.set('k1', 'v1');
|
||||
o1.set('k2', 'v2');
|
||||
return o1.save();
|
||||
})
|
||||
.then(() => {
|
||||
const o2 = new Parse.Object('CompoundUnique');
|
||||
o2.set('k1', 'v1');
|
||||
o2.set('k2', 'not a dupe');
|
||||
return o2.save();
|
||||
})
|
||||
.then(() => {
|
||||
const o3 = new Parse.Object('CompoundUnique');
|
||||
o3.set('k1', 'not a dupe');
|
||||
o3.set('k2', 'v2');
|
||||
return o3.save();
|
||||
})
|
||||
.then(() => {
|
||||
const o4 = new Parse.Object('CompoundUnique');
|
||||
o4.set('k1', 'v1');
|
||||
o4.set('k2', 'v2');
|
||||
return o4.save();
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
var UserController = require('../src/Controllers/UserController').UserController;
|
||||
var emailAdapter = require('./MockEmailAdapter')
|
||||
var AppCache = require('../src/cache').AppCache;
|
||||
|
||||
describe('UserController', () => {
|
||||
var user = {
|
||||
_email_verify_token: 'testToken',
|
||||
username: 'testUser',
|
||||
email: 'test@example.com'
|
||||
}
|
||||
|
||||
describe('sendVerificationEmail', () => {
|
||||
describe('parseFrameURL not provided', () => {
|
||||
it('uses publicServerURL', (done) => {
|
||||
|
||||
AppCache.put(defaultConfiguration.appId, Object.assign({}, defaultConfiguration, {
|
||||
publicServerURL: 'http://www.example.com',
|
||||
customPages: {
|
||||
parseFrameURL: undefined
|
||||
}
|
||||
}))
|
||||
|
||||
emailAdapter.sendVerificationEmail = (options) => {
|
||||
expect(options.link).toEqual('http://www.example.com/apps/test/verify_email?token=testToken&username=testUser')
|
||||
done()
|
||||
}
|
||||
|
||||
var userController = new UserController(emailAdapter, 'test', {
|
||||
verifyUserEmails: true
|
||||
})
|
||||
|
||||
userController.sendVerificationEmail(user)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseFrameURL provided', () => {
|
||||
it('uses parseFrameURL and includes the destination in the link parameter', (done) => {
|
||||
|
||||
AppCache.put(defaultConfiguration.appId, Object.assign({}, defaultConfiguration, {
|
||||
publicServerURL: 'http://www.example.com',
|
||||
customPages: {
|
||||
parseFrameURL: 'http://someother.example.com/handle-parse-iframe'
|
||||
}
|
||||
}))
|
||||
|
||||
emailAdapter.sendVerificationEmail = (options) => {
|
||||
expect(options.link).toEqual('http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=testToken&username=testUser')
|
||||
done()
|
||||
}
|
||||
|
||||
var userController = new UserController(emailAdapter, 'test', {
|
||||
verifyUserEmails: true
|
||||
})
|
||||
|
||||
userController.sendVerificationEmail(user)
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,503 @@
|
||||
'use strict';
|
||||
|
||||
const Parse = require('parse/node');
|
||||
const request = require('request-promise');
|
||||
|
||||
// const Config = require('../src/Config');
|
||||
|
||||
const EMAIL = 'foo@bar.com';
|
||||
const ZIP = '10001';
|
||||
const SSN = '999-99-9999';
|
||||
|
||||
describe('Personally Identifiable Information', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(done => {
|
||||
return Parse.User.signUp('tester', 'abc')
|
||||
.then(loggedInUser => user = loggedInUser)
|
||||
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
|
||||
.then(() => user
|
||||
.set('email', EMAIL)
|
||||
.set('zip', ZIP)
|
||||
.set('ssn', SSN)
|
||||
.save())
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should be able to get own PII via API with object', (done) => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
|
||||
it('should not be able to get PII via API with object', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
})
|
||||
.fail(e => {
|
||||
done.fail(JSON.stringify(e));
|
||||
})
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to get PII via API with object using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch({ useMasterKey: true }).then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Find', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Find', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Find using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first({ useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Get', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Get', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Get using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id, { useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not get PII via REST', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST with self credentials', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST using master key', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should not get PII via REST by ID', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with self credentials', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with master key', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
describe('with configured sensitive fields', () => {
|
||||
beforeEach((done) => {
|
||||
reconfigureServer({ userSensitiveFields: ['ssn', 'zip'] })
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should be able to get own PII via API with object', (done) => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
}, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should not be able to get PII via API with object', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to get PII via API with object using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch({ useMasterKey: true }).then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Find', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Find', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Find using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first({ useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Get', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Get', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Get using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id, { useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not get PII via REST', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(undefined);
|
||||
expect(fetchedUser.ssn).toBe(undefined);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST with self credentials', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
expect(fetchedUser.ssn).toBe(SSN);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST using master key', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
expect(fetchedUser.ssn).toBe(SSN);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should not get PII via REST by ID', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(undefined);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with self credentials', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with master key', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter;
|
||||
var request = require('request');
|
||||
|
||||
describe('info logs', () => {
|
||||
|
||||
it("Verify INFO logs", (done) => {
|
||||
var winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
winstonLoggerAdapter.log('info', 'testing info logs', () => {
|
||||
winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'info'
|
||||
}, (results) => {
|
||||
if (results.length == 0) {
|
||||
fail('The adapter should return non-empty results');
|
||||
} else {
|
||||
expect(results[0].message).toEqual('testing info logs');
|
||||
}
|
||||
// Check the error log
|
||||
// Regression #2639
|
||||
winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'error'
|
||||
}, (results) => {
|
||||
expect(results.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('error logs', () => {
|
||||
it("Verify ERROR logs", (done) => {
|
||||
var winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
winstonLoggerAdapter.log('error', 'testing error logs', () => {
|
||||
winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'error'
|
||||
}, (results) => {
|
||||
if(results.length == 0) {
|
||||
fail('The adapter should return non-empty results');
|
||||
done();
|
||||
}
|
||||
else {
|
||||
expect(results[0].message).toEqual('testing error logs');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('verbose logs', () => {
|
||||
it("mask sensitive information in _User class", (done) => {
|
||||
reconfigureServer({ verbose: true })
|
||||
.then(() => createTestUser())
|
||||
.then(() => {
|
||||
const winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
return winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'verbose'
|
||||
});
|
||||
}).then((results) => {
|
||||
const logString = JSON.stringify(results);
|
||||
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
|
||||
expect(logString.match(/moon-y/g)).toBe(null);
|
||||
|
||||
var headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/login?username=test&password=moon-y'
|
||||
}, () => {
|
||||
const winstonLoggerAdapter = new WinstonLoggerAdapter();
|
||||
return winstonLoggerAdapter.query({
|
||||
from: new Date(Date.now() - 500),
|
||||
size: 100,
|
||||
level: 'verbose'
|
||||
}).then((results) => {
|
||||
const logString = JSON.stringify(results);
|
||||
expect(logString.match(/\*\*\*\*\*\*\*\*/g).length).not.toBe(0);
|
||||
expect(logString.match(/moon-y/g)).toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch((err) => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
var batch = require('../src/batch');
|
||||
|
||||
const originalURL = '/parse/batch';
|
||||
const serverURL = 'http://localhost:1234/parse';
|
||||
const serverURL1 = 'http://localhost:1234/1';
|
||||
const serverURLNaked = 'http://localhost:1234/';
|
||||
const publicServerURL = 'http://domain.com/parse';
|
||||
const publicServerURLNaked = 'http://domain.com/';
|
||||
|
||||
describe('batch', () => {
|
||||
it('should return the proper url', () => {
|
||||
const internalURL = batch.makeBatchRoutingPathFunction(originalURL)('/parse/classes/Object');
|
||||
|
||||
expect(internalURL).toEqual('/classes/Object');
|
||||
});
|
||||
|
||||
it('should return the proper url same public/local endpoint', () => {
|
||||
const originalURL = '/parse/batch';
|
||||
const internalURL = batch.makeBatchRoutingPathFunction(originalURL, serverURL, publicServerURL)('/parse/classes/Object');
|
||||
|
||||
expect(internalURL).toEqual('/classes/Object');
|
||||
});
|
||||
|
||||
it('should return the proper url with different public/local mount', () => {
|
||||
const originalURL = '/parse/batch';
|
||||
const internalURL = batch.makeBatchRoutingPathFunction(originalURL, serverURL1, publicServerURL)('/parse/classes/Object');
|
||||
|
||||
expect(internalURL).toEqual('/classes/Object');
|
||||
});
|
||||
|
||||
it('should return the proper url with naked public', () => {
|
||||
const originalURL = '/batch';
|
||||
const internalURL = batch.makeBatchRoutingPathFunction(originalURL, serverURL, publicServerURLNaked)('/classes/Object');
|
||||
|
||||
expect(internalURL).toEqual('/classes/Object');
|
||||
});
|
||||
|
||||
it('should return the proper url with naked local', () => {
|
||||
const originalURL = '/parse/batch';
|
||||
const internalURL = batch.makeBatchRoutingPathFunction(originalURL, serverURLNaked, publicServerURL)('/parse/classes/Object');
|
||||
|
||||
expect(internalURL).toEqual('/classes/Object');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
Parse.Cloud.define('cloudCodeInFile', (req, res) => {
|
||||
res.success('It is possible to define cloud code in a file.');
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user