Compare commits
161 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8afb82ceae | |||
| 3159f7d12c | |||
| 55550d0562 | |||
| 4db1b1cdfa | |||
| 5f7e3fa4e7 | |||
| fde2359334 | |||
| 95c8958e13 | |||
| 28e09d3905 | |||
| 85b1b49950 | |||
| e19c24024a | |||
| 2cbceef84b | |||
| a308796672 | |||
| b978234531 | |||
| a8deeaec76 | |||
| 38c84ae3a0 | |||
| 89f8952d76 | |||
| 6cccdc67b0 | |||
| ca853c9cfe | |||
| d044df8e33 | |||
| 3de7951e2d | |||
| 3721c044c8 | |||
| 1000e283cc | |||
| db3d233ae5 | |||
| e8d3090a99 | |||
| 39e7f51258 | |||
| abf371ba12 | |||
| f02b7c16e1 | |||
| cec01f49bd | |||
| b88389787d | |||
| 04e366d498 | |||
| 2d5ddfdd87 | |||
| 2c2b776968 | |||
| 1dc8be4842 | |||
| 9d93821758 | |||
| 14e99c6e6c | |||
| 7433eeb883 | |||
| 68ecc2eec4 | |||
| c68c8536de | |||
| fce8028e29 | |||
| 845fcd41b4 | |||
| f172670aa4 | |||
| 405f744770 | |||
| 7222ee0a10 | |||
| 6d4da01d36 | |||
| 84b184617f | |||
| 87e46da6a3 | |||
| d8199f71a8 | |||
| 20a6103457 | |||
| 539ffbf08a | |||
| b2adc29bf6 | |||
| 920ee56bdf | |||
| 1f077451d2 | |||
| 595a31db99 | |||
| 7e0d7a4ba0 | |||
| 1c66b80424 | |||
| 5274ec2cc9 | |||
| 8060422991 | |||
| 69e40e2114 | |||
| 17d918a172 | |||
| bc74e40280 | |||
| 4487363171 | |||
| dc72faad6a | |||
| 7c0b3612f0 | |||
| 9a32eabb47 | |||
| 6fe0d5568a | |||
| 2c44f766cd | |||
| c79e3bea05 | |||
| b56f0cfeeb | |||
| fbf1849148 | |||
| a493bb69ce | |||
| 7b20d02449 | |||
| 207d1b7eaa | |||
| f33aed661b | |||
| 7f1d6ffef0 | |||
| 75f198789b | |||
| 6ad20e7abb | |||
| 2705539a70 | |||
| 8f05fc250a | |||
| a21295c0e1 | |||
| 24e13ddd18 | |||
| 4d9e03b6d2 | |||
| 8a64def893 | |||
| e6f40aee43 | |||
| dc34db98b4 | |||
| bd228d5d69 | |||
| 5d054a0acd | |||
| 9a1fbc2d2f | |||
| 7b0f43b61c | |||
| bc115bb1f6 | |||
| cd790e7228 | |||
| bc1c637023 | |||
| eadbdeb7b8 | |||
| 585359e22f | |||
| f0d6204968 | |||
| 14fed0eb43 | |||
| 427654d8bd | |||
| cdb5ccf76a | |||
| bf28a46803 | |||
| 84c10eb92a | |||
| fa197e1b57 | |||
| 993c5552e8 | |||
| 1eba72dd36 | |||
| 4457b081fa | |||
| 395e1c25d4 | |||
| fdef94e826 | |||
| b3cfb57933 | |||
| b4551088c1 | |||
| 26e6e583ab | |||
| 81c7036cdb | |||
| 23906d8f94 | |||
| 2d091fc667 | |||
| 6d34319455 | |||
| 7072fbdd06 | |||
| 73cd6aec59 | |||
| d05e535c7e | |||
| e8960f1b0b | |||
| 943ea1d8f3 | |||
| 61172f82a3 | |||
| e7faebbf40 | |||
| c800f36178 | |||
| f2fff946a4 | |||
| e6b0bdb05e | |||
| 709fcd5ae6 | |||
| 1b0251a492 | |||
| c5cd20de22 | |||
| 32d3da9310 | |||
| 9ebf435c82 | |||
| f06fefe9c0 | |||
| 4bdaa58592 | |||
| bcae464955 | |||
| 0d1643eb17 | |||
| ccae075a4d | |||
| 5d275aa2a5 | |||
| 26940e4054 | |||
| 119502f285 | |||
| 5c8817c4ef | |||
| 7c081c4607 | |||
| f2b53f651e | |||
| 0e1011b875 | |||
| cb999d9277 | |||
| dcdb3efdc2 | |||
| f2281efe99 | |||
| ff93dd9159 | |||
| 0568e4f2ae | |||
| fe109f0e00 | |||
| 53f19c4da3 | |||
| fa0f60d3a6 | |||
| 74ebcf919e | |||
| 0701fa4286 | |||
| 25d9102674 | |||
| dab9a9b6cb | |||
| 4db0002d5d | |||
| 4a1c532cd3 | |||
| 3c1d7fce5f | |||
| e840661da8 | |||
| ec912a6dda | |||
| 5d6583936d | |||
| bc181819f4 | |||
| 2018962eb5 | |||
| 4aa51db5ec | |||
| 1ed2ebb04d |
@@ -71,6 +71,7 @@
|
||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||
"STRIPE_WEBHOOKS_ENDPOINT_SECRET": "111111",
|
||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
||||
"WEB_CONCURRENCY": 1,
|
||||
"SKIP_SSL_CHECK_KEY": "key",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Fix dates in the database that were stored as $type string instead of Date
|
||||
*/
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const MIGRATION_NAME = '20201111_api_date';
|
||||
|
||||
import * as Tasks from '../../../website/server/models/task';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (todo) {
|
||||
count++;
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${todo._id}`);
|
||||
|
||||
const newDate = new Date(todo.date);
|
||||
if (isValidDate(newDate)) return;
|
||||
|
||||
return await Tasks.Task.update({_id: todo._id, type: 'todo'}, {$unset: {date: ''}}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
type: 'todo',
|
||||
date: {$exists: true},
|
||||
updatedAt: {$gt: new Date('2020-11-23')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
type: 1,
|
||||
date: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await Tasks.Task // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.select(fields)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate tasks found and modified.');
|
||||
console.warn(`\n${count} tasks processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
|
||||
function isValidDate(d) {
|
||||
return !isNaN(d.getTime());
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20201229_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_nye2019 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2020'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2020',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2019'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2019',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2018'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2018',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2017'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2017',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2016'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2016',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2015'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2015',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2014'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2014',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
set['items.gear.owned.head_special_nye'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2020-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
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"version": "4.173.0",
|
||||
"version": "4.178.4",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -18,32 +18,41 @@
|
||||
"integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw=="
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.12.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz",
|
||||
"integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==",
|
||||
"version": "7.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz",
|
||||
"integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/generator": "^7.12.5",
|
||||
"@babel/generator": "^7.12.10",
|
||||
"@babel/helper-module-transforms": "^7.12.1",
|
||||
"@babel/helpers": "^7.12.5",
|
||||
"@babel/parser": "^7.12.7",
|
||||
"@babel/parser": "^7.12.10",
|
||||
"@babel/template": "^7.12.7",
|
||||
"@babel/traverse": "^7.12.9",
|
||||
"@babel/types": "^7.12.7",
|
||||
"@babel/traverse": "^7.12.10",
|
||||
"@babel/types": "^7.12.10",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.1",
|
||||
"json5": "^2.1.2",
|
||||
"lodash": "^4.17.19",
|
||||
"resolve": "^1.3.2",
|
||||
"semver": "^5.4.1",
|
||||
"source-map": "^0.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": {
|
||||
"version": "7.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.10.tgz",
|
||||
"integrity": "sha512-6mCdfhWgmqLdtTkhXjnIz0LcdVCd26wS2JXRtj2XY0u5klDsXBREA/pG5NVOuVnF2LUrBGNFtQkIqqTbblg0ww==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.12.10",
|
||||
"jsesc": "^2.5.1",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.7.tgz",
|
||||
"integrity": "sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg=="
|
||||
"version": "7.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.10.tgz",
|
||||
"integrity": "sha512-PJdRPwyoOqFAWfLytxrWwGrAxghCgh/yTNCYciOz8QgjflA7aZhECPZAa2VUedKg2+QMWkI0L9lynh2SNmNEgA=="
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.12.7",
|
||||
@@ -56,25 +65,25 @@
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.12.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.9.tgz",
|
||||
"integrity": "sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==",
|
||||
"version": "7.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.10.tgz",
|
||||
"integrity": "sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/generator": "^7.12.5",
|
||||
"@babel/generator": "^7.12.10",
|
||||
"@babel/helper-function-name": "^7.10.4",
|
||||
"@babel/helper-split-export-declaration": "^7.11.0",
|
||||
"@babel/parser": "^7.12.7",
|
||||
"@babel/types": "^7.12.7",
|
||||
"@babel/parser": "^7.12.10",
|
||||
"@babel/types": "^7.12.10",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.19"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz",
|
||||
"integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==",
|
||||
"version": "7.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.10.tgz",
|
||||
"integrity": "sha512-sf6wboJV5mGyip2hIpDSKsr80RszPinEFjsHTalMxZAZkoQ2/2yQzxlcFN52SJqsyPfLtPmenL4g2KB3KJXPDw==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"lodash": "^4.17.19",
|
||||
@@ -106,11 +115,28 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-annotate-as-pure": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz",
|
||||
"integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==",
|
||||
"version": "7.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz",
|
||||
"integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.10.4"
|
||||
"@babel/types": "^7.12.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.11.tgz",
|
||||
"integrity": "sha512-ukA9SQtKThINm++CX1CwmliMrE54J6nIYB5XTwL5f/CLFW9owfls+YSU8tVW15RQ2w+a3fSbPjC6HdQNtWZkiA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-builder-binary-assignment-operator-visitor": {
|
||||
@@ -172,12 +198,17 @@
|
||||
"@babel/types": "^7.12.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz",
|
||||
"integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==",
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.11.tgz",
|
||||
"integrity": "sha512-ukA9SQtKThINm++CX1CwmliMrE54J6nIYB5XTwL5f/CLFW9owfls+YSU8tVW15RQ2w+a3fSbPjC6HdQNtWZkiA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
@@ -301,12 +332,17 @@
|
||||
"@babel/types": "^7.12.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz",
|
||||
"integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==",
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.11.tgz",
|
||||
"integrity": "sha512-ukA9SQtKThINm++CX1CwmliMrE54J6nIYB5XTwL5f/CLFW9owfls+YSU8tVW15RQ2w+a3fSbPjC6HdQNtWZkiA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
@@ -364,12 +400,17 @@
|
||||
"@babel/types": "^7.12.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz",
|
||||
"integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==",
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.11.tgz",
|
||||
"integrity": "sha512-ukA9SQtKThINm++CX1CwmliMrE54J6nIYB5XTwL5f/CLFW9owfls+YSU8tVW15RQ2w+a3fSbPjC6HdQNtWZkiA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
@@ -402,9 +443,9 @@
|
||||
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz",
|
||||
"integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A=="
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz",
|
||||
"integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw=="
|
||||
},
|
||||
"@babel/helper-wrap-function": {
|
||||
"version": "7.12.3",
|
||||
@@ -428,9 +469,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz",
|
||||
"integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==",
|
||||
"version": "7.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.10.tgz",
|
||||
"integrity": "sha512-sf6wboJV5mGyip2hIpDSKsr80RszPinEFjsHTalMxZAZkoQ2/2yQzxlcFN52SJqsyPfLtPmenL4g2KB3KJXPDw==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"lodash": "^4.17.19",
|
||||
@@ -709,9 +750,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-block-scoping": {
|
||||
"version": "7.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz",
|
||||
"integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==",
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.11.tgz",
|
||||
"integrity": "sha512-atR1Rxc3hM+VPg/NvNvfYw0npQEAcHuJ+MGZnFn6h3bo+1U3BWXMdFMlvVRApBTWKQMX7SOwRJZA5FBF/JQbvA==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.10.4"
|
||||
}
|
||||
@@ -939,9 +980,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-typeof-symbol": {
|
||||
"version": "7.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz",
|
||||
"integrity": "sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==",
|
||||
"version": "7.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz",
|
||||
"integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.10.4"
|
||||
}
|
||||
@@ -964,15 +1005,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/preset-env": {
|
||||
"version": "7.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.7.tgz",
|
||||
"integrity": "sha512-OnNdfAr1FUQg7ksb7bmbKoby4qFOHw6DKWWUNB9KqnnCldxhxJlP+21dpyaWFmf2h0rTbOkXJtAGevY3XW1eew==",
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.11.tgz",
|
||||
"integrity": "sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==",
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.12.7",
|
||||
"@babel/helper-compilation-targets": "^7.12.5",
|
||||
"@babel/helper-module-imports": "^7.12.5",
|
||||
"@babel/helper-plugin-utils": "^7.10.4",
|
||||
"@babel/helper-validator-option": "^7.12.1",
|
||||
"@babel/helper-validator-option": "^7.12.11",
|
||||
"@babel/plugin-proposal-async-generator-functions": "^7.12.1",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||
"@babel/plugin-proposal-dynamic-import": "^7.12.1",
|
||||
@@ -1001,7 +1042,7 @@
|
||||
"@babel/plugin-transform-arrow-functions": "^7.12.1",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.12.1",
|
||||
"@babel/plugin-transform-block-scoped-functions": "^7.12.1",
|
||||
"@babel/plugin-transform-block-scoping": "^7.12.1",
|
||||
"@babel/plugin-transform-block-scoping": "^7.12.11",
|
||||
"@babel/plugin-transform-classes": "^7.12.1",
|
||||
"@babel/plugin-transform-computed-properties": "^7.12.1",
|
||||
"@babel/plugin-transform-destructuring": "^7.12.1",
|
||||
@@ -1027,21 +1068,26 @@
|
||||
"@babel/plugin-transform-spread": "^7.12.1",
|
||||
"@babel/plugin-transform-sticky-regex": "^7.12.7",
|
||||
"@babel/plugin-transform-template-literals": "^7.12.1",
|
||||
"@babel/plugin-transform-typeof-symbol": "^7.12.1",
|
||||
"@babel/plugin-transform-typeof-symbol": "^7.12.10",
|
||||
"@babel/plugin-transform-unicode-escapes": "^7.12.1",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.12.1",
|
||||
"@babel/preset-modules": "^0.1.3",
|
||||
"@babel/types": "^7.12.7",
|
||||
"core-js-compat": "^3.7.0",
|
||||
"@babel/types": "^7.12.11",
|
||||
"core-js-compat": "^3.8.0",
|
||||
"semver": "^5.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz",
|
||||
"integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==",
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.11.tgz",
|
||||
"integrity": "sha512-ukA9SQtKThINm++CX1CwmliMrE54J6nIYB5XTwL5f/CLFW9owfls+YSU8tVW15RQ2w+a3fSbPjC6HdQNtWZkiA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
@@ -1061,9 +1107,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/register": {
|
||||
"version": "7.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/register/-/register-7.12.1.tgz",
|
||||
"integrity": "sha512-XWcmseMIncOjoydKZnWvWi0/5CUCD+ZYKhRwgYlWOrA8fGZ/FjuLRpqtIhLOVD/fvR1b9DQHtZPn68VvhpYf+Q==",
|
||||
"version": "7.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/register/-/register-7.12.10.tgz",
|
||||
"integrity": "sha512-EvX/BvMMJRAA3jZgILWgbsrHwBQvllC5T8B29McyME8DvkdOxk4ujESfrMvME8IHSDvWXrmMXxPvA/lx2gqPLQ==",
|
||||
"requires": {
|
||||
"find-cache-dir": "^2.0.0",
|
||||
"lodash": "^4.17.19",
|
||||
@@ -1336,20 +1382,10 @@
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"@sinonjs/formatio": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz",
|
||||
"integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1",
|
||||
"@sinonjs/samsam": "^5.0.2"
|
||||
}
|
||||
},
|
||||
"@sinonjs/samsam": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz",
|
||||
"integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.0.tgz",
|
||||
"integrity": "sha512-hXpcfx3aq+ETVBwPlRFICld5EnrkexXuXDwqUNhDdr5L8VjvMeSRwyOa0qL7XFmR+jVWR4rUZtnxlG7RX72sBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.6.0",
|
||||
@@ -1369,23 +1405,13 @@
|
||||
"integrity": "sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg=="
|
||||
},
|
||||
"@slack/webhook": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@slack/webhook/-/webhook-5.0.3.tgz",
|
||||
"integrity": "sha512-51vnejJ2zABNumPVukOLyerpHQT39/Lt0TYFtOEz/N2X77bPofOgfPj2atB3etaM07mxWHLT9IRJ4Zuqx38DkQ==",
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@slack/webhook/-/webhook-5.0.4.tgz",
|
||||
"integrity": "sha512-IC1dpVSc2F/pmwCxOb0QzH2xnGKmyT7MofPGhNkeaoiMrLMU+Oc7xV/AxGnz40mURtCtaDchZSM3tDo9c9x6BA==",
|
||||
"requires": {
|
||||
"@slack/types": "^1.2.1",
|
||||
"@types/node": ">=8.9.0",
|
||||
"axios": "^0.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
}
|
||||
}
|
||||
"axios": "^0.21.1"
|
||||
}
|
||||
},
|
||||
"@szmarczak/http-timer": {
|
||||
@@ -1414,7 +1440,6 @@
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.2.tgz",
|
||||
"integrity": "sha512-+uWmsejEHfmSjyyM/LkrP0orfE2m5Mx9Xel4tXNeqi1ldK5XMQcDsFkBmLDtuyKUbxj2jGDo0H240fbCRJZo7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -1436,17 +1461,17 @@
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
|
||||
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
|
||||
"version": "3.4.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz",
|
||||
"integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/express": {
|
||||
"version": "4.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz",
|
||||
"integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==",
|
||||
"version": "4.17.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.9.tgz",
|
||||
"integrity": "sha512-SDzEIZInC4sivGIFY4Sz1GG6J9UObPwCInYJjko2jzOf/Imx/dlpume6Xxwj1ORL82tBbmN4cPDIDkLbWHk9hw==",
|
||||
"requires": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "*",
|
||||
@@ -1464,9 +1489,9 @@
|
||||
}
|
||||
},
|
||||
"@types/express-serve-static-core": {
|
||||
"version": "4.17.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz",
|
||||
"integrity": "sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA==",
|
||||
"version": "4.17.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.17.tgz",
|
||||
"integrity": "sha512-YYlVaCni5dnHc+bLZfY908IG1+x5xuibKZMGv8srKkvtul3wUuanYvpIj9GXXoWkQbaAdR+kgX46IETKUALWNQ==",
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/qs": "*",
|
||||
@@ -1522,7 +1547,6 @@
|
||||
"version": "3.5.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.27.tgz",
|
||||
"integrity": "sha512-1jxKDgdfJEOO9zp+lv43p8jOqRs02xPrdUTzAZIVK9tVEySfCEmktL2jEu9A3wOBEOs18yKzpVIKUh8b8ALk3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/bson": "*",
|
||||
"@types/node": "*"
|
||||
@@ -1558,9 +1582,9 @@
|
||||
}
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.6.tgz",
|
||||
"integrity": "sha512-nuRJmv7jW7VmCVTn+IgYDkkbbDGyIINOeu/G0d74X3lm6E5KfMeQPJhxIt1ayQeQB3cSxvYs1RA/wipYoFB4EA==",
|
||||
"version": "1.13.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz",
|
||||
"integrity": "sha512-MoJhSQreaVoL+/hurAZzIm8wafFR6ajiTM1m4A0kv6AGeVBl4r4pOV8bGFrjjq1sGxDTnCoF8i22o0/aE5XCyA==",
|
||||
"requires": {
|
||||
"@types/mime": "*",
|
||||
"@types/node": "*"
|
||||
@@ -1729,9 +1753,9 @@
|
||||
}
|
||||
},
|
||||
"apidoc": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.25.0.tgz",
|
||||
"integrity": "sha512-5g9fp8OffXZOdBTzm4BBvV5Vw54s+NmKnGZIUKuH+gRTqqJuRJpcGN6sz6WnjJ+NcvXhB7rIRp6FhtJahazx2Q==",
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.26.0.tgz",
|
||||
"integrity": "sha512-IEw/Z7HMMbjeVjK2sZvZSwAln8AqalLzf3qLDtkcedXVhdxGm6W7UgIW6fshegqNTMLzm8CFEMi4Lxbeu0xKTw==",
|
||||
"requires": {
|
||||
"apidoc-core": "^0.12.0",
|
||||
"commander": "^2.20.0",
|
||||
@@ -1751,15 +1775,10 @@
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"markdown-it": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.0.tgz",
|
||||
"integrity": "sha512-+CvOnmbSubmQFSA9dKz1BRiaSMV7rhexl3sngKqFyXSagoA3fBdJQ8oZWtRy2knXdpDXaBw44euz37DeJQ9asg==",
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.1.tgz",
|
||||
"integrity": "sha512-aU1TzmBKcWNNYvH9pjq6u92BML+Hz3h5S/QpfTFwiQF852pLT+9qHsrhM9JYipkOXZxGn+sGH8oyJE9FD9WezQ==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"entities": "~2.0.0",
|
||||
@@ -1783,15 +1802,13 @@
|
||||
"semver": "~7.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
|
||||
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1813,23 +1830,13 @@
|
||||
}
|
||||
},
|
||||
"apple-auth": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/apple-auth/-/apple-auth-1.0.6.tgz",
|
||||
"integrity": "sha512-dICsYIHBTX+7/1xdJZ4y4U08zOxEnL67GQ77l/HSKqEx9uC1wsY5dNjgbZf2F/1QnzAxDbAAHo0DgUCrA1k7zQ==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/apple-auth/-/apple-auth-1.0.7.tgz",
|
||||
"integrity": "sha512-vfJqy4KtT5KHflxBSemc0mkWuy2GU2wHWaZ6xVWUjPmiXkJcLj5jB3IeCbDLF4wN+ZStjOQXZWfGwccBjclVlA==",
|
||||
"requires": {
|
||||
"axios": "^0.19.0",
|
||||
"axios": "^0.21.1",
|
||||
"express": "^4.17.1",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aproba": {
|
||||
@@ -2133,17 +2140,17 @@
|
||||
"integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz",
|
||||
"integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==",
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"follow-redirects": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
|
||||
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
|
||||
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3003,15 +3010,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.14.7",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.7.tgz",
|
||||
"integrity": "sha512-BSVRLCeG3Xt/j/1cCGj1019Wbty0H+Yvu2AOuZSuoaUWn3RatbL33Cxk+Q4jRMRAbOm0p7SLravLjpnT6s0vzQ==",
|
||||
"version": "4.16.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz",
|
||||
"integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==",
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001157",
|
||||
"caniuse-lite": "^1.0.30001165",
|
||||
"colorette": "^1.2.1",
|
||||
"electron-to-chromium": "^1.3.591",
|
||||
"electron-to-chromium": "^1.3.621",
|
||||
"escalade": "^3.1.1",
|
||||
"node-releases": "^1.1.66"
|
||||
"node-releases": "^1.1.67"
|
||||
}
|
||||
},
|
||||
"bson": {
|
||||
@@ -3033,7 +3040,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"buffer-alloc-unsafe": "^1.1.0",
|
||||
"buffer-fill": "^1.0.0"
|
||||
@@ -3042,8 +3048,7 @@
|
||||
"buffer-alloc-unsafe": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
|
||||
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
|
||||
"optional": true
|
||||
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
|
||||
},
|
||||
"buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
@@ -3063,8 +3068,7 @@
|
||||
"buffer-fill": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
|
||||
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
|
||||
"optional": true
|
||||
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
@@ -3098,9 +3102,9 @@
|
||||
}
|
||||
},
|
||||
"cacheable-lookup": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz",
|
||||
"integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w=="
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
|
||||
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="
|
||||
},
|
||||
"cacheable-request": {
|
||||
"version": "6.1.0",
|
||||
@@ -3160,9 +3164,9 @@
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001159",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001159.tgz",
|
||||
"integrity": "sha512-w9Ph56jOsS8RL20K9cLND3u/+5WASWdhC/PPrf+V3/HsM3uHOavWOR1Xzakbv4Puo/srmPHudkmCRWM7Aq+/UA=="
|
||||
"version": "1.0.30001170",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001170.tgz",
|
||||
"integrity": "sha512-Dd4d/+0tsK0UNLrZs3CvNukqalnVTRrxb5mcQm8rHL49t7V5ZaTygwXkrq+FB+dVDf++4ri8eJnFEJAB8332PA=="
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
@@ -3854,11 +3858,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.7.0.tgz",
|
||||
"integrity": "sha512-V8yBI3+ZLDVomoWICO6kq/CD28Y4r1M7CWeO4AGpMdMfseu8bkSubBmUPySMGKRTS+su4XQ07zUkAsiu9FCWTg==",
|
||||
"version": "3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.1.tgz",
|
||||
"integrity": "sha512-a16TLmy9NVD1rkjUGbwuyWkiDoN0FDpAwrfLONvHFQx0D9k7J9y0srwMT8QP/Z6HE3MIFaVynEeYwZwPX1o5RQ==",
|
||||
"requires": {
|
||||
"browserslist": "^4.14.6",
|
||||
"browserslist": "^4.15.0",
|
||||
"semver": "7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -4001,9 +4005,9 @@
|
||||
}
|
||||
},
|
||||
"csv-stringify": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.5.3.tgz",
|
||||
"integrity": "sha512-JKG8vIHpWPzdilp2SAmvjmAiIhD+XGKGdhZBGi8QIECgJAsFr7k5CmJIW2QkSxBBsctvmojM25s+UINzQ5NLTg=="
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.0.tgz",
|
||||
"integrity": "sha512-E0LNLevBrwaJ1WKsl4HUPOmK96WyhizTfY79mJgfr2dsIb6zyJd3B9+lToO7gSkTaKi8CIo0Pd0vDGfa0whozg=="
|
||||
},
|
||||
"currently-unhandled": {
|
||||
"version": "0.4.1",
|
||||
@@ -4611,9 +4615,9 @@
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.603",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.603.tgz",
|
||||
"integrity": "sha512-J8OHxOeJkoSLgBXfV9BHgKccgfLMHh+CoeRo6wJsi6m0k3otaxS/5vrHpMNSEYY4MISwewqanPOuhAtuE8riQQ=="
|
||||
"version": "1.3.629",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.629.tgz",
|
||||
"integrity": "sha512-iSPPJtPvHrMAvYOt+9cdbDmTasPqwnwz4lkP8Dn200gDNUBQOLQ96xUsWXBwXslAo5XxdoXAoQQ3RAy4uao9IQ=="
|
||||
},
|
||||
"emitter-listener": {
|
||||
"version": "1.1.2",
|
||||
@@ -6658,9 +6662,9 @@
|
||||
}
|
||||
},
|
||||
"got": {
|
||||
"version": "11.8.0",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.0.tgz",
|
||||
"integrity": "sha512-k9noyoIIY9EejuhaBNLyZ31D5328LeqnyPNXJQb2XlJZcKakLqN5m6O/ikhq/0lw56kUYS54fVm+D1x57YC9oQ==",
|
||||
"version": "11.8.1",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.1.tgz",
|
||||
"integrity": "sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q==",
|
||||
"requires": {
|
||||
"@sindresorhus/is": "^4.0.0",
|
||||
"@szmarczak/http-timer": "^4.0.5",
|
||||
@@ -7191,9 +7195,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"helmet": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-4.2.0.tgz",
|
||||
"integrity": "sha512-aoiSxXMd0ks1ojYpSCFoCRzgv4rY/uB9jKStaw8PkXwsdLYa/Gq+Nc5l0soH0cwBIsLAlujPnx4HLQs+LaXCrQ=="
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-4.3.1.tgz",
|
||||
"integrity": "sha512-WsafDyKsIexB0+pUNkq3rL1rB5GVAghR68TP8ssM9DPEMzfBiluEQlVzJ/FEj6Vq2Ag3CNuxf7aYMjXrN0X49Q=="
|
||||
},
|
||||
"hex2dec": {
|
||||
"version": "1.1.2",
|
||||
@@ -7511,9 +7515,9 @@
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz",
|
||||
"integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "7.3.2",
|
||||
@@ -8185,12 +8189,19 @@
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
|
||||
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^1.0.0"
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsonwebtoken": {
|
||||
@@ -8269,12 +8280,12 @@
|
||||
}
|
||||
},
|
||||
"jwks-rsa": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.11.0.tgz",
|
||||
"integrity": "sha512-G7ZgXZ3dlGbOUBQwgF+U/SVzOlI9KxJ9Uzp61bue2S5TV0h7c+kJRCl3bEPkC5PVmeu7/h82B3uQALVJMjzt/Q==",
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.12.2.tgz",
|
||||
"integrity": "sha512-6gPo/mQUxXJt75oPtjhM3Jm3FSXnmwg73QDA8dpgP7YmIKlIY+2StngFxt4w4Y1podtSbtV3jttNOdctuxAX1Q==",
|
||||
"requires": {
|
||||
"@types/express-jwt": "0.0.42",
|
||||
"axios": "^0.19.2",
|
||||
"axios": "^0.21.1",
|
||||
"debug": "^4.1.0",
|
||||
"http-proxy-agent": "^4.0.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
@@ -8283,16 +8294,6 @@
|
||||
"lru-memoizer": "^2.1.2",
|
||||
"ms": "^2.1.2",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"jws": {
|
||||
@@ -8310,9 +8311,9 @@
|
||||
"integrity": "sha512-40aUybvhH9t2h71ncA1/1SbtTNCVZHgsTsTgqPUxGWDmUDrXyDf2wMNQKEbdBjbf4AI+fQhbECNTV6lWxQKUzg=="
|
||||
},
|
||||
"kareem": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
|
||||
"integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
|
||||
"integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ=="
|
||||
},
|
||||
"kerberos": {
|
||||
"version": "1.1.4",
|
||||
@@ -8637,9 +8638,9 @@
|
||||
}
|
||||
},
|
||||
"lru-memoizer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.2.tgz",
|
||||
"integrity": "sha512-N5L5xlnVcbIinNn/TJ17vHBZwBMt9t7aJDz2n97moWubjNl6VO9Ao2XuAGBBddkYdjrwR9HfzXbT6NfMZXAZ/A==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz",
|
||||
"integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==",
|
||||
"requires": {
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lru-cache": "~4.0.0"
|
||||
@@ -9330,16 +9331,17 @@
|
||||
}
|
||||
},
|
||||
"mongoose": {
|
||||
"version": "5.10.18",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.18.tgz",
|
||||
"integrity": "sha512-vaLUzBpUxqacoCqP/xXWMg/uVwCDrlc8LvYjDXCf8hdApvX/CXa0HLa7v2ieFaVd5Fgv3W2QXODLoC4Z/abbNw==",
|
||||
"version": "5.11.11",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.11.tgz",
|
||||
"integrity": "sha512-JgKKAosJf6medPOZi2LmO7sMz7Sg00mgjyPAKari3alzL+R/n8D+zKK29iGtJpNNtv9IKy14H37CWuiaZ7016w==",
|
||||
"requires": {
|
||||
"@types/mongodb": "^3.5.27",
|
||||
"bson": "^1.1.4",
|
||||
"kareem": "2.3.1",
|
||||
"kareem": "2.3.2",
|
||||
"mongodb": "3.6.3",
|
||||
"mongoose-legacy-pluralize": "1.0.2",
|
||||
"mpath": "0.7.0",
|
||||
"mquery": "3.2.2",
|
||||
"mpath": "0.8.3",
|
||||
"mquery": "3.2.3",
|
||||
"ms": "2.1.2",
|
||||
"regexp-clone": "1.0.0",
|
||||
"safe-buffer": "5.2.1",
|
||||
@@ -9515,14 +9517,14 @@
|
||||
}
|
||||
},
|
||||
"mpath": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz",
|
||||
"integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg=="
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz",
|
||||
"integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA=="
|
||||
},
|
||||
"mquery": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz",
|
||||
"integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==",
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.3.tgz",
|
||||
"integrity": "sha512-cIfbP4TyMYX+SkaQ2MntD+F2XbqaBHUYWk3j+kqdDztPWok3tgyssOZxMHMtzbV1w9DaSlvEea0Iocuro41A4g==",
|
||||
"requires": {
|
||||
"bluebird": "3.5.1",
|
||||
"debug": "3.1.0",
|
||||
@@ -9596,9 +9598,9 @@
|
||||
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc="
|
||||
},
|
||||
"nconf": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/nconf/-/nconf-0.11.0.tgz",
|
||||
"integrity": "sha512-c4W7QqYF6p5BC7J/eVTOvtUlQgvS5CgbJ11xgjhSr8yyius7km7xgdIYHkFLR4TWY1HjsFkia/3l5OprGqCHvA==",
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/nconf/-/nconf-0.11.1.tgz",
|
||||
"integrity": "sha512-2XY+7x3GwkkTnmkEVxsKykg0GUqCAtBZUA87FwbcUSaYBfaGCeVSf+82zap16j93B21J2AhpxrsF57jio36t0w==",
|
||||
"requires": {
|
||||
"async": "^1.4.0",
|
||||
"ini": "^1.3.0",
|
||||
@@ -9676,9 +9678,9 @@
|
||||
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
|
||||
},
|
||||
"yargs": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.1.1.tgz",
|
||||
"integrity": "sha512-hAD1RcFP/wfgfxgMVswPE+z3tlPFtxG8/yWUrG2i17sTWGCGqWnxKcLTF4cUKDUK8fzokwsmO9H0TDkRbMHy8w==",
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"requires": {
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
@@ -11131,9 +11133,9 @@
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"rate-limiter-flexible": {
|
||||
"version": "2.1.13",
|
||||
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.1.13.tgz",
|
||||
"integrity": "sha512-EDzvV/ee/rCBKNL5Jw0Rr0rjneT/L4zLGgVS9xB6ShfVMkV5iviWKr+2tjzgBg5kd9by5F08C9DfXfH6v/kz3w=="
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.2.1.tgz",
|
||||
"integrity": "sha512-rxCP6kDDdn0cZmVqVlF06yLU+mG3TuwaHV/fUIw3OQyYhza7pzVBtdMhUmfXbBzMS+O464XP+x33pfTDGRGYVA=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
@@ -11999,15 +12001,14 @@
|
||||
}
|
||||
},
|
||||
"sinon": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.1.tgz",
|
||||
"integrity": "sha512-naPfsamB5KEE1aiioaoqJ6MEhdUs/2vtI5w1hPAXX/UwvoPjXcwh1m5HiKx0HGgKR8lQSoFIgY5jM6KK8VrS9w==",
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.3.tgz",
|
||||
"integrity": "sha512-m+DyAWvqVHZtjnjX/nuShasykFeiZ+nPuEfD4G3gpvKGkXRhkF/6NSt2qN2FjZhfrcHXFzUzI+NLnk+42fnLEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.8.1",
|
||||
"@sinonjs/fake-timers": "^6.0.1",
|
||||
"@sinonjs/formatio": "^5.0.1",
|
||||
"@sinonjs/samsam": "^5.2.0",
|
||||
"@sinonjs/samsam": "^5.3.0",
|
||||
"diff": "^4.0.2",
|
||||
"nise": "^4.0.4",
|
||||
"supports-color": "^7.1.0"
|
||||
@@ -12692,10 +12693,11 @@
|
||||
}
|
||||
},
|
||||
"stripe": {
|
||||
"version": "7.15.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-7.15.0.tgz",
|
||||
"integrity": "sha512-TmouNGv1rIU7cgw7iFKjdQueJSwYKdPRPBuO7eNjrRliZUnsf2bpJqYe+n6ByarUJr38KmhLheVUxDyRawByPQ==",
|
||||
"version": "8.130.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-8.130.0.tgz",
|
||||
"integrity": "sha512-9e283EFhxDz7SUcgNiUFRdTZ/kS2IkoT0KBMOJHdf3vY+mvURq355s2E0Zyy9rtNmt+CEZ0nCMiZ3PqIqpp6Pg==",
|
||||
"requires": {
|
||||
"@types/node": ">=8.1.0",
|
||||
"qs": "^6.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -12894,7 +12896,6 @@
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
@@ -12909,7 +12910,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
@@ -13086,8 +13086,7 @@
|
||||
"to-buffer": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
|
||||
"integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==",
|
||||
"optional": true
|
||||
"integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg=="
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
@@ -13709,9 +13708,9 @@
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
|
||||
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.1.1",
|
||||
@@ -13736,9 +13735,9 @@
|
||||
}
|
||||
},
|
||||
"validator": {
|
||||
"version": "13.1.17",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.1.17.tgz",
|
||||
"integrity": "sha512-zL5QBoemJ3jYFb2/j38y7ljhwYGXVLUp8H6W1nVxadnAOvUOytec+L7BHh1oBQ82/TzWXHd+GSaxUWp4lROkLg=="
|
||||
"version": "13.5.2",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.5.2.tgz",
|
||||
"integrity": "sha512-mD45p0rvHVBlY2Zuy3F3ESIe1h5X58GPfAtslBjY7EtTqGquZTj+VX/J4RnHWN8FKq0C9WRVt1oWAcytWRuYLQ=="
|
||||
},
|
||||
"value-or-function": {
|
||||
"version": "3.0.0",
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.173.0",
|
||||
"version": "4.178.4",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@babel/preset-env": "^7.12.7",
|
||||
"@babel/register": "^7.12.1",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/register": "^7.12.10",
|
||||
"@google-cloud/trace-agent": "^5.1.1",
|
||||
"@slack/webhook": "^5.0.3",
|
||||
"@parse/node-apn": "^4.0.0",
|
||||
"@slack/webhook": "^5.0.4",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.8",
|
||||
"amplitude": "^5.1.4",
|
||||
"apidoc": "^0.25.0",
|
||||
"apple-auth": "^1.0.6",
|
||||
"apidoc": "^0.26.0",
|
||||
"apple-auth": "^1.0.7",
|
||||
"bcrypt": "^5.0.0",
|
||||
"body-parser": "^1.18.3",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-session": "^1.4.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"csv-stringify": "^5.5.3",
|
||||
"csv-stringify": "^5.6.0",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
@@ -30,27 +30,27 @@
|
||||
"express-basic-auth": "^1.1.5",
|
||||
"express-validator": "^5.2.0",
|
||||
"glob": "^7.1.6",
|
||||
"got": "^11.8.0",
|
||||
"got": "^11.8.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-nodemon": "^2.5.0",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"helmet": "^4.2.0",
|
||||
"helmet": "^4.3.1",
|
||||
"image-size": "^0.9.3",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"js2xmlparser": "^4.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "^1.11.0",
|
||||
"jwks-rsa": "^1.12.2",
|
||||
"lodash": "^4.17.20",
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.10.18",
|
||||
"mongoose": "^5.11.11",
|
||||
"morgan": "^1.10.0",
|
||||
"nconf": "^0.11.0",
|
||||
"nconf": "^0.11.1",
|
||||
"node-gcm": "^1.0.3",
|
||||
"on-headers": "^1.0.2",
|
||||
"passport": "^0.4.1",
|
||||
@@ -60,18 +60,18 @@
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"pp-ipn": "^1.1.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"rate-limiter-flexible": "^2.1.13",
|
||||
"rate-limiter-flexible": "^2.2.1",
|
||||
"redis": "^3.0.2",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.1.0",
|
||||
"stripe": "^7.15.0",
|
||||
"stripe": "^8.130.0",
|
||||
"superagent": "^6.1.0",
|
||||
"universal-analytics": "^0.4.23",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^8.3.1",
|
||||
"validator": "^13.1.17",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.5.2",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^3.3.3",
|
||||
"winston-loggly-bulk": "^3.1.1",
|
||||
@@ -109,7 +109,7 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^0.21.0",
|
||||
"axios": "^0.21.1",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
@@ -121,7 +121,7 @@
|
||||
"monk": "^7.3.2",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.7.3",
|
||||
"sinon": "^9.2.1",
|
||||
"sinon": "^9.2.3",
|
||||
"sinon-chai": "^3.5.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -35,13 +35,12 @@ async function deleteAmplitudeData (userId, email) {
|
||||
}
|
||||
|
||||
async function deleteHabiticaData (user, email) {
|
||||
const truncatedEmail = email.slice(0, email.indexOf('@'));
|
||||
const set = {
|
||||
'auth.blocked': false,
|
||||
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
|
||||
'auth.local.passwordHashMethod': 'bcrypt',
|
||||
};
|
||||
if (!user.auth.local.email) set['auth.local.email'] = `${truncatedEmail}-gdpr@example.com`;
|
||||
if (!user.auth.local.email) set['auth.local.email'] = `${user._id}@example.com`;
|
||||
await User.update(
|
||||
{ _id: user._id },
|
||||
{ $set: set },
|
||||
|
||||
@@ -3,6 +3,7 @@ import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
@@ -88,6 +89,7 @@ describe('Amazon Payments - Checkout', () => {
|
||||
paymentCreateSubscritionStub.resolves({});
|
||||
|
||||
sinon.stub(common, 'uuid').returns('uuid-generated');
|
||||
sandbox.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -111,7 +113,10 @@ describe('Amazon Payments - Checkout', () => {
|
||||
if (gift) {
|
||||
expectedArgs.gift = gift;
|
||||
expectedArgs.gemsBlock = undefined;
|
||||
expect(gems.validateGiftMessage).to.be.calledOnce;
|
||||
expect(gems.validateGiftMessage).to.be.calledWith(gift, user);
|
||||
} else {
|
||||
expect(gems.validateGiftMessage).to.not.be.called;
|
||||
expectedArgs.gemsBlock = gemsBlock;
|
||||
}
|
||||
expect(paymentBuyGemsStub).to.be.calledWith(expectedArgs);
|
||||
|
||||
@@ -5,6 +5,7 @@ import applePayments from '../../../../../website/server/libs/payments/apple';
|
||||
import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import * as gems from '../../../../../website/server/libs/payments/gems';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
@@ -15,7 +16,7 @@ describe('Apple Payments', () => {
|
||||
let sku; let user; let token; let receipt; let
|
||||
headers;
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuyGemsStub; let
|
||||
iapGetPurchaseDataStub;
|
||||
iapGetPurchaseDataStub; let validateGiftMessageStub;
|
||||
|
||||
beforeEach(() => {
|
||||
token = 'testToken';
|
||||
@@ -36,6 +37,7 @@ describe('Apple Payments', () => {
|
||||
transactionId: token,
|
||||
}]);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -44,6 +46,7 @@ describe('Apple Payments', () => {
|
||||
iap.isValidated.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
payments.buyGems.restore();
|
||||
gems.validateGiftMessage.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if receipt is invalid', async () => {
|
||||
@@ -143,6 +146,7 @@ describe('Apple Payments', () => {
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.not.be.called;
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
@@ -180,6 +184,9 @@ describe('Apple Payments', () => {
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(validateGiftMessageStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.be.calledWith(gift, user);
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import common from '../../../../../website/common';
|
||||
import { getGemsBlock } from '../../../../../website/server/libs/payments/gems';
|
||||
import {
|
||||
getGemsBlock,
|
||||
validateGiftMessage,
|
||||
} from '../../../../../website/server/libs/payments/gems';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('payments/gems', () => {
|
||||
describe('#getGemsBlock', () => {
|
||||
@@ -11,4 +17,50 @@ describe('payments/gems', () => {
|
||||
expect(getGemsBlock('21gems')).to.equal(common.content.gems['21gems']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#validateGiftMessage', () => {
|
||||
let user;
|
||||
let gift;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
|
||||
gift = {
|
||||
message: (` // exactly 201 chars
|
||||
A gift message that is over the 200 chars limit.
|
||||
A gift message that is over the 200 chars limit.
|
||||
A gift message that is over the 200 chars limit.
|
||||
A gift message that is over the 200 chars limit. 1
|
||||
`).trim().substring(0, 201),
|
||||
};
|
||||
|
||||
expect(gift.message.length).to.equal(201);
|
||||
});
|
||||
|
||||
it('throws if the gift message is too long', () => {
|
||||
let expectedErr;
|
||||
|
||||
try {
|
||||
validateGiftMessage(gift, user);
|
||||
} catch (err) {
|
||||
expectedErr = err;
|
||||
}
|
||||
|
||||
expect(expectedErr).to.exist;
|
||||
expect(expectedErr).to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('giftMessageTooLong', { maxGiftMessageLength: 200 }),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not throw if the gift message is not too long', () => {
|
||||
gift.message = gift.message.substring(0, 200);
|
||||
expect(() => validateGiftMessage(gift, user)).to.not.throw;
|
||||
});
|
||||
|
||||
it('does not throw if it is not a gift', () => {
|
||||
expect(() => validateGiftMessage(null, user)).to.not.throw;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import googlePayments from '../../../../../website/server/libs/payments/google';
|
||||
import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import * as gems from '../../../../../website/server/libs/payments/gems';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
@@ -15,7 +16,7 @@ describe('Google Payments', () => {
|
||||
let sku; let user; let token; let receipt; let signature; let
|
||||
headers; const gemsBlock = common.content.gems['21gems'];
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
|
||||
paymentBuyGemsStub;
|
||||
paymentBuyGemsStub; let validateGiftMessageStub;
|
||||
|
||||
beforeEach(() => {
|
||||
sku = 'com.habitrpg.android.habitica.iap.21gems';
|
||||
@@ -31,6 +32,7 @@ describe('Google Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -38,6 +40,7 @@ describe('Google Payments', () => {
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
payments.buyGems.restore();
|
||||
gems.validateGiftMessage.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if receipt is invalid', async () => {
|
||||
@@ -89,6 +92,8 @@ describe('Google Payments', () => {
|
||||
user, receipt, signature, headers,
|
||||
});
|
||||
|
||||
expect(validateGiftMessageStub).to.not.be.called;
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
|
||||
@@ -119,6 +124,9 @@ describe('Google Payments', () => {
|
||||
user, gift, receipt, signature, headers,
|
||||
});
|
||||
|
||||
expect(validateGiftMessageStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.be.calledWith(gift, user);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
|
||||
|
||||
@@ -209,17 +209,6 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.txnCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('sends a private message about the gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
const msg = '`Hello recipient, sender has sent you 3 months of subscription!`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledOnce;
|
||||
expect(user.sendMessage).to.be.calledWith(
|
||||
recipient,
|
||||
{ receiverMsg: msg, senderMsg: msg, save: false },
|
||||
);
|
||||
});
|
||||
|
||||
it('sends an email about the gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -255,6 +244,109 @@ describe('payments/index', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('No Active Promotion', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(worldState, 'getCurrentEvent').returns(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
worldState.getCurrentEvent.restore();
|
||||
});
|
||||
|
||||
it('sends a private message about the gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
const msg = '`Hello recipient, sender has sent you 3 months of subscription!`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledOnce;
|
||||
expect(user.sendMessage).to.be.calledWith(
|
||||
recipient,
|
||||
{ receiverMsg: msg, senderMsg: msg, save: false },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('Active Promotion', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(worldState, 'getCurrentEvent').returns({
|
||||
...common.content.events.winter2021Promo,
|
||||
event: 'winter2021',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
worldState.getCurrentEvent.restore();
|
||||
});
|
||||
|
||||
it('creates a gift subscription for purchaser and recipient if none exist', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(user.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
|
||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => {
|
||||
user.purchased.plan = plan;
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||
|
||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('adds extraMonths to existing subscription for recipient and creates a gift subscription for purchaser without sub', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||
|
||||
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(user.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('adds extraMonths to existing subscriptions for purchaser and recipient', async () => {
|
||||
user.purchased.plan = plan;
|
||||
recipient.purchased.plan = plan;
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||
});
|
||||
|
||||
it('sends a private message about the promotion', async () => {
|
||||
await api.createSubscription(data);
|
||||
const msg = '`Hello sender, you received 3 months of subscription as part of our holiday gift-giving promotion!`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledTwice;
|
||||
expect(user.sendMessage).to.be.calledWith(user, { receiverMsg: msg, save: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Purchasing a subscription for self', () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import paypalPayments from '../../../../../../website/server/libs/payments/paypa
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import common from '../../../../../../website/common';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
const { i18n } = common;
|
||||
@@ -48,6 +49,7 @@ describe('paypal - checkout', () => {
|
||||
.resolves({
|
||||
links: [{ rel: 'approval_url', href: approvalHerf }],
|
||||
});
|
||||
sandbox.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -57,6 +59,7 @@ describe('paypal - checkout', () => {
|
||||
it('creates a link for gem purchases', async () => {
|
||||
const link = await paypalPayments.checkout({ user: new User(), gemsBlock: gemsBlockKey });
|
||||
|
||||
expect(gems.validateGiftMessage).to.not.be.called;
|
||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 4.99));
|
||||
expect(link).to.eql(approvalHerf);
|
||||
@@ -105,6 +108,7 @@ describe('paypal - checkout', () => {
|
||||
});
|
||||
|
||||
it('creates a link for gifting gems', async () => {
|
||||
const user = new User();
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
@@ -115,14 +119,17 @@ describe('paypal - checkout', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const link = await paypalPayments.checkout({ gift });
|
||||
const link = await paypalPayments.checkout({ user, gift });
|
||||
|
||||
expect(gems.validateGiftMessage).to.be.calledOnce;
|
||||
expect(gems.validateGiftMessage).to.be.calledWith(gift, user);
|
||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems (Gift)', '4.00'));
|
||||
expect(link).to.eql(approvalHerf);
|
||||
});
|
||||
|
||||
it('creates a link for gifting a subscription', async () => {
|
||||
const user = new User();
|
||||
const receivingUser = new User();
|
||||
receivingUser.save();
|
||||
const gift = {
|
||||
@@ -133,7 +140,10 @@ describe('paypal - checkout', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const link = await paypalPayments.checkout({ gift });
|
||||
const link = await paypalPayments.checkout({ user, gift });
|
||||
|
||||
expect(gems.validateGiftMessage).to.be.calledOnce;
|
||||
expect(gems.validateGiftMessage).to.be.calledWith(gift, user);
|
||||
|
||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('mo. Habitica Subscription (Gift)', '15.00'));
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('stripe - cancel subscription', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const stripe = stripeModule('test');
|
||||
let user; let groupId; let
|
||||
group;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = subKey;
|
||||
await group.save();
|
||||
|
||||
groupId = group._id;
|
||||
});
|
||||
|
||||
it('throws an error if there is no customer id', async () => {
|
||||
user.purchased.plan.customerId = undefined;
|
||||
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if the group is not found', async () => {
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: 'fake-group',
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if user is not the group leader', async () => {
|
||||
const nonLeader = new User();
|
||||
nonLeader.guilds.push(groupId);
|
||||
await nonLeader.save();
|
||||
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user: nonLeader,
|
||||
groupId,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let stripeDeleteCustomerStub; let paymentsCancelSubStub;
|
||||
let stripeRetrieveStub; let subscriptionId; let
|
||||
currentPeriodEndTimeStamp;
|
||||
|
||||
beforeEach(() => {
|
||||
subscriptionId = 'subId';
|
||||
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').resolves({});
|
||||
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
|
||||
currentPeriodEndTimeStamp = (new Date()).getTime();
|
||||
stripeRetrieveStub = sinon.stub(stripe.customers, 'retrieve')
|
||||
.resolves({
|
||||
subscriptions: {
|
||||
data: [{
|
||||
id: subscriptionId,
|
||||
current_period_end: currentPeriodEndTimeStamp,
|
||||
}], // eslint-disable-line camelcase
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.customers.del.restore();
|
||||
stripe.customers.retrieve.restore();
|
||||
payments.cancelSubscription.restore();
|
||||
});
|
||||
|
||||
it('cancels a user subscription', async () => {
|
||||
await stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeDeleteCustomerStub).to.be.calledOnce;
|
||||
expect(stripeDeleteCustomerStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(stripeRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(paymentsCancelSubStub).to.be.calledOnce;
|
||||
expect(paymentsCancelSubStub).to.be.calledWith({
|
||||
user,
|
||||
groupId: undefined,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
await stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeDeleteCustomerStub).to.be.calledOnce;
|
||||
expect(stripeDeleteCustomerStub).to.be.calledWith(group.purchased.plan.customerId);
|
||||
expect(stripeRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(paymentsCancelSubStub).to.be.calledOnce;
|
||||
expect(paymentsCancelSubStub).to.be.calledWith({
|
||||
user,
|
||||
groupId,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,321 +0,0 @@
|
||||
import stripeModule from 'stripe';
|
||||
import cc from 'coupon-code';
|
||||
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import { model as Coupon } from '../../../../../../website/server/models/coupon';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('stripe - checkout with subscription', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const stripe = stripeModule('test');
|
||||
let user; let group; let data; let gift; let sub;
|
||||
let groupId; let email; let headers; let coupon;
|
||||
let customerIdResponse; let subscriptionId; let
|
||||
token;
|
||||
let spy;
|
||||
let stripeCreateCustomerSpy;
|
||||
let stripePaymentsCreateSubSpy;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = subKey;
|
||||
await group.save();
|
||||
|
||||
sub = {
|
||||
key: 'basic_3mo',
|
||||
};
|
||||
|
||||
data = {
|
||||
user,
|
||||
sub,
|
||||
customerId: 'customer-id',
|
||||
paymentMethod: 'Payment Method',
|
||||
};
|
||||
|
||||
email = 'example@example.com';
|
||||
customerIdResponse = 'test-id';
|
||||
subscriptionId = 'test-sub-id';
|
||||
token = 'test-token';
|
||||
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.resolves;
|
||||
|
||||
stripeCreateCustomerSpy = sinon.stub(stripe.customers, 'create');
|
||||
const stripCustomerResponse = {
|
||||
id: customerIdResponse,
|
||||
subscriptions: {
|
||||
data: [{ id: subscriptionId }],
|
||||
},
|
||||
};
|
||||
stripeCreateCustomerSpy.resolves(stripCustomerResponse);
|
||||
|
||||
stripePaymentsCreateSubSpy = sinon.stub(payments, 'createSubscription');
|
||||
stripePaymentsCreateSubSpy.resolves({});
|
||||
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.subscriptions.update.restore();
|
||||
stripe.customers.create.restore();
|
||||
payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if we are missing a token', async () => {
|
||||
await expect(stripePayments.checkout({
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: 'Missing req.body.id',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when coupon code is missing', async () => {
|
||||
sub.discount = 40;
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('couponCodeRequired'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when coupon code is invalid', async () => {
|
||||
sub.discount = 40;
|
||||
sub.key = 'google_6mo';
|
||||
coupon = 'example-coupon';
|
||||
|
||||
const couponModel = new Coupon();
|
||||
couponModel.event = 'google_6mo';
|
||||
await couponModel.save();
|
||||
|
||||
sinon.stub(cc, 'validate').returns('invalid');
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('invalidCoupon'),
|
||||
});
|
||||
cc.validate.restore();
|
||||
});
|
||||
|
||||
it('subscribes with stripe with a coupon', async () => {
|
||||
sub.discount = 40;
|
||||
sub.key = 'google_6mo';
|
||||
coupon = 'example-coupon';
|
||||
|
||||
const couponModel = new Coupon();
|
||||
couponModel.event = 'google_6mo';
|
||||
const updatedCouponModel = await couponModel.save();
|
||||
|
||||
sinon.stub(cc, 'validate').returns(updatedCouponModel._id);
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy).to.be.calledOnce;
|
||||
expect(stripeCreateCustomerSpy).to.be.calledWith({
|
||||
email,
|
||||
metadata: { uuid: user._id },
|
||||
card: token,
|
||||
plan: sub.key,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
sub,
|
||||
headers,
|
||||
groupId: undefined,
|
||||
subscriptionId: undefined,
|
||||
});
|
||||
|
||||
cc.validate.restore();
|
||||
});
|
||||
|
||||
it('subscribes a user', async () => {
|
||||
sub = data.sub;
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy).to.be.calledOnce;
|
||||
expect(stripeCreateCustomerSpy).to.be.calledWith({
|
||||
email,
|
||||
metadata: { uuid: user._id },
|
||||
card: token,
|
||||
plan: sub.key,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
sub,
|
||||
headers,
|
||||
groupId: undefined,
|
||||
subscriptionId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes a group', async () => {
|
||||
token = 'test-token';
|
||||
sub = data.sub;
|
||||
groupId = group._id;
|
||||
email = 'test@test.com';
|
||||
|
||||
// Add user to group
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
headers = {};
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy).to.be.calledOnce;
|
||||
expect(stripeCreateCustomerSpy).to.be.calledWith({
|
||||
email,
|
||||
metadata: { uuid: user._id },
|
||||
card: token,
|
||||
plan: sub.key,
|
||||
quantity: 3,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
sub,
|
||||
headers,
|
||||
groupId,
|
||||
subscriptionId,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes a group with the correct number of group members', async () => {
|
||||
token = 'test-token';
|
||||
sub = data.sub;
|
||||
groupId = group._id;
|
||||
email = 'test@test.com';
|
||||
headers = {};
|
||||
|
||||
// Add user to group
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
user = new User();
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
group.memberCount = 2;
|
||||
await group.save();
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy).to.be.calledOnce;
|
||||
expect(stripeCreateCustomerSpy).to.be.calledWith({
|
||||
email,
|
||||
metadata: { uuid: user._id },
|
||||
card: token,
|
||||
plan: sub.key,
|
||||
quantity: 4,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
sub,
|
||||
headers,
|
||||
groupId,
|
||||
subscriptionId,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,235 +1,482 @@
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import nconf from 'nconf';
|
||||
import common from '../../../../../../website/common';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
import * as subscriptions from '../../../../../../website/server/libs/payments/stripe/subscriptions';
|
||||
import * as oneTimePayments from '../../../../../../website/server/libs/payments/stripe/oneTimePayments';
|
||||
import {
|
||||
createCheckoutSession,
|
||||
createEditCardCheckoutSession,
|
||||
} from '../../../../../../website/server/libs/payments/stripe/checkout';
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../../website/server/models/group';
|
||||
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('stripe - checkout', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
describe('Stripe - Checkout', () => {
|
||||
const stripe = stripeModule('test');
|
||||
let stripeChargeStub; let paymentBuyGemsStub; let
|
||||
paymentCreateSubscritionStub;
|
||||
let user; let gift; let groupId; let email; let headers; let coupon; let customerIdResponse; let
|
||||
token; const gemsBlockKey = '21gems'; const gemsBlock = common.content.gems[gemsBlockKey];
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
const redirectUrls = {
|
||||
success_url: `${BASE_URL}/redirect/stripe-success-checkout`,
|
||||
cancel_url: `${BASE_URL}/redirect/stripe-error-checkout`,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
describe('createCheckoutSession', () => {
|
||||
let user;
|
||||
const sessionId = 'session-id';
|
||||
|
||||
token = 'test-token';
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
sandbox.stub(stripe.checkout.sessions, 'create').returns(sessionId);
|
||||
sandbox.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
customerIdResponse = 'example-customerIdResponse';
|
||||
const stripCustomerResponse = {
|
||||
id: customerIdResponse,
|
||||
};
|
||||
stripeChargeStub = sinon.stub(stripe.charges, 'create').resolves(stripCustomerResponse);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.charges.create.restore();
|
||||
payments.buyGems.restore();
|
||||
payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should error if there is no token', async () => {
|
||||
await expect(stripePayments.checkout({
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: 'Missing req.body.id',
|
||||
name: 'BadRequest',
|
||||
it('gems', async () => {
|
||||
const amount = 999;
|
||||
const gemsBlockKey = '21gems';
|
||||
sandbox.stub(oneTimePayments, 'getOneTimePaymentInfo').returns({
|
||||
amount,
|
||||
gemsBlock: common.content.gems[gemsBlockKey],
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if gem amount is too low', async () => {
|
||||
const receivingUser = new User();
|
||||
receivingUser.save();
|
||||
gift = {
|
||||
type: 'gems',
|
||||
gems: {
|
||||
amount: 0,
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
const res = await createCheckoutSession({ user, gemsBlock: gemsBlockKey }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: 'Amount must be at least 1.',
|
||||
name: 'BadRequest',
|
||||
const metadata = {
|
||||
type: 'gems',
|
||||
userId: user._id,
|
||||
gift: undefined,
|
||||
sub: undefined,
|
||||
gemsBlock: gemsBlockKey,
|
||||
};
|
||||
|
||||
expect(gems.validateGiftMessage).to.not.be.called;
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce;
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledWith(gemsBlockKey, undefined, user);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price_data: {
|
||||
product_data: {
|
||||
name: common.i18n.t('nGems', { nGems: 21 }),
|
||||
},
|
||||
unit_amount: amount,
|
||||
currency: 'usd',
|
||||
},
|
||||
quantity: 1,
|
||||
}],
|
||||
mode: 'payment',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if user cannot get gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gemsBlock: gemsBlockKey,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe)).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if the gems block is invalid', async () => {
|
||||
gift = undefined;
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gemsBlock: 'invalid',
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe)).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: apiError('invalidGemsBlock'),
|
||||
name: 'BadRequest',
|
||||
});
|
||||
});
|
||||
|
||||
it('should purchase gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gemsBlock: gemsBlockKey,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeChargeStub).to.be.calledOnce;
|
||||
expect(stripeChargeStub).to.be.calledWith({
|
||||
amount: 499,
|
||||
currency: 'usd',
|
||||
card: token,
|
||||
});
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
gift,
|
||||
gemsBlock,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
it('gems gift', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
|
||||
it('should gift gems', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
},
|
||||
};
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeChargeStub).to.be.calledOnce;
|
||||
expect(stripeChargeStub).to.be.calledWith({
|
||||
amount: '400',
|
||||
currency: 'usd',
|
||||
card: token,
|
||||
});
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Gift',
|
||||
gift,
|
||||
gemsBlock: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should gift a subscription', async () => {
|
||||
const receivingUser = new User();
|
||||
receivingUser.save();
|
||||
gift = {
|
||||
type: 'subscription',
|
||||
subscription: {
|
||||
key: subKey,
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
gems: {
|
||||
amount: 4,
|
||||
},
|
||||
};
|
||||
const amount = 100;
|
||||
sandbox.stub(oneTimePayments, 'getOneTimePaymentInfo').returns({
|
||||
amount,
|
||||
gemsBlock: null,
|
||||
});
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
const res = await createCheckoutSession({ user, gift }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
|
||||
gift.member = receivingUser;
|
||||
expect(stripeChargeStub).to.be.calledOnce;
|
||||
expect(stripeChargeStub).to.be.calledWith({
|
||||
amount: '1500',
|
||||
currency: 'usd',
|
||||
card: token,
|
||||
const metadata = {
|
||||
type: 'gift-gems',
|
||||
userId: user._id,
|
||||
gift: JSON.stringify(gift),
|
||||
sub: undefined,
|
||||
gemsBlock: undefined,
|
||||
};
|
||||
|
||||
expect(gems.validateGiftMessage).to.be.calledOnce;
|
||||
expect(gems.validateGiftMessage).to.be.calledWith(gift, user);
|
||||
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce;
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledWith(undefined, gift, user);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price_data: {
|
||||
product_data: {
|
||||
name: common.i18n.t('nGemsGift', { nGems: 4 }),
|
||||
},
|
||||
unit_amount: amount,
|
||||
currency: 'usd',
|
||||
},
|
||||
quantity: 1,
|
||||
}],
|
||||
mode: 'payment',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
expect(paymentCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Gift',
|
||||
gift,
|
||||
gemsBlock: undefined,
|
||||
it('subscription gift', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const subKey = 'basic_3mo';
|
||||
|
||||
const gift = {
|
||||
type: 'subscription',
|
||||
uuid: receivingUser._id,
|
||||
subscription: {
|
||||
key: subKey,
|
||||
},
|
||||
};
|
||||
const amount = 1500;
|
||||
sandbox.stub(oneTimePayments, 'getOneTimePaymentInfo').returns({
|
||||
amount,
|
||||
gemsBlock: null,
|
||||
subscription: common.content.subscriptionBlocks[subKey],
|
||||
});
|
||||
|
||||
const res = await createCheckoutSession({ user, gift }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
|
||||
const metadata = {
|
||||
type: 'gift-sub',
|
||||
userId: user._id,
|
||||
gift: JSON.stringify(gift),
|
||||
sub: undefined,
|
||||
gemsBlock: undefined,
|
||||
};
|
||||
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce;
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledWith(undefined, gift, user);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price_data: {
|
||||
product_data: {
|
||||
name: common.i18n.t('nMonthsSubscriptionGift', { nMonths: 3 }),
|
||||
},
|
||||
unit_amount: amount,
|
||||
currency: 'usd',
|
||||
},
|
||||
quantity: 1,
|
||||
}],
|
||||
mode: 'payment',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscription', async () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const coupon = null;
|
||||
sandbox.stub(subscriptions, 'checkSubData').returns(undefined);
|
||||
const sub = common.content.subscriptionBlocks[subKey];
|
||||
|
||||
const res = await createCheckoutSession({ user, sub, coupon }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
|
||||
const metadata = {
|
||||
type: 'subscription',
|
||||
userId: user._id,
|
||||
gift: undefined,
|
||||
sub: JSON.stringify(sub),
|
||||
};
|
||||
|
||||
expect(subscriptions.checkSubData).to.be.calledOnce;
|
||||
expect(subscriptions.checkSubData).to.be.calledWith(sub, false, coupon);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price: sub.key,
|
||||
quantity: 1,
|
||||
// @TODO proper copy
|
||||
}],
|
||||
mode: 'subscription',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if group does not exists', async () => {
|
||||
const groupId = 'invalid';
|
||||
sandbox.stub(Group.prototype, 'getMemberCount').resolves(4);
|
||||
|
||||
const subKey = 'group_monthly';
|
||||
const coupon = null;
|
||||
const sub = common.content.subscriptionBlocks[subKey];
|
||||
|
||||
await expect(createCheckoutSession({
|
||||
user, sub, coupon, groupId,
|
||||
}, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('group plan', async () => {
|
||||
const group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
const groupId = group._id;
|
||||
await group.save();
|
||||
sandbox.stub(Group.prototype, 'getMemberCount').resolves(4);
|
||||
|
||||
// Add user to group
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
const subKey = 'group_monthly';
|
||||
const coupon = null;
|
||||
sandbox.stub(subscriptions, 'checkSubData').returns(undefined);
|
||||
const sub = common.content.subscriptionBlocks[subKey];
|
||||
|
||||
const res = await createCheckoutSession({
|
||||
user, sub, coupon, groupId,
|
||||
}, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
|
||||
const metadata = {
|
||||
type: 'subscription',
|
||||
userId: user._id,
|
||||
gift: undefined,
|
||||
sub: JSON.stringify(sub),
|
||||
groupId,
|
||||
};
|
||||
|
||||
expect(Group.prototype.getMemberCount).to.be.calledOnce;
|
||||
expect(subscriptions.checkSubData).to.be.calledOnce;
|
||||
expect(subscriptions.checkSubData).to.be.calledWith(sub, true, coupon);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price: sub.key,
|
||||
quantity: 6,
|
||||
// @TODO proper copy
|
||||
}],
|
||||
mode: 'subscription',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
// no gift, sub or gem payment
|
||||
it('throws if type is invalid', async () => {
|
||||
await expect(createCheckoutSession({ user }, stripe))
|
||||
.to.eventually.be.rejected;
|
||||
});
|
||||
});
|
||||
|
||||
describe('createEditCardCheckoutSession', () => {
|
||||
let user;
|
||||
const sessionId = 'session-id';
|
||||
const customerId = 'customerId';
|
||||
const subscriptionId = 'subscription-id';
|
||||
let subscriptionsListStub;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
sandbox.stub(stripe.checkout.sessions, 'create').returns(sessionId);
|
||||
subscriptionsListStub = sandbox.stub(stripe.subscriptions, 'list');
|
||||
subscriptionsListStub.resolves({ data: [{ id: subscriptionId }] });
|
||||
});
|
||||
|
||||
it('throws if no valid data is supplied', async () => {
|
||||
await expect(createEditCardCheckoutSession({}, stripe))
|
||||
.to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('throws if customer does not exists', async () => {
|
||||
await expect(createEditCardCheckoutSession({ user }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if subscription does not exists', async () => {
|
||||
user.purchased.plan.customerId = customerId;
|
||||
subscriptionsListStub.resolves({ data: [] });
|
||||
|
||||
await expect(createEditCardCheckoutSession({ user }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
it('change card for user subscription', async () => {
|
||||
user.purchased.plan.customerId = customerId;
|
||||
|
||||
const metadata = {
|
||||
userId: user._id,
|
||||
type: 'edit-card-user',
|
||||
};
|
||||
|
||||
const res = await createEditCardCheckoutSession({ user }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
expect(subscriptionsListStub).to.be.calledOnce;
|
||||
expect(subscriptionsListStub).to.be.calledWith({ customer: customerId });
|
||||
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
mode: 'setup',
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
customer: customerId,
|
||||
setup_intent_data: {
|
||||
metadata: {
|
||||
customer_id: customerId,
|
||||
subscription_id: subscriptionId,
|
||||
},
|
||||
},
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if group does not exists', async () => {
|
||||
const groupId = 'invalid';
|
||||
|
||||
await expect(createEditCardCheckoutSession({ user, groupId }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('with group', () => {
|
||||
let group; let groupId;
|
||||
beforeEach(async () => {
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
groupId = group._id;
|
||||
await group.save();
|
||||
});
|
||||
|
||||
it('throws if user is not allowed to change group plan', async () => {
|
||||
const anotherUser = new User();
|
||||
anotherUser.guilds.push(groupId);
|
||||
await anotherUser.save();
|
||||
|
||||
await expect(createEditCardCheckoutSession({ user: anotherUser, groupId }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if customer does not exists (group)', async () => {
|
||||
await expect(createEditCardCheckoutSession({ user, groupId }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if subscription does not exists (group)', async () => {
|
||||
group.purchased.plan.customerId = customerId;
|
||||
subscriptionsListStub.resolves({ data: [] });
|
||||
|
||||
await expect(createEditCardCheckoutSession({ user, groupId }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('change card for group plans - leader', async () => {
|
||||
group.purchased.plan.customerId = customerId;
|
||||
await group.save();
|
||||
|
||||
const metadata = {
|
||||
userId: user._id,
|
||||
type: 'edit-card-group',
|
||||
groupId,
|
||||
};
|
||||
|
||||
const res = await createEditCardCheckoutSession({ user, groupId }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
expect(subscriptionsListStub).to.be.calledOnce;
|
||||
expect(subscriptionsListStub).to.be.calledWith({ customer: customerId });
|
||||
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
mode: 'setup',
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
customer: customerId,
|
||||
setup_intent_data: {
|
||||
metadata: {
|
||||
customer_id: customerId,
|
||||
subscription_id: subscriptionId,
|
||||
},
|
||||
},
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('change card for group plans - plan owner', async () => {
|
||||
const anotherUser = new User();
|
||||
anotherUser.guilds.push(groupId);
|
||||
await anotherUser.save();
|
||||
|
||||
group.purchased.plan.customerId = customerId;
|
||||
group.purchased.plan.owner = anotherUser._id;
|
||||
await group.save();
|
||||
|
||||
const metadata = {
|
||||
userId: anotherUser._id,
|
||||
type: 'edit-card-group',
|
||||
groupId,
|
||||
};
|
||||
|
||||
const res = await createEditCardCheckoutSession({ user: anotherUser, groupId }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
expect(subscriptionsListStub).to.be.calledOnce;
|
||||
expect(subscriptionsListStub).to.be.calledWith({ customer: customerId });
|
||||
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
mode: 'setup',
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
customer: customerId,
|
||||
setup_intent_data: {
|
||||
metadata: {
|
||||
customer_id: customerId,
|
||||
subscription_id: subscriptionId,
|
||||
},
|
||||
},
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('stripe - edit subscription', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const stripe = stripeModule('test');
|
||||
let user; let groupId; let group; let
|
||||
token;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = subKey;
|
||||
await group.save();
|
||||
|
||||
groupId = group._id;
|
||||
|
||||
token = 'test-token';
|
||||
});
|
||||
|
||||
it('throws an error if there is no customer id', async () => {
|
||||
user.purchased.plan.customerId = undefined;
|
||||
|
||||
await expect(stripePayments.editSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if a token is not provided', async () => {
|
||||
await expect(stripePayments.editSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: 'Missing req.body.id',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if the group is not found', async () => {
|
||||
await expect(stripePayments.editSubscription({
|
||||
token,
|
||||
user,
|
||||
groupId: 'fake-group',
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if user is not the group leader', async () => {
|
||||
const nonLeader = new User();
|
||||
nonLeader.guilds.push(groupId);
|
||||
await nonLeader.save();
|
||||
|
||||
await expect(stripePayments.editSubscription({
|
||||
token,
|
||||
user: nonLeader,
|
||||
groupId,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let stripeListSubscriptionStub; let stripeUpdateSubscriptionStub; let
|
||||
subscriptionId;
|
||||
|
||||
beforeEach(() => {
|
||||
subscriptionId = 'subId';
|
||||
stripeListSubscriptionStub = sinon.stub(stripe.subscriptions, 'list')
|
||||
.resolves({
|
||||
data: [{ id: subscriptionId }],
|
||||
});
|
||||
|
||||
stripeUpdateSubscriptionStub = sinon.stub(stripe.subscriptions, 'update').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.subscriptions.list.restore();
|
||||
stripe.subscriptions.update.restore();
|
||||
});
|
||||
|
||||
it('edits a user subscription', async () => {
|
||||
await stripePayments.editSubscription({
|
||||
token,
|
||||
user,
|
||||
groupId: undefined,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeListSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeListSubscriptionStub).to.be.calledWith({
|
||||
customer: user.purchased.plan.customerId,
|
||||
});
|
||||
expect(stripeUpdateSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeUpdateSubscriptionStub).to.be.calledWith(
|
||||
subscriptionId,
|
||||
{ card: token },
|
||||
);
|
||||
});
|
||||
|
||||
it('edits a group subscription', async () => {
|
||||
await stripePayments.editSubscription({
|
||||
token,
|
||||
user,
|
||||
groupId,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeListSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeListSubscriptionStub).to.be.calledWith({
|
||||
customer: group.purchased.plan.customerId,
|
||||
});
|
||||
expect(stripeUpdateSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeUpdateSubscriptionStub).to.be.calledWith(
|
||||
subscriptionId,
|
||||
{ card: token },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,316 @@
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
import common from '../../../../../../website/common';
|
||||
import {
|
||||
getOneTimePaymentInfo,
|
||||
applyGemPayment,
|
||||
} from '../../../../../../website/server/libs/payments/stripe/oneTimePayments';
|
||||
import * as subscriptions from '../../../../../../website/server/libs/payments/stripe/subscriptions';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('Stripe - One Time Payments', () => {
|
||||
describe('getOneTimePaymentInfo', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
sandbox.stub(subscriptions, 'checkSubData');
|
||||
});
|
||||
|
||||
describe('gemsBlock', () => {
|
||||
it('returns the gemsBlock and amount', async () => {
|
||||
const { gemsBlock, amount, subscription } = await getOneTimePaymentInfo('21gems', null, user);
|
||||
expect(gemsBlock).to.equal(common.content.gems['21gems']);
|
||||
expect(amount).to.equal(gemsBlock.price);
|
||||
expect(amount).to.equal(499);
|
||||
expect(subscription).to.be.null;
|
||||
expect(subscriptions.checkSubData).to.not.be.called;
|
||||
});
|
||||
|
||||
it('throws if the gemsBlock does not exist', async () => {
|
||||
await expect(getOneTimePaymentInfo('not existant', null, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: apiError('invalidGemsBlock'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the user cannot receive gems', async () => {
|
||||
sandbox.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(getOneTimePaymentInfo('21gems', null, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('gift', () => {
|
||||
it('throws if the receiver does not exist', async () => {
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: 'invalid',
|
||||
gems: {
|
||||
amount: 3,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(getOneTimePaymentInfo(null, gift, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('userWithIDNotFound', { userId: 'invalid' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the user cannot receive gems', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
sandbox.stub(User.prototype, 'canGetGems').resolves(false);
|
||||
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 2,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(getOneTimePaymentInfo(null, gift, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the amount of gems is <= 0', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 0,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(getOneTimePaymentInfo(null, gift, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('badAmountOfGemsToPurchase'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the subscription block does not exist', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
type: 'subscription',
|
||||
uuid: receivingUser._id,
|
||||
subscription: {
|
||||
key: 'invalid',
|
||||
},
|
||||
};
|
||||
|
||||
await expect(getOneTimePaymentInfo(null, gift, user))
|
||||
.to.eventually.throw;
|
||||
});
|
||||
|
||||
it('returns the amount (gems)', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 4,
|
||||
},
|
||||
};
|
||||
|
||||
expect(subscriptions.checkSubData).to.not.be.called;
|
||||
|
||||
const { gemsBlock, amount, subscription } = await getOneTimePaymentInfo(null, gift, user);
|
||||
expect(gemsBlock).to.equal(null);
|
||||
expect(amount).to.equal('100');
|
||||
expect(subscription).to.be.null;
|
||||
});
|
||||
|
||||
it('returns the amount (subscription)', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
type: 'subscription',
|
||||
uuid: receivingUser._id,
|
||||
subscription: {
|
||||
key: 'basic_3mo',
|
||||
},
|
||||
};
|
||||
const sub = common.content.subscriptionBlocks['basic_3mo']; // eslint-disable-line dot-notation
|
||||
|
||||
const { gemsBlock, amount, subscription } = await getOneTimePaymentInfo(null, gift, user);
|
||||
|
||||
expect(subscriptions.checkSubData).to.be.calledOnce;
|
||||
expect(subscriptions.checkSubData).to.be.calledWith(sub, false, null);
|
||||
|
||||
expect(gemsBlock).to.equal(null);
|
||||
expect(amount).to.equal('1500');
|
||||
expect(Number(amount)).to.equal(sub.price * 100);
|
||||
expect(subscription).to.equal(sub);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyGemPayment', () => {
|
||||
let user;
|
||||
let customerId;
|
||||
let subKey;
|
||||
let userFindByIdStub;
|
||||
let paymentsCreateSubSpy;
|
||||
let paymentBuyGemsStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subKey = 'basic_3mo';
|
||||
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
customerId = 'test-id';
|
||||
|
||||
paymentsCreateSubSpy = sandbox.stub(payments, 'createSubscription');
|
||||
paymentsCreateSubSpy.resolves({});
|
||||
|
||||
paymentBuyGemsStub = sandbox.stub(payments, 'buyGems');
|
||||
paymentBuyGemsStub.resolves({});
|
||||
});
|
||||
|
||||
it('throws if the user does not exist', async () => {
|
||||
const metadata = { userId: 'invalid' };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await expect(applyGemPayment(session))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('userWithIDNotFound', { userId: metadata.userId }),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the receiving user does not exist', async () => {
|
||||
const metadata = { userId: 'invalid' };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await expect(applyGemPayment(session))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('userWithIDNotFound', { userId: metadata.userId }),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the gems block does not exist', async () => {
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: 'invalid',
|
||||
gems: {
|
||||
amount: 16,
|
||||
},
|
||||
};
|
||||
|
||||
const metadata = { userId: user._id, gift: JSON.stringify(gift) };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await expect(applyGemPayment(session))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('userWithIDNotFound', { userId: 'invalid' }),
|
||||
});
|
||||
});
|
||||
|
||||
describe('with existing user', () => {
|
||||
beforeEach(() => {
|
||||
const execStub = sandbox.stub().resolves(user);
|
||||
userFindByIdStub = sandbox.stub(User, 'findById');
|
||||
userFindByIdStub.withArgs(user._id).returns({ exec: execStub });
|
||||
});
|
||||
|
||||
it('buys gems', async () => {
|
||||
const metadata = { userId: user._id, gemsBlock: '21gems' };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await applyGemPayment(session);
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
paymentMethod: 'Stripe',
|
||||
gift: undefined,
|
||||
gemsBlock: common.content.gems['21gems'],
|
||||
});
|
||||
});
|
||||
|
||||
it('gift gems', async () => {
|
||||
const receivingUser = new User();
|
||||
const execStub = sandbox.stub().resolves(receivingUser);
|
||||
userFindByIdStub.withArgs(receivingUser._id).returns({ exec: execStub });
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
},
|
||||
};
|
||||
|
||||
sandbox.stub(JSON, 'parse').returns(gift);
|
||||
const metadata = { userId: user._id, gift: JSON.stringify(gift) };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await applyGemPayment(session);
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
paymentMethod: 'Gift',
|
||||
gift,
|
||||
gemsBlock: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('gift sub', async () => {
|
||||
const receivingUser = new User();
|
||||
const execStub = sandbox.stub().resolves(receivingUser);
|
||||
userFindByIdStub.withArgs(receivingUser._id).returns({ exec: execStub });
|
||||
const gift = {
|
||||
type: 'subscription',
|
||||
uuid: receivingUser._id,
|
||||
subscription: {
|
||||
key: subKey,
|
||||
},
|
||||
};
|
||||
|
||||
sandbox.stub(JSON, 'parse').returns(gift);
|
||||
const metadata = { userId: user._id, gift: JSON.stringify(gift) };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await applyGemPayment(session);
|
||||
|
||||
expect(paymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(paymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
paymentMethod: 'Gift',
|
||||
gift,
|
||||
gemsBlock: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,436 @@
|
||||
import cc from 'coupon-code';
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import { model as Coupon } from '../../../../../../website/server/models/coupon';
|
||||
import common from '../../../../../../website/common';
|
||||
import {
|
||||
checkSubData,
|
||||
applySubscription,
|
||||
chargeForAdditionalGroupMember,
|
||||
handlePaymentMethodChange,
|
||||
} from '../../../../../../website/server/libs/payments/stripe/subscriptions';
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('Stripe - Subscriptions', () => {
|
||||
describe('checkSubData', () => {
|
||||
it('does not throw if the subscription can be used', async () => {
|
||||
const sub = common.content.subscriptionBlocks['basic_3mo']; // eslint-disable-line dot-notation
|
||||
const res = await checkSubData(sub);
|
||||
expect(res).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('throws if the subscription does not exists', async () => {
|
||||
await expect(checkSubData())
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the subscription can\'t be used', async () => {
|
||||
const sub = common.content.subscriptionBlocks['group_plan_auto']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, true))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the subscription targets a group and an user is making the request', async () => {
|
||||
const sub = common.content.subscriptionBlocks['group_monthly']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, false))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the subscription targets an user and a group is making the request', async () => {
|
||||
const sub = common.content.subscriptionBlocks['basic_3mo']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, true))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the coupon is required but not passed', async () => {
|
||||
const sub = common.content.subscriptionBlocks['google_6mo']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, false))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('couponCodeRequired'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the coupon is required but does not exist', async () => {
|
||||
const coupon = 'not-valid';
|
||||
const sub = common.content.subscriptionBlocks['google_6mo']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, false, coupon))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('invalidCoupon'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the coupon is required but is invalid', async () => {
|
||||
const couponModel = new Coupon();
|
||||
couponModel.event = 'google_6mo';
|
||||
await couponModel.save();
|
||||
|
||||
sandbox.stub(cc, 'validate').returns('invalid');
|
||||
|
||||
const sub = common.content.subscriptionBlocks['google_6mo']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, false, couponModel._id))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('invalidCoupon'),
|
||||
});
|
||||
});
|
||||
|
||||
it('works if the coupon is required and valid', async () => {
|
||||
const couponModel = new Coupon();
|
||||
couponModel.event = 'google_6mo';
|
||||
await couponModel.save();
|
||||
|
||||
sandbox.stub(cc, 'validate').returns(couponModel._id);
|
||||
|
||||
const sub = common.content.subscriptionBlocks['google_6mo']; // eslint-disable-line dot-notation
|
||||
await checkSubData(sub, false, couponModel._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applySubscription', () => {
|
||||
let user; let group; let sub;
|
||||
let groupId;
|
||||
let customerId; let subscriptionId;
|
||||
let subKey;
|
||||
let userFindByIdStub;
|
||||
let stripePaymentsCreateSubSpy;
|
||||
|
||||
beforeEach(async () => {
|
||||
subKey = 'basic_3mo';
|
||||
sub = common.content.subscriptionBlocks[subKey];
|
||||
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
const execStub = sandbox.stub().resolves(user);
|
||||
userFindByIdStub = sandbox.stub(User, 'findById');
|
||||
userFindByIdStub.withArgs(user._id).returns({ exec: execStub });
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
groupId = group._id;
|
||||
await group.save();
|
||||
|
||||
// Add user to group
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
customerId = 'test-id';
|
||||
subscriptionId = 'test-sub-id';
|
||||
|
||||
stripePaymentsCreateSubSpy = sandbox.stub(payments, 'createSubscription');
|
||||
stripePaymentsCreateSubSpy.resolves({});
|
||||
});
|
||||
|
||||
it('subscribes a user', async () => {
|
||||
await applySubscription({
|
||||
customer: customerId,
|
||||
subscription: subscriptionId,
|
||||
metadata: {
|
||||
sub: JSON.stringify(sub),
|
||||
userId: user._id,
|
||||
groupId: null,
|
||||
},
|
||||
user,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
subscriptionId,
|
||||
paymentMethod: 'Stripe',
|
||||
sub: sinon.match({ ...sub }),
|
||||
groupId: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes a group', async () => {
|
||||
sub = common.content.subscriptionBlocks['group_monthly']; // eslint-disable-line dot-notation
|
||||
await applySubscription({
|
||||
customer: customerId,
|
||||
subscription: subscriptionId,
|
||||
metadata: {
|
||||
sub: JSON.stringify(sub),
|
||||
userId: user._id,
|
||||
groupId,
|
||||
},
|
||||
user,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
subscriptionId,
|
||||
paymentMethod: 'Stripe',
|
||||
sub: sinon.match({ ...sub }),
|
||||
groupId,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes a group with multiple users', async () => {
|
||||
const user2 = new User();
|
||||
user2.guilds.push(groupId);
|
||||
await user2.save();
|
||||
|
||||
const execStub2 = sandbox.stub().resolves(user);
|
||||
userFindByIdStub.withArgs(user2._id).returns({ exec: execStub2 });
|
||||
|
||||
group.memberCount = 2;
|
||||
await group.save();
|
||||
|
||||
sub = common.content.subscriptionBlocks['group_monthly']; // eslint-disable-line dot-notation
|
||||
await applySubscription({
|
||||
customer: customerId,
|
||||
subscription: subscriptionId,
|
||||
metadata: {
|
||||
sub: JSON.stringify(sub),
|
||||
userId: user._id,
|
||||
groupId,
|
||||
},
|
||||
user,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
subscriptionId,
|
||||
paymentMethod: 'Stripe',
|
||||
sub: sinon.match({ ...sub }),
|
||||
groupId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlePaymentMethodChange', () => {
|
||||
const stripe = stripeModule('test');
|
||||
|
||||
it('updates the plan quantity based on the number of group members', async () => {
|
||||
const stripeIntentRetrieveStub = sandbox.stub(stripe.setupIntents, 'retrieve').resolves({
|
||||
payment_method: 1,
|
||||
metadata: {
|
||||
subscription_id: 2,
|
||||
},
|
||||
});
|
||||
const stripeSubUpdateStub = sandbox.stub(stripe.subscriptions, 'update');
|
||||
|
||||
await handlePaymentMethodChange({}, stripe);
|
||||
expect(stripeIntentRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeSubUpdateStub).to.be.calledOnce;
|
||||
expect(stripeSubUpdateStub).to.be.calledWith(2, {
|
||||
default_payment_method: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('chargeForAdditionalGroupMember', () => {
|
||||
const stripe = stripeModule('test');
|
||||
let stripeUpdateSubStub;
|
||||
const plan = common.content.subscriptionBlocks['group_monthly']; // eslint-disable-line dot-notation
|
||||
|
||||
let user; let group;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = plan.key;
|
||||
group.purchased.plan.subscriptionId = 'sub-id';
|
||||
await group.save();
|
||||
|
||||
stripeUpdateSubStub = sandbox.stub(stripe.subscriptions, 'update').resolves({});
|
||||
});
|
||||
|
||||
it('updates the plan quantity based on the number of group members', async () => {
|
||||
group.memberCount = 4;
|
||||
const newQuantity = group.memberCount + plan.quantity - 1;
|
||||
|
||||
await chargeForAdditionalGroupMember(group, stripe);
|
||||
expect(stripeUpdateSubStub).to.be.calledWithMatch(
|
||||
group.purchased.plan.subscriptionId,
|
||||
sinon.match({
|
||||
plan: group.purchased.plan.planId,
|
||||
quantity: newQuantity,
|
||||
}),
|
||||
);
|
||||
expect(group.purchased.plan.quantity).to.equal(newQuantity);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancelSubscription', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const stripe = stripeModule('test');
|
||||
let user; let groupId; let
|
||||
group;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = subKey;
|
||||
await group.save();
|
||||
|
||||
groupId = group._id;
|
||||
});
|
||||
|
||||
it('throws an error if there is no customer id', async () => {
|
||||
user.purchased.plan.customerId = undefined;
|
||||
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if the group is not found', async () => {
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: 'fake-group',
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if user is not the group leader', async () => {
|
||||
const nonLeader = new User();
|
||||
nonLeader.guilds.push(groupId);
|
||||
await nonLeader.save();
|
||||
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user: nonLeader,
|
||||
groupId,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let stripeDeleteCustomerStub; let paymentsCancelSubStub;
|
||||
let stripeRetrieveStub; let subscriptionId; let
|
||||
currentPeriodEndTimeStamp;
|
||||
|
||||
beforeEach(() => {
|
||||
subscriptionId = 'subId';
|
||||
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').resolves({});
|
||||
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
|
||||
currentPeriodEndTimeStamp = (new Date()).getTime();
|
||||
stripeRetrieveStub = sinon.stub(stripe.customers, 'retrieve')
|
||||
.resolves({
|
||||
subscriptions: {
|
||||
data: [{
|
||||
id: subscriptionId,
|
||||
current_period_end: currentPeriodEndTimeStamp,
|
||||
}], // eslint-disable-line camelcase
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.customers.del.restore();
|
||||
stripe.customers.retrieve.restore();
|
||||
payments.cancelSubscription.restore();
|
||||
});
|
||||
|
||||
it('cancels a user subscription', async () => {
|
||||
await stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeDeleteCustomerStub).to.be.calledOnce;
|
||||
expect(stripeDeleteCustomerStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(stripeRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(paymentsCancelSubStub).to.be.calledOnce;
|
||||
expect(paymentsCancelSubStub).to.be.calledWith({
|
||||
user,
|
||||
groupId: undefined,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
await stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeDeleteCustomerStub).to.be.calledOnce;
|
||||
expect(stripeDeleteCustomerStub).to.be.calledWith(group.purchased.plan.customerId);
|
||||
expect(stripeRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(paymentsCancelSubStub).to.be.calledOnce;
|
||||
expect(paymentsCancelSubStub).to.be.calledWith({
|
||||
user,
|
||||
groupId,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,70 +0,0 @@
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../../website/server/models/group';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
|
||||
describe('Stripe - Upgrade Group Plan', () => {
|
||||
const stripe = stripeModule('test');
|
||||
let spy; let data; let user; let
|
||||
group;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
|
||||
data = {
|
||||
user,
|
||||
sub: {
|
||||
key: 'basic_3mo', // @TODO: Validate that this is group
|
||||
},
|
||||
customerId: 'customer-id',
|
||||
paymentMethod: 'Payment Method',
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
};
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
leader: user._id,
|
||||
});
|
||||
await group.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.resolves([]);
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
stripePayments.setStripeApi(stripe);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.subscriptions.update.restore();
|
||||
});
|
||||
|
||||
it('updates a group plan quantity', async () => {
|
||||
data.paymentMethod = 'Stripe';
|
||||
await payments.createSubscription(data);
|
||||
|
||||
const updatedGroup = await Group.findById(group._id).exec();
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(3);
|
||||
|
||||
updatedGroup.memberCount += 1;
|
||||
await updatedGroup.save();
|
||||
|
||||
await stripePayments.chargeForAdditionalGroupMember(updatedGroup);
|
||||
|
||||
expect(spy.calledOnce).to.be.true;
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(4);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import nconf from 'nconf';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
@@ -10,76 +10,102 @@ import stripePayments from '../../../../../../website/server/libs/payments/strip
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
import logger from '../../../../../../website/server/libs/logger';
|
||||
import * as oneTimePayments from '../../../../../../website/server/libs/payments/stripe/oneTimePayments';
|
||||
import * as subscriptions from '../../../../../../website/server/libs/payments/stripe/subscriptions';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('Stripe - Webhooks', () => {
|
||||
const stripe = stripeModule('test');
|
||||
const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET');
|
||||
const headers = {};
|
||||
const body = {};
|
||||
|
||||
describe('all events', () => {
|
||||
const eventType = 'account.updated';
|
||||
const event = { id: 123 };
|
||||
const eventRetrieved = { type: eventType };
|
||||
let event;
|
||||
let constructEventStub;
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(stripe.events, 'retrieve').resolves(eventRetrieved);
|
||||
sinon.stub(logger, 'error');
|
||||
event = { type: 'payment_intent.created' };
|
||||
constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent');
|
||||
constructEventStub.returns(event);
|
||||
sandbox.stub(logger, 'error');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.events.retrieve.restore();
|
||||
logger.error.restore();
|
||||
it('throws if the event can\'t be validated', async () => {
|
||||
const err = new Error('fail');
|
||||
constructEventStub.throws(err);
|
||||
await expect(stripePayments.handleWebhooks({ body: event, headers }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: `Webhook Error: ${err.message}`,
|
||||
});
|
||||
|
||||
expect(logger.error).to.have.been.calledOnce;
|
||||
const calledWith = logger.error.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal('Error verifying Stripe webhook');
|
||||
expect(calledWith[1]).to.eql({ err });
|
||||
});
|
||||
|
||||
it('logs an error if an unsupported webhook event is passed', async () => {
|
||||
const error = new Error(`Missing handler for Stripe webhook ${eventType}`);
|
||||
await stripePayments.handleWebhooks({ requestBody: event }, stripe);
|
||||
expect(logger.error).to.have.been.calledOnce;
|
||||
event.type = 'account.updated';
|
||||
await expect(stripePayments.handleWebhooks({ body, headers }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: `Missing handler for Stripe webhook ${event.type}`,
|
||||
});
|
||||
|
||||
expect(logger.error).to.have.been.calledOnce;
|
||||
const calledWith = logger.error.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal(error.message);
|
||||
expect(calledWith[1].event).to.equal(eventRetrieved);
|
||||
expect(calledWith[0].message).to.equal('Error handling Stripe webhook');
|
||||
expect(calledWith[1].event).to.eql(event);
|
||||
expect(calledWith[1].err.message).to.eql(`Missing handler for Stripe webhook ${event.type}`);
|
||||
});
|
||||
|
||||
it('retrieves and validates the event from Stripe', async () => {
|
||||
await stripePayments.handleWebhooks({ requestBody: event }, stripe);
|
||||
expect(stripe.events.retrieve).to.have.been.calledOnce;
|
||||
expect(stripe.events.retrieve).to.have.been.calledWith(event.id);
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(stripe.webhooks.constructEvent)
|
||||
.to.have.been.calledWith(body, undefined, endpointSecret);
|
||||
});
|
||||
});
|
||||
|
||||
describe('customer.subscription.deleted', () => {
|
||||
const eventType = 'customer.subscription.deleted';
|
||||
let event;
|
||||
let constructEventStub;
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(stripe.customers, 'del').resolves({});
|
||||
sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
event = { type: eventType };
|
||||
constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent');
|
||||
constructEventStub.returns(event);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.customers.del.restore();
|
||||
payments.cancelSubscription.restore();
|
||||
beforeEach(() => {
|
||||
sandbox.stub(stripe.customers, 'del').resolves({});
|
||||
sandbox.stub(payments, 'cancelSubscription').resolves({});
|
||||
});
|
||||
|
||||
it('does not do anything if event.request is null (subscription cancelled manually)', async () => {
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
it('does not do anything if event.request is not null (subscription cancelled manually)', async () => {
|
||||
constructEventStub.returns({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
request: 123,
|
||||
request: { id: 123 },
|
||||
});
|
||||
|
||||
await stripePayments.handleWebhooks({ requestBody: {} }, stripe);
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.events.retrieve).to.have.been.calledOnce;
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(stripe.customers.del).to.not.have.been.called;
|
||||
expect(payments.cancelSubscription).to.not.have.been.called;
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
|
||||
describe('user subscription', () => {
|
||||
it('throws an error if the user is not found', async () => {
|
||||
const customerId = 456;
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
constructEventStub.returns({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -90,10 +116,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: null,
|
||||
request: { id: null },
|
||||
});
|
||||
|
||||
await expect(stripePayments.handleWebhooks({ requestBody: {} }, stripe))
|
||||
await expect(stripePayments.handleWebhooks({ body, headers }, stripe))
|
||||
.to.eventually.be.rejectedWith({
|
||||
message: i18n.t('userNotFound'),
|
||||
httpCode: 404,
|
||||
@@ -102,8 +128,6 @@ describe('Stripe - Webhooks', () => {
|
||||
|
||||
expect(stripe.customers.del).to.not.have.been.called;
|
||||
expect(payments.cancelSubscription).to.not.have.been.called;
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
|
||||
it('deletes the customer on Stripe and calls payments.cancelSubscription', async () => {
|
||||
@@ -114,7 +138,7 @@ describe('Stripe - Webhooks', () => {
|
||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||
await subscriber.save();
|
||||
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
constructEventStub.returns({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -125,10 +149,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: null,
|
||||
request: { id: null },
|
||||
});
|
||||
|
||||
await stripePayments.handleWebhooks({ requestBody: {} }, stripe);
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.customers.del).to.have.been.calledOnce;
|
||||
expect(stripe.customers.del).to.have.been.calledWith(customerId);
|
||||
@@ -139,15 +163,13 @@ describe('Stripe - Webhooks', () => {
|
||||
expect(cancelSubscriptionOpts.paymentMethod).to.equal('Stripe');
|
||||
expect(Math.round(moment(cancelSubscriptionOpts.nextBill).diff(new Date(), 'days', true))).to.equal(3);
|
||||
expect(cancelSubscriptionOpts.groupId).to.be.undefined;
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('group plan subscription', () => {
|
||||
it('throws an error if the group is not found', async () => {
|
||||
const customerId = 456;
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
constructEventStub.returns({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -158,10 +180,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: null,
|
||||
request: { id: null },
|
||||
});
|
||||
|
||||
await expect(stripePayments.handleWebhooks({ requestBody: {} }, stripe))
|
||||
await expect(stripePayments.handleWebhooks({ body, headers }, stripe))
|
||||
.to.eventually.be.rejectedWith({
|
||||
message: i18n.t('groupNotFound'),
|
||||
httpCode: 404,
|
||||
@@ -170,8 +192,6 @@ describe('Stripe - Webhooks', () => {
|
||||
|
||||
expect(stripe.customers.del).to.not.have.been.called;
|
||||
expect(payments.cancelSubscription).to.not.have.been.called;
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
|
||||
it('throws an error if the group leader is not found', async () => {
|
||||
@@ -187,7 +207,7 @@ describe('Stripe - Webhooks', () => {
|
||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||
await subscriber.save();
|
||||
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
constructEventStub.returns({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -198,10 +218,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: null,
|
||||
request: { id: null },
|
||||
});
|
||||
|
||||
await expect(stripePayments.handleWebhooks({ requestBody: {} }, stripe))
|
||||
await expect(stripePayments.handleWebhooks({ body, headers }, stripe))
|
||||
.to.eventually.be.rejectedWith({
|
||||
message: i18n.t('userNotFound'),
|
||||
httpCode: 404,
|
||||
@@ -210,8 +230,6 @@ describe('Stripe - Webhooks', () => {
|
||||
|
||||
expect(stripe.customers.del).to.not.have.been.called;
|
||||
expect(payments.cancelSubscription).to.not.have.been.called;
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
|
||||
it('deletes the customer on Stripe and calls payments.cancelSubscription', async () => {
|
||||
@@ -230,7 +248,7 @@ describe('Stripe - Webhooks', () => {
|
||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||
await subscriber.save();
|
||||
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
constructEventStub.returns({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -241,10 +259,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: null,
|
||||
request: { id: null },
|
||||
});
|
||||
|
||||
await stripePayments.handleWebhooks({ requestBody: {} }, stripe);
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.customers.del).to.have.been.calledOnce;
|
||||
expect(stripe.customers.del).to.have.been.calledWith(customerId);
|
||||
@@ -255,9 +273,65 @@ describe('Stripe - Webhooks', () => {
|
||||
expect(cancelSubscriptionOpts.paymentMethod).to.equal('Stripe');
|
||||
expect(Math.round(moment(cancelSubscriptionOpts.nextBill).diff(new Date(), 'days', true))).to.equal(3);
|
||||
expect(cancelSubscriptionOpts.groupId).to.equal(subscriber._id);
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkout.session.completed', () => {
|
||||
const eventType = 'checkout.session.completed';
|
||||
let event;
|
||||
let constructEventStub;
|
||||
const session = {};
|
||||
|
||||
beforeEach(() => {
|
||||
session.metadata = {};
|
||||
event = { type: eventType, data: { object: session } };
|
||||
constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent');
|
||||
constructEventStub.returns(event);
|
||||
|
||||
sandbox.stub(oneTimePayments, 'applyGemPayment').resolves({});
|
||||
sandbox.stub(subscriptions, 'applySubscription').resolves({});
|
||||
sandbox.stub(subscriptions, 'handlePaymentMethodChange').resolves({});
|
||||
});
|
||||
|
||||
it('handles changing an user sub', async () => {
|
||||
session.metadata.type = 'edit-card-user';
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(subscriptions.handlePaymentMethodChange).to.have.been.calledOnce;
|
||||
expect(subscriptions.handlePaymentMethodChange).to.have.been.calledWith(session);
|
||||
});
|
||||
|
||||
it('handles changing a group sub', async () => {
|
||||
session.metadata.type = 'edit-card-group';
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(subscriptions.handlePaymentMethodChange).to.have.been.calledOnce;
|
||||
expect(subscriptions.handlePaymentMethodChange).to.have.been.calledWith(session);
|
||||
});
|
||||
|
||||
it('applies a subscription', async () => {
|
||||
session.metadata.type = 'subscription';
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(subscriptions.applySubscription).to.have.been.calledOnce;
|
||||
expect(subscriptions.applySubscription).to.have.been.calledWith(session);
|
||||
});
|
||||
|
||||
it('handles a one time payment', async () => {
|
||||
session.metadata.type = 'something else';
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(oneTimePayments.applyGemPayment).to.have.been.calledOnce;
|
||||
expect(oneTimePayments.applyGemPayment).to.have.been.calledWith(session);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
describe('payments - stripe - #createCheckoutSession', () => {
|
||||
const endpoint = '/stripe/checkout-session';
|
||||
let user; const groupId = 'groupId';
|
||||
const gift = {}; const subKey = 'basic_3mo';
|
||||
const gemsBlock = '21gems'; const coupon = 'coupon';
|
||||
let stripeCreateCheckoutSessionStub; const sessionId = 'sessionId';
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
stripeCreateCheckoutSessionStub = sinon
|
||||
.stub(stripePayments, 'createCheckoutSession')
|
||||
.resolves({ id: sessionId });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripePayments.createCheckoutSession.restore();
|
||||
});
|
||||
|
||||
it('works', async () => {
|
||||
const res = await user.post(endpoint, {
|
||||
groupId,
|
||||
gift,
|
||||
sub: subKey,
|
||||
gemsBlock,
|
||||
coupon,
|
||||
});
|
||||
|
||||
expect(res.sessionId).to.equal(sessionId);
|
||||
|
||||
expect(stripeCreateCheckoutSessionStub).to.be.calledOnce;
|
||||
expect(stripeCreateCheckoutSessionStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(stripeCreateCheckoutSessionStub.args[0][0].groupId).to.eql(groupId);
|
||||
expect(stripeCreateCheckoutSessionStub.args[0][0].gift).to.eql(gift);
|
||||
expect(stripeCreateCheckoutSessionStub.args[0][0].sub)
|
||||
.to.eql(common.content.subscriptionBlocks[subKey]);
|
||||
expect(stripeCreateCheckoutSessionStub.args[0][0].gemsBlock).to.eql(gemsBlock);
|
||||
expect(stripeCreateCheckoutSessionStub.args[0][0].coupon).to.eql(coupon);
|
||||
});
|
||||
});
|
||||
@@ -1,79 +0,0 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
describe('payments - stripe - #checkout', () => {
|
||||
const endpoint = '/stripe/checkout';
|
||||
let user; let
|
||||
group;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies credentials', async () => {
|
||||
await expect(user.post(
|
||||
`${endpoint}?gemsBlock=4gems`,
|
||||
{ id: 123 },
|
||||
)).to.eventually.be.rejected.and.include({
|
||||
code: 401,
|
||||
error: 'Error',
|
||||
// message: 'Invalid API Key provided: aaaabbbb********************1111',
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let stripeCheckoutSubscriptionStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
stripeCheckoutSubscriptionStub = sinon.stub(stripePayments, 'checkout').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripePayments.checkout.restore();
|
||||
});
|
||||
|
||||
it('creates a user subscription', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint);
|
||||
|
||||
expect(stripeCheckoutSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeCheckoutSubscriptionStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(stripeCheckoutSubscriptionStub.args[0][0].groupId).to.eql(undefined);
|
||||
});
|
||||
|
||||
it('creates a group subscription', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
});
|
||||
|
||||
await user.post(`${endpoint}?groupId=${group._id}`);
|
||||
|
||||
expect(stripeCheckoutSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeCheckoutSubscriptionStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(stripeCheckoutSubscriptionStub.args[0][0].groupId).to.eql(group._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,79 +1,31 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
describe('payments - stripe - #subscribeEdit', () => {
|
||||
const endpoint = '/stripe/subscribe/edit';
|
||||
let user; let
|
||||
group;
|
||||
let user; const groupId = 'groupId';
|
||||
let stripeEditSubscriptionStub;
|
||||
const sessionId = 'sessionId';
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
stripeEditSubscriptionStub = sinon
|
||||
.stub(stripePayments, 'createEditCardCheckoutSession')
|
||||
.resolves({ id: sessionId });
|
||||
});
|
||||
|
||||
it('verifies credentials', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('missingSubscription'),
|
||||
});
|
||||
afterEach(() => {
|
||||
stripePayments.createEditCardCheckoutSession.restore();
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let stripeEditSubscriptionStub;
|
||||
it('works', async () => {
|
||||
const res = await user.post(endpoint, { groupId });
|
||||
expect(res.sessionId).to.equal(sessionId);
|
||||
|
||||
beforeEach(async () => {
|
||||
stripeEditSubscriptionStub = sinon.stub(stripePayments, 'editSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripePayments.editSubscription.restore();
|
||||
});
|
||||
|
||||
it('cancels a user subscription', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint);
|
||||
|
||||
expect(stripeEditSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeEditSubscriptionStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(stripeEditSubscriptionStub.args[0][0].groupId).to.eql(undefined);
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
groupId: group._id,
|
||||
});
|
||||
|
||||
expect(stripeEditSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeEditSubscriptionStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(stripeEditSubscriptionStub.args[0][0].groupId).to.eql(group._id);
|
||||
});
|
||||
expect(stripeEditSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeEditSubscriptionStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(stripeEditSubscriptionStub.args[0][0].groupId).to.eql(groupId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
describe('payments - stripe - #handleWebhooks', () => {
|
||||
const endpoint = '/stripe/webhooks';
|
||||
let user; const body = '{"key": "val"}';
|
||||
let stripeHandleWebhooksStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
stripeHandleWebhooksStub = sinon
|
||||
.stub(stripePayments, 'handleWebhooks')
|
||||
.resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripePayments.handleWebhooks.restore();
|
||||
});
|
||||
|
||||
it('works', async () => {
|
||||
const res = await user.post(endpoint, body);
|
||||
expect(res).to.eql({});
|
||||
|
||||
expect(stripeHandleWebhooksStub).to.be.calledOnce;
|
||||
expect(stripeHandleWebhooksStub.args[0][0].body).to.exist;
|
||||
expect(stripeHandleWebhooksStub.args[0][0].headers).to.exist;
|
||||
});
|
||||
});
|
||||
@@ -8,9 +8,14 @@ import {
|
||||
|
||||
describe('POST /tasks/user', () => {
|
||||
let user;
|
||||
let tzoffset;
|
||||
|
||||
before(async () => {
|
||||
tzoffset = new Date().getTimezoneOffset();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({ 'preferences.timezoneOffset': tzoffset });
|
||||
});
|
||||
|
||||
context('validates params', async () => {
|
||||
@@ -220,6 +225,18 @@ describe('POST /tasks/user', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if todo due date supplied is an invalid date', async () => {
|
||||
await expect(user.post('/tasks/user', {
|
||||
type: 'todo',
|
||||
text: 'todo text',
|
||||
date: 'invalid date',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'todo validation failed',
|
||||
});
|
||||
});
|
||||
|
||||
context('sending task activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
@@ -532,7 +549,7 @@ describe('POST /tasks/user', () => {
|
||||
expect(task.everyX).to.eql(5);
|
||||
expect(task.daysOfMonth).to.eql([15]);
|
||||
expect(task.weeksOfMonth).to.eql([3]);
|
||||
expect(new Date(task.startDate)).to.eql(now);
|
||||
expect(new Date(task.startDate)).to.eql(new Date(now.setHours(0, 0, 0, 0)));
|
||||
expect(task.isDue).to.be.true;
|
||||
expect(task.nextDue.length).to.eql(6);
|
||||
});
|
||||
|
||||
@@ -10,9 +10,14 @@ import {
|
||||
|
||||
describe('PUT /tasks/:id', () => {
|
||||
let user;
|
||||
let tzoffset;
|
||||
|
||||
before(async () => {
|
||||
tzoffset = (new Date()).getTimezoneOffset();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({ 'preferences.timezoneOffset': tzoffset });
|
||||
});
|
||||
|
||||
context('validates params', () => {
|
||||
@@ -503,7 +508,8 @@ describe('PUT /tasks/:id', () => {
|
||||
let monthly;
|
||||
|
||||
beforeEach(async () => {
|
||||
const date1 = moment.utc('2020-07-01').toDate();
|
||||
// using date literals is discouraged here, daylight savings will break everything
|
||||
const date1 = moment().toDate();
|
||||
monthly = await user.post('/tasks/user', {
|
||||
text: 'test monthly',
|
||||
type: 'daily',
|
||||
@@ -514,7 +520,7 @@ describe('PUT /tasks/:id', () => {
|
||||
});
|
||||
|
||||
it('updates days of month when start date updated', async () => {
|
||||
const date2 = moment.utc('2020-07-01').toDate();
|
||||
const date2 = moment().add(6, 'months').toDate();
|
||||
const savedMonthly = await user.put(`/tasks/${monthly._id}`, {
|
||||
startDate: date2,
|
||||
});
|
||||
@@ -523,18 +529,30 @@ describe('PUT /tasks/:id', () => {
|
||||
});
|
||||
|
||||
it('updates next due when start date updated', async () => {
|
||||
const date2 = moment.utc('2022-07-01').toDate();
|
||||
const date2 = moment().add(6, 'months').toDate();
|
||||
const savedMonthly = await user.put(`/tasks/${monthly._id}`, {
|
||||
startDate: date2,
|
||||
});
|
||||
|
||||
expect(savedMonthly.nextDue.length).to.eql(6);
|
||||
expect(moment(savedMonthly.nextDue[0]).toDate()).to.eql(moment.utc('2022-08-01').toDate());
|
||||
expect(moment(savedMonthly.nextDue[1]).toDate()).to.eql(moment.utc('2022-09-01').toDate());
|
||||
expect(moment(savedMonthly.nextDue[2]).toDate()).to.eql(moment.utc('2022-10-01').toDate());
|
||||
expect(moment(savedMonthly.nextDue[3]).toDate()).to.eql(moment.utc('2022-11-01').toDate());
|
||||
expect(moment(savedMonthly.nextDue[4]).toDate()).to.eql(moment.utc('2022-12-01').toDate());
|
||||
expect(moment(savedMonthly.nextDue[5]).toDate()).to.eql(moment.utc('2023-01-01').toDate());
|
||||
expect(moment(savedMonthly.nextDue[0]).toDate()).to.eql(
|
||||
moment(date2).add(1, 'months').startOf('day').toDate(),
|
||||
);
|
||||
expect(moment(savedMonthly.nextDue[1]).toDate()).to.eql(
|
||||
moment(date2).add(2, 'months').startOf('day').toDate(),
|
||||
);
|
||||
expect(moment(savedMonthly.nextDue[2]).toDate()).to.eql(
|
||||
moment(date2).add(3, 'months').startOf('day').toDate(),
|
||||
);
|
||||
expect(moment(savedMonthly.nextDue[3]).toDate()).to.eql(
|
||||
moment(date2).add(4, 'months').startOf('day').toDate(),
|
||||
);
|
||||
expect(moment(savedMonthly.nextDue[4]).toDate()).to.eql(
|
||||
moment(date2).add(5, 'months').startOf('day').toDate(),
|
||||
);
|
||||
expect(moment(savedMonthly.nextDue[5]).toDate()).to.eql(
|
||||
moment(date2).add(6, 'months').startOf('day').toDate(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,13 +11,18 @@ describe('POST /tasks/challenge/:challengeId', () => {
|
||||
let user;
|
||||
let guild;
|
||||
let challenge;
|
||||
let tzoffset;
|
||||
|
||||
function findUserChallengeTask (memberTask) {
|
||||
return memberTask.challenge.id === challenge._id;
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
tzoffset = new Date().getTimezoneOffset();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({ balance: 1 });
|
||||
user = await generateUser({ balance: 1, 'preferences.timezoneOffset': tzoffset });
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
@@ -165,7 +170,7 @@ describe('POST /tasks/challenge/:challengeId', () => {
|
||||
expect(task.type).to.eql('daily');
|
||||
expect(task.frequency).to.eql('daily');
|
||||
expect(task.everyX).to.eql(5);
|
||||
expect(new Date(task.startDate)).to.eql(now);
|
||||
expect(new Date(task.startDate)).to.eql(new Date(now.setHours(0, 0, 0, 0)));
|
||||
|
||||
expect(userChallengeTask.notes).to.eql(task.notes);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('PUT /tasks/:id', () => {
|
||||
let challenge;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({ 'preferences.timezoneOffset': new Date().getTimezoneOffset() });
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
@@ -8,12 +8,18 @@ import {
|
||||
describe('POST /tasks/group/:groupid', () => {
|
||||
let user; let guild; let
|
||||
manager;
|
||||
let tzoffset;
|
||||
const groupName = 'Test Public Guild';
|
||||
const groupType = 'guild';
|
||||
|
||||
before(async () => {
|
||||
tzoffset = new Date().getTimezoneOffset();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({ balance: 1 });
|
||||
// user = await generateUser({ balance: 1, 'preferences.timezoneOffset': tzoffset });
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
leaderDetails: { balance: 10, 'preferences.timezoneOffset': tzoffset },
|
||||
groupDetails: {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
@@ -128,7 +134,7 @@ describe('POST /tasks/group/:groupid', () => {
|
||||
expect(task.type).to.eql('daily');
|
||||
expect(task.frequency).to.eql('daily');
|
||||
expect(task.everyX).to.eql(5);
|
||||
expect(new Date(task.startDate)).to.eql(now);
|
||||
expect(new Date(task.startDate)).to.eql(new Date(now.setHours(0, 0, 0, 0)));
|
||||
});
|
||||
|
||||
it('allows a manager to add a group task', async () => {
|
||||
|
||||
@@ -18,20 +18,20 @@
|
||||
"@storybook/addon-links": "^5.3.19",
|
||||
"@storybook/addon-notes": "^5.3.21",
|
||||
"@storybook/vue": "^5.3.19",
|
||||
"@vue/cli-plugin-babel": "^4.5.9",
|
||||
"@vue/cli-plugin-eslint": "^4.5.9",
|
||||
"@vue/cli-plugin-router": "^4.5.9",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.5.9",
|
||||
"@vue/cli-service": "^4.5.9",
|
||||
"@vue/cli-plugin-babel": "^4.5.10",
|
||||
"@vue/cli-plugin-eslint": "^4.5.10",
|
||||
"@vue/cli-plugin-router": "^4.5.10",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.5.10",
|
||||
"@vue/cli-service": "^4.5.10",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^7.3.3",
|
||||
"axios": "^0.21.0",
|
||||
"amplitude-js": "^7.4.0",
|
||||
"axios": "^0.21.1",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^4.5.3",
|
||||
"bootstrap-vue": "^2.19.0",
|
||||
"bootstrap-vue": "^2.21.2",
|
||||
"chai": "^4.1.2",
|
||||
"core-js": "^3.8.0",
|
||||
"core-js": "^3.8.2",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
@@ -43,16 +43,16 @@
|
||||
"jquery": "^3.5.1",
|
||||
"lodash": "^4.17.20",
|
||||
"moment": "^2.29.1",
|
||||
"nconf": "^0.11.0",
|
||||
"sass": "^1.29.0",
|
||||
"nconf": "^0.11.1",
|
||||
"sass": "^1.32.2",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.16.0",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
"svg-url-loader": "^6.0.0",
|
||||
"svgo": "^1.3.2",
|
||||
"svgo-loader": "^2.2.1",
|
||||
"uuid": "^8.3.1",
|
||||
"validator": "^13.1.17",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.5.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-cli-plugin-storybook": "^0.6.1",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
@@ -60,6 +60,6 @@
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0",
|
||||
"webpack": "^4.44.2"
|
||||
"webpack": "^4.45.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |