mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-14 05:07:22 +01:00
Compare commits
715 Commits
v5.11.1
...
phillip/co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
192d649ffa | ||
|
|
e7aae55eca | ||
|
|
36b03613e1 | ||
|
|
2de9a16a2c | ||
|
|
895241b7fa | ||
|
|
2535fd7095 | ||
|
|
30f1820a49 | ||
|
|
3bb6c391af | ||
|
|
a0383c785a | ||
|
|
99790c05f4 | ||
|
|
fc5fec9bfe | ||
|
|
9db5d4116d | ||
|
|
6676e94ef6 | ||
|
|
723adceb25 | ||
|
|
440d06da4a | ||
|
|
0ea84668a8 | ||
|
|
5893d8b9bb | ||
|
|
2c799b9c07 | ||
|
|
1550d9b4ee | ||
|
|
ade812b86d | ||
|
|
62e6fbef61 | ||
|
|
67a0f8b65a | ||
|
|
aa432022d3 | ||
|
|
86fb3c1fd1 | ||
|
|
ff2b4add8b | ||
|
|
4ba73dfbec | ||
|
|
e675ea9bd1 | ||
|
|
9c27d86ced | ||
|
|
58ee81adfc | ||
|
|
32c9904a6e | ||
|
|
b86e0a1549 | ||
|
|
154ac9bb38 | ||
|
|
a97060445a | ||
|
|
26b59de1de | ||
|
|
21c8b00ef6 | ||
|
|
c25b7293bb | ||
|
|
15e078cb34 | ||
|
|
f7bb17202b | ||
|
|
213b7696c5 | ||
|
|
fe5c95316b | ||
|
|
54617f8583 | ||
|
|
75c9731ca4 | ||
|
|
31afc45744 | ||
|
|
f6466b161b | ||
|
|
a36114e904 | ||
|
|
529f856ab9 | ||
|
|
9077e66973 | ||
|
|
a47a96b70d | ||
|
|
8a94e88786 | ||
|
|
b3aa236d3d | ||
|
|
4dd58ad89e | ||
|
|
317f7ab598 | ||
|
|
d6c47e7e81 | ||
|
|
1ed61a3d3d | ||
|
|
5c734cfa00 | ||
|
|
07f485a654 | ||
|
|
ae76271469 | ||
|
|
c8a8ecbe1f | ||
|
|
fbf69a4a34 | ||
|
|
7f38ffe676 | ||
|
|
a0e0c392e9 | ||
|
|
573e472077 | ||
|
|
955d22278d | ||
|
|
171ee93108 | ||
|
|
5fb0560f0b | ||
|
|
88b616e206 | ||
|
|
08829425cb | ||
|
|
1dbd2bf0dc | ||
|
|
157f98b331 | ||
|
|
3d689837d6 | ||
|
|
2b76bbe0db | ||
|
|
e75db79b50 | ||
|
|
60919671ea | ||
|
|
bca21c1cf0 | ||
|
|
f1993db0fa | ||
|
|
7351c16578 | ||
|
|
5bc8f5dd64 | ||
|
|
20517cd0b2 | ||
|
|
9a4081c54b | ||
|
|
97e0b31a3d | ||
|
|
af17930314 | ||
|
|
094b19f289 | ||
|
|
8e54cef68b | ||
|
|
1df8d5832f | ||
|
|
0542008b7f | ||
|
|
ffa89202e6 | ||
|
|
1203cbbad8 | ||
|
|
f9fb463128 | ||
|
|
ea398f6294 | ||
|
|
5f41042826 | ||
|
|
486b7d4da1 | ||
|
|
91b47e56ff | ||
|
|
9934e59629 | ||
|
|
50cc66d51c | ||
|
|
936c9dc4f3 | ||
|
|
946ade5da1 | ||
|
|
80068a3674 | ||
|
|
d7c9a7874b | ||
|
|
768e5b3f5b | ||
|
|
f3320d9ae3 | ||
|
|
d4538b0909 | ||
|
|
676ee74f19 | ||
|
|
9059f227fa | ||
|
|
6a14d0f3f3 | ||
|
|
3e5c623125 | ||
|
|
e559fb7e4b | ||
|
|
88a1cfb689 | ||
|
|
f12c4e75e6 | ||
|
|
90f08c58cd | ||
|
|
f6aa96c64c | ||
|
|
2b04a1b50c | ||
|
|
7297fb5241 | ||
|
|
98c5a68a8c | ||
|
|
8e643747f8 | ||
|
|
2483e19bee | ||
|
|
f9d3c6ed48 | ||
|
|
09a0e75351 | ||
|
|
644edc5b76 | ||
|
|
a64b994376 | ||
|
|
fb626ebf7e | ||
|
|
dd334f487e | ||
|
|
cd5c86fb69 | ||
|
|
7878761b6f | ||
|
|
d3b63abdd3 | ||
|
|
23fad37205 | ||
|
|
88558e6b98 | ||
|
|
a84ee8497b | ||
|
|
d560ee2da1 | ||
|
|
fd3fce110e | ||
|
|
1bce2b0e28 | ||
|
|
06a59bfe03 | ||
|
|
83a430afad | ||
|
|
949f638b6e | ||
|
|
2b2193e9ce | ||
|
|
0709bada87 | ||
|
|
506586b74c | ||
|
|
99b2ee273f | ||
|
|
aa6e536851 | ||
|
|
2a2c1af7ba | ||
|
|
48e381d702 | ||
|
|
9aafd76746 | ||
|
|
0069af78a3 | ||
|
|
c25fe7eb3d | ||
|
|
b9a9013685 | ||
|
|
54d075e4fd | ||
|
|
1c40044525 | ||
|
|
5784694dc9 | ||
|
|
7af4a6ff11 | ||
|
|
a601be0666 | ||
|
|
1be169a105 | ||
|
|
6b02af69f2 | ||
|
|
1fe4bd2de7 | ||
|
|
afd00a8ab6 | ||
|
|
63918b3c20 | ||
|
|
6293a4b936 | ||
|
|
44502092ad | ||
|
|
ce0e8284fe | ||
|
|
15f104ddd0 | ||
|
|
7f6ae8ffbf | ||
|
|
b2ecfb5a32 | ||
|
|
fa6ba8b668 | ||
|
|
826dffc794 | ||
|
|
688190ac4a | ||
|
|
4909a3b537 | ||
|
|
64e2150f44 | ||
|
|
3f7abc459c | ||
|
|
618cdafd10 | ||
|
|
e7b37d0378 | ||
|
|
3f3e2525d2 | ||
|
|
765e08f999 | ||
|
|
598bc29647 | ||
|
|
39ccddfb1c | ||
|
|
108214a217 | ||
|
|
271f40e355 | ||
|
|
e801547580 | ||
|
|
fc11941186 | ||
|
|
882fad3113 | ||
|
|
6168492711 | ||
|
|
2aade9aaa6 | ||
|
|
4789946c4e | ||
|
|
a69d8877c9 | ||
|
|
7cc0c3bc57 | ||
|
|
fb78495a1b | ||
|
|
22def5111f | ||
|
|
6e91d51def | ||
|
|
f3b8a4e931 | ||
|
|
69afa52beb | ||
|
|
f09a39d27c | ||
|
|
2e71963fbf | ||
|
|
f740a92fb7 | ||
|
|
21e7ddea16 | ||
|
|
923d90cf22 | ||
|
|
b386a1917d | ||
|
|
4a5427b2b2 | ||
|
|
04554c5309 | ||
|
|
5ef88b5c56 | ||
|
|
1f2397b81a | ||
|
|
892c4934d5 | ||
|
|
60d5aaaaa6 | ||
|
|
f506b840ed | ||
|
|
f357750d88 | ||
|
|
a2dbe68338 | ||
|
|
491d2cfab1 | ||
|
|
fa91abb739 | ||
|
|
6b46d04537 | ||
|
|
b90457c04f | ||
|
|
379d98a91e | ||
|
|
07352480cd | ||
|
|
a6ff8e095a | ||
|
|
1fb44bbe73 | ||
|
|
5323849f90 | ||
|
|
034327f647 | ||
|
|
de9aac0988 | ||
|
|
f55d836398 | ||
|
|
287014518d | ||
|
|
b46e2da61b | ||
|
|
ef47d6cf0b | ||
|
|
1f5d66cd58 | ||
|
|
a88602a21f | ||
|
|
ffd2b4b76f | ||
|
|
c50ed843fb | ||
|
|
760c05df5d | ||
|
|
26d070f2c3 | ||
|
|
850ae5114f | ||
|
|
bc9577439e | ||
|
|
10cd596f0b | ||
|
|
d180062ad2 | ||
|
|
bfacf4b36e | ||
|
|
2912f31dec | ||
|
|
c47b287a89 | ||
|
|
3aa626d2ae | ||
|
|
647ee2a073 | ||
|
|
2080c3f7b8 | ||
|
|
6f65c72921 | ||
|
|
22bbdd6a28 | ||
|
|
1a3d6f6520 | ||
|
|
aa1e78ac94 | ||
|
|
858caa4582 | ||
|
|
a7e1091f3f | ||
|
|
78fca804b7 | ||
|
|
a919ef99fe | ||
|
|
de9ca06607 | ||
|
|
b5c5990e56 | ||
|
|
756af8aafb | ||
|
|
54f84af274 | ||
|
|
c151b6e1bc | ||
|
|
effd729222 | ||
|
|
b7cdbc5c94 | ||
|
|
28193f86fb | ||
|
|
877fe48225 | ||
|
|
e0f6f79c5b | ||
|
|
f62254d68e | ||
|
|
d054e6fc16 | ||
|
|
7231f699c1 | ||
|
|
1dae0793fd | ||
|
|
f18fbe86b6 | ||
|
|
61a61724ca | ||
|
|
93cf30eb18 | ||
|
|
379f41ff04 | ||
|
|
cff08adcd0 | ||
|
|
71936c1f0a | ||
|
|
4da2ed4a1f | ||
|
|
11c5b26c59 | ||
|
|
19c79ce510 | ||
|
|
0ba14c18b1 | ||
|
|
ac85bb2e2d | ||
|
|
74dfb2710f | ||
|
|
d2a0ab684a | ||
|
|
12d38fa813 | ||
|
|
8dbd3c3db1 | ||
|
|
74b3b348ff | ||
|
|
3386d61fde | ||
|
|
db41e00990 | ||
|
|
5d5275ce70 | ||
|
|
e39c63700e | ||
|
|
550ac2db9d | ||
|
|
19da14531c | ||
|
|
254dd80f24 | ||
|
|
0bc3f16b4b | ||
|
|
5184973bd5 | ||
|
|
b6accca5ca | ||
|
|
fec68e6211 | ||
|
|
fc63c906dd | ||
|
|
3333f8f0f5 | ||
|
|
89a3ac3dde | ||
|
|
16551ec83f | ||
|
|
2645bf6023 | ||
|
|
f5f4974a73 | ||
|
|
162e337d14 | ||
|
|
21a7d36b7b | ||
|
|
f2506c3231 | ||
|
|
d47641e25a | ||
|
|
983e01cb3f | ||
|
|
a55ede9175 | ||
|
|
fb8479ad1e | ||
|
|
28491cb01d | ||
|
|
b49dddeb47 | ||
|
|
31e501f65a | ||
|
|
3810cf3ef3 | ||
|
|
050c227e6f | ||
|
|
ddb8725052 | ||
|
|
e11b9ebe26 | ||
|
|
4da53f83c9 | ||
|
|
d05da3722c | ||
|
|
b8a3440ef2 | ||
|
|
44d63032d8 | ||
|
|
9d7da91ec6 | ||
|
|
dde675fdc1 | ||
|
|
d34502bba2 | ||
|
|
309954eb44 | ||
|
|
98f9d2a8f4 | ||
|
|
5719e5e996 | ||
|
|
39add61618 | ||
|
|
1c1543f012 | ||
|
|
10a27354bb | ||
|
|
a00f199d18 | ||
|
|
6c5bff7843 | ||
|
|
388c3d38ed | ||
|
|
485584c144 | ||
|
|
b83f62bd82 | ||
|
|
758b6138c2 | ||
|
|
37f08c4534 | ||
|
|
544d67e7e5 | ||
|
|
1f0a4dad23 | ||
|
|
eb3220c96b | ||
|
|
d4ba96796c | ||
|
|
ebdac0b388 | ||
|
|
6d13a257dd | ||
|
|
c2b370f4d3 | ||
|
|
3313584d60 | ||
|
|
b76585cce3 | ||
|
|
8d9af82521 | ||
|
|
dcf25c0b4a | ||
|
|
31036ad9e4 | ||
|
|
1ba85b403f | ||
|
|
c26f410cc3 | ||
|
|
242e64cedc | ||
|
|
a44418c4fa | ||
|
|
ea66e4e4a1 | ||
|
|
6889b65123 | ||
|
|
fd5bc8f0b9 | ||
|
|
f33aff577c | ||
|
|
9ba986f5e5 | ||
|
|
bf46c798a6 | ||
|
|
54b1afd5b4 | ||
|
|
3cd966bc03 | ||
|
|
28531f3e2a | ||
|
|
7ecaf098cd | ||
|
|
ef1912d571 | ||
|
|
cd685c1b2b | ||
|
|
b501e06f27 | ||
|
|
2062c68877 | ||
|
|
b2dde8a977 | ||
|
|
19521b1894 | ||
|
|
688e7181f0 | ||
|
|
a53a9be4b7 | ||
|
|
66c56225a4 | ||
|
|
cabc08c04b | ||
|
|
9ae6063f78 | ||
|
|
7936677fd8 | ||
|
|
88a0b57335 | ||
|
|
2303d5de32 | ||
|
|
1f5de1ab42 | ||
|
|
435047cace | ||
|
|
c0d6338eba | ||
|
|
36b589e92d | ||
|
|
42cafbeaab | ||
|
|
5b5d5a39a4 | ||
|
|
8d479e358d | ||
|
|
3bb1cceed1 | ||
|
|
0756d36fb3 | ||
|
|
c709adeaec | ||
|
|
c5dcd089a6 | ||
|
|
57deadaa5c | ||
|
|
becdf640b5 | ||
|
|
756e99c089 | ||
|
|
709a14fd51 | ||
|
|
33181c0ac4 | ||
|
|
ee974dfa19 | ||
|
|
b697598d75 | ||
|
|
6e5b13668a | ||
|
|
4c13f3193e | ||
|
|
15cea33c4b | ||
|
|
cac0a84763 | ||
|
|
c56c07a0f8 | ||
|
|
929778bdad | ||
|
|
d6dba9767d | ||
|
|
7e7ce44c77 | ||
|
|
4d38880249 | ||
|
|
46d164ddd1 | ||
|
|
31e4c51c3f | ||
|
|
eebfb81bd2 | ||
|
|
06623991b3 | ||
|
|
fe697898ee | ||
|
|
f3fc14bd53 | ||
|
|
7f0b0a3909 | ||
|
|
0f395bcc3e | ||
|
|
d366b2cde1 | ||
|
|
7d8611bae2 | ||
|
|
17964c0ab7 | ||
|
|
5b6cc23fb7 | ||
|
|
efe8cff1ad | ||
|
|
6591f6780c | ||
|
|
592c320d1d | ||
|
|
df641d0866 | ||
|
|
da4606df5e | ||
|
|
ceb6b93dc1 | ||
|
|
e006f3f7de | ||
|
|
246cc25b6d | ||
|
|
a1bb61793b | ||
|
|
6523ed08cd | ||
|
|
ab34257c03 | ||
|
|
6afdffae92 | ||
|
|
c3b17e3db0 | ||
|
|
b9e128b387 | ||
|
|
cac14ab2cc | ||
|
|
c64a6eb66e | ||
|
|
cd33a539cf | ||
|
|
934b85d716 | ||
|
|
c6df34a7fc | ||
|
|
c51c90ba41 | ||
|
|
d3f420144c | ||
|
|
ec76757f93 | ||
|
|
03adff80f7 | ||
|
|
a73abcca74 | ||
|
|
a8c8fffa7c | ||
|
|
f835cf2761 | ||
|
|
96fed21fbd | ||
|
|
7d62c87de3 | ||
|
|
b28251dc9e | ||
|
|
b713e10c14 | ||
|
|
fce5371fce | ||
|
|
a9cefd284a | ||
|
|
6ed422cd28 | ||
|
|
02914685dc | ||
|
|
856ed24dcb | ||
|
|
4a9ec734c1 | ||
|
|
61d151d2bb | ||
|
|
32cb201b81 | ||
|
|
44a7006295 | ||
|
|
1567f1c283 | ||
|
|
21a0bf7d65 | ||
|
|
0089506165 | ||
|
|
fbce5aae32 | ||
|
|
a8726eee0b | ||
|
|
9efe370d33 | ||
|
|
47df62e716 | ||
|
|
dac792dd27 | ||
|
|
eacf6de19a | ||
|
|
c1ca4e84b8 | ||
|
|
87fc01cb81 | ||
|
|
71f21c643c | ||
|
|
1ced4a18d6 | ||
|
|
9dabe79d5e | ||
|
|
4c2cdfe5b8 | ||
|
|
f05888b116 | ||
|
|
ae8607c0c3 | ||
|
|
3e19b8aa96 | ||
|
|
d1bc1ab05a | ||
|
|
13149d4acf | ||
|
|
2d4ee636ae | ||
|
|
42964c91f3 | ||
|
|
de62207504 | ||
|
|
d6cabeedb4 | ||
|
|
99a7b90247 | ||
|
|
fbdaa50fcf | ||
|
|
30e81297da | ||
|
|
b373eaff39 | ||
|
|
80a212683d | ||
|
|
b0a4ed30d4 | ||
|
|
c2cb37ffe6 | ||
|
|
558894fafd | ||
|
|
4bbdf27f48 | ||
|
|
daa296f2af | ||
|
|
4c51212315 | ||
|
|
4b796fae5d | ||
|
|
2e9573ef92 | ||
|
|
384bfce3eb | ||
|
|
5a8c7fb924 | ||
|
|
246775256e | ||
|
|
fa4cd8dd5a | ||
|
|
5224e063f7 | ||
|
|
e5e8b9a7ec | ||
|
|
7cd76c50eb | ||
|
|
b520202544 | ||
|
|
bbae882eda | ||
|
|
372763c57c | ||
|
|
3bf323032c | ||
|
|
7a50b2d2ff | ||
|
|
8df326bf92 | ||
|
|
2d6555fe0f | ||
|
|
b0d6f7722b | ||
|
|
aedbe7d333 | ||
|
|
6079dd4af6 | ||
|
|
5c448188cf | ||
|
|
d7dc878b1c | ||
|
|
7baec4e48e | ||
|
|
5cd58d4119 | ||
|
|
4cdfefd92b | ||
|
|
28b936e2d1 | ||
|
|
e4ec7e3e1e | ||
|
|
3c7ecef6a8 | ||
|
|
e2a5a1ab39 | ||
|
|
5f64b2fb25 | ||
|
|
f2a2d4cde5 | ||
|
|
ee93c8bec5 | ||
|
|
c65e93e514 | ||
|
|
0fd808727c | ||
|
|
edc3c58876 | ||
|
|
4277c08324 | ||
|
|
424f29a82b | ||
|
|
e52c7ff9ce | ||
|
|
fdc709d1c2 | ||
|
|
ea750571a0 | ||
|
|
f8fbea4654 | ||
|
|
e0f4a4ecb8 | ||
|
|
5ce12d97be | ||
|
|
4fdd064cd6 | ||
|
|
37731b236a | ||
|
|
b06c708480 | ||
|
|
76d7f02fe8 | ||
|
|
5a9f2c610a | ||
|
|
8a88d165e6 | ||
|
|
f8c452ae3f | ||
|
|
9befbec2b0 | ||
|
|
7b46f3bc23 | ||
|
|
64a500987c | ||
|
|
92eaece5eb | ||
|
|
ee73d5b628 | ||
|
|
a7eda1355b | ||
|
|
8b9b79db8e | ||
|
|
69c0488335 | ||
|
|
c18e06f071 | ||
|
|
a50c0eb1e7 | ||
|
|
fb56f7df20 | ||
|
|
3540a274b3 | ||
|
|
fe2c02679e | ||
|
|
bca3e96e9c | ||
|
|
041edb3042 | ||
|
|
6e96085f99 | ||
|
|
249394b4ad | ||
|
|
2a84561e00 | ||
|
|
962456204e | ||
|
|
593524905e | ||
|
|
1b12e9d8b7 | ||
|
|
127f105934 | ||
|
|
2dfe5585eb | ||
|
|
93011f182f | ||
|
|
d11e95ab26 | ||
|
|
f99ddbe60f | ||
|
|
982069df36 | ||
|
|
db4bec37e3 | ||
|
|
736ef16430 | ||
|
|
129cb7627c | ||
|
|
f223b5dd2a | ||
|
|
b3521be629 | ||
|
|
17db6a1772 | ||
|
|
278d9b74f9 | ||
|
|
ce796fa1d9 | ||
|
|
ec0275e6f6 | ||
|
|
39252c7828 | ||
|
|
ed790c1c4d | ||
|
|
0b230b0a87 | ||
|
|
75a88ab25a | ||
|
|
9b72221482 | ||
|
|
ef1f27c09f | ||
|
|
c762146ec9 | ||
|
|
39fc6248d6 | ||
|
|
ff0bb9d005 | ||
|
|
6425afd58d | ||
|
|
c8498bd4e3 | ||
|
|
64f5c170d0 | ||
|
|
515b62d1ce | ||
|
|
70434b17cc | ||
|
|
a921a8bc61 | ||
|
|
7e29c7d624 | ||
|
|
669d24fe43 | ||
|
|
439451a7e8 | ||
|
|
3e5226de67 | ||
|
|
0aa9d4d1d5 | ||
|
|
0ead06937b | ||
|
|
8c7a0b4861 | ||
|
|
8fa91a3805 | ||
|
|
037fb6737d | ||
|
|
c554a1a57d | ||
|
|
9406f0fa22 | ||
|
|
d081a2bdba | ||
|
|
4e0d8cba51 | ||
|
|
ecc8a65d28 | ||
|
|
dac09a9027 | ||
|
|
f8e56c02f0 | ||
|
|
56f73a35b0 | ||
|
|
3842b633d7 | ||
|
|
caa73057d8 | ||
|
|
28fef8df86 | ||
|
|
72d83d242d | ||
|
|
33b54a734e | ||
|
|
1f8aa7d778 | ||
|
|
2c6e82a58a | ||
|
|
e1f0bccb81 | ||
|
|
09ff3ee865 | ||
|
|
cbfeb18517 | ||
|
|
63e7ace693 | ||
|
|
0f9cf48b55 | ||
|
|
cd706445f6 | ||
|
|
3896fdd6a2 | ||
|
|
1842480088 | ||
|
|
bb850867c8 | ||
|
|
5a48436eff | ||
|
|
1a3c2f64e4 | ||
|
|
2f42422d35 | ||
|
|
4dadb64af0 | ||
|
|
88611aeb80 | ||
|
|
29f555ac5c | ||
|
|
49082714d7 | ||
|
|
5be019864f | ||
|
|
91795875b5 | ||
|
|
67a6e6f8ac | ||
|
|
e95f1cf003 | ||
|
|
90bd743816 | ||
|
|
76e5e8c069 | ||
|
|
12c75b7488 | ||
|
|
8b3267458b | ||
|
|
bccfaab350 | ||
|
|
eed7c9aabc | ||
|
|
753f12979e | ||
|
|
f74160c16a | ||
|
|
2ba74f1645 | ||
|
|
200af2cf16 | ||
|
|
8b373b9283 | ||
|
|
2fa26db93f | ||
|
|
bd5c669374 | ||
|
|
5626991074 | ||
|
|
a3fd55d56e | ||
|
|
27eedaf347 | ||
|
|
11baeda71e | ||
|
|
0b5ce61555 | ||
|
|
1c617d90aa | ||
|
|
724c710636 | ||
|
|
6d62ecdf17 | ||
|
|
bfe7c263cb | ||
|
|
987b6949c9 | ||
|
|
1ade4c6b3e | ||
|
|
67069b1adc | ||
|
|
29e4a62ba7 | ||
|
|
87eb067fb5 | ||
|
|
f8d315ff6e | ||
|
|
d0e4b533e3 | ||
|
|
d6d131647a | ||
|
|
4cbc3d7664 | ||
|
|
8b2e13b5fd | ||
|
|
e5b873c9aa | ||
|
|
2e904dcda0 | ||
|
|
5300de834f | ||
|
|
b7def686e9 | ||
|
|
7a2c7c5b30 | ||
|
|
167e6b2bf1 | ||
|
|
9b54e4d80a | ||
|
|
987a27ffa1 | ||
|
|
7fb1c9db8c | ||
|
|
f66b05f707 | ||
|
|
5a19c25fea | ||
|
|
f8ba191eea | ||
|
|
a03d265cd3 | ||
|
|
0dbd597d0b | ||
|
|
b97f85fa60 | ||
|
|
0a06fe6d2e | ||
|
|
70f5aa1f55 | ||
|
|
5a384d8d16 | ||
|
|
4fea87af8a | ||
|
|
8a45f753ca | ||
|
|
19253cd9b5 | ||
|
|
eac5e58ac7 | ||
|
|
3eee1a572e | ||
|
|
f572aa442e | ||
|
|
101be5d6a6 | ||
|
|
1c9c9908c5 | ||
|
|
916cb03a3a | ||
|
|
bb095ae296 | ||
|
|
97c63e2be7 | ||
|
|
b7c6ded375 | ||
|
|
8db8a8267b | ||
|
|
4b9a5472bd | ||
|
|
a374add2eb | ||
|
|
592c178544 | ||
|
|
ed907c1ae5 | ||
|
|
021825ebb9 | ||
|
|
45aae7ff62 | ||
|
|
47bd890dd7 | ||
|
|
add435d4f7 | ||
|
|
d262a16269 | ||
|
|
aeef31d0d4 | ||
|
|
5717e58613 | ||
|
|
535477b804 | ||
|
|
a80ef78be0 | ||
|
|
f119304d9e | ||
|
|
cb168338ee | ||
|
|
1892d6288a | ||
|
|
1aa81d1440 | ||
|
|
efe4483a04 | ||
|
|
1026a721ac | ||
|
|
affeca6045 | ||
|
|
05ac144e4f | ||
|
|
fdb5d75372 | ||
|
|
02770dd1a9 | ||
|
|
6aed03380a | ||
|
|
be822b6bbe | ||
|
|
9aa15d9e64 | ||
|
|
4e33a0f7f8 | ||
|
|
7ff9f67aab | ||
|
|
43fcce7242 | ||
|
|
099274969e | ||
|
|
5bb97218bd | ||
|
|
57fe1d6b22 | ||
|
|
7aecadfe68 |
@@ -1,6 +1,11 @@
|
|||||||
|
/* eslint-disable import/no-commonjs */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
extends: [
|
extends: [
|
||||||
'habitrpg/lib/node'
|
'habitrpg/lib/node',
|
||||||
],
|
],
|
||||||
}
|
rules: {
|
||||||
|
'prefer-regex-literals': 'warn',
|
||||||
|
'import/no-extraneous-dependencies': 'off',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
186
.github/dependabot.yml
vendored
186
.github/dependabot.yml
vendored
@@ -1,186 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: npm
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
time: "06:00"
|
|
||||||
timezone: Europe/Rome
|
|
||||||
open-pull-requests-limit: 99
|
|
||||||
ignore:
|
|
||||||
- dependency-name: express-validator
|
|
||||||
versions:
|
|
||||||
- 6.10.0
|
|
||||||
- 6.10.1
|
|
||||||
- 6.9.2
|
|
||||||
- dependency-name: "@babel/core"
|
|
||||||
versions:
|
|
||||||
- 7.12.13
|
|
||||||
- 7.12.16
|
|
||||||
- 7.12.17
|
|
||||||
- 7.13.1
|
|
||||||
- 7.13.10
|
|
||||||
- 7.13.13
|
|
||||||
- 7.13.14
|
|
||||||
- 7.13.15
|
|
||||||
- 7.13.8
|
|
||||||
- dependency-name: redis
|
|
||||||
versions:
|
|
||||||
- 3.1.0
|
|
||||||
- dependency-name: stripe
|
|
||||||
versions:
|
|
||||||
- 8.134.0
|
|
||||||
- 8.135.0
|
|
||||||
- 8.137.0
|
|
||||||
- 8.138.0
|
|
||||||
- 8.140.0
|
|
||||||
- 8.142.0
|
|
||||||
- dependency-name: "@babel/register"
|
|
||||||
versions:
|
|
||||||
- 7.12.13
|
|
||||||
- 7.13.14
|
|
||||||
- 7.13.8
|
|
||||||
- dependency-name: mongoose
|
|
||||||
versions:
|
|
||||||
- 5.11.14
|
|
||||||
- 5.11.15
|
|
||||||
- 5.11.16
|
|
||||||
- 5.11.17
|
|
||||||
- 5.11.18
|
|
||||||
- 5.11.19
|
|
||||||
- 5.12.0
|
|
||||||
- 5.12.1
|
|
||||||
- 5.12.2
|
|
||||||
- 5.12.3
|
|
||||||
- dependency-name: jwks-rsa
|
|
||||||
versions:
|
|
||||||
- 1.12.3
|
|
||||||
- 2.0.1
|
|
||||||
- 2.0.2
|
|
||||||
- dependency-name: "@babel/preset-env"
|
|
||||||
versions:
|
|
||||||
- 7.12.13
|
|
||||||
- 7.12.16
|
|
||||||
- 7.12.17
|
|
||||||
- 7.13.10
|
|
||||||
- 7.13.12
|
|
||||||
- 7.13.8
|
|
||||||
- 7.13.9
|
|
||||||
- dependency-name: image-size
|
|
||||||
versions:
|
|
||||||
- 0.9.4
|
|
||||||
- 0.9.5
|
|
||||||
- 0.9.7
|
|
||||||
- dependency-name: winston-loggly-bulk
|
|
||||||
versions:
|
|
||||||
- 3.2.0
|
|
||||||
- dependency-name: chai
|
|
||||||
versions:
|
|
||||||
- 4.3.0
|
|
||||||
- 4.3.3
|
|
||||||
- dependency-name: mocha
|
|
||||||
versions:
|
|
||||||
- 8.2.1
|
|
||||||
- 8.3.0
|
|
||||||
- 8.3.1
|
|
||||||
- dependency-name: "@google-cloud/trace-agent"
|
|
||||||
versions:
|
|
||||||
- 5.1.2
|
|
||||||
- dependency-name: monk
|
|
||||||
versions:
|
|
||||||
- 7.3.3
|
|
||||||
- package-ecosystem: npm
|
|
||||||
directory: "/website/client"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
time: "06:00"
|
|
||||||
timezone: Europe/Rome
|
|
||||||
open-pull-requests-limit: 99
|
|
||||||
ignore:
|
|
||||||
- dependency-name: eslint-plugin-vue
|
|
||||||
versions:
|
|
||||||
- 7.5.0
|
|
||||||
- 7.6.0
|
|
||||||
- 7.7.0
|
|
||||||
- 7.8.0
|
|
||||||
- 7.9.0
|
|
||||||
- dependency-name: "@storybook/addon-knobs"
|
|
||||||
versions:
|
|
||||||
- 6.1.17
|
|
||||||
- 6.1.18
|
|
||||||
- 6.1.20
|
|
||||||
- 6.1.21
|
|
||||||
- 6.2.2
|
|
||||||
- 6.2.3
|
|
||||||
- 6.2.7
|
|
||||||
- dependency-name: "@storybook/addon-links"
|
|
||||||
versions:
|
|
||||||
- 6.1.17
|
|
||||||
- 6.1.18
|
|
||||||
- 6.1.20
|
|
||||||
- 6.1.21
|
|
||||||
- 6.2.2
|
|
||||||
- 6.2.3
|
|
||||||
- 6.2.7
|
|
||||||
- dependency-name: "@storybook/vue"
|
|
||||||
versions:
|
|
||||||
- 6.1.17
|
|
||||||
- 6.1.18
|
|
||||||
- 6.1.20
|
|
||||||
- 6.1.21
|
|
||||||
- 6.2.2
|
|
||||||
- 6.2.3
|
|
||||||
- 6.2.7
|
|
||||||
- dependency-name: "@storybook/addon-actions"
|
|
||||||
versions:
|
|
||||||
- 6.1.17
|
|
||||||
- 6.1.18
|
|
||||||
- 6.1.20
|
|
||||||
- 6.1.21
|
|
||||||
- 6.2.2
|
|
||||||
- 6.2.3
|
|
||||||
- 6.2.7
|
|
||||||
- dependency-name: core-js
|
|
||||||
versions:
|
|
||||||
- 3.10.0
|
|
||||||
- 3.10.1
|
|
||||||
- 3.9.0
|
|
||||||
- 3.9.1
|
|
||||||
- dependency-name: bootstrap
|
|
||||||
versions:
|
|
||||||
- 4.6.0
|
|
||||||
- dependency-name: y18n
|
|
||||||
versions:
|
|
||||||
- 4.0.1
|
|
||||||
- dependency-name: hellojs
|
|
||||||
versions:
|
|
||||||
- 1.18.8
|
|
||||||
- 1.19.2
|
|
||||||
- dependency-name: chai
|
|
||||||
versions:
|
|
||||||
- 4.3.0
|
|
||||||
- 4.3.3
|
|
||||||
- dependency-name: amplitude-js
|
|
||||||
versions:
|
|
||||||
- 7.4.2
|
|
||||||
- 7.4.3
|
|
||||||
- 7.4.4
|
|
||||||
- dependency-name: pug
|
|
||||||
versions:
|
|
||||||
- 3.0.2
|
|
||||||
- dependency-name: sass
|
|
||||||
versions:
|
|
||||||
- 1.32.6
|
|
||||||
- 1.32.7
|
|
||||||
- 1.32.8
|
|
||||||
- dependency-name: "@vue/test-utils"
|
|
||||||
versions:
|
|
||||||
- 1.1.2
|
|
||||||
- 1.1.3
|
|
||||||
- dependency-name: intro.js
|
|
||||||
versions:
|
|
||||||
- 3.2.1
|
|
||||||
- 3.3.1
|
|
||||||
- dependency-name: sass-loader
|
|
||||||
versions:
|
|
||||||
- 10.1.1
|
|
||||||
98
.github/workflows/test.yml
vendored
98
.github/workflows/test.yml
vendored
@@ -10,19 +10,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm i
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
@@ -31,19 +33,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm i
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
@@ -52,19 +56,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm i
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
@@ -74,19 +80,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm i
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
@@ -95,19 +103,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm i
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
@@ -117,14 +127,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
mongodb-version: [4.2]
|
mongodb-version: [4.2]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||||
@@ -132,10 +142,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
mongodb-version: ${{ matrix.mongodb-version }}
|
mongodb-version: ${{ matrix.mongodb-version }}
|
||||||
mongodb-replica-set: rs
|
mongodb-replica-set: rs
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm i
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
@@ -146,14 +158,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
mongodb-version: [4.2]
|
mongodb-version: [4.2]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||||
@@ -161,10 +173,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
mongodb-version: ${{ matrix.mongodb-version }}
|
mongodb-version: ${{ matrix.mongodb-version }}
|
||||||
mongodb-replica-set: rs
|
mongodb-replica-set: rs
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm i
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
@@ -175,14 +189,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
mongodb-version: [4.2]
|
mongodb-version: [4.2]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||||
@@ -190,10 +204,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
mongodb-version: ${{ matrix.mongodb-version }}
|
mongodb-version: ${{ matrix.mongodb-version }}
|
||||||
mongodb-replica-set: rs
|
mongodb-replica-set: rs
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm i
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
@@ -205,19 +221,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm i
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
@@ -228,15 +246,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [21.x]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: sudo apt update
|
||||||
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -8,7 +8,7 @@ i18n_cache
|
|||||||
apidoc/html
|
apidoc/html
|
||||||
*.swp
|
*.swp
|
||||||
.idea*
|
.idea*
|
||||||
config.json
|
config*.json
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
lib
|
lib
|
||||||
newrelic_agent.log
|
newrelic_agent.log
|
||||||
@@ -40,10 +40,12 @@ yarn.lock
|
|||||||
!.elasticbeanstalk/*.global.yml
|
!.elasticbeanstalk/*.global.yml
|
||||||
|
|
||||||
/.vscode
|
/.vscode
|
||||||
|
habitica.code-workspace
|
||||||
|
|
||||||
# webstorm fake webpack for path intellisense
|
# webstorm fake webpack for path intellisense
|
||||||
webpack.webstorm.config
|
webpack.webstorm.config
|
||||||
|
|
||||||
# mongodb replica set for local dev
|
# mongodb replica set for local dev
|
||||||
mongodb-*.tgz
|
mongodb-*.tgz
|
||||||
/mongodb-data
|
/mongodb-data*
|
||||||
|
/.nyc_output
|
||||||
|
|||||||
25
.heroku/report_deploy.sh
Executable file
25
.heroku/report_deploy.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DEVELOPER="someone"
|
||||||
|
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||||
|
DEVELOPERS=$(git log -5 --pretty=format:'%an')
|
||||||
|
IFS=$'\n'
|
||||||
|
DEVELOPER=""
|
||||||
|
for dev in $DEVELOPERS
|
||||||
|
do
|
||||||
|
if [ "$DEVELOPER" == "someone" ]; then
|
||||||
|
if [[ ${dev} != *"[bot]"* ]]; then
|
||||||
|
DEVELOPER=$dev
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
PARTS=$(cut -d"." -f1 <<< $BASE_URL)
|
||||||
|
SERVER_NAME=$(cut -d"/" -f3 <<< ${PARTS[0]})
|
||||||
|
|
||||||
|
SERVER_NAME=":$SERVER_EMOJI: $SERVER_NAME"
|
||||||
|
|
||||||
|
wget $SLACK_DEPLOY_URL --post-data="{\"server_name\": \"$SERVER_NAME\", \"developer\": \"$DEVELOPER\", \"base_url\": \"$BASE_URL\"}" -O /dev/null
|
||||||
30
Dockerfile
30
Dockerfile
@@ -1,30 +0,0 @@
|
|||||||
FROM node:14
|
|
||||||
|
|
||||||
ENV ADMIN_EMAIL admin@habitica.com
|
|
||||||
ENV EMAILS_COMMUNITY_MANAGER_EMAIL admin@habitica.com
|
|
||||||
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
|
|
||||||
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
|
|
||||||
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
|
|
||||||
ENV BASE_URL https://habitica.com
|
|
||||||
ENV FACEBOOK_KEY 128307497299777
|
|
||||||
ENV GA_ID UA-33510635-1
|
|
||||||
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
|
|
||||||
ENV LOGGLY_CLIENT_TOKEN ab5663bf-241f-4d14-8783-7d80db77089a
|
|
||||||
ENV NODE_ENV production
|
|
||||||
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
|
||||||
ENV APPLE_AUTH_CLIENT_ID 9Q9SMRMCNN.com.habitrpg.ios.Habitica
|
|
||||||
|
|
||||||
# Install global packages
|
|
||||||
RUN npm install -g gulp-cli mocha
|
|
||||||
|
|
||||||
# Clone Habitica repo and install dependencies
|
|
||||||
RUN mkdir -p /usr/src/habitrpg
|
|
||||||
WORKDIR /usr/src/habitrpg
|
|
||||||
RUN git clone --branch release --depth 1 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
|
||||||
RUN git config --global url."https://".insteadOf git://
|
|
||||||
RUN npm set unsafe-perm true
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Start Habitica
|
|
||||||
EXPOSE 80 8080 36612
|
|
||||||
CMD ["node", "./website/transpiled-babel/index.js"]
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
FROM node:14
|
FROM node:20
|
||||||
|
|
||||||
# Install global packages
|
# Install global packages
|
||||||
RUN npm install -g gulp-cli mocha
|
RUN npm install -g gulp-cli mocha
|
||||||
|
|
||||||
# Copy package.json and package-lock.json into image, then install
|
# Copy package.json and package-lock.json into image
|
||||||
# dependencies.
|
|
||||||
WORKDIR /usr/src/habitica
|
WORKDIR /usr/src/habitica
|
||||||
COPY ["package.json", "package-lock.json", "./"]
|
COPY ["package.json", "package-lock.json", "./"]
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Copy the remaining source files in.
|
# Copy the remaining source files in.
|
||||||
COPY . /usr/src/habitica
|
COPY . /usr/src/habitica
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
RUN npm run postinstall
|
RUN npm run postinstall
|
||||||
|
RUN npm run client:build
|
||||||
|
RUN gulp build:prod
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,14 +1,20 @@
|
|||||||
Habitica  [](https://codeclimate.com/github/HabitRPG/habitrpg) [](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
|
Habitica 
|
||||||
===============
|
===============
|
||||||
|
|
||||||
[Habitica](https://habitica.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor.
|
[Habitica](https://habitica.com) is an open-source habit-building program that treats your life like a role-playing game. Level up as you succeed, lose HP as you fail, and earn Gold to buy weapons and armor!
|
||||||
|
|
||||||
**We need more programmers!** Your assistance will be greatly appreciated. The wiki pages below and the additional pages they link to will tell you how to get started on contributing code and where you can go to seek further help or ask questions:
|
**Want to contribute code to Habitica?** We're always looking for assistance on any issues in our repo with the "Help Wanted" label. The wiki pages below and the additional linked pages will tell you how to start contributing code and where you can seek further help or ask questions:
|
||||||
* [Guidance for Blacksmiths](https://habitica.fandom.com/wiki/Guidance_for_Blacksmiths) - an introduction to the technologies used and how the software is organized.
|
* [Guidance for Blacksmiths](https://habitica.fandom.com/wiki/Guidance_for_Blacksmiths) - an introduction to the technologies used and how the software is organized.
|
||||||
* [Setting up Habitica Locally](https://habitica.fandom.com/wiki/Setting_up_Habitica_Locally) - how to set up a local install of Habitica for development and testing on various platforms.
|
* [Setting up Habitica Locally](https://github.com/HabitRPG/habitica/wiki/Setting-Up-Habitica-for-Local-Development) - how to set up a local install of Habitica for development and testing.
|
||||||
|
|
||||||
|
**Interested in contributing to Habitica’s mobile apps?** Visit the links below for our mobile repositories.
|
||||||
|
* **Android:** https://github.com/HabitRPG/habitica-android
|
||||||
|
* **iOS:** https://github.com/HabitRPG/habitica-ios
|
||||||
|
|
||||||
Habitica's code is licensed as described at https://github.com/HabitRPG/habitica/blob/develop/LICENSE
|
Habitica's code is licensed as described at https://github.com/HabitRPG/habitica/blob/develop/LICENSE
|
||||||
|
|
||||||
**Found a bug?** Please report it to [admin email](mailto:admin@habitica.com) rather than creating an issue (an admin will advise you if a new issue is necessary; usually it is not).
|
**Found a bug?** Please report it to [admin email](mailto:admin@habitica.com) rather than create an issue (an admin will advise you if a new issue is necessary; usually it is not).
|
||||||
|
|
||||||
**Have any questions about Habitica or its community?** See the links in the [habitica.com](https://habitica.com) website's Help menu or drop in to [Guilds > Tavern Chat](https://habitica.com/groups/tavern) to ask questions or chat socially!
|
**Creating a third-party tool?** Please review our [API Usage Guidelines](https://github.com/HabitRPG/habitica/wiki/API-Usage-Guidelines) to ensure that your tool is compliant and maintains the best experience for Habitica players.
|
||||||
|
|
||||||
|
**Have any questions about Habitica or contributing?** See the links in the [Habitica](https://habitica.com) website's Help menu. There’s FAQ’s, guides, and the option to reach out to us with any further questions!
|
||||||
|
|||||||
@@ -32,10 +32,12 @@
|
|||||||
"LOGGLY_CLIENT_TOKEN": "token",
|
"LOGGLY_CLIENT_TOKEN": "token",
|
||||||
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
||||||
"LOGGLY_TOKEN": "example-token",
|
"LOGGLY_TOKEN": "example-token",
|
||||||
|
"LOG_REQUESTS_EXCESSIVE_MODE": "false",
|
||||||
"MAINTENANCE_MODE": "false",
|
"MAINTENANCE_MODE": "false",
|
||||||
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
|
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
|
||||||
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
|
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
|
||||||
"MONGODB_POOL_SIZE": "10",
|
"MONGODB_POOL_SIZE": "10",
|
||||||
|
"MONGODB_SOCKET_TIMEOUT": "20000",
|
||||||
"NODE_ENV": "development",
|
"NODE_ENV": "development",
|
||||||
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
||||||
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
|
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
|
||||||
@@ -84,8 +86,12 @@
|
|||||||
"BLOCKED_IPS": "",
|
"BLOCKED_IPS": "",
|
||||||
"LOG_AMPLITUDE_EVENTS": "false",
|
"LOG_AMPLITUDE_EVENTS": "false",
|
||||||
"RATE_LIMITER_ENABLED": "false",
|
"RATE_LIMITER_ENABLED": "false",
|
||||||
|
"LIVELINESS_PROBE_KEY": "",
|
||||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||||
"REDIS_PORT": "1234",
|
"REDIS_PORT": "1234",
|
||||||
"REDIS_PASSWORD": "12345678",
|
"REDIS_PASSWORD": "12345678",
|
||||||
"TRUSTED_DOMAINS": "localhost,habitica.com"
|
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
|
||||||
|
"TIME_TRAVEL_ENABLED": "false",
|
||||||
|
"DEBUG_ENABLED": "false",
|
||||||
|
"CONTENT_SWITCHOVER_TIME_OFFSET": 8
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ services:
|
|||||||
dockerfile: ./Dockerfile-Dev
|
dockerfile: ./Dockerfile-Dev
|
||||||
command: ["npm", "start"]
|
command: ["npm", "start"]
|
||||||
depends_on:
|
depends_on:
|
||||||
- mongo
|
mongo:
|
||||||
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||||
networks:
|
networks:
|
||||||
@@ -33,7 +34,16 @@ services:
|
|||||||
- .:/usr/src/habitica
|
- .:/usr/src/habitica
|
||||||
- /usr/src/habitica/node_modules
|
- /usr/src/habitica/node_modules
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo:3.6
|
image: mongo:5.0.23
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ["--replSet", "rs", "--bind_ip_all", "--port", "27017"]
|
||||||
|
healthcheck:
|
||||||
|
test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet
|
||||||
|
interval: 10s
|
||||||
|
timeout: 30s
|
||||||
|
start_period: 0s
|
||||||
|
start_interval: 1s
|
||||||
|
retries: 30
|
||||||
networks:
|
networks:
|
||||||
- habitica
|
- habitica
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ gulp.task('build:prepare-mongo', async () => {
|
|||||||
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
|
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
|
||||||
|
|
||||||
// use run-rs without --keep, kill it as soon as the replica set starts
|
// use run-rs without --keep, kill it as soon as the replica set starts
|
||||||
const runRsProcess = spawn('run-rs', ['-v', '4.2.8', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
|
const runRsProcess = spawn('run-rs', ['-v', '4.1.1', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
|
||||||
|
|
||||||
for await (const chunk of runRsProcess.stdout) {
|
for await (const chunk of runRsProcess.stdout) {
|
||||||
const stringChunk = chunk.toString();
|
const stringChunk = chunk.toString();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import mongoose from 'mongoose';
|
|||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import repl from 'repl';
|
import repl from 'repl';
|
||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
import logger from '../website/server/libs/logger';
|
|
||||||
import {
|
import {
|
||||||
getDevelopmentConnectionUrl,
|
getDevelopmentConnectionUrl,
|
||||||
getDefaultConnectionOptions,
|
getDefaultConnectionOptions,
|
||||||
@@ -39,10 +38,6 @@ const improveRepl = context => {
|
|||||||
mongoose.connect(
|
mongoose.connect(
|
||||||
connectionUrl,
|
connectionUrl,
|
||||||
mongooseOptions,
|
mongooseOptions,
|
||||||
err => {
|
|
||||||
if (err) throw err;
|
|
||||||
logger.info('Connected with Mongoose');
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,41 @@ function cssVarMap (sprite) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSpritesStream (name, src) {
|
function filterFile (file) {
|
||||||
|
if (file.relative.indexOf('Mount_Icon_') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.path.indexOf('shop/') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.path.indexOf('stable/eggs') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.path.indexOf('stable/food') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.path.indexOf('stable/potions') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.relative.indexOf('shop_') === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.relative.indexOf('icon_background') === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSpritesStream (name, src) {
|
||||||
const stream = mergeStream();
|
const stream = mergeStream();
|
||||||
|
// need to import this way bc of weird dependency things
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
const filter = require('gulp-filter');
|
||||||
|
|
||||||
|
const f = filter(filterFile);
|
||||||
|
|
||||||
const spriteData = gulp.src(src)
|
const spriteData = gulp.src(src)
|
||||||
|
.pipe(f)
|
||||||
.pipe(spritesmith({
|
.pipe(spritesmith({
|
||||||
imgName: `spritesmith-${name}.png`,
|
imgName: `spritesmith-${name}.png`,
|
||||||
cssName: `spritesmith-${name}.css`,
|
cssName: `spritesmith-${name}.css`,
|
||||||
@@ -63,7 +94,7 @@ function createSpritesStream (name, src) {
|
|||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
gulp.task('sprites:main', () => {
|
gulp.task('sprites:main', async () => {
|
||||||
const mainSrc = sync('habitica-images/**/*.png');
|
const mainSrc = sync('habitica-images/**/*.png');
|
||||||
return createSpritesStream('main', mainSrc);
|
return createSpritesStream('main', mainSrc);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ function runInChildProcess (command, options = {}, envVariables = '') {
|
|||||||
return done => pipe(exec(testBin(command, envVariables), options, done));
|
return done => pipe(exec(testBin(command, envVariables), options, done));
|
||||||
}
|
}
|
||||||
|
|
||||||
function integrationTestCommand (testDir, coverageDir) {
|
function integrationTestCommand (testDir) {
|
||||||
return `istanbul cover --dir coverage/${coverageDir} --report lcovonly node_modules/mocha/bin/_mocha -- ${testDir} --recursive --require ./test/helpers/start-server`;
|
return `nyc --silent --no-clean mocha ${testDir} --recursive --require ./test/helpers/start-server`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Test task definitions */
|
/* Test task definitions */
|
||||||
@@ -59,12 +59,14 @@ gulp.task('test:prepare:mongo', cb => {
|
|||||||
const mongooseOptions = getDefaultConnectionOptions();
|
const mongooseOptions = getDefaultConnectionOptions();
|
||||||
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
||||||
|
|
||||||
mongoose.connect(connectionUrl, mongooseOptions, err => {
|
mongoose.connect(connectionUrl, mongooseOptions)
|
||||||
|
.then(() => mongoose.connection.dropDatabase())
|
||||||
|
.then(() => mongoose.connection.close()).then(() => {
|
||||||
|
cb();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
|
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
|
||||||
return mongoose.connection.dropDatabase(err2 => {
|
throw err;
|
||||||
if (err2) return cb(err2);
|
|
||||||
return mongoose.connection.close(cb);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,8 +118,10 @@ gulp.task('test:common:safe', gulp.series('test:prepare:build', cb => {
|
|||||||
pipe(runner);
|
pipe(runner);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
gulp.task('test:content', gulp.series('test:prepare:build',
|
gulp.task('test:content', gulp.series(
|
||||||
runInChildProcess(CONTENT_TEST_COMMAND, LIMIT_MAX_BUFFER_OPTIONS)));
|
'test:prepare:build',
|
||||||
|
runInChildProcess(CONTENT_TEST_COMMAND, LIMIT_MAX_BUFFER_OPTIONS),
|
||||||
|
));
|
||||||
|
|
||||||
gulp.task('test:content:clean', cb => {
|
gulp.task('test:content:clean', cb => {
|
||||||
pipe(exec(testBin(CONTENT_TEST_COMMAND), LIMIT_MAX_BUFFER_OPTIONS, () => cb()));
|
pipe(exec(testBin(CONTENT_TEST_COMMAND), LIMIT_MAX_BUFFER_OPTIONS, () => cb()));
|
||||||
@@ -142,16 +146,20 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', cb => {
|
|||||||
pipe(runner);
|
pipe(runner);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
gulp.task('test:api:unit:run',
|
gulp.task(
|
||||||
runInChildProcess(integrationTestCommand('test/api/unit', 'coverage/api-unit')));
|
'test:api:unit:run',
|
||||||
|
runInChildProcess(integrationTestCommand('test/api/unit')),
|
||||||
|
);
|
||||||
|
|
||||||
gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done())));
|
gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done())));
|
||||||
|
|
||||||
gulp.task('test:api-v3:integration', gulp.series('test:prepare:mongo',
|
gulp.task('test:api-v3:integration', gulp.series(
|
||||||
|
'test:prepare:mongo',
|
||||||
runInChildProcess(
|
runInChildProcess(
|
||||||
integrationTestCommand('test/api/v3/integration', 'coverage/api-v3-integration'),
|
integrationTestCommand('test/api/v3/integration'),
|
||||||
LIMIT_MAX_BUFFER_OPTIONS,
|
LIMIT_MAX_BUFFER_OPTIONS,
|
||||||
)));
|
),
|
||||||
|
));
|
||||||
|
|
||||||
gulp.task('test:api-v3:integration:watch', () => gulp.watch([
|
gulp.task('test:api-v3:integration:watch', () => gulp.watch([
|
||||||
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
|
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
|
||||||
@@ -164,11 +172,13 @@ gulp.task('test:api-v3:integration:separate-server', runInChildProcess(
|
|||||||
'LOAD_SERVER=0',
|
'LOAD_SERVER=0',
|
||||||
));
|
));
|
||||||
|
|
||||||
gulp.task('test:api-v4:integration', gulp.series('test:prepare:mongo',
|
gulp.task('test:api-v4:integration', gulp.series(
|
||||||
|
'test:prepare:mongo',
|
||||||
runInChildProcess(
|
runInChildProcess(
|
||||||
integrationTestCommand('test/api/v4', 'api-v4-integration'),
|
integrationTestCommand('test/api/v4'),
|
||||||
LIMIT_MAX_BUFFER_OPTIONS,
|
LIMIT_MAX_BUFFER_OPTIONS,
|
||||||
)));
|
),
|
||||||
|
));
|
||||||
|
|
||||||
gulp.task('test:api-v4:integration:separate-server', runInChildProcess(
|
gulp.task('test:api-v4:integration:separate-server', runInChildProcess(
|
||||||
'mocha test/api/v4 --recursive --require ./test/helpers/start-server',
|
'mocha test/api/v4 --recursive --require ./test/helpers/start-server',
|
||||||
|
|||||||
Submodule habitica-images updated: f74a3b4e97...dfb04339a4
@@ -3,6 +3,6 @@ module.exports = {
|
|||||||
root: false,
|
root: false,
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': 0,
|
'no-console': 0,
|
||||||
'no-use-before-define': ['error', { functions: false }]
|
'no-use-before-define': ['error', { functions: false }],
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ function getAchievementUpdate (newUser, oldUser) {
|
|||||||
// Rebirth level
|
// Rebirth level
|
||||||
if (achievementsUpdate.rebirthLevel) {
|
if (achievementsUpdate.rebirthLevel) {
|
||||||
achievementsUpdate.rebirthLevel = Math.max(
|
achievementsUpdate.rebirthLevel = Math.max(
|
||||||
achievementsUpdate.rebirthLevel, oldAchievements.rebirthLevel,
|
achievementsUpdate.rebirthLevel,
|
||||||
|
oldAchievements.rebirthLevel,
|
||||||
);
|
);
|
||||||
} else if (oldAchievements.rebirthLevel) {
|
} else if (oldAchievements.rebirthLevel) {
|
||||||
achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel;
|
achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel;
|
||||||
@@ -61,7 +61,7 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
let query = {
|
||||||
// migration: {$ne: MIGRATION_NAME},
|
migration: {$ne: MIGRATION_NAME},
|
||||||
'auth.timestamps.loggedin': {$gt: new Date('2021-01-01')},
|
'auth.timestamps.loggedin': {$gt: new Date('2021-01-01')},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
let query = {
|
||||||
// migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') },
|
'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
let query = {
|
||||||
// migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') },
|
'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
let query = {
|
||||||
// migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') },
|
'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
let query = {
|
||||||
// migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2022-01-01') },
|
'auth.timestamps.loggedin': { $gt: new Date('2022-01-01') },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
let query = {
|
||||||
// migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2022-01-01') },
|
'auth.timestamps.loggedin': { $gt: new Date('2022-01-01') },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ async function updateUser (user) {
|
|||||||
&& pets['Wolf-Shade']
|
&& pets['Wolf-Shade']
|
||||||
&& pets['Wolf-Skeleton']
|
&& pets['Wolf-Skeleton']
|
||||||
&& pets['Wolf-White']
|
&& pets['Wolf-White']
|
||||||
&& pets['Wolf-Zombie'] {
|
&& pets['Wolf-Zombie']) {
|
||||||
set['achievements.polarPro'] = true;
|
set['achievements.polarPro'] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
let query = {
|
||||||
// migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2022-11-01') },
|
'auth.timestamps.loggedin': { $gt: new Date('2022-11-01') },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
let query = {
|
||||||
// migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2023-04-15') },
|
'auth.timestamps.loggedin': { $gt: new Date('2023-04-15') },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ async function updateUser (user) {
|
|||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
let query = {
|
||||||
migration: {$ne: MIGRATION_NAME},
|
migration: {$ne: MIGRATION_NAME},
|
||||||
// 'auth.timestamps.loggedin': { $gt: new Date('2023-07-08') },
|
'auth.timestamps.loggedin': { $gt: new Date('2023-07-08') },
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
|
|||||||
124
migrations/archive/2023/20231114_pet_group_achievements.js
Normal file
124
migrations/archive/2023/20231114_pet_group_achievements.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20231114_pet_group_achievements';
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
let set = {
|
||||||
|
migration: MIGRATION_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.pets) {
|
||||||
|
const pets = user.items.pets;
|
||||||
|
if (pets['Cactus-Zombie'] > 0
|
||||||
|
&& pets['Cactus-Skeleton'] > 0
|
||||||
|
&& pets['Cactus-Base'] > 0
|
||||||
|
&& pets['Cactus-Desert'] > 0
|
||||||
|
&& pets['Cactus-Red'] > 0
|
||||||
|
&& pets['Cactus-Shade'] > 0
|
||||||
|
&& pets['Cactus-White']> 0
|
||||||
|
&& pets['Cactus-Golden'] > 0
|
||||||
|
&& pets['Cactus-CottonCandyBlue'] > 0
|
||||||
|
&& pets['Cactus-CottonCandyPink'] > 0
|
||||||
|
&& pets['Hedgehog-Zombie'] > 0
|
||||||
|
&& pets['Hedgehog-Skeleton'] > 0
|
||||||
|
&& pets['Hedgehog-Base'] > 0
|
||||||
|
&& pets['Hedgehog-Desert'] > 0
|
||||||
|
&& pets['Hedgehog-Red'] > 0
|
||||||
|
&& pets['Hedgehog-Shade'] > 0
|
||||||
|
&& pets['Hedgehog-White'] > 0
|
||||||
|
&& pets['Hedgehog-Golder'] > 0
|
||||||
|
&& pets['Hedgehog-CottonCandyBlue'] > 0
|
||||||
|
&& pets['Hedgehog-CottonCandyPink'] > 0
|
||||||
|
&& pets['Rock-Zombie'] > 0
|
||||||
|
&& pets['Rock-Skeleton'] > 0
|
||||||
|
&& pets['Rock-Base'] > 0
|
||||||
|
&& pets['Rock-Desert'] > 0
|
||||||
|
&& pets['Rock-Red'] > 0
|
||||||
|
&& pets['Rock-Shade'] > 0
|
||||||
|
&& pets['Rock-White'] > 0
|
||||||
|
&& pets['Rock-Golden'] > 0
|
||||||
|
&& pets['Rock-CottonCandyBlue'] > 0
|
||||||
|
&& pets['Rock-CottonCandyPink'] > 0 ) {
|
||||||
|
set['achievements.roughRider'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user && user.items && user.items.mounts) {
|
||||||
|
const mounts = user.items.mounts;
|
||||||
|
if (mounts['Cactus-Zombie']
|
||||||
|
&& mounts['Cactus-Skeleton']
|
||||||
|
&& mounts['Cactus-Base']
|
||||||
|
&& mounts['Cactus-Desert']
|
||||||
|
&& mounts['Cactus-Red']
|
||||||
|
&& mounts['Cactus-Shade']
|
||||||
|
&& mounts['Cactus-White']
|
||||||
|
&& mounts['Cactus-Golden']
|
||||||
|
&& mounts['Cactus-CottonCandyPink']
|
||||||
|
&& mounts['Cactus-CottonCandyBlue']
|
||||||
|
&& mounts['Hedgehog-Zombie']
|
||||||
|
&& mounts['Hedgehog-Skeleton']
|
||||||
|
&& mounts['Hedgehog-Base']
|
||||||
|
&& mounts['Hedgehog-Desert']
|
||||||
|
&& mounts['Hedgehog-Red']
|
||||||
|
&& mounts['Hedgehog-Shade']
|
||||||
|
&& mounts['Hedgehog-White']
|
||||||
|
&& mounts['Hedgehog-Golden']
|
||||||
|
&& mounts['Hedgehog-CottonCandyPink']
|
||||||
|
&& mounts['Hedgehog-CottonCandyBlue']
|
||||||
|
&& mounts['Rock-Zombie']
|
||||||
|
&& mounts['Rock-Skeleton']
|
||||||
|
&& mounts['Rock-Base']
|
||||||
|
&& mounts['Rock-Desert']
|
||||||
|
&& mounts['Rock-Red']
|
||||||
|
&& mounts['Rock-Shade']
|
||||||
|
&& mounts['Rock-White']
|
||||||
|
&& mounts['Rock-Golden']
|
||||||
|
&& mounts['Rock-CottonCandyPink']
|
||||||
|
&& mounts['Rock-CottonCandyBlue'] ) {
|
||||||
|
set['achievements.roughRider'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2023-02-01') },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1]._id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
87
migrations/archive/2023/20231228_nye.js
Normal file
87
migrations/archive/2023/20231228_nye.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20231228_nye';
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const set = { migration: MIGRATION_NAME };
|
||||||
|
let push = {};
|
||||||
|
|
||||||
|
if (typeof user.items.gear.owned.head_special_nye2022 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2023'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2022'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2021'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2020'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2019'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2018'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2017'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2016'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2015'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2014'] = true;
|
||||||
|
} else {
|
||||||
|
set['items.gear.owned.head_special_nye'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
push.notifications = {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_head_special_nye',
|
||||||
|
title: 'Happy New Year!',
|
||||||
|
text: 'Check your Equipment for this year\'s party hat!',
|
||||||
|
destination: 'inventory/equipment',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return await User.updateOne({_id: user._id}, {$set: set, $push: push}).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2023-12-01') },
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
102
migrations/archive/2024/20240131_habit_birthday.js
Normal file
102
migrations/archive/2024/20240131_habit_birthday.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const MIGRATION_NAME = '20240131_habit_birthday';
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
const inc = {
|
||||||
|
'items.food.Cake_Skeleton': 1,
|
||||||
|
'items.food.Cake_Base': 1,
|
||||||
|
'items.food.Cake_CottonCandyBlue': 1,
|
||||||
|
'items.food.Cake_CottonCandyPink': 1,
|
||||||
|
'items.food.Cake_Shade': 1,
|
||||||
|
'items.food.Cake_White': 1,
|
||||||
|
'items.food.Cake_Golden': 1,
|
||||||
|
'items.food.Cake_Zombie': 1,
|
||||||
|
'items.food.Cake_Desert': 1,
|
||||||
|
'items.food.Cake_Red': 1,
|
||||||
|
'achievements.habitBirthdays': 1,
|
||||||
|
};
|
||||||
|
const set = {};
|
||||||
|
const push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_namingDay_cake',
|
||||||
|
title: 'Happy Habit Birthday!',
|
||||||
|
text: 'Habitica turns 11 today! Enjoy free party robes and cake!',
|
||||||
|
destination: 'inventory/equipment',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
set.migration = MIGRATION_NAME;
|
||||||
|
|
||||||
|
if (typeof user.items.gear.owned.armor_special_birthday2023 !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2024'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_birthday2022 !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2023'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_birthday2021 !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2022'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_birthday2020 !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2021'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_birthday2019 !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2020'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2019'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2018'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2017'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2016'] = true;
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_birthday2015'] = true;
|
||||||
|
} else {
|
||||||
|
set['items.gear.owned.armor_special_birthday'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return await User.updateOne({_id: user._id}, {$inc: inc, $set: set, $push: push}).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2023-12-23')},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
89
migrations/archive/2024/20240318_pet_group_achievements.js
Normal file
89
migrations/archive/2024/20240318_pet_group_achievements.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '202403_pet_group_achievements';
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
let set = {
|
||||||
|
migration: MIGRATION_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.pets) {
|
||||||
|
const pets = user.items.pets;
|
||||||
|
if (pets['GuineaPig-Zombie'] > 0
|
||||||
|
&& pets['GuineaPig-Skeleton'] > 0
|
||||||
|
&& pets['GuineaPig-Base'] > 0
|
||||||
|
&& pets['GuineaPig-Desert'] > 0
|
||||||
|
&& pets['GuineaPig-Red'] > 0
|
||||||
|
&& pets['GuineaPig-Shade'] > 0
|
||||||
|
&& pets['GuineaPig-White']> 0
|
||||||
|
&& pets['GuineaPig-Golden'] > 0
|
||||||
|
&& pets['GuineaPig-CottonCandyBlue'] > 0
|
||||||
|
&& pets['GuineaPig-CottonCandyPink'] > 0
|
||||||
|
&& pets['Squirrel-Zombie'] > 0
|
||||||
|
&& pets['Squirrel-Skeleton'] > 0
|
||||||
|
&& pets['Squirrel-Base'] > 0
|
||||||
|
&& pets['Squirrel-Desert'] > 0
|
||||||
|
&& pets['Squirrel-Red'] > 0
|
||||||
|
&& pets['Squirrel-Shade'] > 0
|
||||||
|
&& pets['Squirrel-White'] > 0
|
||||||
|
&& pets['Squirrel-Golden'] > 0
|
||||||
|
&& pets['Squirrel-CottonCandyBlue'] > 0
|
||||||
|
&& pets['Squirrel-CottonCandyPink'] > 0
|
||||||
|
&& pets['Rat-Zombie'] > 0
|
||||||
|
&& pets['Rat-Skeleton'] > 0
|
||||||
|
&& pets['Rat-Base'] > 0
|
||||||
|
&& pets['Rat-Desert'] > 0
|
||||||
|
&& pets['Rat-Red'] > 0
|
||||||
|
&& pets['Rat-Shade'] > 0
|
||||||
|
&& pets['Rat-White'] > 0
|
||||||
|
&& pets['Rat-Golden'] > 0
|
||||||
|
&& pets['Rat-CottonCandyBlue'] > 0
|
||||||
|
&& pets['Rat-CottonCandyPink'] > 0 ) {
|
||||||
|
set['achievements.rodentRuler'] = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return await User.updateOne({ _id: user._id }, { $set: set }).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2024-02-01') },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1]._id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
99
migrations/archive/2024/20240516_pet_group_achievements.js
Normal file
99
migrations/archive/2024/20240516_pet_group_achievements.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '202405_pet_group_achievements';
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
let set = {
|
||||||
|
migration: MIGRATION_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.pets) {
|
||||||
|
const pets = user.items.pets;
|
||||||
|
if (pets['LionCub-Zombie'] > 0
|
||||||
|
&& pets['LionCub-Skeleton'] > 0
|
||||||
|
&& pets['LionCub-Base'] > 0
|
||||||
|
&& pets['LionCub-Desert'] > 0
|
||||||
|
&& pets['LionCub-Red'] > 0
|
||||||
|
&& pets['LionCub-Shade'] > 0
|
||||||
|
&& pets['LionCub-White']> 0
|
||||||
|
&& pets['LionCub-Golden'] > 0
|
||||||
|
&& pets['LionCub-CottonCandyBlue'] > 0
|
||||||
|
&& pets['LionCub-CottonCandyPink'] > 0
|
||||||
|
&& pets['TigerCub-Zombie'] > 0
|
||||||
|
&& pets['TigerCub-Skeleton'] > 0
|
||||||
|
&& pets['TigerCub-Base'] > 0
|
||||||
|
&& pets['TigerCub-Desert'] > 0
|
||||||
|
&& pets['TigerCub-Red'] > 0
|
||||||
|
&& pets['TigerCub-Shade'] > 0
|
||||||
|
&& pets['TigerCub-White'] > 0
|
||||||
|
&& pets['TigerCub-Golden'] > 0
|
||||||
|
&& pets['TigerCub-CottonCandyBlue'] > 0
|
||||||
|
&& pets['TigerCub-CottonCandyPink'] > 0
|
||||||
|
&& pets['Sabretooth-Zombie'] > 0
|
||||||
|
&& pets['Sabretooth-Skeleton'] > 0
|
||||||
|
&& pets['Sabretooth-Base'] > 0
|
||||||
|
&& pets['Sabretooth-Desert'] > 0
|
||||||
|
&& pets['Sabretooth-Red'] > 0
|
||||||
|
&& pets['Sabretooth-Shade'] > 0
|
||||||
|
&& pets['Sabretooth-White'] > 0
|
||||||
|
&& pets['Sabretooth-Golden'] > 0
|
||||||
|
&& pets['Sabretooth-CottonCandyBlue'] > 0
|
||||||
|
&& pets['Sabretooth-CottonCandyPink'] > 0
|
||||||
|
&& pets['Cheetah-Zombie'] > 0
|
||||||
|
&& pets['Cheetah-Skeleton'] > 0
|
||||||
|
&& pets['Cheetah-Base'] > 0
|
||||||
|
&& pets['Cheetah-Desert'] > 0
|
||||||
|
&& pets['Cheetah-Red'] > 0
|
||||||
|
&& pets['Cheetah-Shade'] > 0
|
||||||
|
&& pets['Cheetah-White'] > 0
|
||||||
|
&& pets['Cheetah-Golden'] > 0
|
||||||
|
&& pets['Cheetah-CottonCandyBlue'] > 0
|
||||||
|
&& pets['Cheetah-CottonCandyPink'] > 0 ) {
|
||||||
|
set['achievements.cats'] = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return await User.updateOne({ _id: user._id }, { $set: set }).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2024-03-01') },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1]._id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
149
migrations/archive/2024/20240621_veteran_pets.js
Normal file
149
migrations/archive/2024/20240621_veteran_pets.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20240621_veteran_pet_ladder';
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const set = {};
|
||||||
|
let push = { notifications: { $each: [] }};
|
||||||
|
|
||||||
|
set.migration = MIGRATION_NAME;
|
||||||
|
if (user.items.pets['Dragon-Veteran']) {
|
||||||
|
set['items.pets.Cactus-Veteran'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'icon_pet_veteran_cactus',
|
||||||
|
title: 'You’ve received a Veteran Pet!',
|
||||||
|
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Cactus and 24 Gems!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user.items.pets['Fox-Veteran']) {
|
||||||
|
set['items.pets.Dragon-Veteran'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'icon_pet_veteran_dragon',
|
||||||
|
title: 'You’ve received a Veteran Pet!',
|
||||||
|
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Dragon and 24 Gems!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user.items.pets['Bear-Veteran']) {
|
||||||
|
set['items.pets.Fox-Veteran'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'icon_pet_veteran_fox',
|
||||||
|
title: 'You’ve received a Veteran Pet!',
|
||||||
|
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Fox and 24 Gems!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user.items.pets['Lion-Veteran']) {
|
||||||
|
set['items.pets.Bear-Veteran'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'icon_pet_veteran_bear',
|
||||||
|
title: 'You’ve received a Veteran Pet!',
|
||||||
|
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Bear and 24 Gems!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user.items.pets['Tiger-Veteran']) {
|
||||||
|
set['items.pets.Lion-Veteran'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'icon_pet_veteran_lion',
|
||||||
|
title: 'You’ve received a Veteran Pet!',
|
||||||
|
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Lion and 24 Gems!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user.items.pets['Wolf-Veteran']) {
|
||||||
|
set['items.pets.Tiger-Veteran'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'icon_pet_veteran_tiger',
|
||||||
|
title: 'You’ve received a Veteran Pet!',
|
||||||
|
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Tiger and 24 Gems!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
set['items.pets.Wolf-Veteran'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'icon_pet_veteran_wolf',
|
||||||
|
title: 'You’ve received a Veteran Pet!',
|
||||||
|
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Wolf and 24 Gems!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
await user.updateBalance(
|
||||||
|
6,
|
||||||
|
'admin_update_balance',
|
||||||
|
'',
|
||||||
|
'Veteran Ladder award',
|
||||||
|
);
|
||||||
|
|
||||||
|
return await User.updateOne(
|
||||||
|
{ _id: user._id },
|
||||||
|
{ $set: set, $push: push, $inc: { balance: 6 } },
|
||||||
|
).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2024-05-21') },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
migration: 1,
|
||||||
|
contributor: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
47
migrations/archive/2024/20240806_purge_invite_accepted.js
Normal file
47
migrations/archive/2024/20240806_purge_invite_accepted.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const MIGRATION_NAME = '2024_purge_invite_accepted';
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUsers (userIds) {
|
||||||
|
count += userIds.length;
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${userIds[0]}`);
|
||||||
|
|
||||||
|
return await User.updateMany(
|
||||||
|
{ _id: { $in: userIds } },
|
||||||
|
{ $pull: { notifications: { type: 'GROUP_INVITE_ACCEPTED' } } },
|
||||||
|
).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
'notifications.type': 'GROUP_INVITE_ACCEPTED',
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2024-06-25') },
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({ _id: 1 })
|
||||||
|
.select({ _id: 1 })
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const userIds = users.map(user => user._id);
|
||||||
|
|
||||||
|
await updateUsers(userIds); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
115
migrations/archive/2024/20241119_gem_caps_hourglasses.js
Normal file
115
migrations/archive/2024/20241119_gem_caps_hourglasses.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20241119_gem_caps_hourglasses';
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count += 1;
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
const { consecutive, customerId, dateTerminated, planId } = user.purchased.plan;
|
||||||
|
const isRecurring = customerId !== 'Gift' && !dateTerminated;
|
||||||
|
const updateOp = {
|
||||||
|
$set: {
|
||||||
|
migration: MIGRATION_NAME,
|
||||||
|
'purchased.plan.consecutive.gemCapExtra': Math.max(2 * Math.ceil((consecutive.gemCapExtra + 1) / 2, 26)),
|
||||||
|
},
|
||||||
|
$inc: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
let hourglassBonus = 0;
|
||||||
|
|
||||||
|
if (isRecurring) {
|
||||||
|
await user.updateBalance(
|
||||||
|
5,
|
||||||
|
'admin_update_balance',
|
||||||
|
'',
|
||||||
|
'Subscription Reward Migration',
|
||||||
|
);
|
||||||
|
updateOp.$inc.balance = 5;
|
||||||
|
switch (planId) {
|
||||||
|
case 'basic':
|
||||||
|
case 'basic_earned':
|
||||||
|
case 'group_plan_auto':
|
||||||
|
hourglassBonus = 2;
|
||||||
|
break;
|
||||||
|
case 'basic_3mo':
|
||||||
|
case 'basic_6mo':
|
||||||
|
case 'google_6mo':
|
||||||
|
hourglassBonus = 4;
|
||||||
|
break;
|
||||||
|
case 'basic_12mo':
|
||||||
|
hourglassBonus = 12;
|
||||||
|
updateOp.$set['purchased.plan.hourglassPromoReceived'] = new Date();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
hourglassBonus = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hourglassBonus) {
|
||||||
|
updateOp.$inc['purchased.plan.consecutive.trinkets'] = hourglassBonus;
|
||||||
|
await user.updateHourglasses(
|
||||||
|
hourglassBonus,
|
||||||
|
'admin_update_balance',
|
||||||
|
'',
|
||||||
|
'Subscription Reward Migration',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
updateOp.$push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_subscriber_reward',
|
||||||
|
title: 'Thanks for being a subscriber!',
|
||||||
|
text: 'Enjoy these extra Mystic Hourglasses and Gems to celebrate our new benefits.',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return await User.updateOne(
|
||||||
|
{ _id: user._id },
|
||||||
|
updateOp,
|
||||||
|
).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
'purchased.plan.customerId': { $exists: true },
|
||||||
|
$or: [
|
||||||
|
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
||||||
|
{ 'purchased.plan.dateTerminated': null },
|
||||||
|
{ 'purchased.plan.dateTerminated': { $gt: new Date() } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
purchased: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
let migrationName = '20180904_takeThis.js'; // Update per month
|
|
||||||
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
|
||||||
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Award Take This ladder items to participants in this month's challenge
|
|
||||||
*/
|
|
||||||
|
|
||||||
import monk from 'monk';
|
|
||||||
import nconf from 'nconf';
|
|
||||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
|
||||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
|
||||||
|
|
||||||
function processUsers (lastId) {
|
|
||||||
// specify a query to limit the affected users (empty for all users):
|
|
||||||
let query = {
|
|
||||||
migration: {$ne: migrationName},
|
|
||||||
challenges: {$in: ['1044ec0c-4a85-48c5-9f36-d51c0c62c7d3']}, // Update per month
|
|
||||||
};
|
|
||||||
|
|
||||||
if (lastId) {
|
|
||||||
query._id = {
|
|
||||||
$gt: lastId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
dbUsers.find(query, {
|
|
||||||
sort: {_id: 1},
|
|
||||||
limit: 250,
|
|
||||||
fields: [
|
|
||||||
'items.gear.owned',
|
|
||||||
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
|
||||||
})
|
|
||||||
.then(updateUsers)
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
return exiting(1, `ERROR! ${ err}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let progressCount = 1000;
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
function updateUsers (users) {
|
|
||||||
if (!users || users.length === 0) {
|
|
||||||
console.warn('All appropriate users found and modified.');
|
|
||||||
displayData();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let userPromises = users.map(updateUser);
|
|
||||||
let lastUser = users[users.length - 1];
|
|
||||||
|
|
||||||
return Promise.all(userPromises)
|
|
||||||
.then(() => {
|
|
||||||
processUsers(lastUser._id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUser (user) {
|
|
||||||
count++;
|
|
||||||
|
|
||||||
let set = {};
|
|
||||||
let push;
|
|
||||||
|
|
||||||
if (typeof user.items.gear.owned.back_special_takeThis !== 'undefined') {
|
|
||||||
set = {migration: migrationName};
|
|
||||||
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
|
|
||||||
set = {migration: migrationName, 'items.gear.owned.back_special_takeThis': false};
|
|
||||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', _id: monk.id()}};
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
|
|
||||||
set = {migration: migrationName, 'items.gear.owned.body_special_takeThis': false};
|
|
||||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', _id: monk.id()}};
|
|
||||||
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
|
|
||||||
set = {migration: migrationName, 'items.gear.owned.head_special_takeThis': false};
|
|
||||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', _id: monk.id()}};
|
|
||||||
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
|
|
||||||
set = {migration: migrationName, 'items.gear.owned.armor_special_takeThis': false};
|
|
||||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', _id: monk.id()}};
|
|
||||||
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
|
|
||||||
set = {migration: migrationName, 'items.gear.owned.weapon_special_takeThis': false};
|
|
||||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', _id: monk.id()}};
|
|
||||||
} else {
|
|
||||||
set = {migration: migrationName, 'items.gear.owned.shield_special_takeThis': false};
|
|
||||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', _id: monk.id()}};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (push) {
|
|
||||||
dbUsers.update({_id: user._id}, {$set: set, $push: push});
|
|
||||||
} else {
|
|
||||||
dbUsers.update({_id: user._id}, {$set: set});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
|
||||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayData () {
|
|
||||||
console.warn(`\n${ count } users processed\n`);
|
|
||||||
return exiting(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function exiting (code, msg) {
|
|
||||||
code = code || 0; // 0 = success
|
|
||||||
if (code && !msg) {
|
|
||||||
msg = 'ERROR!';
|
|
||||||
}
|
|
||||||
if (msg) {
|
|
||||||
if (code) {
|
|
||||||
console.error(msg);
|
|
||||||
} else {
|
|
||||||
console.log(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
process.exit(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = processUsers;
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import csv
|
|
||||||
|
|
||||||
with open(r"/home/slappybag/Documents/SurveyScrape.csv") as f:
|
|
||||||
reader = csv.reader(f, delimiter=',', quotechar='"')
|
|
||||||
column = []
|
|
||||||
for row in reader:
|
|
||||||
if row:
|
|
||||||
column.append(row[4])
|
|
||||||
|
|
||||||
print column
|
|
||||||
@@ -21,12 +21,14 @@ async function handOutJackalopes () {
|
|||||||
if (user.party._id) groupList.push(user.party._id);
|
if (user.party._id) groupList.push(user.party._id);
|
||||||
groupList = groupList.concat(user.guilds);
|
groupList = groupList.concat(user.guilds);
|
||||||
|
|
||||||
const subscribedGroup = await Group.findOne({
|
const subscribedGroup = await Group.findOne(
|
||||||
|
{
|
||||||
_id: { $in: groupList },
|
_id: { $in: groupList },
|
||||||
'purchased.plan.planId': 'group_monthly',
|
'purchased.plan.planId': 'group_monthly',
|
||||||
'purchased.plan.dateTerminated': null,
|
'purchased.plan.dateTerminated': null,
|
||||||
},
|
},
|
||||||
{ _id: 1 });
|
{ _id: 1 },
|
||||||
|
);
|
||||||
|
|
||||||
if (subscribedGroup) {
|
if (subscribedGroup) {
|
||||||
User.update({ _id: user._id }, { $set: { 'items.mounts.Jackalope-RoyalPurple': true } }).exec();
|
User.update({ _id: user._id }, { $set: { 'items.mounts.Jackalope-RoyalPurple': true } }).exec();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
if (count % progressCount === 0) {
|
if (count % progressCount === 0) {
|
||||||
console.warn(`${count} ${user._id}`);
|
console.warn(`${count} ${user._id}`);
|
||||||
|
// eslint-disable-next-line no-promise-executor-return
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ async function updateUser (user) {
|
|||||||
[{ name: 'BASE_URL', content: BASE_URL }], // Add variables from template
|
[{ name: 'BASE_URL', content: BASE_URL }], // Add variables from template
|
||||||
);
|
);
|
||||||
|
|
||||||
return User.update({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
|
return User.updateOne({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
return User.update({ _id: user._id }, { $set: set }).exec();
|
return User.updateOne({ _id: user._id }, { $set: set }).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
const query = {
|
const query = {
|
||||||
migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.local.lowerCaseUsername': 'olson1',
|
'auth.local.username': 'ExampleHabitican',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
return User.update({ _id: user._id }, { $set: set }).exec();
|
return User.updateOne({ _id: user._id }, { $set: set }).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
const query = {
|
const query = {
|
||||||
migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.local.username': 'SabreTest',
|
'auth.local.username': 'ExampleHabitican',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
|
|||||||
175
migrations/users/habitoween.js
Normal file
175
migrations/users/habitoween.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Award Habitoween ladder items to participants in this month's Habitoween festivities
|
||||||
|
*/
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
|
const MIGRATION_NAME = '20241030_habitoween_ladder'; // Update when running in future years
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
const set = { migration: MIGRATION_NAME };
|
||||||
|
const inc = {
|
||||||
|
'items.food.Candy_Skeleton': 1,
|
||||||
|
'items.food.Candy_Base': 1,
|
||||||
|
'items.food.Candy_CottonCandyBlue': 1,
|
||||||
|
'items.food.Candy_CottonCandyPink': 1,
|
||||||
|
'items.food.Candy_Shade': 1,
|
||||||
|
'items.food.Candy_White': 1,
|
||||||
|
'items.food.Candy_Golden': 1,
|
||||||
|
'items.food.Candy_Zombie': 1,
|
||||||
|
'items.food.Candy_Desert': 1,
|
||||||
|
'items.food.Candy_Red': 1,
|
||||||
|
};
|
||||||
|
const push = { notifications: { $each: [] } };
|
||||||
|
|
||||||
|
if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-RoyalPurple']) {
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_habitoween_candy',
|
||||||
|
title: 'Happy Habitoween!',
|
||||||
|
text: 'For this spooky celebration, you\'ve received an assortment of candy for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-RoyalPurple']) {
|
||||||
|
set['items.mounts.JackOLantern-RoyalPurple'] = true;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_habitoween_purple_mount',
|
||||||
|
title: 'Happy Habitoween!',
|
||||||
|
text: 'For this spooky celebration, you\'ve received a Royal Purple Jack-O-Lantern Mount and an assortment of candy for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Glow']) {
|
||||||
|
set['items.pets.JackOLantern-RoyalPurple'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_habitoween_purple_pet',
|
||||||
|
title: 'Happy Habitoween!',
|
||||||
|
text: 'For this spooky celebration, you\'ve received a Royal Purple Jack-O-Lantern Pet and an assortment of candy for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Glow']) {
|
||||||
|
set['items.mounts.JackOLantern-Glow'] = true;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_habitoween_glow_mount',
|
||||||
|
title: 'Happy Habitoween!',
|
||||||
|
text: 'For this spooky celebration, you\'ve received a Glow-in-the-Dark Jack-O-Lantern Mount and an assortment of candy for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Ghost']) {
|
||||||
|
set['items.pets.JackOLantern-Glow'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_habitoween_glow_pet',
|
||||||
|
title: 'Happy Habitoween!',
|
||||||
|
text: 'For this spooky celebration, you\'ve received a Glow-in-the-Dark Jack-O-Lantern Pet and an assortment of candy for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
|
||||||
|
set['items.mounts.JackOLantern-Ghost'] = true;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_habitoween_ghost_mount',
|
||||||
|
title: 'Happy Habitoween!',
|
||||||
|
text: 'For this spooky celebration, you\'ve received a Ghost Jack-O-Lantern Mount and an assortment of candy for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
|
||||||
|
set['items.pets.JackOLantern-Ghost'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_habitoween_ghost_pet',
|
||||||
|
title: 'Happy Habitoween!',
|
||||||
|
text: 'For this spooky celebration, you\'ve received a Ghost Jack-O-Lantern Pet and an assortment of candy for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
|
||||||
|
set['items.mounts.JackOLantern-Base'] = true;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_habitoween_base_mount',
|
||||||
|
title: 'Happy Habitoween!',
|
||||||
|
text: 'For this spooky celebration, you\'ve received a Jack-O-Lantern Mount and an assortment of candy for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
set['items.pets.JackOLantern-Base'] = 5;
|
||||||
|
push.notifications.$each.push({
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_habitoween_base_pet',
|
||||||
|
title: 'Happy Habitoween!',
|
||||||
|
text: 'For this spooky celebration, you\'ve received a Jack-O-Lantern Pet and an assortment of candy for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
return User.updateOne({ _id: user._id }, { $inc: inc, $push: push, $set: set }).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
const query = {
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2024-10-01') },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({ _id: 1 })
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
}
|
||||||
167
migrations/users/harvest_feast.js
Normal file
167
migrations/users/harvest_feast.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
|
const MIGRATION_NAME = '20241120_harvest_feast';
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
const updateOp = {
|
||||||
|
$set: { migration: MIGRATION_NAME },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof user.items.gear.owned.head_special_turkeyHelmGilded !== 'undefined') {
|
||||||
|
updateOp.$inc = {
|
||||||
|
'items.food.Pie_Base': 1,
|
||||||
|
'items.food.Pie_CottonCandyBlue': 1,
|
||||||
|
'items.food.Pie_CottonCandyPink': 1,
|
||||||
|
'items.food.Pie_Desert': 1,
|
||||||
|
'items.food.Pie_Golden': 1,
|
||||||
|
'items.food.Pie_Red': 1,
|
||||||
|
'items.food.Pie_Shade': 1,
|
||||||
|
'items.food.Pie_Skeleton': 1,
|
||||||
|
'items.food.Pie_Zombie': 1,
|
||||||
|
'items.food.Pie_White': 1,
|
||||||
|
};
|
||||||
|
updateOp.$push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_harvestfeast_pie',
|
||||||
|
title: 'Happy Harvest Feast!',
|
||||||
|
text: 'Gobble gobble, you\'ve received an assortment of pie for your Pets!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_turkeyArmorBase !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_turkeyHelmGilded'] = true;
|
||||||
|
updateOp.$set['items.gear.owned.armor_special_turkeyArmorGilded'] = true;
|
||||||
|
updateOp.$set['items.gear.owned.back_special_turkeyTailGilded'] = true;
|
||||||
|
updateOp.$push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_harvestfeast_gilded_set',
|
||||||
|
title: 'Happy Harvest Feast!',
|
||||||
|
text: 'Gobble gobble, you\'ve received the Gilded Turkey Armor, Helm, and Tail!',
|
||||||
|
destination: '/inventory/equipment',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_turkeyHelmBase'] = true;
|
||||||
|
updateOp.$set['items.gear.owned.armor_special_turkeyArmorBase'] = true;
|
||||||
|
updateOp.$set['items.gear.owned.back_special_turkeyTailBase'] = true;
|
||||||
|
updateOp.$push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_harvestfeast_base_set',
|
||||||
|
title: 'Happy Harvest Feast!',
|
||||||
|
text: 'Gobble gobble, you\'ve received the Turkey Armor, Helm, and Tail!',
|
||||||
|
destination: '/inventory/equipment',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (user.items && user.items.pets && user.items.pets['Turkey-Gilded']) {
|
||||||
|
updateOp.$set['items.mounts.Turkey-Gilded'] = true;
|
||||||
|
updateOp.$push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_harvestfeast_gilded_mount',
|
||||||
|
title: 'Happy Harvest Feast!',
|
||||||
|
text: 'Gobble gobble, you\'ve received the Gilded Turkey Mount!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Base']) {
|
||||||
|
updateOp.$set['items.pets.Turkey-Gilded'] = 5;
|
||||||
|
updateOp.$push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_harvestfeast_gilded_pet',
|
||||||
|
title: 'Happy Harvest Feast!',
|
||||||
|
text: 'Gobble gobble, you\'ve received the Gilded Turkey Pet!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (user.items && user.items.pets && user.items.pets['Turkey-Base']) {
|
||||||
|
updateOp.$set['items.mounts.Turkey-Base'] = true;
|
||||||
|
updateOp.$push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_harvestfeast_base_mount',
|
||||||
|
title: 'Happy Harvest Feast!',
|
||||||
|
text: 'Gobble gobble, you\'ve received the Turkey Mount!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
updateOp.$set['items.pets.Turkey-Base'] = 5;
|
||||||
|
updateOp.$push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_harvestfeast_base_pet',
|
||||||
|
title: 'Happy Harvest Feast!',
|
||||||
|
text: 'Gobble gobble, you\'ve received the Turkey Pet!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return User.updateOne({ _id: user._id }, updateOp).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
const query = {
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2024-10-20') },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({ _id: 1 })
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
const MIGRATION_NAME = '20230731_naming_day';
|
import { model as User } from '../../website/server/models/user';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
|
|
||||||
import { model as User } from '../../../website/server/models/user';
|
|
||||||
|
|
||||||
|
const MIGRATION_NAME = '20240731_naming_day';
|
||||||
const progressCount = 1000;
|
const progressCount = 1000;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
async function updateUser (user) {
|
async function updateUser (user) {
|
||||||
count++;
|
count += 1;
|
||||||
|
|
||||||
let set;
|
let set;
|
||||||
let push;
|
let push;
|
||||||
@@ -115,16 +113,16 @@ async function updateUser (user) {
|
|||||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
if (push) {
|
if (push) {
|
||||||
return await user.updateOne({ $set: set, $inc: inc, $push: push }).exec();
|
return user.updateOne({ $set: set, $inc: inc, $push: push }).exec();
|
||||||
} else {
|
|
||||||
return await user.updateOne({ $set: set, $inc: inc }).exec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return user.updateOne({ $set: set, $inc: inc }).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
let query = {
|
const query = {
|
||||||
migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2023-07-01') },
|
'auth.timestamps.loggedin': { $gt: new Date('2024-07-01') },
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
@@ -152,4 +150,4 @@ export default async function processUsers () {
|
|||||||
|
|
||||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
125
migrations/users/nye.js
Normal file
125
migrations/users/nye.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
|
const MIGRATION_NAME = '20231228_nye';
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
const updateOp = {
|
||||||
|
$set: { migration: MIGRATION_NAME },
|
||||||
|
$push: { },
|
||||||
|
};
|
||||||
|
const data = {
|
||||||
|
title: 'Happy New Year!',
|
||||||
|
destination: '/inventory/equipment',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof user.items.gear.owned.head_special_nye2023 !== 'undefined') {
|
||||||
|
updateOp.$inc = {
|
||||||
|
'items.food.Candy_Skeleton': 1,
|
||||||
|
'items.food.Candy_Base': 1,
|
||||||
|
'items.food.Candy_CottonCandyBlue': 1,
|
||||||
|
'items.food.Candy_CottonCandyPink': 1,
|
||||||
|
'items.food.Candy_Shade': 1,
|
||||||
|
'items.food.Candy_White': 1,
|
||||||
|
'items.food.Candy_Golden': 1,
|
||||||
|
'items.food.Candy_Zombie': 1,
|
||||||
|
'items.food.Candy_Desert': 1,
|
||||||
|
'items.food.Candy_Red': 1,
|
||||||
|
};
|
||||||
|
data.icon = 'notif_candy_nye';
|
||||||
|
data.text = 'You’ve received an assortment of candy to celebrate with your Pets!';
|
||||||
|
data.destination = '/inventory/stable';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2022 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2023'] = true;
|
||||||
|
data.icon = 'notif_2023hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Ludicrous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2022'] = true;
|
||||||
|
data.icon = 'notif_2022hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Fabulous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2021'] = true;
|
||||||
|
data.icon = 'notif_2021hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Preposterous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2020'] = true;
|
||||||
|
data.icon = 'notif_2020hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Extravagant Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2019'] = true;
|
||||||
|
data.icon = 'notif_2019hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Outrageous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2018'] = true;
|
||||||
|
data.icon = 'notif_2018hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Outlandish Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2017'] = true;
|
||||||
|
data.icon = 'notif_2017hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Fanciful Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2016'] = true;
|
||||||
|
data.icon = 'notif_2016hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Whimsical Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2015'] = true;
|
||||||
|
data.icon = 'notif_2015hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Ridiculous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2014'] = true;
|
||||||
|
data.icon = 'notif_2014hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Silly Party Hat!';
|
||||||
|
} else {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye'] = true;
|
||||||
|
data.icon = 'notif_2013hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Absurd Party Hat!';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOp.$push.notifications = {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data,
|
||||||
|
seen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return User.updateOne({ _id: user._id }, updateOp).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
const query = {
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2023-12-01') },
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({ _id: 1 })
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
|
|||||||
|
|
||||||
import { model as User } from '../../website/server/models/user';
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
const MIGRATION_NAME = '20230314_pi_day';
|
const MIGRATION_NAME = '20240314_pi_day';
|
||||||
|
|
||||||
const progressCount = 1000;
|
const progressCount = 1000;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|||||||
24179
package-lock.json
generated
24179
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
60
package.json
60
package.json
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||||
"version": "5.11.1",
|
"version": "5.32.5",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.22.10",
|
||||||
"@babel/preset-env": "^7.22.10",
|
"@babel/preset-env": "^7.22.10",
|
||||||
"@babel/register": "^7.22.5",
|
"@babel/register": "^7.22.15",
|
||||||
"@google-cloud/trace-agent": "^7.1.2",
|
"@google-cloud/trace-agent": "^7.1.2",
|
||||||
"@parse/node-apn": "^5.2.3",
|
"@parse/node-apn": "^5.2.3",
|
||||||
"@slack/webhook": "^6.1.0",
|
"@slack/webhook": "^6.1.0",
|
||||||
@@ -15,8 +15,9 @@
|
|||||||
"amplitude": "^6.0.0",
|
"amplitude": "^6.0.0",
|
||||||
"apidoc": "^0.54.0",
|
"apidoc": "^0.54.0",
|
||||||
"apple-auth": "^1.0.9",
|
"apple-auth": "^1.0.9",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.3",
|
||||||
"bootstrap": "^4.6.2",
|
"bootstrap": "^4.6.2",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-session": "^2.0.0",
|
"cookie-session": "^2.0.0",
|
||||||
@@ -24,36 +25,37 @@
|
|||||||
"csv-stringify": "^5.6.5",
|
"csv-stringify": "^5.6.5",
|
||||||
"cwait": "^1.1.1",
|
"cwait": "^1.1.1",
|
||||||
"domain-middleware": "~0.1.0",
|
"domain-middleware": "~0.1.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-config-habitrpg": "^6.2.0",
|
"eslint-config-habitrpg": "^6.2.3",
|
||||||
"eslint-plugin-mocha": "^5.0.0",
|
"eslint-plugin-mocha": "^5.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.21.1",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"express-validator": "^5.2.0",
|
"express-validator": "^5.2.0",
|
||||||
|
"firebase-admin": "^12.1.1",
|
||||||
"glob": "^8.1.0",
|
"glob": "^8.1.0",
|
||||||
"got": "^11.8.6",
|
"got": "^11.8.6",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"gulp-babel": "^8.0.0",
|
"gulp-babel": "^8.0.0",
|
||||||
|
"gulp-filter": "^7.0.0",
|
||||||
"gulp-imagemin": "^7.1.0",
|
"gulp-imagemin": "^7.1.0",
|
||||||
"gulp-nodemon": "^2.5.0",
|
"gulp-nodemon": "^2.5.0",
|
||||||
"nodemon": "^2.0.20",
|
|
||||||
"gulp.spritesmith": "^6.13.0",
|
"gulp.spritesmith": "^6.13.0",
|
||||||
"habitica-markdown": "^3.0.0",
|
"habitica-markdown": "^3.0.0",
|
||||||
"helmet": "^4.6.0",
|
"helmet": "^4.6.0",
|
||||||
"image-size": "^1.0.2",
|
|
||||||
"in-app-purchase": "^1.11.3",
|
"in-app-purchase": "^1.11.3",
|
||||||
"js2xmlparser": "^5.0.0",
|
"js2xmlparser": "^5.0.0",
|
||||||
"jsonwebtoken": "^9.0.1",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"jwks-rsa": "^2.1.5",
|
"jwks-rsa": "^2.1.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"merge-stream": "^2.0.0",
|
"merge-stream": "^2.0.0",
|
||||||
"method-override": "^3.0.0",
|
"method-override": "^3.0.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-recur": "^1.0.7",
|
"moment-recur": "^1.0.7",
|
||||||
"mongoose": "^5.13.20",
|
"mongoose": "^7.8.3",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"nconf": "^0.12.0",
|
"nconf": "^0.12.1",
|
||||||
"node-gcm": "^1.0.5",
|
"node-gcm": "^1.0.5",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
"on-headers": "^1.0.2",
|
"on-headers": "^1.0.2",
|
||||||
"passport": "^0.5.3",
|
"passport": "^0.5.3",
|
||||||
"passport-facebook": "^3.0.0",
|
"passport-facebook": "^3.0.0",
|
||||||
@@ -64,25 +66,25 @@
|
|||||||
"ps-tree": "^1.0.0",
|
"ps-tree": "^1.0.0",
|
||||||
"rate-limiter-flexible": "^2.4.2",
|
"rate-limiter-flexible": "^2.4.2",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
"regenerator-runtime": "^0.13.11",
|
|
||||||
"remove-markdown": "^0.5.0",
|
"remove-markdown": "^0.5.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"short-uuid": "^4.2.2",
|
"short-uuid": "^4.2.2",
|
||||||
|
"sinon": "^15.2.0",
|
||||||
"stripe": "^12.18.0",
|
"stripe": "^12.18.0",
|
||||||
"superagent": "^8.1.2",
|
"superagent": "^8.1.2",
|
||||||
"universal-analytics": "^0.5.3",
|
"universal-analytics": "^0.5.3",
|
||||||
"useragent": "^2.1.9",
|
"useragent": "^2.1.9",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"validator": "^13.11.0",
|
"validator": "^13.11.0",
|
||||||
"vinyl-buffer": "^1.0.1",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"winston": "^3.10.0",
|
"winston": "^3.10.0",
|
||||||
"winston-loggly-bulk": "^3.2.1",
|
"winston-loggly-bulk": "^3.3.0",
|
||||||
"xml2js": "^0.6.2"
|
"xml2js": "^0.6.2"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14",
|
"node": "20",
|
||||||
"npm": "^6"
|
"npm": "^10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext .js --fix . && cd website/client && npm run lint",
|
"lint": "eslint --ext .js --fix . && cd website/client && npm run lint",
|
||||||
@@ -95,37 +97,37 @@
|
|||||||
"test:api-v3:integration:separate-server": "NODE_ENV=test gulp test:api-v3:integration:separate-server",
|
"test:api-v3:integration:separate-server": "NODE_ENV=test gulp test:api-v3:integration:separate-server",
|
||||||
"test:api-v4:integration": "gulp test:api-v4:integration",
|
"test:api-v4:integration": "gulp test:api-v4:integration",
|
||||||
"test:api-v4:integration:separate-server": "NODE_ENV=test gulp test:api-v4:integration:separate-server",
|
"test:api-v4:integration:separate-server": "NODE_ENV=test gulp test:api-v4:integration:separate-server",
|
||||||
"test:sanity": "istanbul cover --dir coverage/sanity --report lcovonly node_modules/mocha/bin/_mocha -- test/sanity --recursive",
|
"test:sanity": "nyc --silent --no-clean mocha test/sanity --recursive",
|
||||||
"test:common": "istanbul cover --dir coverage/common --report lcovonly node_modules/mocha/bin/_mocha -- test/common --recursive",
|
"test:common": "nyc --silent --no-clean mocha test/common --recursive",
|
||||||
"test:content": "istanbul cover --dir coverage/content --report lcovonly node_modules/mocha/bin/_mocha -- test/content --recursive",
|
"test:content": "nyc --silent --no-clean mocha test/content --recursive",
|
||||||
"test:nodemon": "gulp test:nodemon",
|
"test:nodemon": "gulp test:nodemon",
|
||||||
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
|
"coverage": "nyc report --reporter=html --report-dir coverage/results; open coverage/results/index.html",
|
||||||
"sprites": "gulp sprites:compile",
|
"sprites": "gulp sprites:compile",
|
||||||
"client:dev": "cd website/client && npm run serve",
|
"client:dev": "cd website/client && npm run serve",
|
||||||
"client:build": "cd website/client && npm run build",
|
"client:build": "cd website/client && npm run build",
|
||||||
"client:unit": "cd website/client && npm run test:unit",
|
"client:unit": "cd website/client && npm run test:unit",
|
||||||
"start": "gulp nodemon",
|
"start": "gulp nodemon",
|
||||||
|
"start:simple": "node ./website/server/index.js",
|
||||||
"debug": "gulp nodemon --inspect",
|
"debug": "gulp nodemon --inspect",
|
||||||
"mongo:dev": "run-rs -v 4.2.8 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||||
|
"mongo:test": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data-testing --number 1 --quiet",
|
||||||
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
||||||
"apidoc": "gulp apidoc"
|
"apidoc": "gulp apidoc",
|
||||||
|
"heroku-postbuild": ".heroku/report_deploy.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.7.4",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-moment": "^0.1.0",
|
"chai-moment": "^0.1.0",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"cross-spawn": "^7.0.3",
|
"cross-spawn": "^7.0.5",
|
||||||
"expect.js": "^0.3.1",
|
|
||||||
"istanbul": "^1.1.0-alpha.1",
|
|
||||||
"mocha": "^5.1.1",
|
"mocha": "^5.1.1",
|
||||||
"monk": "^7.3.4",
|
"monk": "^7.3.4",
|
||||||
|
"nyc": "^15.1.0",
|
||||||
"require-again": "^2.0.0",
|
"require-again": "^2.0.0",
|
||||||
"run-rs": "^0.7.7",
|
"run-rs": "^0.7.7",
|
||||||
"sinon": "^15.2.0",
|
|
||||||
"sinon-chai": "^3.7.0",
|
"sinon-chai": "^3.7.0",
|
||||||
"sinon-stub-promise": "^4.0.0"
|
"sinon-stub-promise": "^4.0.0"
|
||||||
},
|
}
|
||||||
"optionalDependencies": {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ async function deleteHabiticaData (user, email) {
|
|||||||
{ _id: user._id },
|
{ _id: user._id },
|
||||||
{ $set: set },
|
{ $set: set },
|
||||||
);
|
);
|
||||||
|
// eslint-disable-next-line no-promise-executor-return
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
const response = await axios.delete(
|
const response = await axios.delete(
|
||||||
`${BASE_URL}/api/v3/user`,
|
`${BASE_URL}/api/v3/user`,
|
||||||
@@ -96,6 +97,7 @@ async function processEmailAddress (email) {
|
|||||||
return console.log(`No users found with email address ${email}`);
|
return console.log(`No users found with email address ${email}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-promise-executor-return
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
return Promise.all(users.map(user => (async () => {
|
return Promise.all(users.map(user => (async () => {
|
||||||
await deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
|
await deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable import/no-commonjs */
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: [
|
extends: [
|
||||||
'habitrpg/lib/mocha',
|
'habitrpg/lib/mocha',
|
||||||
@@ -7,6 +9,9 @@ module.exports = {
|
|||||||
chai: true,
|
chai: true,
|
||||||
expect: true,
|
expect: true,
|
||||||
sinon: true,
|
sinon: true,
|
||||||
sandbox: true
|
sandbox: true,
|
||||||
},
|
},
|
||||||
}
|
rules: {
|
||||||
|
'import/no-extraneous-dependencies': 'off',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import apiError from '../../../../website/server/libs/apiError';
|
import { apiError } from '../../../../website/server/libs/apiError';
|
||||||
|
|
||||||
describe('API Messages', () => {
|
describe('API Messages', () => {
|
||||||
const message = 'Only public guilds support pagination.';
|
const message = 'Only public guilds support pagination.';
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ describe('bug-report', () => {
|
|||||||
_id: userId,
|
_id: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await bugReportLogic(
|
const result = await bugReportLogic(user, userMail, userMessage, userAgent);
|
||||||
user, userMail, userMessage, userAgent,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(emailLib.sendTxn).to.be.called;
|
expect(emailLib.sendTxn).to.be.called;
|
||||||
expect(result).to.deep.equal({
|
expect(result).to.deep.equal({
|
||||||
@@ -46,7 +44,6 @@ describe('bug-report', () => {
|
|||||||
USER_HOURGLASSES: 0,
|
USER_HOURGLASSES: 0,
|
||||||
USER_ID: userId,
|
USER_ID: userId,
|
||||||
USER_LEVEL: 1,
|
USER_LEVEL: 1,
|
||||||
USER_OFFSET_MONTHS: 0,
|
|
||||||
USER_PAYMENT_PLATFORM: undefined,
|
USER_PAYMENT_PLATFORM: undefined,
|
||||||
USER_SUBSCRIPTION: undefined,
|
USER_SUBSCRIPTION: undefined,
|
||||||
USER_TIMEZONE_OFFSET: 0,
|
USER_TIMEZONE_OFFSET: 0,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import fs from 'fs';
|
||||||
import * as contentLib from '../../../../website/server/libs/content';
|
import * as contentLib from '../../../../website/server/libs/content';
|
||||||
import content from '../../../../website/common/script/content';
|
import content from '../../../../website/common/script/content';
|
||||||
|
import {
|
||||||
|
generateRes,
|
||||||
|
} from '../../../helpers/api-unit.helper';
|
||||||
|
|
||||||
describe('contentLib', () => {
|
describe('contentLib', () => {
|
||||||
describe('CONTENT_CACHE_PATH', () => {
|
describe('CONTENT_CACHE_PATH', () => {
|
||||||
@@ -13,5 +17,90 @@ describe('contentLib', () => {
|
|||||||
contentLib.getLocalizedContentResponse();
|
contentLib.getLocalizedContentResponse();
|
||||||
expect(typeof content.backgrounds.backgrounds062014.beach.text).to.equal('function');
|
expect(typeof content.backgrounds.backgrounds062014.beach.text).to.equal('function');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('removes keys from the content data', () => {
|
||||||
|
const response = contentLib.localizeContentData(content, 'en', { backgroundsFlat: true, dropHatchingPotions: true });
|
||||||
|
expect(response.backgroundsFlat).to.not.exist;
|
||||||
|
expect(response.backgrounds).to.exist;
|
||||||
|
expect(response.dropHatchingPotions).to.not.exist;
|
||||||
|
expect(response.hatchingPotions).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes nested keys from the content data', () => {
|
||||||
|
const response = contentLib.localizeContentData(content, 'en', { gear: { tree: true } });
|
||||||
|
expect(response.gear.tree).to.not.exist;
|
||||||
|
expect(response.gear.flat).to.exist;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a hash for a filter', () => {
|
||||||
|
const hash = contentLib.hashForFilter('backgroundsFlat,gear.flat');
|
||||||
|
expect(hash).to.equal('-1791877526');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('serves content', () => {
|
||||||
|
const resSpy = generateRes();
|
||||||
|
contentLib.serveContent(resSpy, 'en', '', false);
|
||||||
|
expect(resSpy.send).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('serves filtered content', () => {
|
||||||
|
const resSpy = generateRes();
|
||||||
|
contentLib.serveContent(resSpy, 'en', 'backgroundsFlat,gear.flat', false);
|
||||||
|
expect(resSpy.send).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('caches content', async () => {
|
||||||
|
let resSpy;
|
||||||
|
beforeEach(() => {
|
||||||
|
resSpy = generateRes();
|
||||||
|
if (fs.existsSync(contentLib.CONTENT_CACHE_PATH)) {
|
||||||
|
fs.rmSync(contentLib.CONTENT_CACHE_PATH, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.mkdirSync(contentLib.CONTENT_CACHE_PATH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not cache requests in development mode', async () => {
|
||||||
|
contentLib.serveContent(resSpy, 'en', '', false);
|
||||||
|
expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en.json`)).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('caches unfiltered requests', async () => {
|
||||||
|
expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en.json`)).to.be.false;
|
||||||
|
contentLib.serveContent(resSpy, 'en', '', true);
|
||||||
|
expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en.json`)).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('serves cached requests', async () => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
`${contentLib.CONTENT_CACHE_PATH}en.json`,
|
||||||
|
'{"success": true, "data": {"all": {}}}',
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
contentLib.serveContent(resSpy, 'en', '', true);
|
||||||
|
expect(resSpy.sendFile).to.have.been.calledOnce;
|
||||||
|
expect(resSpy.sendFile).to.have.been.calledWith(`${contentLib.CONTENT_CACHE_PATH}en.json`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('caches filtered requests', async () => {
|
||||||
|
const filter = 'backgroundsFlat,gear.flat';
|
||||||
|
const hash = contentLib.hashForFilter(filter);
|
||||||
|
expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`)).to.be.false;
|
||||||
|
contentLib.serveContent(resSpy, 'en', filter, true);
|
||||||
|
expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`)).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('serves filtered cached requests', async () => {
|
||||||
|
const filter = 'backgroundsFlat,gear.flat';
|
||||||
|
const hash = contentLib.hashForFilter(filter);
|
||||||
|
fs.writeFileSync(
|
||||||
|
`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`,
|
||||||
|
'{"success": true, "data": {}}',
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
contentLib.serveContent(resSpy, 'en', filter, true);
|
||||||
|
expect(resSpy.sendFile).to.have.been.calledOnce;
|
||||||
|
expect(resSpy.sendFile).to.have.been.calledWith(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -154,6 +154,14 @@ describe('cron', async () => {
|
|||||||
expect(user.purchased.plan.consecutive.count).to.equal(1);
|
expect(user.purchased.plan.consecutive.count).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('increments plan.cumulativeCount', async () => {
|
||||||
|
user.purchased.plan.cumulativeCount = 0;
|
||||||
|
await cron({
|
||||||
|
user, tasksByType, daysMissed, analytics,
|
||||||
|
});
|
||||||
|
expect(user.purchased.plan.cumulativeCount).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('increments plan.consecutive.count by more than 1 if user skipped months between logins', async () => {
|
it('increments plan.consecutive.count by more than 1 if user skipped months between logins', async () => {
|
||||||
user.purchased.plan.dateUpdated = moment().subtract(2, 'months').toDate();
|
user.purchased.plan.dateUpdated = moment().subtract(2, 'months').toDate();
|
||||||
user.purchased.plan.consecutive.count = 0;
|
user.purchased.plan.consecutive.count = 0;
|
||||||
@@ -163,12 +171,13 @@ describe('cron', async () => {
|
|||||||
expect(user.purchased.plan.consecutive.count).to.equal(2);
|
expect(user.purchased.plan.consecutive.count).to.equal(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('decrements plan.consecutive.offset when offset is greater than 0', async () => {
|
it('increments plan.cumulativeCount by more than 1 if user skipped months between logins', async () => {
|
||||||
user.purchased.plan.consecutive.offset = 2;
|
user.purchased.plan.dateUpdated = moment().subtract(3, 'months').toDate();
|
||||||
|
user.purchased.plan.cumulativeCount = 0;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.offset).to.equal(1);
|
expect(user.purchased.plan.cumulativeCount).to.equal(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not award unearned plan.consecutive.trinkets if subscription ended during an absence', async () => {
|
it('does not award unearned plan.consecutive.trinkets if subscription ended during an absence', async () => {
|
||||||
@@ -185,12 +194,12 @@ describe('cron', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => {
|
it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => {
|
||||||
user.purchased.plan.consecutive.gemCapExtra = 25;
|
user.purchased.plan.consecutive.gemCapExtra = 26;
|
||||||
user.purchased.plan.consecutive.count = 5;
|
user.purchased.plan.consecutive.count = 5;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not reset plan stats if we are before the last day of the cancelled month', async () => {
|
it('does not reset plan stats if we are before the last day of the cancelled month', async () => {
|
||||||
@@ -205,16 +214,14 @@ describe('cron', async () => {
|
|||||||
user.purchased.plan.dateTerminated = moment(new Date()).subtract({ days: 1 });
|
user.purchased.plan.dateTerminated = moment(new Date()).subtract({ days: 1 });
|
||||||
user.purchased.plan.consecutive.gemCapExtra = 20;
|
user.purchased.plan.consecutive.gemCapExtra = 20;
|
||||||
user.purchased.plan.consecutive.count = 5;
|
user.purchased.plan.consecutive.count = 5;
|
||||||
user.purchased.plan.consecutive.offset = 1;
|
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.purchased.plan.customerId).to.not.exist;
|
expect(user.purchased.plan.customerId).to.not.exist;
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||||
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
||||||
expect(user.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('for a 1-month recurring subscription', async () => {
|
describe('for a 1-month recurring subscription', async () => {
|
||||||
@@ -236,13 +243,11 @@ describe('cron', async () => {
|
|||||||
user1.purchased.plan.dateUpdated = moment().toDate();
|
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||||
user1.purchased.plan.planId = 'basic';
|
user1.purchased.plan.planId = 'basic';
|
||||||
user1.purchased.plan.consecutive.count = 0;
|
user1.purchased.plan.consecutive.count = 0;
|
||||||
user1.purchased.plan.perkMonthCount = 0;
|
user1.purchased.plan.consecutive.trinkets = 1;
|
||||||
user1.purchased.plan.consecutive.offset = 0;
|
|
||||||
user1.purchased.plan.consecutive.trinkets = 0;
|
|
||||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not increment consecutive benefits after the first month', async () => {
|
it('increments consecutive benefits', async () => {
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
@@ -253,75 +258,8 @@ describe('cron', async () => {
|
|||||||
user: user1, tasksByType, daysMissed, analytics,
|
user: user1, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user1.purchased.plan.consecutive.count).to.equal(1);
|
expect(user1.purchased.plan.consecutive.count).to.equal(1);
|
||||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
|
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(2);
|
||||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits after the second month', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
|
||||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects
|
|
||||||
// e.g., from time zone oddness.
|
|
||||||
await cron({
|
|
||||||
user: user1, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user1.purchased.plan.consecutive.count).to.equal(2);
|
|
||||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
|
|
||||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits after the second month if they also received a 1 month gift subscription', async () => {
|
|
||||||
user1.purchased.plan.perkMonthCount = 1;
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
|
||||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects
|
|
||||||
// e.g., from time zone oddness.
|
|
||||||
await cron({
|
|
||||||
user: user1, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user1.purchased.plan.perkMonthCount).to.equal(0);
|
|
||||||
expect(user1.purchased.plan.consecutive.count).to.equal(2);
|
|
||||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
|
||||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits after the third month', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
|
||||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects
|
|
||||||
// e.g., from time zone oddness.
|
|
||||||
await cron({
|
|
||||||
user: user1, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user1.purchased.plan.consecutive.count).to.equal(3);
|
|
||||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
|
||||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits after the fourth month', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
|
||||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects
|
|
||||||
// e.g., from time zone oddness.
|
|
||||||
await cron({
|
|
||||||
user: user1, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user1.purchased.plan.consecutive.count).to.equal(4);
|
|
||||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
|
||||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
|
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
|
||||||
@@ -332,33 +270,8 @@ describe('cron', async () => {
|
|||||||
user: user1, tasksByType, daysMissed, analytics,
|
user: user1, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user1.purchased.plan.consecutive.count).to.equal(10);
|
expect(user1.purchased.plan.consecutive.count).to.equal(10);
|
||||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(11);
|
||||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
|
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('initializes plan.perkMonthCount if necessary', async () => {
|
|
||||||
user.purchased.plan.perkMonthCount = undefined;
|
|
||||||
clock = sinon.useFakeTimers(moment(user.purchased.plan.dateUpdated)
|
|
||||||
.utcOffset(0)
|
|
||||||
.startOf('month')
|
|
||||||
.add(1, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user.purchased.plan.perkMonthCount).to.equal(1);
|
|
||||||
user.purchased.plan.perkMonthCount = undefined;
|
|
||||||
user.purchased.plan.consecutive.count = 8;
|
|
||||||
clock.restore();
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user.purchased.plan.perkMonthCount).to.equal(2);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -379,14 +292,12 @@ describe('cron', async () => {
|
|||||||
user3.purchased.plan.customerId = 'subscribedId';
|
user3.purchased.plan.customerId = 'subscribedId';
|
||||||
user3.purchased.plan.dateUpdated = moment().toDate();
|
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||||
user3.purchased.plan.planId = 'basic_3mo';
|
user3.purchased.plan.planId = 'basic_3mo';
|
||||||
user3.purchased.plan.perkMonthCount = 0;
|
|
||||||
user3.purchased.plan.consecutive.count = 0;
|
user3.purchased.plan.consecutive.count = 0;
|
||||||
user3.purchased.plan.consecutive.offset = 3;
|
|
||||||
user3.purchased.plan.consecutive.trinkets = 1;
|
user3.purchased.plan.consecutive.trinkets = 1;
|
||||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
user3.purchased.plan.consecutive.gemCapExtra = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
|
it('increments consecutive benefits', async () => {
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
@@ -394,102 +305,8 @@ describe('cron', async () => {
|
|||||||
user: user3, tasksByType, daysMissed, analytics,
|
user: user3, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user3.purchased.plan.consecutive.count).to.equal(1);
|
expect(user3.purchased.plan.consecutive.count).to.equal(1);
|
||||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
|
||||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the middle of the period that they already have benefits for', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user3, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user3.purchased.plan.consecutive.count).to.equal(2);
|
|
||||||
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
|
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
|
||||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user3, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user3.purchased.plan.consecutive.count).to.equal(3);
|
|
||||||
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
|
||||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits the month after the second paid period has started', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user3, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user3.purchased.plan.consecutive.count).to.equal(4);
|
|
||||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(2);
|
||||||
});
|
|
||||||
|
|
||||||
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
|
|
||||||
user3.purchased.plan.perkMonthCount = 2;
|
|
||||||
user3.purchased.plan.consecutive.trinkets = 1;
|
|
||||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user3, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user3.purchased.plan.perkMonthCount).to.equal(2);
|
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
|
||||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user3, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user3.purchased.plan.consecutive.count).to.equal(5);
|
|
||||||
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
|
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
|
||||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user3, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user3.purchased.plan.consecutive.count).to.equal(6);
|
|
||||||
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
|
||||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits the month after the third paid period has started', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user3, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user3.purchased.plan.consecutive.count).to.equal(7);
|
|
||||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(3);
|
|
||||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
|
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
|
||||||
@@ -500,8 +317,7 @@ describe('cron', async () => {
|
|||||||
user: user3, tasksByType, daysMissed, analytics,
|
user: user3, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user3.purchased.plan.consecutive.count).to.equal(10);
|
expect(user3.purchased.plan.consecutive.count).to.equal(10);
|
||||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(11);
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(4);
|
|
||||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -523,14 +339,12 @@ describe('cron', async () => {
|
|||||||
user6.purchased.plan.customerId = 'subscribedId';
|
user6.purchased.plan.customerId = 'subscribedId';
|
||||||
user6.purchased.plan.dateUpdated = moment().toDate();
|
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||||
user6.purchased.plan.planId = 'google_6mo';
|
user6.purchased.plan.planId = 'google_6mo';
|
||||||
user6.purchased.plan.perkMonthCount = 0;
|
|
||||||
user6.purchased.plan.consecutive.count = 0;
|
user6.purchased.plan.consecutive.count = 0;
|
||||||
user6.purchased.plan.consecutive.offset = 6;
|
user6.purchased.plan.consecutive.trinkets = 1;
|
||||||
user6.purchased.plan.consecutive.trinkets = 2;
|
user6.purchased.plan.consecutive.gemCapExtra = 0;
|
||||||
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
|
it('increments benefits', async () => {
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
@@ -538,74 +352,8 @@ describe('cron', async () => {
|
|||||||
user: user6, tasksByType, daysMissed, analytics,
|
user: user6, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user6.purchased.plan.consecutive.count).to.equal(1);
|
expect(user6.purchased.plan.consecutive.count).to.equal(1);
|
||||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
|
||||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(2);
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user6, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user6.purchased.plan.consecutive.count).to.equal(6);
|
|
||||||
expect(user6.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
|
||||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits the month after the second paid period has started', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user6, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user6.purchased.plan.consecutive.count).to.equal(7);
|
|
||||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
|
||||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
|
|
||||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
|
|
||||||
user6.purchased.plan.perkMonthCount = 2;
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user6, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user6.purchased.plan.perkMonthCount).to.equal(2);
|
|
||||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
|
|
||||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits the month after the third paid period has started', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user6, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user6.purchased.plan.consecutive.count).to.equal(13);
|
|
||||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
|
||||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(6);
|
|
||||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(19, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user6, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user6.purchased.plan.consecutive.count).to.equal(19);
|
|
||||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
|
||||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(8);
|
|
||||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -626,11 +374,10 @@ describe('cron', async () => {
|
|||||||
user12.purchased.plan.dateUpdated = moment().toDate();
|
user12.purchased.plan.dateUpdated = moment().toDate();
|
||||||
user12.purchased.plan.planId = 'basic_12mo';
|
user12.purchased.plan.planId = 'basic_12mo';
|
||||||
user12.purchased.plan.consecutive.count = 0;
|
user12.purchased.plan.consecutive.count = 0;
|
||||||
user12.purchased.plan.consecutive.offset = 12;
|
user12.purchased.plan.consecutive.trinkets = 1;
|
||||||
user12.purchased.plan.consecutive.trinkets = 4;
|
user12.purchased.plan.consecutive.gemCapExtra = 26;
|
||||||
user12.purchased.plan.consecutive.gemCapExtra = 20;
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
|
it('increments consecutive benefits the month after the second paid period has started', async () => {
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
@@ -638,61 +385,20 @@ describe('cron', async () => {
|
|||||||
user: user12, tasksByType, daysMissed, analytics,
|
user: user12, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user12.purchased.plan.consecutive.count).to.equal(1);
|
expect(user12.purchased.plan.consecutive.count).to.equal(1);
|
||||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
expect(user12.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
|
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26);
|
||||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(12, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user12, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user12.purchased.plan.consecutive.count).to.equal(12);
|
|
||||||
expect(user12.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
|
|
||||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits the month after the second paid period has started', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user12, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user12.purchased.plan.consecutive.count).to.equal(13);
|
|
||||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
|
||||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(8);
|
|
||||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits the month after the third paid period has started', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(25, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user12, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user12.purchased.plan.consecutive.count).to.equal(25);
|
|
||||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
|
||||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(12);
|
|
||||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
|
it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => {
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(37, 'months')
|
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(10, 'months')
|
||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
await cron({
|
await cron({
|
||||||
user: user12, tasksByType, daysMissed, analytics,
|
user: user12, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user12.purchased.plan.consecutive.count).to.equal(37);
|
expect(user12.purchased.plan.consecutive.count).to.equal(10);
|
||||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
expect(user12.purchased.plan.consecutive.trinkets).to.equal(11);
|
||||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(16);
|
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26);
|
||||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -715,11 +421,11 @@ describe('cron', async () => {
|
|||||||
.toDate();
|
.toDate();
|
||||||
user3g.purchased.plan.planId = null;
|
user3g.purchased.plan.planId = null;
|
||||||
user3g.purchased.plan.consecutive.count = 0;
|
user3g.purchased.plan.consecutive.count = 0;
|
||||||
user3g.purchased.plan.consecutive.offset = 3;
|
user3g.purchased.plan.cumulativeCount = 0;
|
||||||
user3g.purchased.plan.consecutive.trinkets = 1;
|
user3g.purchased.plan.consecutive.trinkets = 1;
|
||||||
user3g.purchased.plan.consecutive.gemCapExtra = 5;
|
user3g.purchased.plan.consecutive.gemCapExtra = 0;
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the first month of the gift subscription', async () => {
|
it('increments benefits', async () => {
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
@@ -727,35 +433,9 @@ describe('cron', async () => {
|
|||||||
user: user3g, tasksByType, daysMissed, analytics,
|
user: user3g, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user3g.purchased.plan.consecutive.count).to.equal(1);
|
expect(user3g.purchased.plan.consecutive.count).to.equal(1);
|
||||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(2);
|
expect(user3g.purchased.plan.cumulativeCount).to.equal(1);
|
||||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(2);
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the second month of the gift subscription', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user3g, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user3g.purchased.plan.consecutive.count).to.equal(2);
|
|
||||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(1);
|
|
||||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
|
||||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the third month of the gift subscription', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user3g, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user3g.purchased.plan.consecutive.count).to.equal(3);
|
|
||||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
|
||||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the month after the gift subscription has ended', async () => {
|
it('does not increment consecutive benefits in the month after the gift subscription has ended', async () => {
|
||||||
@@ -767,84 +447,9 @@ describe('cron', async () => {
|
|||||||
});
|
});
|
||||||
// subscription has been erased by now
|
// subscription has been erased by now
|
||||||
expect(user3g.purchased.plan.consecutive.count).to.equal(0);
|
expect(user3g.purchased.plan.consecutive.count).to.equal(0);
|
||||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
|
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(2);
|
||||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(0); // erased
|
expect(user3g.purchased.plan.cumulativeCount).to.equal(1);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('for a 6-month recurring subscription where the user has incorrect consecutive month data from prior bugs', async () => {
|
|
||||||
const user6x = new User({
|
|
||||||
auth: {
|
|
||||||
local: {
|
|
||||||
username: 'username6x',
|
|
||||||
lowerCaseUsername: 'username6x',
|
|
||||||
email: 'email6x@example.com',
|
|
||||||
salt: 'salt',
|
|
||||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// user6x has a 6-month recurring subscription starting 8 months in the past
|
|
||||||
// before issue #4819 was fixed
|
|
||||||
user6x.purchased.plan.customerId = 'subscribedId';
|
|
||||||
user6x.purchased.plan.dateUpdated = moment().toDate();
|
|
||||||
user6x.purchased.plan.planId = 'basic_6mo';
|
|
||||||
user6x.purchased.plan.consecutive.count = 8;
|
|
||||||
user6x.purchased.plan.consecutive.offset = 0;
|
|
||||||
user6x.purchased.plan.consecutive.trinkets = 3;
|
|
||||||
user6x.purchased.plan.consecutive.gemCapExtra = 15;
|
|
||||||
|
|
||||||
it('increments consecutive benefits in the first month since the fix for #4819 goes live', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user6x, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user6x.purchased.plan.consecutive.count).to.equal(9);
|
|
||||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
|
|
||||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
|
||||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the second month after the fix goes live', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user6x, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user6x.purchased.plan.consecutive.count).to.equal(10);
|
|
||||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(4);
|
|
||||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
|
||||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits in the third month after the fix goes live', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user6x, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user6x.purchased.plan.consecutive.count).to.equal(11);
|
|
||||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(3);
|
|
||||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
|
||||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments consecutive benefits in the seventh month after the fix goes live', async () => {
|
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
|
||||||
.add(2, 'days')
|
|
||||||
.toDate());
|
|
||||||
await cron({
|
|
||||||
user: user6x, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
expect(user6x.purchased.plan.consecutive.count).to.equal(15);
|
|
||||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
|
|
||||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(7);
|
|
||||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -888,12 +493,12 @@ describe('cron', async () => {
|
|||||||
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not decrement plan.consecutive.offset when offset is greater than 0', async () => {
|
it('does not increment plan.cumulativeCount', async () => {
|
||||||
user.purchased.plan.consecutive.offset = 1;
|
user.purchased.plan.cumulativeCount = 0;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.offset).to.equal(1);
|
expect(user.purchased.plan.cumulativeCount).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', async () => {
|
it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', async () => {
|
||||||
@@ -913,12 +518,12 @@ describe('cron', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => {
|
it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => {
|
||||||
user.purchased.plan.consecutive.gemCapExtra = 25;
|
user.purchased.plan.consecutive.gemCapExtra = 26;
|
||||||
user.purchased.plan.consecutive.count = 5;
|
user.purchased.plan.consecutive.count = 5;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does nothing to plan stats if we are before the last day of the cancelled month', async () => {
|
it('does nothing to plan stats if we are before the last day of the cancelled month', async () => {
|
||||||
@@ -928,22 +533,6 @@ describe('cron', async () => {
|
|||||||
});
|
});
|
||||||
expect(user.purchased.plan.customerId).to.not.exist;
|
expect(user.purchased.plan.customerId).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
xit('does nothing to plan stats when we are after the last day of the cancelled month', async () => {
|
|
||||||
user.purchased.plan.dateTerminated = moment(new Date()).subtract({ days: 1 });
|
|
||||||
user.purchased.plan.consecutive.gemCapExtra = 20;
|
|
||||||
user.purchased.plan.consecutive.count = 5;
|
|
||||||
user.purchased.plan.consecutive.offset = 1;
|
|
||||||
|
|
||||||
await cron({
|
|
||||||
user, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(user.purchased.plan.customerId).to.exist;
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.exist;
|
|
||||||
expect(user.purchased.plan.consecutive.count).to.exist;
|
|
||||||
expect(user.purchased.plan.consecutive.offset).to.exist;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('todos', async () => {
|
describe('todos', async () => {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ describe('Items Utils', () => {
|
|||||||
it('converts values for owned gear to true/false', () => {
|
it('converts values for owned gear to true/false', () => {
|
||||||
expect(castItemVal('items.gear.owned.shield_warrior_0', 'true')).to.equal(true);
|
expect(castItemVal('items.gear.owned.shield_warrior_0', 'true')).to.equal(true);
|
||||||
expect(castItemVal('items.gear.owned.invalid', 'false')).to.equal(false);
|
expect(castItemVal('items.gear.owned.invalid', 'false')).to.equal(false);
|
||||||
expect(castItemVal('items.gear.owned.invalid', 'null')).to.equal(false);
|
expect(castItemVal('items.gear.owned.invalid', 'null')).to.equal(undefined);
|
||||||
expect(castItemVal('items.gear.owned.invalid', 'truthy')).to.equal(true);
|
expect(castItemVal('items.gear.owned.invalid', 'truthy')).to.equal(true);
|
||||||
expect(castItemVal('items.gear.owned.invalid', 0)).to.equal(false);
|
expect(castItemVal('items.gear.owned.invalid', 0)).to.equal(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe('mongodb', () => {
|
|||||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
const mongoLibOverride = requireAgain(pathToMongoLib);
|
||||||
|
|
||||||
const options = mongoLibOverride.getDefaultConnectionOptions();
|
const options = mongoLibOverride.getDefaultConnectionOptions();
|
||||||
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology', 'keepAlive', 'keepAliveInitialDelay']);
|
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ describe('Password Utilities', () => {
|
|||||||
expiresAt: moment().subtract({ minutes: 1 }),
|
expiresAt: moment().subtract({ minutes: 1 }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
'auth.local.passwordResetCode': code,
|
'auth.local.passwordResetCode': code,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ describe('Password Utilities', () => {
|
|||||||
expiresAt: moment().add({ days: 1 }),
|
expiresAt: moment().add({ days: 1 }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
'auth.local.passwordResetCode': 'invalid',
|
'auth.local.passwordResetCode': 'invalid',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ describe('Password Utilities', () => {
|
|||||||
expiresAt: moment().add({ days: 1 }),
|
expiresAt: moment().add({ days: 1 }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
'auth.local.passwordResetCode': code,
|
'auth.local.passwordResetCode': code,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { model as User } from '../../../../../../website/server/models/user';
|
|||||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import common from '../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
import apiError from '../../../../../../website/server/libs/apiError';
|
import { apiError } from '../../../../../../website/server/libs/apiError';
|
||||||
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
||||||
|
|
||||||
const { i18n } = common;
|
const { i18n } = common;
|
||||||
|
|||||||
@@ -342,10 +342,12 @@ describe('Apple Payments', () => {
|
|||||||
}]);
|
}]);
|
||||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||||
|
|
||||||
await applePayments.subscribe(user,
|
await applePayments.subscribe(
|
||||||
|
user,
|
||||||
receipt,
|
receipt,
|
||||||
headers,
|
headers,
|
||||||
nextPaymentProcessing);
|
nextPaymentProcessing,
|
||||||
|
);
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
expect(iapValidateStub).to.be.calledOnce;
|
expect(iapValidateStub).to.be.calledOnce;
|
||||||
@@ -387,10 +389,12 @@ describe('Apple Payments', () => {
|
|||||||
}]);
|
}]);
|
||||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||||
|
|
||||||
await applePayments.subscribe(user,
|
await applePayments.subscribe(
|
||||||
|
user,
|
||||||
receipt,
|
receipt,
|
||||||
headers,
|
headers,
|
||||||
nextPaymentProcessing);
|
nextPaymentProcessing,
|
||||||
|
);
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
expect(iapValidateStub).to.be.calledOnce;
|
expect(iapValidateStub).to.be.calledOnce;
|
||||||
@@ -517,9 +521,7 @@ describe('Apple Payments', () => {
|
|||||||
|
|
||||||
const secondUser = new User();
|
const secondUser = new User();
|
||||||
await secondUser.save();
|
await secondUser.save();
|
||||||
await expect(applePayments.subscribe(
|
await expect(applePayments.subscribe(secondUser, receipt, headers, nextPaymentProcessing))
|
||||||
secondUser, receipt, headers, nextPaymentProcessing,
|
|
||||||
))
|
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 401,
|
||||||
name: 'NotAuthorized',
|
name: 'NotAuthorized',
|
||||||
@@ -559,9 +561,7 @@ describe('Apple Payments', () => {
|
|||||||
|
|
||||||
const thirdUser = new User();
|
const thirdUser = new User();
|
||||||
await thirdUser.save();
|
await thirdUser.save();
|
||||||
await expect(applePayments.subscribe(
|
await expect(applePayments.subscribe(thirdUser, receipt, headers, nextPaymentProcessing))
|
||||||
thirdUser, receipt, headers, nextPaymentProcessing,
|
|
||||||
))
|
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 401,
|
||||||
name: 'NotAuthorized',
|
name: 'NotAuthorized',
|
||||||
|
|||||||
@@ -715,7 +715,7 @@ describe('Purchasing a group plan for group', () => {
|
|||||||
const mysteryItem = { title: 'item' };
|
const mysteryItem = { title: 'item' };
|
||||||
const mysteryItems = [mysteryItem];
|
const mysteryItems = [mysteryItem];
|
||||||
const consecutive = {
|
const consecutive = {
|
||||||
trinkets: 3,
|
trinkets: 4,
|
||||||
gemCapExtra: 20,
|
gemCapExtra: 20,
|
||||||
offset: 1,
|
offset: 1,
|
||||||
count: 13,
|
count: 13,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from '../../../../helpers/api-unit.helper';
|
} from '../../../../helpers/api-unit.helper';
|
||||||
import * as worldState from '../../../../../website/server/libs/worldState';
|
import * as worldState from '../../../../../website/server/libs/worldState';
|
||||||
import { TransactionModel } from '../../../../../website/server/models/transaction';
|
import { TransactionModel } from '../../../../../website/server/models/transaction';
|
||||||
|
import { REPEATING_EVENTS } from '../../../../../website/common/script/content/constants/events';
|
||||||
|
|
||||||
describe('payments/index', () => {
|
describe('payments/index', () => {
|
||||||
let user;
|
let user;
|
||||||
@@ -65,7 +66,6 @@ describe('payments/index', () => {
|
|||||||
mysteryItems: [],
|
mysteryItems: [],
|
||||||
consecutive: {
|
consecutive: {
|
||||||
trinkets: 0,
|
trinkets: 0,
|
||||||
offset: 0,
|
|
||||||
gemCapExtra: 0,
|
gemCapExtra: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -108,14 +108,8 @@ describe('payments/index', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('add a transaction entry to the recipient', async () => {
|
it('add a transaction entry to the recipient', async () => {
|
||||||
recipient.purchased.plan = plan;
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
|
||||||
|
|
||||||
const transactions = await TransactionModel
|
const transactions = await TransactionModel
|
||||||
.find({ userId: recipient._id })
|
.find({ userId: recipient._id })
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
@@ -177,6 +171,45 @@ describe('payments/index', () => {
|
|||||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not reset gemCapExtra if they already had one', async () => {
|
||||||
|
recipient.purchased.plan.consecutive.gemCapExtra = 10;
|
||||||
|
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets gemCapExtra to 0 if they receive a 3 month sub', async () => {
|
||||||
|
data.gift.subscription.key = 'basic_3mo';
|
||||||
|
data.gift.subscription.months = 3;
|
||||||
|
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets gemCapExtra to max if they receive a 12 month sub', async () => {
|
||||||
|
recipient.purchased.plan.consecutive.gemCapExtra = 10;
|
||||||
|
|
||||||
|
data.gift.subscription.key = 'basic_12mo';
|
||||||
|
data.gift.subscription.months = 12;
|
||||||
|
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gives user 1 hourglass if they have no active subscription', async () => {
|
||||||
|
await api.createSubscription(data);
|
||||||
|
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not give any hourglasses if they have an active subscription', async () => {
|
||||||
|
recipient.purchased.plan = plan;
|
||||||
|
await api.createSubscription(data);
|
||||||
|
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(plan.consecutive.trinkets);
|
||||||
|
});
|
||||||
|
|
||||||
it('sets plan.dateUpdated if it did exist but the user has cancelled', async () => {
|
it('sets plan.dateUpdated if it did exist but the user has cancelled', async () => {
|
||||||
recipient.purchased.plan.dateUpdated = moment().subtract(1, 'days').toDate();
|
recipient.purchased.plan.dateUpdated = moment().subtract(1, 'days').toDate();
|
||||||
recipient.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
|
recipient.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
|
||||||
@@ -235,116 +268,6 @@ describe('payments/index', () => {
|
|||||||
expect(recipient.purchased.plan.customerId).to.eql('customer-id');
|
expect(recipient.purchased.plan.customerId).to.eql('customer-id');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets plan.perkMonthCount to 1 if user is not subscribed', async () => {
|
|
||||||
recipient.purchased.plan = plan;
|
|
||||||
recipient.purchased.plan.perkMonthCount = 1;
|
|
||||||
recipient.purchased.plan.customerId = undefined;
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
data.gift.subscription.key = 'basic_earned';
|
|
||||||
data.gift.subscription.months = 1;
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sets plan.perkMonthCount to 1 if field is not initialized', async () => {
|
|
||||||
recipient.purchased.plan = plan;
|
|
||||||
recipient.purchased.plan.perkMonthCount = -1;
|
|
||||||
recipient.purchased.plan.customerId = undefined;
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
data.gift.subscription.key = 'basic_earned';
|
|
||||||
data.gift.subscription.months = 1;
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sets plan.perkMonthCount to 1 if user had previous count but lapsed subscription', async () => {
|
|
||||||
recipient.purchased.plan = plan;
|
|
||||||
recipient.purchased.plan.perkMonthCount = 2;
|
|
||||||
recipient.purchased.plan.customerId = undefined;
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
data.gift.subscription.key = 'basic_earned';
|
|
||||||
data.gift.subscription.months = 1;
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds to plan.perkMonthCount if user is already subscribed', async () => {
|
|
||||||
recipient.purchased.plan = plan;
|
|
||||||
recipient.purchased.plan.perkMonthCount = 1;
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
data.gift.subscription.key = 'basic_earned';
|
|
||||||
data.gift.subscription.months = 1;
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('awards perks if plan.perkMonthCount reaches 3 with existing subscription', async () => {
|
|
||||||
recipient.purchased.plan = plan;
|
|
||||||
recipient.purchased.plan.perkMonthCount = 2;
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
data.gift.subscription.key = 'basic_earned';
|
|
||||||
data.gift.subscription.months = 1;
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
|
||||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
|
||||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
|
||||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
|
||||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('awards perks if plan.perkMonthCount reaches 3 without existing subscription', async () => {
|
|
||||||
recipient.purchased.plan.perkMonthCount = 0;
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
|
||||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
|
||||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
|
||||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
|
||||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('awards perks if plan.perkMonthCount reaches 3 without initialized field', async () => {
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
|
|
||||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
|
||||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
|
||||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
|
||||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('awards perks if plan.perkMonthCount goes over 3', async () => {
|
|
||||||
recipient.purchased.plan = plan;
|
|
||||||
recipient.purchased.plan.perkMonthCount = 2;
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
|
||||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
|
||||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
|
||||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
|
||||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sets plan.customerId to "Gift" if it does not already exist', async () => {
|
it('sets plan.customerId to "Gift" if it does not already exist', async () => {
|
||||||
expect(recipient.purchased.plan.customerId).to.not.exist;
|
expect(recipient.purchased.plan.customerId).to.not.exist;
|
||||||
|
|
||||||
@@ -421,8 +344,8 @@ describe('payments/index', () => {
|
|||||||
context('Active Promotion', () => {
|
context('Active Promotion', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.stub(worldState, 'getCurrentEventList').returns([{
|
sinon.stub(worldState, 'getCurrentEventList').returns([{
|
||||||
...common.content.events.winter2021Promo,
|
...REPEATING_EVENTS.giftOneGetOne,
|
||||||
event: 'winter2021',
|
event: 'g1g1',
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -438,22 +361,30 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.dateTerminated).to.exist;
|
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||||
expect(user.purchased.plan.dateCreated).to.exist;
|
expect(user.purchased.plan.dateCreated).to.exist;
|
||||||
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
|
|
||||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||||
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||||
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
||||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||||
|
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => {
|
it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => {
|
||||||
user.purchased.plan = plan;
|
user.purchased.plan = plan;
|
||||||
|
|
||||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||||
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.extraMonths).to.eql(3);
|
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||||
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
|
|
||||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||||
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||||
@@ -466,10 +397,12 @@ describe('payments/index', () => {
|
|||||||
recipient.purchased.plan = plan;
|
recipient.purchased.plan = plan;
|
||||||
|
|
||||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||||
|
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||||
|
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
|
|
||||||
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||||
expect(user.purchased.plan.customerId).to.eql('Gift');
|
expect(user.purchased.plan.customerId).to.eql('Gift');
|
||||||
@@ -484,11 +417,15 @@ describe('payments/index', () => {
|
|||||||
|
|
||||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||||
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
|
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.extraMonths).to.eql(3);
|
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||||
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
|
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends a private message about the promotion', async () => {
|
it('sends a private message about the promotion', async () => {
|
||||||
@@ -511,7 +448,6 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||||
expect(user.purchased.plan.gemsBought).to.eql(0);
|
expect(user.purchased.plan.gemsBought).to.eql(0);
|
||||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
|
||||||
expect(user.purchased.plan.paymentMethod).to.eql('Payment Method');
|
expect(user.purchased.plan.paymentMethod).to.eql('Payment Method');
|
||||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||||
expect(user.purchased.plan.dateTerminated).to.eql(null);
|
expect(user.purchased.plan.dateTerminated).to.eql(null);
|
||||||
@@ -549,33 +485,6 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
|
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps plan.perkMonthCount when changing subscription type', async () => {
|
|
||||||
await api.createSubscription(data);
|
|
||||||
user.purchased.plan.perkMonthCount = 2;
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.perkMonthCount).to.eql(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sets plan.perkMonthCount to zero when creating new monthly subscription', async () => {
|
|
||||||
user.purchased.plan.perkMonthCount = 2;
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sets plan.perkMonthCount to zero when creating new 3 month subscription', async () => {
|
|
||||||
user.purchased.plan.perkMonthCount = 2;
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates plan.consecutive.offset when changing subscription type', async () => {
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.consecutive.offset).to.eql(3);
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.consecutive.offset).to.eql(6);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('awards the Royal Purple Jackalope pet', async () => {
|
it('awards the Royal Purple Jackalope pet', async () => {
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
@@ -694,6 +603,7 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||||
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -741,55 +651,20 @@ describe('payments/index', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('Block subscription perks', () => {
|
context('Block subscription perks', () => {
|
||||||
it('adds block months to plan.consecutive.offset', async () => {
|
it('adds 26 to plan.consecutive.gemCapExtra for 12 month block', async () => {
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.offset).to.eql(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('resets plans.consecutive.offset if 1 month subscription', async () => {
|
|
||||||
user.purchased.plan.consecutive.offset = 1;
|
|
||||||
await user.save();
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => {
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => {
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => {
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not raise plan.consecutive.gemCapExtra higher than 25', async () => {
|
it('does not raise plan.consecutive.gemCapExtra higher than 26', async () => {
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds a plan.consecutive.trinkets for 3 month block', async () => {
|
it('adds a plan.consecutive.trinkets for 3 month block', async () => {
|
||||||
@@ -798,20 +673,29 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds 2 plan.consecutive.trinkets for 6 month block', async () => {
|
it('adds 1 plan.consecutive.trinkets for 6 month block', async () => {
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds 4 plan.consecutive.trinkets for 12 month block', async () => {
|
it('adds 1 plan.consecutive.trinkets for 12 month block if they had promo', async () => {
|
||||||
|
user.purchased.plan.hourglassPromoReceived = new Date();
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds 12 plan.consecutive.trinkets for 12 month block', async () => {
|
||||||
|
data.sub.key = 'basic_12mo';
|
||||||
|
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Upgrades subscription', () => {
|
context('Upgrades subscription', () => {
|
||||||
@@ -819,70 +703,38 @@ describe('payments/index', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
data.updatedFrom = { logic: 'payDifference' };
|
data.updatedFrom = { logic: 'payDifference' };
|
||||||
});
|
});
|
||||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
|
||||||
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
|
||||||
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
data.updatedFrom.key = 'basic_earned';
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_3mo';
|
data.updatedFrom.key = 'basic_3mo';
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
|
||||||
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
|
||||||
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
data.updatedFrom.key = 'basic_earned';
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_6mo';
|
data.updatedFrom.key = 'basic_6mo';
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
@@ -894,7 +746,7 @@ describe('payments/index', () => {
|
|||||||
data.updatedFrom.key = 'basic_3mo';
|
data.updatedFrom.key = 'basic_3mo';
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -902,70 +754,39 @@ describe('payments/index', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
data.updatedFrom = { logic: 'payFull' };
|
data.updatedFrom = { logic: 'payFull' };
|
||||||
});
|
});
|
||||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
|
||||||
|
|
||||||
await api.createSubscription(data);
|
it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
|
||||||
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
data.updatedFrom.key = 'basic_earned';
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_3mo';
|
data.updatedFrom.key = 'basic_3mo';
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
|
||||||
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
|
||||||
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
data.updatedFrom.key = 'basic_earned';
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_6mo';
|
data.updatedFrom.key = 'basic_6mo';
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
@@ -977,7 +798,7 @@ describe('payments/index', () => {
|
|||||||
data.updatedFrom.key = 'basic_3mo';
|
data.updatedFrom.key = 'basic_3mo';
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -988,30 +809,13 @@ describe('payments/index', () => {
|
|||||||
data.updatedFrom = { logic: 'refundAndRepay' };
|
data.updatedFrom = { logic: 'refundAndRepay' };
|
||||||
});
|
});
|
||||||
context('Upgrades within first half of subscription', () => {
|
context('Upgrades within first half of subscription', () => {
|
||||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
|
||||||
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
data.updatedFrom.key = 'basic_earned';
|
|
||||||
clock.restore();
|
|
||||||
clock = sinon.useFakeTimers(new Date('2022-01-10'));
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_3mo';
|
data.updatedFrom.key = 'basic_3mo';
|
||||||
@@ -1019,28 +823,10 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-02-05'));
|
clock = sinon.useFakeTimers(new Date('2022-02-05'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
|
||||||
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
|
||||||
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
data.updatedFrom.key = 'basic_earned';
|
|
||||||
clock.restore();
|
|
||||||
clock = sinon.useFakeTimers(new Date('2022-01-08'));
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
@@ -1054,17 +840,17 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-01-31'));
|
clock = sinon.useFakeTimers(new Date('2022-01-31'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_6mo';
|
data.updatedFrom.key = 'basic_6mo';
|
||||||
@@ -1072,35 +858,17 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
|
it('2 plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
|
||||||
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
|
||||||
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
data.updatedFrom.key = 'basic_earned';
|
|
||||||
clock.restore();
|
|
||||||
clock = sinon.useFakeTimers(new Date('2024-01-08'));
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_6mo';
|
data.updatedFrom.key = 'basic_6mo';
|
||||||
@@ -1108,10 +876,10 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-08-28'));
|
clock = sinon.useFakeTimers(new Date('2022-08-28'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
@@ -1125,11 +893,11 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-07-31'));
|
clock = sinon.useFakeTimers(new Date('2022-07-31'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
context('Upgrades within second half of subscription', () => {
|
context('Upgrades within second half of subscription', () => {
|
||||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
it('Adds 0 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
data.sub.key = 'basic_earned';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
@@ -1144,16 +912,16 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-01-20'));
|
clock = sinon.useFakeTimers(new Date('2022-01-20'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_3mo';
|
data.updatedFrom.key = 'basic_3mo';
|
||||||
@@ -1161,17 +929,17 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-02-24'));
|
clock = sinon.useFakeTimers(new Date('2022-02-24'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
it('Adds 0 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
data.sub.key = 'basic_earned';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
data.updatedFrom.key = 'basic_earned';
|
data.updatedFrom.key = 'basic_earned';
|
||||||
@@ -1179,17 +947,17 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_6mo';
|
data.updatedFrom.key = 'basic_6mo';
|
||||||
@@ -1197,10 +965,10 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
@@ -1214,17 +982,17 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-03-03'));
|
clock = sinon.useFakeTimers(new Date('2022-03-03'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
|
it('Adds 0 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
data.sub.key = 'basic_earned';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
data.updatedFrom.key = 'basic_earned';
|
data.updatedFrom.key = 'basic_earned';
|
||||||
@@ -1232,17 +1000,17 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
|
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
data.updatedFrom.key = 'basic_6mo';
|
data.updatedFrom.key = 'basic_6mo';
|
||||||
@@ -1250,10 +1018,10 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2023-05-28'));
|
clock = sinon.useFakeTimers(new Date('2023-05-28'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
@@ -1267,7 +1035,7 @@ describe('payments/index', () => {
|
|||||||
clock = sinon.useFakeTimers(new Date('2023-09-03'));
|
clock = sinon.useFakeTimers(new Date('2023-09-03'));
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -1277,22 +1045,6 @@ describe('payments/index', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('Downgrades subscription', () => {
|
context('Downgrades subscription', () => {
|
||||||
it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => {
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
|
||||||
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
|
||||||
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
data.updatedFrom = { key: 'basic_6mo' };
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => {
|
it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => {
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
@@ -1300,28 +1052,12 @@ describe('payments/index', () => {
|
|||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
|
|
||||||
data.sub.key = 'basic_3mo';
|
data.sub.key = 'basic_3mo';
|
||||||
data.updatedFrom = { key: 'basic_12mo' };
|
data.updatedFrom = { key: 'basic_12mo' };
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26);
|
||||||
});
|
|
||||||
|
|
||||||
it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => {
|
|
||||||
data.sub.key = 'basic_6mo';
|
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
|
||||||
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
|
||||||
|
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
data.updatedFrom = { key: 'basic_6mo' };
|
|
||||||
await api.createSubscription(data);
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => {
|
it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => {
|
||||||
@@ -1331,12 +1067,12 @@ describe('payments/index', () => {
|
|||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
|
|
||||||
data.sub.key = 'basic_3mo';
|
data.sub.key = 'basic_3mo';
|
||||||
data.updatedFrom = { key: 'basic_12mo' };
|
data.updatedFrom = { key: 'basic_12mo' };
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(13);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1382,18 +1118,6 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(1);
|
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(1);
|
||||||
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
|
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not award mystery item when user already has the item in the mystery box', async () => {
|
|
||||||
user.purchased.plan.mysteryItems = [mayMysteryItem];
|
|
||||||
|
|
||||||
sandbox.spy(user.purchased.plan.mysteryItems, 'push');
|
|
||||||
|
|
||||||
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
|
||||||
await api.createSubscription(data);
|
|
||||||
|
|
||||||
expect(user.purchased.plan.mysteryItems.push).to.be.calledOnce;
|
|
||||||
expect(user.purchased.plan.mysteryItems.push).to.be.calledWith('head_mystery_201605');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1465,6 +1189,32 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not reset gemCapExtra', async () => {
|
||||||
|
user.purchased.plan.consecutive.gemCapExtra = 12;
|
||||||
|
|
||||||
|
await api.cancelSubscription(data);
|
||||||
|
|
||||||
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes gemCapExtra', async () => {
|
||||||
|
await api.cancelSubscription(data);
|
||||||
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes hourglasses', async () => {
|
||||||
|
await api.cancelSubscription(data);
|
||||||
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not reset owned hourglasses', async () => {
|
||||||
|
user.purchased.plan.consecutive.trinkets = 12;
|
||||||
|
|
||||||
|
await api.cancelSubscription(data);
|
||||||
|
|
||||||
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(12);
|
||||||
|
});
|
||||||
|
|
||||||
it('sends an email', async () => {
|
it('sends an email', async () => {
|
||||||
await api.cancelSubscription(data);
|
await api.cancelSubscription(data);
|
||||||
|
|
||||||
@@ -1599,10 +1349,10 @@ describe('payments/index', () => {
|
|||||||
it('sends gem donation message in each participant\'s language', async () => {
|
it('sends gem donation message in each participant\'s language', async () => {
|
||||||
// TODO using english for both users because other languages are not loaded
|
// TODO using english for both users because other languages are not loaded
|
||||||
// for api.buyGems
|
// for api.buyGems
|
||||||
await recipient.update({
|
await recipient.updateOne({
|
||||||
'preferences.language': 'en',
|
'preferences.language': 'en',
|
||||||
});
|
});
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
'preferences.language': 'en',
|
'preferences.language': 'en',
|
||||||
});
|
});
|
||||||
await api.buyGems(data);
|
await api.buyGems(data);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import nconf from 'nconf';
|
|||||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||||
import { model as User } from '../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import common from '../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
import apiError from '../../../../../../website/server/libs/apiError';
|
import { apiError } from '../../../../../../website/server/libs/apiError';
|
||||||
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
||||||
|
|
||||||
const BASE_URL = nconf.get('BASE_URL');
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ describe('Stripe - Checkout', () => {
|
|||||||
gift: undefined,
|
gift: undefined,
|
||||||
sub: undefined,
|
sub: undefined,
|
||||||
gemsBlock: gemsBlockKey,
|
gemsBlock: gemsBlockKey,
|
||||||
|
server_url: BASE_URL,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(gems.validateGiftMessage).to.not.be.called;
|
expect(gems.validateGiftMessage).to.not.be.called;
|
||||||
@@ -101,6 +102,7 @@ describe('Stripe - Checkout', () => {
|
|||||||
gift: JSON.stringify(gift),
|
gift: JSON.stringify(gift),
|
||||||
sub: undefined,
|
sub: undefined,
|
||||||
gemsBlock: undefined,
|
gemsBlock: undefined,
|
||||||
|
server_url: BASE_URL,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(gems.validateGiftMessage).to.be.calledOnce;
|
expect(gems.validateGiftMessage).to.be.calledOnce;
|
||||||
@@ -155,6 +157,7 @@ describe('Stripe - Checkout', () => {
|
|||||||
gift: JSON.stringify(gift),
|
gift: JSON.stringify(gift),
|
||||||
sub: undefined,
|
sub: undefined,
|
||||||
gemsBlock: undefined,
|
gemsBlock: undefined,
|
||||||
|
server_url: BASE_URL,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce;
|
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce;
|
||||||
@@ -192,6 +195,7 @@ describe('Stripe - Checkout', () => {
|
|||||||
userId: user._id,
|
userId: user._id,
|
||||||
gift: undefined,
|
gift: undefined,
|
||||||
sub: JSON.stringify(sub),
|
sub: JSON.stringify(sub),
|
||||||
|
server_url: BASE_URL,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(subscriptions.checkSubData).to.be.calledOnce;
|
expect(subscriptions.checkSubData).to.be.calledOnce;
|
||||||
@@ -258,6 +262,7 @@ describe('Stripe - Checkout', () => {
|
|||||||
userId: user._id,
|
userId: user._id,
|
||||||
gift: undefined,
|
gift: undefined,
|
||||||
sub: JSON.stringify(sub),
|
sub: JSON.stringify(sub),
|
||||||
|
server_url: BASE_URL,
|
||||||
groupId,
|
groupId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -328,8 +333,9 @@ describe('Stripe - Checkout', () => {
|
|||||||
user.purchased.plan.customerId = customerId;
|
user.purchased.plan.customerId = customerId;
|
||||||
|
|
||||||
const metadata = {
|
const metadata = {
|
||||||
userId: user._id,
|
|
||||||
type: 'edit-card-user',
|
type: 'edit-card-user',
|
||||||
|
userId: user._id,
|
||||||
|
server_url: BASE_URL,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await createEditCardCheckoutSession({ user }, stripe);
|
const res = await createEditCardCheckoutSession({ user }, stripe);
|
||||||
@@ -418,6 +424,7 @@ describe('Stripe - Checkout', () => {
|
|||||||
const metadata = {
|
const metadata = {
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
type: 'edit-card-group',
|
type: 'edit-card-group',
|
||||||
|
server_url: BASE_URL,
|
||||||
groupId,
|
groupId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -455,6 +462,7 @@ describe('Stripe - Checkout', () => {
|
|||||||
userId: anotherUser._id,
|
userId: anotherUser._id,
|
||||||
type: 'edit-card-group',
|
type: 'edit-card-group',
|
||||||
groupId,
|
groupId,
|
||||||
|
server_url: BASE_URL,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await createEditCardCheckoutSession({ user: anotherUser, groupId }, stripe);
|
const res = await createEditCardCheckoutSession({ user: anotherUser, groupId }, stripe);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import apiError from '../../../../../../website/server/libs/apiError';
|
import { apiError } from '../../../../../../website/server/libs/apiError';
|
||||||
import common from '../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
import {
|
import {
|
||||||
getOneTimePaymentInfo,
|
getOneTimePaymentInfo,
|
||||||
@@ -308,6 +308,7 @@ describe('Stripe - One Time Payments', () => {
|
|||||||
customerId,
|
customerId,
|
||||||
paymentMethod: 'Gift',
|
paymentMethod: 'Gift',
|
||||||
gift,
|
gift,
|
||||||
|
autoRenews: false,
|
||||||
gemsBlock: undefined,
|
gemsBlock: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ describe('Stripe - Subscriptions', () => {
|
|||||||
paymentMethod: 'Stripe',
|
paymentMethod: 'Stripe',
|
||||||
sub: sinon.match({ ...sub }),
|
sub: sinon.match({ ...sub }),
|
||||||
groupId: null,
|
groupId: null,
|
||||||
|
autoRenews: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,6 +198,7 @@ describe('Stripe - Subscriptions', () => {
|
|||||||
paymentMethod: 'Stripe',
|
paymentMethod: 'Stripe',
|
||||||
sub: sinon.match({ ...sub }),
|
sub: sinon.match({ ...sub }),
|
||||||
groupId,
|
groupId,
|
||||||
|
autoRenews: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -231,6 +233,7 @@ describe('Stripe - Subscriptions', () => {
|
|||||||
paymentMethod: 'Stripe',
|
paymentMethod: 'Stripe',
|
||||||
sub: sinon.match({ ...sub }),
|
sub: sinon.match({ ...sub }),
|
||||||
groupId,
|
groupId,
|
||||||
|
autoRenews: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import * as subscriptions from '../../../../../../website/server/libs/payments/s
|
|||||||
const { i18n } = common;
|
const { i18n } = common;
|
||||||
|
|
||||||
describe('Stripe - Webhooks', () => {
|
describe('Stripe - Webhooks', () => {
|
||||||
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
const stripe = stripeModule('test');
|
const stripe = stripeModule('test');
|
||||||
const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET');
|
const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET');
|
||||||
const headers = {};
|
const headers = {};
|
||||||
@@ -284,7 +285,9 @@ describe('Stripe - Webhooks', () => {
|
|||||||
const session = {};
|
const session = {};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
session.metadata = {};
|
session.metadata = {
|
||||||
|
server_url: BASE_URL,
|
||||||
|
};
|
||||||
event = { type: eventType, data: { object: session } };
|
event = { type: eventType, data: { object: session } };
|
||||||
constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent');
|
constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent');
|
||||||
constructEventStub.returns(event);
|
constructEventStub.returns(event);
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
import apn from '@parse/node-apn/mock';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import nconf from 'nconf';
|
|
||||||
import gcmLib from 'node-gcm'; // works with FCM notifications too
|
|
||||||
import { model as User } from '../../../../website/server/models/user';
|
|
||||||
import {
|
|
||||||
sendNotification as sendPushNotification,
|
|
||||||
MAX_MESSAGE_LENGTH,
|
|
||||||
} from '../../../../website/server/libs/pushNotifications';
|
|
||||||
|
|
||||||
describe('pushNotifications', () => {
|
|
||||||
let user;
|
|
||||||
let fcmSendSpy;
|
|
||||||
let apnSendSpy;
|
|
||||||
|
|
||||||
const identifier = 'identifier';
|
|
||||||
const title = 'title';
|
|
||||||
const message = 'message';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
user = new User();
|
|
||||||
fcmSendSpy = sinon.spy();
|
|
||||||
apnSendSpy = sinon.spy();
|
|
||||||
|
|
||||||
sandbox.stub(nconf, 'get').returns('true-key');
|
|
||||||
|
|
||||||
sandbox.stub(gcmLib.Sender.prototype, 'send').callsFake(fcmSendSpy);
|
|
||||||
|
|
||||||
sandbox.stub(apn.Provider.prototype, 'send').returns({
|
|
||||||
on: () => null,
|
|
||||||
send: apnSendSpy,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
sandbox.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if user is not supplied', () => {
|
|
||||||
expect(sendPushNotification).to.throw;
|
|
||||||
expect(fcmSendSpy).to.not.have.been.called;
|
|
||||||
expect(apnSendSpy).to.not.have.been.called;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if user.preferences.pushNotifications.unsubscribeFromAll is true', () => {
|
|
||||||
user.preferences.pushNotifications.unsubscribeFromAll = true;
|
|
||||||
expect(() => sendPushNotification(user)).to.throw;
|
|
||||||
expect(fcmSendSpy).to.not.have.been.called;
|
|
||||||
expect(apnSendSpy).to.not.have.been.called;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if details.identifier is not supplied', () => {
|
|
||||||
expect(() => sendPushNotification(user, {
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
})).to.throw;
|
|
||||||
expect(fcmSendSpy).to.not.have.been.called;
|
|
||||||
expect(apnSendSpy).to.not.have.been.called;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if details.title is not supplied', () => {
|
|
||||||
expect(() => sendPushNotification(user, {
|
|
||||||
identifier,
|
|
||||||
message,
|
|
||||||
})).to.throw;
|
|
||||||
expect(fcmSendSpy).to.not.have.been.called;
|
|
||||||
expect(apnSendSpy).to.not.have.been.called;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if details.message is not supplied', () => {
|
|
||||||
expect(() => sendPushNotification(user, {
|
|
||||||
identifier,
|
|
||||||
title,
|
|
||||||
})).to.throw;
|
|
||||||
expect(fcmSendSpy).to.not.have.been.called;
|
|
||||||
expect(apnSendSpy).to.not.have.been.called;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns if no device is registered', () => {
|
|
||||||
sendPushNotification(user, {
|
|
||||||
identifier,
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
expect(fcmSendSpy).to.not.have.been.called;
|
|
||||||
expect(apnSendSpy).to.not.have.been.called;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cuts the message to 300 chars', () => {
|
|
||||||
const longMessage = `12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
|
|
||||||
|
|
||||||
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
|
|
||||||
|
|
||||||
const details = {
|
|
||||||
identifier,
|
|
||||||
title,
|
|
||||||
message: longMessage,
|
|
||||||
payload: {
|
|
||||||
message: longMessage,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
sendPushNotification(user, details);
|
|
||||||
|
|
||||||
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
|
|
||||||
expect(details.payload.message)
|
|
||||||
.to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
|
|
||||||
|
|
||||||
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
|
|
||||||
expect(details.payload.message.length).to.equal(MAX_MESSAGE_LENGTH);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cuts the message to 300 chars (no payload)', () => {
|
|
||||||
const longMessage = `12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
|
||||||
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
|
|
||||||
|
|
||||||
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
|
|
||||||
|
|
||||||
const details = {
|
|
||||||
identifier,
|
|
||||||
title,
|
|
||||||
message: longMessage,
|
|
||||||
};
|
|
||||||
|
|
||||||
sendPushNotification(user, details);
|
|
||||||
|
|
||||||
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
|
|
||||||
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO disabled because APN relies on a Promise
|
|
||||||
xit('uses APN for iOS devices', () => {
|
|
||||||
user.pushDevices.push({
|
|
||||||
type: 'ios',
|
|
||||||
regId: '123',
|
|
||||||
});
|
|
||||||
|
|
||||||
const details = {
|
|
||||||
identifier,
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
category: 'fun',
|
|
||||||
payload: {
|
|
||||||
a: true,
|
|
||||||
b: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const expectedNotification = new apn.Notification({
|
|
||||||
alert: message,
|
|
||||||
sound: 'default',
|
|
||||||
category: 'fun',
|
|
||||||
payload: {
|
|
||||||
identifier,
|
|
||||||
a: true,
|
|
||||||
b: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
sendPushNotification(user, details);
|
|
||||||
expect(apnSendSpy).to.have.been.calledOnce;
|
|
||||||
expect(apnSendSpy).to.have.been.calledWithMatch(expectedNotification, '123');
|
|
||||||
expect(fcmSendSpy).to.not.have.been.called;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
354
test/api/unit/libs/pushNotifications.test.js
Normal file
354
test/api/unit/libs/pushNotifications.test.js
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
import apn from '@parse/node-apn';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import admin from 'firebase-admin';
|
||||||
|
import { model as User } from '../../../../website/server/models/user';
|
||||||
|
import {
|
||||||
|
MAX_MESSAGE_LENGTH,
|
||||||
|
} from '../../../../website/server/libs/pushNotifications';
|
||||||
|
|
||||||
|
let sendPushNotification;
|
||||||
|
|
||||||
|
describe('pushNotifications', () => {
|
||||||
|
let user;
|
||||||
|
let fcmSendSpy;
|
||||||
|
let apnSendSpy;
|
||||||
|
let updateStub;
|
||||||
|
let classStubbedInstance;
|
||||||
|
|
||||||
|
const identifier = 'identifier';
|
||||||
|
const title = 'title';
|
||||||
|
const message = 'message';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
user = new User();
|
||||||
|
fcmSendSpy = sinon.stub().returns(Promise.resolve('success'));
|
||||||
|
apnSendSpy = sinon.stub().returns(Promise.resolve());
|
||||||
|
|
||||||
|
nconf.set('PUSH_CONFIGS_APN_ENABLED', 'true');
|
||||||
|
|
||||||
|
classStubbedInstance = sandbox.createStubInstance(apn.Provider, {
|
||||||
|
send: apnSendSpy,
|
||||||
|
});
|
||||||
|
sandbox.stub(apn, 'Provider').returns(classStubbedInstance);
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../../../website/server/libs/pushNotifications')];
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
sendPushNotification = require('../../../../website/server/libs/pushNotifications').sendNotification;
|
||||||
|
|
||||||
|
updateStub = sandbox.stub(User, 'updateOne').resolves();
|
||||||
|
sandbox.stub(admin, 'messaging').get(() => () => ({ send: fcmSendSpy }));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validates supplied data', () => {
|
||||||
|
it('throws if user is not supplied', () => {
|
||||||
|
expect(sendPushNotification).to.throw;
|
||||||
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
|
expect(apnSendSpy).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if user.preferences.pushNotifications.unsubscribeFromAll is true', () => {
|
||||||
|
user.preferences.pushNotifications.unsubscribeFromAll = true;
|
||||||
|
expect(() => sendPushNotification(user)).to.throw;
|
||||||
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
|
expect(apnSendSpy).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if details.identifier is not supplied', () => {
|
||||||
|
expect(() => sendPushNotification(user, {
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
})).to.throw;
|
||||||
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
|
expect(apnSendSpy).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if details.title is not supplied', () => {
|
||||||
|
expect(() => sendPushNotification(user, {
|
||||||
|
identifier,
|
||||||
|
message,
|
||||||
|
})).to.throw;
|
||||||
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
|
expect(apnSendSpy).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if details.message is not supplied', () => {
|
||||||
|
expect(() => sendPushNotification(user, {
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
})).to.throw;
|
||||||
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
|
expect(apnSendSpy).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns if no device is registered', () => {
|
||||||
|
sendPushNotification(user, {
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
|
expect(apnSendSpy).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cuts the message to 300 chars', () => {
|
||||||
|
const longMessage = `12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
|
||||||
|
|
||||||
|
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
|
||||||
|
|
||||||
|
const details = {
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
message: longMessage,
|
||||||
|
payload: {
|
||||||
|
message: longMessage,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
sendPushNotification(user, details);
|
||||||
|
|
||||||
|
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
|
||||||
|
expect(details.payload.message)
|
||||||
|
.to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
|
||||||
|
|
||||||
|
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
|
||||||
|
expect(details.payload.message.length).to.equal(MAX_MESSAGE_LENGTH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cuts the message to 300 chars (no payload)', () => {
|
||||||
|
const longMessage = `12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345
|
||||||
|
12345 12345 12345 12345 12345 12345 12345 12345 12345 12345`;
|
||||||
|
|
||||||
|
expect(longMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
|
||||||
|
|
||||||
|
const details = {
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
message: longMessage,
|
||||||
|
};
|
||||||
|
|
||||||
|
sendPushNotification(user, details);
|
||||||
|
|
||||||
|
expect(details.message).to.equal(_.truncate(longMessage, { length: MAX_MESSAGE_LENGTH }));
|
||||||
|
expect(details.message.length).to.equal(MAX_MESSAGE_LENGTH);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sends notifications', () => {
|
||||||
|
let details;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
details = {
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
category: 'fun',
|
||||||
|
payload: {
|
||||||
|
a: true,
|
||||||
|
b: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses APN for iOS devices', async () => {
|
||||||
|
user.pushDevices.push({
|
||||||
|
type: 'ios',
|
||||||
|
regId: '123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedNotification = new apn.Notification({
|
||||||
|
alert: {
|
||||||
|
title,
|
||||||
|
body: message,
|
||||||
|
},
|
||||||
|
sound: 'default',
|
||||||
|
category: 'fun',
|
||||||
|
payload: {
|
||||||
|
identifier,
|
||||||
|
a: true,
|
||||||
|
b: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await sendPushNotification(user, details);
|
||||||
|
expect(apnSendSpy).to.have.been.calledOnce;
|
||||||
|
expect(apnSendSpy).to.have.been.calledWithMatch(expectedNotification, '123');
|
||||||
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses FCM for Android devices', async () => {
|
||||||
|
user.pushDevices.push({
|
||||||
|
type: 'android',
|
||||||
|
regId: '123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedMessage = {
|
||||||
|
notification: {
|
||||||
|
title,
|
||||||
|
body: message,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
identifier,
|
||||||
|
notificationIdentifier: identifier,
|
||||||
|
},
|
||||||
|
token: '123',
|
||||||
|
};
|
||||||
|
|
||||||
|
await sendPushNotification(user, details);
|
||||||
|
expect(fcmSendSpy).to.have.been.calledOnce;
|
||||||
|
expect(fcmSendSpy).to.have.been.calledWithMatch(expectedMessage);
|
||||||
|
expect(apnSendSpy).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles multiple devices', async () => {
|
||||||
|
user.pushDevices.push({
|
||||||
|
type: 'android',
|
||||||
|
regId: '123',
|
||||||
|
});
|
||||||
|
user.pushDevices.push({
|
||||||
|
type: 'ios',
|
||||||
|
regId: '456',
|
||||||
|
});
|
||||||
|
user.pushDevices.push({
|
||||||
|
type: 'android',
|
||||||
|
regId: '789',
|
||||||
|
});
|
||||||
|
|
||||||
|
await sendPushNotification(user, details);
|
||||||
|
expect(fcmSendSpy).to.have.been.calledTwice;
|
||||||
|
expect(apnSendSpy).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handles sending errors', () => {
|
||||||
|
let clock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
clock = sinon.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
clock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes unregistered fcm devices', async () => {
|
||||||
|
user.pushDevices.push({
|
||||||
|
type: 'android',
|
||||||
|
regId: '123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = new Error();
|
||||||
|
error.code = 'messaging/registration-token-not-registered';
|
||||||
|
fcmSendSpy.rejects(error);
|
||||||
|
|
||||||
|
await sendPushNotification(user, {
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fcmSendSpy).to.have.been.calledOnce;
|
||||||
|
expect(apnSendSpy).to.not.have.been.called;
|
||||||
|
await clock.tick(10);
|
||||||
|
expect(updateStub).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes invalid fcm devices', async () => {
|
||||||
|
user.pushDevices.push({
|
||||||
|
type: 'android',
|
||||||
|
regId: '123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = new Error();
|
||||||
|
error.code = 'messaging/registration-token-not-registered';
|
||||||
|
fcmSendSpy.rejects(error);
|
||||||
|
|
||||||
|
await sendPushNotification(user, {
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fcmSendSpy).to.have.been.calledOnce;
|
||||||
|
expect(apnSendSpy).to.not.have.been.called;
|
||||||
|
expect(updateStub).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes unregistered apn devices', async () => {
|
||||||
|
user.pushDevices.push({
|
||||||
|
type: 'ios',
|
||||||
|
regId: '123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = {
|
||||||
|
failed: [
|
||||||
|
{
|
||||||
|
device: '123',
|
||||||
|
response: { reason: 'Unregistered' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
apnSendSpy.resolves(error);
|
||||||
|
|
||||||
|
await sendPushNotification(user, {
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
|
expect(apnSendSpy).to.have.been.calledOnce;
|
||||||
|
expect(updateStub).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes invalid apn devices', async () => {
|
||||||
|
user.pushDevices.push({
|
||||||
|
type: 'ios',
|
||||||
|
regId: '123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = {
|
||||||
|
failed: [
|
||||||
|
{
|
||||||
|
device: '123',
|
||||||
|
response: { reason: 'BadDeviceToken' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
apnSendSpy.resolves(error);
|
||||||
|
|
||||||
|
await sendPushNotification(user, {
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
|
expect(apnSendSpy).to.have.been.calledOnce;
|
||||||
|
expect(updateStub).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -53,11 +53,9 @@ describe('cron middleware', () => {
|
|||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
|
|
||||||
Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
|
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
||||||
if (secondErr) return reject(err);
|
expect(foundTask).to.not.exist;
|
||||||
expect(secondErr).to.not.exist;
|
resolve();
|
||||||
expect(taskFound).to.not.exist;
|
|
||||||
return resolve();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -78,10 +76,8 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
|
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
||||||
if (secondErr) return reject(secondErr);
|
expect(foundTask).to.exist;
|
||||||
expect(secondErr).to.not.exist;
|
|
||||||
expect(taskFound).to.exist;
|
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
@@ -103,10 +99,8 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
|
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
||||||
if (secondErr) return reject(secondErr);
|
expect(foundTask).to.not.exist;
|
||||||
expect(secondErr).to.not.exist;
|
|
||||||
expect(taskFound).to.not.exist;
|
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
@@ -170,8 +164,7 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
return User.findOne({ _id: user._id }, (secondErr, updatedUser) => {
|
return User.findOne({ _id: user._id }).then(updatedUser => {
|
||||||
if (secondErr) return reject(secondErr);
|
|
||||||
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
@@ -188,8 +181,7 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
return Tasks.Task.findOne({ _id: todo._id }, (secondErr, todoFound) => {
|
return Tasks.Task.findOne({ _id: todo._id }).then(todoFound => {
|
||||||
if (secondErr) return reject(secondErr);
|
|
||||||
expect(todoFound.value).to.be.lessThan(todoValueBefore);
|
expect(todoFound.value).to.be.lessThan(todoValueBefore);
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
@@ -224,8 +216,7 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
return User.findOne({ _id: user._id }, (secondErr, updatedUser) => {
|
return User.findOne({ _id: user._id }).then(updatedUser => {
|
||||||
if (secondErr) return reject(secondErr);
|
|
||||||
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
@@ -238,7 +229,7 @@ describe('cron middleware', () => {
|
|||||||
await user.save();
|
await user.save();
|
||||||
|
|
||||||
const updatedUser = user.toObject();
|
const updatedUser = user.toObject();
|
||||||
updatedUser.nMatched = 0;
|
updatedUser.matchedCount = 0;
|
||||||
|
|
||||||
sandbox.spy(cronLib, 'recoverCron');
|
sandbox.spy(cronLib, 'recoverCron');
|
||||||
|
|
||||||
@@ -269,7 +260,7 @@ describe('cron middleware', () => {
|
|||||||
it('cronSignature less than an hour ago should error', async () => {
|
it('cronSignature less than an hour ago should error', async () => {
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
await User.update({
|
await User.updateOne({
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
}, {
|
}, {
|
||||||
$set: {
|
$set: {
|
||||||
@@ -291,7 +282,7 @@ describe('cron middleware', () => {
|
|||||||
it('cronSignature longer than an hour ago should allow cron', async () => {
|
it('cronSignature longer than an hour ago should allow cron', async () => {
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
await User.update({
|
await User.updateOne({
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
}, {
|
}, {
|
||||||
$set: {
|
$set: {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
} from '../../../helpers/api-unit.helper';
|
} from '../../../helpers/api-unit.helper';
|
||||||
import { ensurePermission } from '../../../../website/server/middlewares/ensureAccessRight';
|
import { ensurePermission } from '../../../../website/server/middlewares/ensureAccessRight';
|
||||||
import { NotAuthorized } from '../../../../website/server/libs/errors';
|
import { NotAuthorized } from '../../../../website/server/libs/errors';
|
||||||
import apiError from '../../../../website/server/libs/apiError';
|
import { apiError } from '../../../../website/server/libs/apiError';
|
||||||
|
|
||||||
describe('ensure access middlewares', () => {
|
describe('ensure access middlewares', () => {
|
||||||
let res; let req; let
|
let res; let req; let
|
||||||
|
|||||||
51
test/api/unit/middlewares/ensureDevelopmentMode.js
Normal file
51
test/api/unit/middlewares/ensureDevelopmentMode.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/* eslint-disable global-require */
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import {
|
||||||
|
generateRes,
|
||||||
|
generateReq,
|
||||||
|
generateNext,
|
||||||
|
} from '../../../helpers/api-unit.helper';
|
||||||
|
import ensureDevelopmentMode from '../../../../website/server/middlewares/ensureDevelopmentMode';
|
||||||
|
import { NotFound } from '../../../../website/server/libs/errors';
|
||||||
|
|
||||||
|
describe('developmentMode middleware', () => {
|
||||||
|
let res; let req; let next;
|
||||||
|
let nconfStub;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
res = generateRes();
|
||||||
|
req = generateReq();
|
||||||
|
next = generateNext();
|
||||||
|
nconfStub = sandbox.stub(nconf, 'get');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns not found when on production URL', () => {
|
||||||
|
nconfStub.withArgs('DEBUG_ENABLED').returns(true);
|
||||||
|
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||||
|
|
||||||
|
ensureDevelopmentMode(req, res, next);
|
||||||
|
|
||||||
|
const calledWith = next.getCall(0).args;
|
||||||
|
expect(calledWith[0] instanceof NotFound).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns not found when intentionally disabled', () => {
|
||||||
|
nconfStub.withArgs('DEBUG_ENABLED').returns(false);
|
||||||
|
nconfStub.withArgs('BASE_URL').returns('http://localhost:3000');
|
||||||
|
|
||||||
|
ensureDevelopmentMode(req, res, next);
|
||||||
|
|
||||||
|
const calledWith = next.getCall(0).args;
|
||||||
|
expect(calledWith[0] instanceof NotFound).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes when enabled and on non-production URL', () => {
|
||||||
|
nconfStub.withArgs('DEBUG_ENABLED').returns(true);
|
||||||
|
nconfStub.withArgs('BASE_URL').returns('http://localhost:3000');
|
||||||
|
|
||||||
|
ensureDevelopmentMode(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.be.calledOnce;
|
||||||
|
expect(next.args[0]).to.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/* eslint-disable global-require */
|
|
||||||
import nconf from 'nconf';
|
|
||||||
import {
|
|
||||||
generateRes,
|
|
||||||
generateReq,
|
|
||||||
generateNext,
|
|
||||||
} from '../../../helpers/api-unit.helper';
|
|
||||||
import ensureDevelpmentMode from '../../../../website/server/middlewares/ensureDevelpmentMode';
|
|
||||||
import { NotFound } from '../../../../website/server/libs/errors';
|
|
||||||
|
|
||||||
describe('developmentMode middleware', () => {
|
|
||||||
let res; let req; let
|
|
||||||
next;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
res = generateRes();
|
|
||||||
req = generateReq();
|
|
||||||
next = generateNext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns not found when in production mode', () => {
|
|
||||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
|
||||||
|
|
||||||
ensureDevelpmentMode(req, res, next);
|
|
||||||
|
|
||||||
const calledWith = next.getCall(0).args;
|
|
||||||
expect(calledWith[0] instanceof NotFound).to.equal(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('passes when not in production', () => {
|
|
||||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
|
|
||||||
|
|
||||||
ensureDevelpmentMode(req, res, next);
|
|
||||||
|
|
||||||
expect(next).to.be.calledOnce;
|
|
||||||
expect(next.args[0]).to.be.empty;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
51
test/api/unit/middlewares/ensureTimeTravelMode.js
Normal file
51
test/api/unit/middlewares/ensureTimeTravelMode.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/* eslint-disable global-require */
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import {
|
||||||
|
generateRes,
|
||||||
|
generateReq,
|
||||||
|
generateNext,
|
||||||
|
} from '../../../helpers/api-unit.helper';
|
||||||
|
import { NotFound } from '../../../../website/server/libs/errors';
|
||||||
|
import ensureTimeTravelMode from '../../../../website/server/middlewares/ensureTimeTravelMode';
|
||||||
|
|
||||||
|
describe('timetravelMode middleware', () => {
|
||||||
|
let res; let req; let next;
|
||||||
|
let nconfStub;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
res = generateRes();
|
||||||
|
req = generateReq();
|
||||||
|
next = generateNext();
|
||||||
|
nconfStub = sandbox.stub(nconf, 'get');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns not found when using production URL', () => {
|
||||||
|
nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(false);
|
||||||
|
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||||
|
|
||||||
|
ensureTimeTravelMode(req, res, next);
|
||||||
|
|
||||||
|
const calledWith = next.getCall(0).args;
|
||||||
|
expect(calledWith[0] instanceof NotFound).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns not found when not in time travel mode', () => {
|
||||||
|
nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(false);
|
||||||
|
nconfStub.withArgs('BASE_URL').returns('http://localhost:3000');
|
||||||
|
|
||||||
|
ensureTimeTravelMode(req, res, next);
|
||||||
|
|
||||||
|
const calledWith = next.getCall(0).args;
|
||||||
|
expect(calledWith[0] instanceof NotFound).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes when in time travel mode', () => {
|
||||||
|
nconfStub.withArgs('TIME_TRAVEL_ENABLED').returns(true);
|
||||||
|
nconfStub.withArgs('BASE_URL').returns('http://localhost:3000');
|
||||||
|
|
||||||
|
ensureTimeTravelMode(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.be.calledOnce;
|
||||||
|
expect(next.args[0]).to.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
generateNext,
|
generateNext,
|
||||||
} from '../../../helpers/api-unit.helper';
|
} from '../../../helpers/api-unit.helper';
|
||||||
import { Forbidden } from '../../../../website/server/libs/errors';
|
import { Forbidden } from '../../../../website/server/libs/errors';
|
||||||
import apiError from '../../../../website/server/libs/apiError';
|
import { apiError } from '../../../../website/server/libs/apiError';
|
||||||
|
|
||||||
function checkErrorThrown (next) {
|
function checkErrorThrown (next) {
|
||||||
expect(next).to.have.been.calledOnce;
|
expect(next).to.have.been.calledOnce;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
generateNext,
|
generateNext,
|
||||||
} from '../../../helpers/api-unit.helper';
|
} from '../../../helpers/api-unit.helper';
|
||||||
import { TooManyRequests } from '../../../../website/server/libs/errors';
|
import { TooManyRequests } from '../../../../website/server/libs/errors';
|
||||||
import apiError from '../../../../website/server/libs/apiError';
|
import { apiError } from '../../../../website/server/libs/apiError';
|
||||||
import logger from '../../../../website/server/libs/logger';
|
import logger from '../../../../website/server/libs/logger';
|
||||||
|
|
||||||
describe('rateLimiter middleware', () => {
|
describe('rateLimiter middleware', () => {
|
||||||
@@ -54,6 +54,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
|
|
||||||
it('does not throw when there are available points', async () => {
|
it('does not throw when there are available points', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
|
|
||||||
it('does not throw when an unknown error is thrown by the rate limiter', async () => {
|
it('does not throw when an unknown error is thrown by the rate limiter', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
sandbox.stub(logger, 'error');
|
sandbox.stub(logger, 'error');
|
||||||
sandbox.stub(RateLimiterMemory.prototype, 'consume')
|
sandbox.stub(RateLimiterMemory.prototype, 'consume')
|
||||||
.returns(Promise.reject(new Error('Unknown error.')));
|
.returns(Promise.reject(new Error('Unknown error.')));
|
||||||
@@ -87,8 +89,73 @@ describe('rateLimiter middleware', () => {
|
|||||||
expect(logger.error).to.have.been.calledWithMatch(Error, 'Rate Limiter Error');
|
expect(logger.error).to.have.been.calledWithMatch(Error, 'Rate Limiter Error');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not throw when LIVELINESS_PROBE_KEY is correct', async () => {
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
|
||||||
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
|
req.query.liveliness = 'abc';
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.have.been.calledOnce;
|
||||||
|
const calledWith = next.getCall(0).args;
|
||||||
|
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||||
|
expect(res.set).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('limits when LIVELINESS_PROBE_KEY is incorrect', async () => {
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
|
req.query.liveliness = 'das';
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.have.been.calledOnce;
|
||||||
|
expect(res.set).to.have.been.calledWithMatch({
|
||||||
|
'X-RateLimit-Limit': 30,
|
||||||
|
'X-RateLimit-Remaining': 29,
|
||||||
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('limits when LIVELINESS_PROBE_KEY is not set', async () => {
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns(undefined);
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.have.been.calledOnce;
|
||||||
|
expect(res.set).to.have.been.calledWithMatch({
|
||||||
|
'X-RateLimit-Limit': 30,
|
||||||
|
'X-RateLimit-Remaining': 29,
|
||||||
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when LIVELINESS_PROBE_KEY is blank', async () => {
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
|
req.query.liveliness = '';
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.have.been.calledOnce;
|
||||||
|
expect(res.set).to.have.been.calledWithMatch({
|
||||||
|
'X-RateLimit-Limit': 30,
|
||||||
|
'X-RateLimit-Remaining': 29,
|
||||||
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('throws when there are no available points remaining', async () => {
|
it('throws when there are no available points remaining', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
// call for 31 times
|
// call for 31 times
|
||||||
@@ -112,6 +179,7 @@ describe('rateLimiter middleware', () => {
|
|||||||
|
|
||||||
it('uses the user id if supplied or the ip address', async () => {
|
it('uses the user id if supplied or the ip address', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
req.ip = 1;
|
req.ip = 1;
|
||||||
@@ -138,4 +206,51 @@ describe('rateLimiter middleware', () => {
|
|||||||
'X-RateLimit-Reset': sinon.match(Date),
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('applies increased cost for registration calls with and without user id', async () => {
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_REGISTRATION_COST').returns(3);
|
||||||
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
req.path = '/api/v4/user/auth/local/register';
|
||||||
|
|
||||||
|
req.ip = 1;
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
req.headers['x-api-user'] = 'user-1';
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
// user id an ip are counted as separate sources
|
||||||
|
expect(res.set).to.have.been.calledWithMatch({
|
||||||
|
'X-RateLimit-Limit': 30,
|
||||||
|
'X-RateLimit-Remaining': 27, // 2 calls with user id
|
||||||
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
|
});
|
||||||
|
|
||||||
|
req.headers['x-api-user'] = undefined;
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
expect(res.set).to.have.been.calledWithMatch({
|
||||||
|
'X-RateLimit-Limit': 30,
|
||||||
|
'X-RateLimit-Remaining': 24, // 3 calls with only ip
|
||||||
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies increased cost for unauthenticated API calls', async () => {
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(10);
|
||||||
|
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
|
||||||
|
req.ip = 1;
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
|
expect(res.set).to.have.been.calledWithMatch({
|
||||||
|
'X-RateLimit-Limit': 30,
|
||||||
|
'X-RateLimit-Remaining': 10,
|
||||||
|
'X-RateLimit-Reset': sinon.match(Date),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
37
test/api/unit/middlewares/requestLogHandler.test.js
Normal file
37
test/api/unit/middlewares/requestLogHandler.test.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/* eslint-disable global-require */
|
||||||
|
import requireAgain from 'require-again';
|
||||||
|
import {
|
||||||
|
generateRes,
|
||||||
|
generateReq,
|
||||||
|
generateNext,
|
||||||
|
} from '../../../helpers/api-unit.helper';
|
||||||
|
|
||||||
|
describe('requestLogHandler middleware', () => {
|
||||||
|
let res; let req; let
|
||||||
|
next;
|
||||||
|
const pathToMiddleWare = '../../../../website/server/middlewares/requestLogHandler';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
res = generateRes();
|
||||||
|
req = generateReq();
|
||||||
|
next = generateNext();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('attaches start time and request ID object to req', () => {
|
||||||
|
const middleware = requireAgain(pathToMiddleWare);
|
||||||
|
|
||||||
|
middleware.logRequestData(req, res, next);
|
||||||
|
|
||||||
|
expect(req.requestStartTime).to.exist;
|
||||||
|
expect(req.requestStartTime).to.be.a('number');
|
||||||
|
expect(req.requestIdentifier).to.exist;
|
||||||
|
expect(req.requestIdentifier).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls next', () => {
|
||||||
|
const middleware = requireAgain(pathToMiddleWare);
|
||||||
|
const spy = sinon.spy();
|
||||||
|
middleware.logRequestData(req, res, spy);
|
||||||
|
expect(spy.calledOnce).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1358,12 +1358,12 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
describe('#sendChat', () => {
|
describe('#sendChat', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox.spy(User, 'update');
|
sandbox.spy(User, 'updateOne');
|
||||||
sandbox.spy(User, 'updateMany');
|
sandbox.spy(User, 'updateMany');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats message', () => {
|
it('formats message', async () => {
|
||||||
const chatMessage = party.sendChat({
|
const chatMessage = await party.sendChat({
|
||||||
message: 'a _new_ message with *markdown*',
|
message: 'a _new_ message with *markdown*',
|
||||||
user: {
|
user: {
|
||||||
_id: 'user-id',
|
_id: 'user-id',
|
||||||
@@ -1396,8 +1396,8 @@ describe('Group Model', () => {
|
|||||||
expect(chat.user).to.eql('user name');
|
expect(chat.user).to.eql('user name');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats message as system if no user is passed in', () => {
|
it('formats message as system if no user is passed in', async () => {
|
||||||
const chat = party.sendChat({ message: 'a system message' });
|
const chat = await party.sendChat({ message: 'a system message' });
|
||||||
|
|
||||||
expect(chat.text).to.eql('a system message');
|
expect(chat.text).to.eql('a system message');
|
||||||
expect(validator.isUUID(chat.id)).to.eql(true);
|
expect(validator.isUUID(chat.id)).to.eql(true);
|
||||||
@@ -1411,8 +1411,8 @@ describe('Group Model', () => {
|
|||||||
expect(chat.user).to.not.exist;
|
expect(chat.user).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates users about new messages in party', () => {
|
it('updates users about new messages in party', async () => {
|
||||||
party.sendChat({ message: 'message' });
|
await party.sendChat({ message: 'message' });
|
||||||
|
|
||||||
expect(User.updateMany).to.be.calledOnce;
|
expect(User.updateMany).to.be.calledOnce;
|
||||||
expect(User.updateMany).to.be.calledWithMatch({
|
expect(User.updateMany).to.be.calledWithMatch({
|
||||||
@@ -1421,12 +1421,12 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates users about new messages in group', () => {
|
it('updates users about new messages in group', async () => {
|
||||||
const group = new Group({
|
const group = new Group({
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
});
|
});
|
||||||
|
|
||||||
group.sendChat({ message: 'message' });
|
await group.sendChat({ message: 'message' });
|
||||||
|
|
||||||
expect(User.updateMany).to.be.calledOnce;
|
expect(User.updateMany).to.be.calledOnce;
|
||||||
expect(User.updateMany).to.be.calledWithMatch({
|
expect(User.updateMany).to.be.calledWithMatch({
|
||||||
@@ -1435,8 +1435,8 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not send update to user that sent the message', () => {
|
it('does not send update to user that sent the message', async () => {
|
||||||
party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
|
await party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
|
||||||
|
|
||||||
expect(User.updateMany).to.be.calledOnce;
|
expect(User.updateMany).to.be.calledOnce;
|
||||||
expect(User.updateMany).to.be.calledWithMatch({
|
expect(User.updateMany).to.be.calledWithMatch({
|
||||||
@@ -1445,20 +1445,20 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips sending new message notification for guilds with > 5000 members', () => {
|
it('skips sending new message notification for guilds with > 5000 members', async () => {
|
||||||
party.memberCount = 5001;
|
party.memberCount = 5001;
|
||||||
|
|
||||||
party.sendChat({ message: 'message' });
|
await party.sendChat({ message: 'message' });
|
||||||
|
|
||||||
expect(User.update).to.not.be.called;
|
expect(User.updateMany).to.not.be.called;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips sending messages to the tavern', () => {
|
it('skips sending messages to the tavern', async () => {
|
||||||
party._id = TAVERN_ID;
|
party._id = TAVERN_ID;
|
||||||
|
|
||||||
party.sendChat({ message: 'message' });
|
await party.sendChat({ message: 'message' });
|
||||||
|
|
||||||
expect(User.update).to.not.be.called;
|
expect(User.updateMany).to.not.be.called;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2326,7 +2326,7 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
const groupMessage = guild.sendChat({ message: 'Test message.' });
|
const groupMessage = await guild.sendChat({ message: 'Test message.' });
|
||||||
await groupMessage.save();
|
await groupMessage.save();
|
||||||
|
|
||||||
await sleep();
|
await sleep();
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ describe('NewsPost Model', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Delete all existing posts from the database
|
// Delete all existing posts from the database
|
||||||
await NewsPost.remove();
|
await NewsPost.deleteMany();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -116,7 +116,7 @@ describe('NewsPost Model', () => {
|
|||||||
_id: v4(), publishDate: new Date(), published: true,
|
_id: v4(), publishDate: new Date(), published: true,
|
||||||
};
|
};
|
||||||
NewsPost.updateLastNewsPost(previousPost);
|
NewsPost.updateLastNewsPost(previousPost);
|
||||||
intervalId = refreshNewsPost(50); // refreshes every 50ms
|
intervalId = refreshNewsPost(100); // refreshes every 100ms
|
||||||
|
|
||||||
await sleep(0.1); // wait 100ms to make sure the new post has a more recent publishDate
|
await sleep(0.1); // wait 100ms to make sure the new post has a more recent publishDate
|
||||||
const newPost = await NewsPost.create({
|
const newPost = await NewsPost.create({
|
||||||
|
|||||||
@@ -221,7 +221,8 @@ describe('Task Model', () => {
|
|||||||
|
|
||||||
it('returns task by alias', async () => {
|
it('returns task by alias', async () => {
|
||||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||||
[taskWithAlias.alias], user._id,
|
[taskWithAlias.alias],
|
||||||
|
user._id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
|
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
|
||||||
@@ -229,7 +230,8 @@ describe('Task Model', () => {
|
|||||||
|
|
||||||
it('returns multiple tasks', async () => {
|
it('returns multiple tasks', async () => {
|
||||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||||
[taskWithAlias.alias, secondTask._id], user._id,
|
[taskWithAlias.alias, secondTask._id],
|
||||||
|
user._id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(foundTasks.length).to.eql(2);
|
expect(foundTasks.length).to.eql(2);
|
||||||
@@ -239,7 +241,8 @@ describe('Task Model', () => {
|
|||||||
|
|
||||||
it('returns a task only once if searched by both id and alias', async () => {
|
it('returns a task only once if searched by both id and alias', async () => {
|
||||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||||
[taskWithAlias.alias, taskWithAlias._id], user._id,
|
[taskWithAlias.alias, taskWithAlias._id],
|
||||||
|
user._id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(foundTasks.length).to.eql(1);
|
expect(foundTasks.length).to.eql(1);
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ describe('User Model', () => {
|
|||||||
it('removes invalid tags when loading the user', async () => {
|
it('removes invalid tags when loading the user', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
$set: {
|
$set: {
|
||||||
tags: [
|
tags: [
|
||||||
null, // invalid, not an object
|
null, // invalid, not an object
|
||||||
@@ -212,7 +212,7 @@ describe('User Model', () => {
|
|||||||
it('removes invalid push devices when loading the user', async () => {
|
it('removes invalid push devices when loading the user', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
$set: {
|
$set: {
|
||||||
pushDevices: [
|
pushDevices: [
|
||||||
null, // invalid, not an object
|
null, // invalid, not an object
|
||||||
@@ -236,7 +236,7 @@ describe('User Model', () => {
|
|||||||
it('removes duplicate push devices when loading the user', async () => {
|
it('removes duplicate push devices when loading the user', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
$set: {
|
$set: {
|
||||||
pushDevices: [
|
pushDevices: [
|
||||||
{ type: 'android', regId: '1234' },
|
{ type: 'android', regId: '1234' },
|
||||||
@@ -258,7 +258,7 @@ describe('User Model', () => {
|
|||||||
it('removes invalid notifications when loading the user', async () => {
|
it('removes invalid notifications when loading the user', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
$set: {
|
$set: {
|
||||||
notifications: [
|
notifications: [
|
||||||
null, // invalid, not an object
|
null, // invalid, not an object
|
||||||
@@ -284,7 +284,7 @@ describe('User Model', () => {
|
|||||||
it('removes multiple NEW_CHAT_MESSAGE for the same group', async () => {
|
it('removes multiple NEW_CHAT_MESSAGE for the same group', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
$set: {
|
$set: {
|
||||||
notifications: [
|
notifications: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import { model as Webhook } from '../../../../website/server/models/webhook';
|
import { model as Webhook } from '../../../../website/server/models/webhook';
|
||||||
import { BadRequest } from '../../../../website/server/libs/errors';
|
import { BadRequest } from '../../../../website/server/libs/errors';
|
||||||
import apiError from '../../../../website/server/libs/apiError';
|
import { apiError } from '../../../../website/server/libs/apiError';
|
||||||
|
|
||||||
describe('Webhook Model', () => {
|
describe('Webhook Model', () => {
|
||||||
context('Instance Methods', () => {
|
context('Instance Methods', () => {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ describe('DELETE /challenges/:challengeId', () => {
|
|||||||
const testTask = _.find(tasks, task => task.text === taskText);
|
const testTask = _.find(tasks, task => task.text === taskText);
|
||||||
|
|
||||||
expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED');
|
expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED');
|
||||||
expect(testTask.challenge.winner).to.be.null;
|
expect(testTask.challenge.winner).to.be.undefined;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should successfully return when it contains erroneous residue user data', async () => {
|
it('should successfully return when it contains erroneous residue user data', async () => {
|
||||||
await members[0].update({ challenges: [] });
|
await members[0].updateOne({ challenges: [] });
|
||||||
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
|
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
|
||||||
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
|
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
|
||||||
const splitRes = res.split('\n');
|
const splitRes = res.split('\n');
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ describe('GET challenges/groups/:groupId', () => {
|
|||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
await user.update({ balance: 0.5 });
|
await user.updateOne({ balance: 0.5 });
|
||||||
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
||||||
|
|
||||||
challenge = await generateChallenge(user, tavern, { prize: 1 });
|
challenge = await generateChallenge(user, tavern, { prize: 1 });
|
||||||
@@ -269,7 +269,7 @@ describe('GET challenges/groups/:groupId', () => {
|
|||||||
let officialChallenge; let unofficialChallenges;
|
let officialChallenge; let unofficialChallenges;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
'permissions.challengeAdmin': true,
|
'permissions.challengeAdmin': true,
|
||||||
balance: 3,
|
balance: 3,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ describe('GET challenges/user', () => {
|
|||||||
|
|
||||||
challenge = await generateChallenge(user, group);
|
challenge = await generateChallenge(user, group);
|
||||||
challenge2 = await generateChallenge(user, group);
|
challenge2 = await generateChallenge(user, group);
|
||||||
await user.update({ balance: 0.25 });
|
await user.updateOne({ balance: 0.25 });
|
||||||
publicChallenge = await generateChallenge(user, tavern, { prize: 1 });
|
publicChallenge = await generateChallenge(user, tavern, { prize: 1 });
|
||||||
|
|
||||||
await member.post(`/challenges/${challenge._id}/join`);
|
await member.post(`/challenges/${challenge._id}/join`);
|
||||||
@@ -234,7 +234,7 @@ describe('GET challenges/user', () => {
|
|||||||
upgradeToGroupPlan: true,
|
upgradeToGroupPlan: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await user.update({
|
await user.updateOne({
|
||||||
'permissions.challengeAdmin': true,
|
'permissions.challengeAdmin': true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ describe('GET challenges/user', () => {
|
|||||||
guild = group;
|
guild = group;
|
||||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||||
|
|
||||||
await user.update({ balance: 20 });
|
await user.updateOne({ balance: 20 });
|
||||||
|
|
||||||
for (let i = 0; i < 11; i += 1) {
|
for (let i = 0; i < 11; i += 1) {
|
||||||
let challenge = await generateChallenge(user, group); // eslint-disable-line
|
let challenge = await generateChallenge(user, group); // eslint-disable-line
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user