mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-10-29 20:24:53 +01:00
Compare commits
303 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63cf5b6be7 | ||
|
|
f3a947339c | ||
|
|
1bb8acad5d | ||
|
|
8e04d6e284 | ||
|
|
f7415df6ba | ||
|
|
f85e1c2dc4 | ||
|
|
33628a0a6a | ||
|
|
5e6541faa6 | ||
|
|
c1ed02d383 | ||
|
|
3793e92b80 | ||
|
|
e3ce1c5322 | ||
|
|
84b16f28c2 | ||
|
|
27c73e028a | ||
|
|
d125b8d2f8 | ||
|
|
451e08ce1c | ||
|
|
16b5b8b8c7 | ||
|
|
30a717148e | ||
|
|
bcf9670dbe | ||
|
|
a864e69042 | ||
|
|
2ccd9eaa1e | ||
|
|
5faf00d489 | ||
|
|
f211610f5d | ||
|
|
332f285ea2 | ||
|
|
006159cc9c | ||
|
|
3722452b51 | ||
|
|
d6b5d275da | ||
|
|
72073386ec | ||
|
|
d34ec62901 | ||
|
|
ca73b9af41 | ||
|
|
8b9bf88fa0 | ||
|
|
5f0ef2d8f0 | ||
|
|
cf274310a8 | ||
|
|
a2ee73a2e2 | ||
|
|
c6c9503e22 | ||
|
|
403ac1ab7e | ||
|
|
63598f497b | ||
|
|
9fcc953b18 | ||
|
|
17408d01a9 | ||
|
|
ae786f28a2 | ||
|
|
1effa16b5b | ||
|
|
6b7333927a | ||
|
|
31b439129d | ||
|
|
2de85b937f | ||
|
|
4f963e99dc | ||
|
|
58ce3a9a42 | ||
|
|
e45d0c9b80 | ||
|
|
84a20ef4f4 | ||
|
|
59a22805b9 | ||
|
|
95865f5ec8 | ||
|
|
79903d242f | ||
|
|
90959c18cd | ||
|
|
8b2019c292 | ||
|
|
9ab70ca276 | ||
|
|
d51aa25470 | ||
|
|
0b2c1e6d2e | ||
|
|
58ee6e9703 | ||
|
|
0044778497 | ||
|
|
46d6590fec | ||
|
|
b10f056a73 | ||
|
|
eeb890466a | ||
|
|
8d25a5d140 | ||
|
|
3b35a0a203 | ||
|
|
d787ad43d3 | ||
|
|
7d7fe6047c | ||
|
|
0ec1a91774 | ||
|
|
adf3281bef | ||
|
|
ea86b35833 | ||
|
|
ade14edcd7 | ||
|
|
3a1888739a | ||
|
|
3b54ce4949 | ||
|
|
4a8aaf7389 | ||
|
|
45eec47b7f | ||
|
|
4b9af8aa86 | ||
|
|
631bbcb786 | ||
|
|
76a10d6cf9 | ||
|
|
a1c9ebd661 | ||
|
|
9f06d78db6 | ||
|
|
ac98aa9271 | ||
|
|
455f7ac59b | ||
|
|
a42cb0e3ab | ||
|
|
d05d2fb9d7 | ||
|
|
6c4c5b4697 | ||
|
|
5da87640e4 | ||
|
|
fa044ffb44 | ||
|
|
5449652bd2 | ||
|
|
c12ae9ea25 | ||
|
|
734a300b92 | ||
|
|
1109ae308d | ||
|
|
8f1d241e83 | ||
|
|
acbca4d1dc | ||
|
|
1ea9be8aa2 | ||
|
|
ace02893e5 | ||
|
|
1c3e043fac | ||
|
|
71c9e7a685 | ||
|
|
fa945c7689 | ||
|
|
c54ce96033 | ||
|
|
85c4e93763 | ||
|
|
25e5e78373 | ||
|
|
06181d0a1a | ||
|
|
d5a8259fdb | ||
|
|
9db7141853 | ||
|
|
ec2a1927a0 | ||
|
|
1c1b0f00ad | ||
|
|
fb4d3e44d3 | ||
|
|
37fd062cf9 | ||
|
|
485c3c5c46 | ||
|
|
5007393f24 | ||
|
|
e111ac730c | ||
|
|
e7c78eabce | ||
|
|
5da7699548 | ||
|
|
f42955a0ba | ||
|
|
4d67df4da6 | ||
|
|
ab7459f4f3 | ||
|
|
469db7c0e2 | ||
|
|
952e813b30 | ||
|
|
f04d05fee1 | ||
|
|
6d9aa43c07 | ||
|
|
f527221079 | ||
|
|
d9b852e1ea | ||
|
|
a1207c1d8d | ||
|
|
f4fb90013d | ||
|
|
73a7c0eebc | ||
|
|
1819398f41 | ||
|
|
ab14312368 | ||
|
|
690d3e3fd2 | ||
|
|
36f9a4918f | ||
|
|
a4b5e27614 | ||
|
|
0abfe86296 | ||
|
|
e11c777325 | ||
|
|
63a04f36c9 | ||
|
|
e58af6e3ea | ||
|
|
6ba28b5757 | ||
|
|
ed607d2bae | ||
|
|
1f7fc594e5 | ||
|
|
45d0a4fac2 | ||
|
|
e50bc189aa | ||
|
|
95f8867bf8 | ||
|
|
4968b291f7 | ||
|
|
f7b9ca124d | ||
|
|
4623bcd877 | ||
|
|
4a368a1128 | ||
|
|
bec8cb01e0 | ||
|
|
f3c041a561 | ||
|
|
c21726ec61 | ||
|
|
df69208caa | ||
|
|
08d07cdd67 | ||
|
|
a309e48183 | ||
|
|
70c539cc81 | ||
|
|
11f136ac89 | ||
|
|
567d5f74ba | ||
|
|
338781f57b | ||
|
|
bd07f3cd38 | ||
|
|
0b735abd44 | ||
|
|
a88cdaf1fc | ||
|
|
7cae5f1a37 | ||
|
|
e453330535 | ||
|
|
b1e5fcdeaf | ||
|
|
10e0848a5c | ||
|
|
64e86bad91 | ||
|
|
21cf5d2321 | ||
|
|
a6106a801b | ||
|
|
769405ff34 | ||
|
|
a0803796b2 | ||
|
|
cb418882f3 | ||
|
|
b17a09ac17 | ||
|
|
88bb4f6a72 | ||
|
|
ae0c440846 | ||
|
|
2f69f4039e | ||
|
|
10370ea1dc | ||
|
|
0d65e5219e | ||
|
|
fed2d3fb19 | ||
|
|
6ec50ed0c1 | ||
|
|
6f1a551d76 | ||
|
|
bed97f0610 | ||
|
|
f86f98f4a6 | ||
|
|
0e442a0076 | ||
|
|
89f047b15b | ||
|
|
e64bc2e39a | ||
|
|
3b3fcbdfce | ||
|
|
558dd2e4bf | ||
|
|
7914a959b3 | ||
|
|
8a27524fa0 | ||
|
|
a64fed97ac | ||
|
|
e937d1722e | ||
|
|
f537e8142f | ||
|
|
e4b13eecd1 | ||
|
|
b5872a9577 | ||
|
|
48fa78bef2 | ||
|
|
781256c917 | ||
|
|
dcd680c293 | ||
|
|
ec6f53bb1b | ||
|
|
9834afee4a | ||
|
|
9b279563ea | ||
|
|
347fe69667 | ||
|
|
552cf70abd | ||
|
|
01c8ef9382 | ||
|
|
298a6a743c | ||
|
|
fa17ab7c17 | ||
|
|
0f339d8d3e | ||
|
|
99852fcd89 | ||
|
|
ca3d044aa1 | ||
|
|
b338e65dc9 | ||
|
|
1475c93962 | ||
|
|
ddec458364 | ||
|
|
bfe74a8dcb | ||
|
|
13d9da404d | ||
|
|
85e0af0c0e | ||
|
|
a2e5548b1c | ||
|
|
36dabad2c9 | ||
|
|
25b9e6f330 | ||
|
|
d0a786554c | ||
|
|
de79e0e3c3 | ||
|
|
0e74d25ede | ||
|
|
bde8b76da7 | ||
|
|
4235be4b43 | ||
|
|
3af7f89d10 | ||
|
|
396362d27e | ||
|
|
972efb1878 | ||
|
|
6c18d19d95 | ||
|
|
9b8bdb90d8 | ||
|
|
a84ea8b1b7 | ||
|
|
480a839bc5 | ||
|
|
df9c54fe20 | ||
|
|
8dbab1a976 | ||
|
|
df6088bc6d | ||
|
|
f0ff3a4eb6 | ||
|
|
786e4419da | ||
|
|
fff4ea3ad3 | ||
|
|
e18e89bc10 | ||
|
|
68ea28305d | ||
|
|
242b3508a1 | ||
|
|
8c316d939f | ||
|
|
45eb19e992 | ||
|
|
3ad0ffcaec | ||
|
|
fef8929dd9 | ||
|
|
911f894750 | ||
|
|
d4e2f5ac9e | ||
|
|
e43749bed1 | ||
|
|
8be29e27d0 | ||
|
|
301668fe22 | ||
|
|
767f3ebe12 | ||
|
|
25e6f8e26e | ||
|
|
1c6608d071 | ||
|
|
d2b160438c | ||
|
|
37d70f089c | ||
|
|
04b4912d59 | ||
|
|
b9a6d9ceec | ||
|
|
2a97915477 | ||
|
|
29a9deaeb8 | ||
|
|
0fcc1f2080 | ||
|
|
9287098e70 | ||
|
|
1812f63812 | ||
|
|
3d4107db3e | ||
|
|
67e750a81c | ||
|
|
7fe62a731b | ||
|
|
3c224fe353 | ||
|
|
bb6f465ac8 | ||
|
|
a2a79e4607 | ||
|
|
5125cc5f59 | ||
|
|
cb42a31c43 | ||
|
|
b1b1e512f5 | ||
|
|
5ba09c45df | ||
|
|
70dec611e3 | ||
|
|
2195464772 | ||
|
|
b662f8bdff | ||
|
|
0e6d6336c6 | ||
|
|
d8078adacd | ||
|
|
a88800df78 | ||
|
|
42e0095bbd | ||
|
|
ee1aa653f0 | ||
|
|
8d1b1ff794 | ||
|
|
70500d7c98 | ||
|
|
35849ebdd7 | ||
|
|
1332fd68b0 | ||
|
|
f9a47b1420 | ||
|
|
090c571f3e | ||
|
|
07b3824c4c | ||
|
|
1bf3736198 | ||
|
|
92fa6805eb | ||
|
|
d5efa7b2b0 | ||
|
|
f64b34318f | ||
|
|
5e13a9c503 | ||
|
|
b27aec855c | ||
|
|
fe61c0f29e | ||
|
|
2db3ac7bd3 | ||
|
|
201ec0e865 | ||
|
|
a0253cf289 | ||
|
|
5256569bac | ||
|
|
5f0b957dc2 | ||
|
|
16524d4464 | ||
|
|
63a11c6c28 | ||
|
|
37650ca674 | ||
|
|
b5dfa54052 | ||
|
|
2e7deffb69 | ||
|
|
b4dab2e13c | ||
|
|
535c41dba8 | ||
|
|
7357fb0528 | ||
|
|
49e67fa87c | ||
|
|
b827b17481 | ||
|
|
27ef187e66 | ||
|
|
9c9b67aa9d | ||
|
|
7cff331800 | ||
|
|
e7bc505b88 |
8
.babelrc
8
.babelrc
@@ -1,10 +1,6 @@
|
||||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread",
|
||||
["transform-async-to-module-method", {
|
||||
"module": "bluebird",
|
||||
"method": "coroutine"
|
||||
}]
|
||||
"transform-es2015-modules-commonjs",
|
||||
"syntax-object-rest-spread",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ node_modules/**
|
||||
.bower-registry/**
|
||||
website/client-old/**
|
||||
website/client/**
|
||||
website/client/store/**
|
||||
website/views/**
|
||||
website/build/**
|
||||
dist/**
|
||||
@@ -16,3 +17,4 @@ CHANGELOG.md
|
||||
newrelic_agent.log
|
||||
*.swp
|
||||
*.swx
|
||||
website/raw_sprites/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
- '8'
|
||||
services:
|
||||
- mongodb
|
||||
cache:
|
||||
@@ -8,8 +8,6 @@ cache:
|
||||
- 'node_modules'
|
||||
addons:
|
||||
chrome: stable
|
||||
before_install:
|
||||
- npm install -g npm@5
|
||||
before_script:
|
||||
- npm run test:build
|
||||
- cp config.json.example config.json
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
FROM node:boron
|
||||
FROM node:8
|
||||
|
||||
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
|
||||
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
|
||||
RUN yarn global add npm@5
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:boron
|
||||
FROM node:8
|
||||
|
||||
ENV ADMIN_EMAIL admin@habitica.com
|
||||
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
|
||||
@@ -11,16 +11,13 @@ ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleu
|
||||
ENV NODE_ENV production
|
||||
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
||||
|
||||
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
|
||||
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
|
||||
RUN yarn global add npm@5
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone --branch v4.29.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN git clone --branch v4.41.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN gulp build:prod --force
|
||||
|
||||
|
||||
@@ -10,4 +10,3 @@ We need more programmers! Your assistance will be greatly appreciated.
|
||||
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths).
|
||||
|
||||
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
|
||||
|
||||
|
||||
@@ -98,9 +98,9 @@
|
||||
},
|
||||
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"EMAILS" : {
|
||||
"COMMUNITY_MANAGER_EMAIL" : "leslie@habitica.com",
|
||||
"COMMUNITY_MANAGER_EMAIL" : "admin@habitica.com",
|
||||
"TECH_ASSISTANCE_EMAIL" : "admin@habitica.com",
|
||||
"PRESS_ENQUIRY_EMAIL" : "leslie@habitica.com"
|
||||
"PRESS_ENQUIRY_EMAIL" : "admin@habitica.com"
|
||||
},
|
||||
"LOGGLY" : {
|
||||
"TOKEN" : "example-token",
|
||||
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
- mongo
|
||||
|
||||
mongo:
|
||||
image: mongo
|
||||
image: mongo:3.4
|
||||
ports:
|
||||
- "27017:27017"
|
||||
networks:
|
||||
|
||||
@@ -26,7 +26,6 @@ let improveRepl = (context) => {
|
||||
const mongooseOptions = !isProd ? {} : {
|
||||
keepAlive: 1,
|
||||
connectTimeoutMS: 30000,
|
||||
useMongoClient: true,
|
||||
};
|
||||
mongoose.connect(
|
||||
nconf.get('NODE_DB_URI'),
|
||||
|
||||
@@ -2,7 +2,6 @@ import { exec } from 'child_process';
|
||||
import psTree from 'ps-tree';
|
||||
import nconf from 'nconf';
|
||||
import net from 'net';
|
||||
import Bluebird from 'bluebird';
|
||||
import { post } from 'superagent';
|
||||
import { sync as glob } from 'glob';
|
||||
import Mocha from 'mocha';
|
||||
@@ -45,7 +44,7 @@ export function kill (proc) {
|
||||
* before failing.
|
||||
*/
|
||||
export function awaitPort (port, max = 60) {
|
||||
return new Bluebird((rej, res) => {
|
||||
return new Promise((rej, res) => {
|
||||
let socket;
|
||||
let timeout;
|
||||
let interval;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
const authorName = 'Blade';
|
||||
const authorUuid = '75f270e8-c5db-4722-a5e6-a83f1b23f76b';
|
||||
|
||||
global.Promise = require('bluebird');
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
const TaskQueue = require('cwait').TaskQueue;
|
||||
const logger = require('./utils/logger');
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
* pm'ed each user asking if they would like their tasks reset to the previous day
|
||||
***************************************/
|
||||
|
||||
global.Promise = require('bluebird');
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
const TaskQueue = require('cwait').TaskQueue;
|
||||
const logger = require('./utils/logger');
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* from an object to a number, hence this migration.
|
||||
***************************************/
|
||||
|
||||
global.Promise = require('bluebird');
|
||||
const TaskQueue = require('cwait').TaskQueue;
|
||||
const logger = require('./utils/logger');
|
||||
const Timer = require('./utils/timer');
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
* and transfers a group's progress to it
|
||||
***************************************/
|
||||
|
||||
global.Promise = require('bluebird');
|
||||
const TaskQueue = require('cwait').TaskQueue;
|
||||
const logger = require('./utils/logger');
|
||||
const Timer = require('./utils/timer');
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* <userid>@example.com
|
||||
***************************************/
|
||||
|
||||
global.Promise = require('bluebird');
|
||||
const TaskQueue = require('cwait').TaskQueue;
|
||||
const logger = require('./utils/logger');
|
||||
const Timer = require('./utils/timer');
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
* they support a type and options and label
|
||||
* ***************************************/
|
||||
|
||||
global.Promise = require('bluebird');
|
||||
const TaskQueue = require('cwait').TaskQueue;
|
||||
const logger = require('./utils/logger');
|
||||
const Timer = require('./utils/timer');
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* message into the chat for affected parties.
|
||||
***************************************/
|
||||
|
||||
global.Promise = require('bluebird');
|
||||
const uuid = require('uuid');
|
||||
const TaskQueue = require('cwait').TaskQueue;
|
||||
const logger = require('./utils/logger');
|
||||
|
||||
88
migrations/archive/2017/20171211_sanitize_emails.js
Normal file
88
migrations/archive/2017/20171211_sanitize_emails.js
Normal file
@@ -0,0 +1,88 @@
|
||||
var migrationName = '20171211_sanitize_emails.js';
|
||||
var authorName = 'Julius'; // in case script author needs to know when their ...
|
||||
var authorUuid = 'dd16c270-1d6d-44bd-b4f9-737342e79be6'; //... own data is done
|
||||
|
||||
/*
|
||||
User creation saves email as lowercase, but updating an email did not.
|
||||
Run this script to ensure all lowercased emails in db AFTER fix for updating emails is implemented.
|
||||
This will fix inconsistent querying for an email when attempting to password reset.
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId) {
|
||||
var query = {
|
||||
'auth.local.email': /[A-Z]/
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [ // specify fields we are interested in to limit retrieved data (empty if we're not reading data)
|
||||
'auth.local.email'
|
||||
],
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var push;
|
||||
var set = {
|
||||
'auth.local.email': user.auth.local.email.toLowerCase()
|
||||
};
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set});
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
@@ -1,4 +1,6 @@
|
||||
If you need to use a migration from this folder, move it to /migrations.
|
||||
|
||||
Note that /migrations files (excluding /archive) are linted, so to pass test you'll have to make sure
|
||||
that the file is written correctly.
|
||||
that the file is written correctly.
|
||||
|
||||
They might also be using some old deps that we don't use anymore like Bluebird, mongoskin, ...
|
||||
@@ -1,5 +1,3 @@
|
||||
import Bluebird from 'Bluebird';
|
||||
|
||||
import { model as Challenges } from '../../website/server/models/challenge';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
@@ -17,10 +15,10 @@ async function syncChallengeToMembers (challenges) {
|
||||
promises.push(user.save());
|
||||
});
|
||||
|
||||
return Bluebird.all(promises);
|
||||
return Promise.all(promises);
|
||||
});
|
||||
|
||||
return await Bluebird.all(challengSyncPromises);
|
||||
return await Promise.all(challengSyncPromises);
|
||||
}
|
||||
|
||||
async function syncChallenges (lastChallengeDate) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
import { model as Group } from '../../website/server/models/group';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
@@ -16,7 +14,7 @@ async function createGroup (name, privacy, type, leaderId) {
|
||||
group.leader = user._id;
|
||||
user.guilds.push(group._id);
|
||||
|
||||
return Bluebird.all([group.save(), user.save()]);
|
||||
return Promise.all([group.save(), user.save()]);
|
||||
}
|
||||
|
||||
module.exports = async function groupCreator () {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
/*
|
||||
* This migration will find users with unlimited subscriptions who are also eligible for Jackalope mounts, and award them
|
||||
*/
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
import { model as Group } from '../../website/server/models/group';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
@@ -38,7 +37,7 @@ async function handOutJackalopes () {
|
||||
|
||||
cursor.on('close', async () => {
|
||||
console.log('done');
|
||||
return await Bluebird.all(promises);
|
||||
return await Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
52
migrations/groups/migrate-chat.js
Normal file
52
migrations/groups/migrate-chat.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// @migrationName = 'MigrateGroupChat';
|
||||
// @authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||
// @authorUuid = ''; // ... own data is done
|
||||
|
||||
|
||||
/*
|
||||
* This migration moves chat off of groups and into their own model
|
||||
*/
|
||||
|
||||
import { model as Group } from '../../website/server/models/group';
|
||||
import { model as Chat } from '../../website/server/models/chat';
|
||||
|
||||
async function moveGroupChatToModel (skip = 0) {
|
||||
const groups = await Group.find({})
|
||||
.limit(50)
|
||||
.skip(skip)
|
||||
.sort({ _id: -1 })
|
||||
.exec();
|
||||
|
||||
if (groups.length === 0) {
|
||||
console.log('End of groups');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
const promises = groups.map(group => {
|
||||
const chatpromises = group.chat.map(message => {
|
||||
const newChat = new Chat();
|
||||
Object.assign(newChat, message);
|
||||
newChat._id = message.id;
|
||||
newChat.groupId = group._id;
|
||||
|
||||
return newChat.save();
|
||||
});
|
||||
|
||||
group.chat = [];
|
||||
chatpromises.push(group.save());
|
||||
|
||||
return chatpromises;
|
||||
});
|
||||
|
||||
|
||||
const reducedPromises = promises.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
console.log(reducedPromises);
|
||||
await Promise.all(reducedPromises);
|
||||
moveGroupChatToModel(skip + 50);
|
||||
}
|
||||
|
||||
module.exports = moveGroupChatToModel;
|
||||
@@ -9,8 +9,6 @@ let authorUuid = ''; // ... own data is done
|
||||
* subscription to all members
|
||||
*/
|
||||
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
import { model as Group } from '../../website/server/models/group';
|
||||
import * as payments from '../../website/server/libs/payments';
|
||||
|
||||
@@ -28,7 +26,7 @@ async function updateGroupsWithGroupPlans () {
|
||||
});
|
||||
|
||||
cursor.on('close', async () => {
|
||||
return await Bluebird.all(promises);
|
||||
return await Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
require('babel-register');
|
||||
require('babel-polyfill');
|
||||
|
||||
// This file must use ES5, everything required can be in ES6
|
||||
|
||||
function setUpServer () {
|
||||
const nconf = require('nconf'); // eslint-disable-line global-require, no-unused-vars
|
||||
const mongoose = require('mongoose'); // eslint-disable-line global-require, no-unused-vars
|
||||
const Bluebird = require('bluebird'); // eslint-disable-line global-require, no-unused-vars
|
||||
const setupNconf = require('../website/server/libs/setupNconf'); // eslint-disable-line global-require
|
||||
|
||||
setupNconf();
|
||||
|
||||
// We require src/server and npt src/index because
|
||||
// 1. nconf is already setup
|
||||
// 2. we don't need clustering
|
||||
@@ -17,5 +17,5 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
const processUsers = require('./20180125_clean_new_notifications.js');
|
||||
const processUsers = require('./groups/migrate-chat.js');
|
||||
processUsers();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
let Bluebird = require('bluebird');
|
||||
let request = require('superagent');
|
||||
let last = require('lodash/last');
|
||||
let AWS = require('aws-sdk');
|
||||
@@ -74,7 +73,7 @@ function uploadToS3 (start, end, filesUrls) {
|
||||
});
|
||||
console.log(promises.length);
|
||||
|
||||
return Bluebird.all(promises)
|
||||
return Promise.all(promises)
|
||||
.then(() => {
|
||||
currentIndex += 50;
|
||||
uploadToS3(currentIndex, currentIndex + 50, filesUrls);
|
||||
|
||||
117
migrations/users/full-stable.js
Normal file
117
migrations/users/full-stable.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import each from 'lodash/each';
|
||||
import keys from 'lodash/keys';
|
||||
import content from '../../website/common/script/content/index';
|
||||
const migrationName = 'full-stable.js';
|
||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Award users every extant pet and mount
|
||||
*/
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
let monk = require('monk');
|
||||
let dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
'profile.name': 'SabreCat',
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
let set = {
|
||||
migration: migrationName,
|
||||
};
|
||||
|
||||
each(keys(content.pets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
});
|
||||
each(keys(content.premiumPets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
});
|
||||
each(keys(content.questPets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
});
|
||||
each(keys(content.specialPets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
});
|
||||
each(keys(content.mounts), (mount) => {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
each(keys(content.premiumMounts), (mount) => {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
each(keys(content.questMounts), (mount) => {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
each(keys(content.specialMounts), (mount) => {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set});
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
@@ -5,7 +5,7 @@ const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is do
|
||||
/*
|
||||
* Award this month's mystery items to subscribers
|
||||
*/
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201802', 'head_mystery_201802', 'shield_mystery_201802'];
|
||||
const MYSTERY_ITEMS = ['back_mystery_201804', 'headAccessory_mystery_201804'];
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
let monk = require('monk');
|
||||
|
||||
9107
package-lock.json
generated
9107
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
141
package.json
141
package.json
@@ -1,54 +1,48 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.29.2",
|
||||
"version": "4.41.5",
|
||||
"main": "./website/server/index.js",
|
||||
"greenkeeper": {
|
||||
"ignore": [
|
||||
"mongoose"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
"accepts": "^1.3.2",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.6",
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"autoprefixer": "^8.0.0",
|
||||
"aws-sdk": "^2.200.0",
|
||||
"autoprefixer": "^8.4.1",
|
||||
"aws-sdk": "^2.230.1",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.1.8",
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-eslint": "^8.2.2",
|
||||
"babel-loader": "^7.1.2",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-plugin-syntax-async-functions": "^6.13.0",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-async-to-module-method": "^6.8.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.16.0",
|
||||
"babel-plugin-transform-regenerator": "^6.16.1",
|
||||
"babel-polyfill": "^6.6.1",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.6.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"bcrypt": "^1.0.2",
|
||||
"bluebird": "^3.3.5",
|
||||
"bcrypt": "^2.0.0",
|
||||
"body-parser": "^1.15.0",
|
||||
"bootstrap": "^4.0.0",
|
||||
"bootstrap-vue": "^2.0.0-rc.1",
|
||||
"bootstrap": "^4.1.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.9",
|
||||
"compression": "^1.7.2",
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.1.3",
|
||||
"css-loader": "^0.28.0",
|
||||
"csv-stringify": "^2.0.4",
|
||||
"cross-env": "^5.1.4",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^2.1.0",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"express": "^4.16.2",
|
||||
"express-basic-auth": "^1.1.4",
|
||||
"express-validator": "^5.0.1",
|
||||
"express": "^4.16.3",
|
||||
"express-basic-auth": "^1.1.5",
|
||||
"express-validator": "^5.1.2",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"glob": "^7.1.2",
|
||||
"got": "^8.2.0",
|
||||
"got": "^8.3.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^7.0.1",
|
||||
"gulp-imagemin": "^4.1.0",
|
||||
@@ -56,70 +50,69 @@
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.15.1",
|
||||
"html-webpack-plugin": "^2.8.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"image-size": "^0.6.2",
|
||||
"in-app-purchase": "^1.1.6",
|
||||
"intro.js": "^2.6.0",
|
||||
"in-app-purchase": "^1.9.3",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": ">=3.0.0",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
"lodash": "^4.17.4",
|
||||
"lodash": "^4.17.10",
|
||||
"merge-stream": "^1.0.0",
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.13.0",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^4.13.11",
|
||||
"mongoose": "^5.0.17",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^0.14.4",
|
||||
"node-rdkafka": "^2.2.3",
|
||||
"node-sass": "^4.5.0",
|
||||
"nodemailer": "^4.5.0",
|
||||
"ora": "^2.0.0",
|
||||
"node-sass": "^4.9.0",
|
||||
"nodemailer": "^4.6.4",
|
||||
"ora": "^2.1.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.0.0",
|
||||
"passport-google-oauth20": "1.0.0",
|
||||
"paypal-ipn": "3.0.0",
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"popper.js": "^1.13.0",
|
||||
"popper.js": "^1.14.3",
|
||||
"postcss-easy-import": "^3.0.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"pug": "^2.0.0-rc.4",
|
||||
"pug": "^2.0.3",
|
||||
"push-notify": "git://github.com/habitrpg/push-notify.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
|
||||
"pusher": "^1.3.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"sass-loader": "^6.0.2",
|
||||
"sass-loader": "^7.0.0",
|
||||
"shelljs": "^0.8.1",
|
||||
"stripe": "^5.5.0",
|
||||
"superagent": "^3.4.3",
|
||||
"stripe": "^5.8.0",
|
||||
"superagent": "^3.8.3",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^2.0.2",
|
||||
"svgo": "^1.0.4",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"svgo": "^1.0.5",
|
||||
"svgo-loader": "^2.1.0",
|
||||
"universal-analytics": "^0.4.16",
|
||||
"url-loader": "^0.6.2",
|
||||
"url-loader": "^1.0.0",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^3.0.1",
|
||||
"validator": "^9.4.1",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vue": "^2.5.2",
|
||||
"vue-loader": "^14.1.1",
|
||||
"vue": "^2.5.16",
|
||||
"vue-loader": "^14.2.2",
|
||||
"vue-mugen-scroll": "^0.2.1",
|
||||
"vue-router": "^3.0.0",
|
||||
"vue-style-loader": "^4.0.2",
|
||||
"vue-template-compiler": "^2.5.2",
|
||||
"vue-style-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"winston": "^2.1.0",
|
||||
"winston": "^2.4.2",
|
||||
"winston-loggly-bulk": "^2.0.2",
|
||||
"xml2js": "^0.4.4"
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "^6.9.1",
|
||||
"npm": "^5.0.0"
|
||||
"node": "^8.9.4",
|
||||
"npm": "^5.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.vue .",
|
||||
@@ -146,48 +139,54 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-istanbul": "^4.0.0",
|
||||
"@vue/test-utils": "^1.0.0-beta.15",
|
||||
"babel-plugin-istanbul": "^4.1.6",
|
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chalk": "^2.3.1",
|
||||
"chromedriver": "^2.27.2",
|
||||
"chalk": "^2.4.1",
|
||||
"chromedriver": "^2.38.2",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^3.0.0",
|
||||
"cross-spawn": "^6.0.4",
|
||||
"eslint": "^4.18.1",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-habitrpg": "^4.0.0",
|
||||
"eslint-friendly-formatter": "^3.0.0",
|
||||
"eslint-loader": "^1.3.0",
|
||||
"eslint-plugin-html": "^4.0.2",
|
||||
"eslint-plugin-mocha": "^4.7.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-html": "^4.0.3",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"expect.js": "^0.3.1",
|
||||
"http-proxy-middleware": "^0.17.0",
|
||||
"http-proxy-middleware": "^0.18.0",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"karma": "^2.0.0",
|
||||
"karma": "^2.0.2",
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
"karma-chai-plugins": "^0.9.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-sinon-chai": "^1.3.3",
|
||||
"karma-sinon-chai": "^1.3.4",
|
||||
"karma-sinon-stub-promise": "^1.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.32",
|
||||
"karma-webpack": "^2.0.2",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"lcov-result-merger": "^2.0.0",
|
||||
"mocha": "^5.0.1",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^6.0.5",
|
||||
"nightwatch": "^0.9.12",
|
||||
"puppeteer": "^1.1.0",
|
||||
"nightwatch": "^0.9.21",
|
||||
"puppeteer": "^1.3.0",
|
||||
"require-again": "^2.0.0",
|
||||
"selenium-server": "^3.9.1",
|
||||
"sinon": "^4.3.0",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"selenium-server": "^3.11.0",
|
||||
"sinon": "^4.5.0",
|
||||
"sinon-chai": "^3.0.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"webpack-bundle-analyzer": "^2.2.1",
|
||||
"webpack-bundle-analyzer": "^2.11.1",
|
||||
"webpack-dev-middleware": "^2.0.5",
|
||||
"webpack-hot-middleware": "^2.6.1"
|
||||
"webpack-hot-middleware": "^2.22.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"memwatch-next": "^0.3.0",
|
||||
"node-rdkafka": "^2.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
require('babel-register');
|
||||
require('babel-polyfill');
|
||||
|
||||
// This file is used for creating paypal billing plans. PayPal doesn't have a web interface for setting up recurring
|
||||
// payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this
|
||||
|
||||
@@ -60,9 +60,9 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
||||
});
|
||||
|
||||
it('should return a valid CSV file with export data', async () => {
|
||||
let res = await members[0].get(`/challenges/${challenge._id}/export/csv`);
|
||||
let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
|
||||
let splitRes = res.split('\n');
|
||||
const res = await members[0].get(`/challenges/${challenge._id}/export/csv`);
|
||||
const sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
|
||||
const splitRes = res.split('\n');
|
||||
|
||||
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
|
||||
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
@@ -71,4 +71,16 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
||||
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[5]).to.equal('');
|
||||
});
|
||||
|
||||
it('should successfully return when it contains erroneous residue user data', async () => {
|
||||
await members[0].update({challenges: []});
|
||||
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
|
||||
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
|
||||
const splitRes = res.split('\n');
|
||||
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
|
||||
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[4]).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -151,7 +151,10 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
});
|
||||
|
||||
officialChallenge = await generateChallenge(user, group, {
|
||||
official: true,
|
||||
categories: [{
|
||||
name: 'habitica_official',
|
||||
slug: 'habitica_official',
|
||||
}],
|
||||
});
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
|
||||
@@ -193,7 +193,10 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
|
||||
officialChallenge = await generateChallenge(user, group, {
|
||||
official: true,
|
||||
categories: [{
|
||||
name: 'habitica_official',
|
||||
slug: 'habitica_official',
|
||||
}],
|
||||
});
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
@@ -224,4 +227,61 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
context('filters and paging', () => {
|
||||
let user, guild, member;
|
||||
const categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
|
||||
before(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
guild = group;
|
||||
member = members[0];
|
||||
|
||||
await user.update({balance: 20});
|
||||
|
||||
for (let i = 0; i < 11; i += 1) {
|
||||
await generateChallenge(user, group); // eslint-disable-line
|
||||
}
|
||||
});
|
||||
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
const categoryChallenge = await generateChallenge(user, guild, {categories});
|
||||
const challenges = await user.get(`/challenges/user?categories=${categories[0].slug}`);
|
||||
|
||||
expect(challenges[0]._id).to.eql(categoryChallenge._id);
|
||||
expect(challenges.length).to.eql(1);
|
||||
});
|
||||
|
||||
it('does not page challenges if page parameter is absent', async () => {
|
||||
const challenges = await user.get('/challenges/user');
|
||||
|
||||
expect(challenges.length).to.be.above(11);
|
||||
});
|
||||
|
||||
it('paginates challenges', async () => {
|
||||
const challenges = await user.get('/challenges/user?page=0');
|
||||
const challengesPaged = await user.get('/challenges/user?page=1&owned=owned');
|
||||
|
||||
expect(challenges.length).to.eql(10);
|
||||
expect(challengesPaged.length).to.eql(2);
|
||||
});
|
||||
|
||||
it('filters by owned', async () => {
|
||||
const challenges = await member.get('/challenges/user?owned=owned');
|
||||
|
||||
expect(challenges.length).to.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,16 +53,26 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
|
||||
it('allows creator to delete a their message', async () => {
|
||||
await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
|
||||
let messages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
||||
expect(messages).is.an('array');
|
||||
expect(messages).to.not.include(nextMessage);
|
||||
|
||||
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
||||
const messageFromUser = returnedMessages.find(returnedMessage => {
|
||||
return returnedMessage.id === nextMessage.id;
|
||||
});
|
||||
|
||||
expect(returnedMessages).is.an('array');
|
||||
expect(messageFromUser).to.not.exist;
|
||||
});
|
||||
|
||||
it('allows admin to delete another user\'s message', async () => {
|
||||
await admin.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
|
||||
let messages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
||||
expect(messages).is.an('array');
|
||||
expect(messages).to.not.include(nextMessage);
|
||||
|
||||
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
||||
const messageFromUser = returnedMessages.find(returnedMessage => {
|
||||
return returnedMessage.id === nextMessage.id;
|
||||
});
|
||||
|
||||
expect(returnedMessages).is.an('array');
|
||||
expect(messageFromUser).to.not.exist;
|
||||
});
|
||||
|
||||
it('returns empty when previous message parameter is passed and the last message was deleted', async () => {
|
||||
@@ -71,9 +81,9 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
});
|
||||
|
||||
it('returns the update chat when previous message parameter is passed and the chat is updated', async () => {
|
||||
let deleteResult = await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${message.id}`);
|
||||
const updatedChat = await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${message.id}`);
|
||||
|
||||
expect(deleteResult[0].id).to.eql(message.id);
|
||||
expect(updatedChat[0].id).to.eql(message.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,16 +23,17 @@ describe('GET /groups/:groupId/chat', () => {
|
||||
privacy: 'public',
|
||||
}, {
|
||||
chat: [
|
||||
{text: 'Hello', flags: {}},
|
||||
{text: 'Welcome to the Guild', flags: {}},
|
||||
{text: 'Hello', flags: {}, id: 1},
|
||||
{text: 'Welcome to the Guild', flags: {}, id: 2},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns Guild chat', async () => {
|
||||
let chat = await user.get(`/groups/${group._id}/chat`);
|
||||
const chat = await user.get(`/groups/${group._id}/chat`);
|
||||
|
||||
expect(chat).to.eql(group.chat);
|
||||
expect(chat[0].id).to.eql(group.chat[0].id);
|
||||
expect(chat[1].id).to.eql(group.chat[1].id);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { getMatchesByWordArray, removePunctuationFromString } from '../../../../../website/server/libs/stringUtils';
|
||||
import { getMatchesByWordArray } from '../../../../../website/server/libs/stringUtils';
|
||||
import bannedWords from '../../../../../website/server/libs/bannedWords';
|
||||
import guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
@@ -23,11 +23,11 @@ const BASE_URL = nconf.get('BASE_URL');
|
||||
describe('POST /chat', () => {
|
||||
let user, groupWithChat, member, additionalMember;
|
||||
let testMessage = 'Test Message';
|
||||
let testBannedWordMessage = 'TEST_PLACEHOLDER_SWEAR_WORD_HERE';
|
||||
let testSlurMessage = 'message with TEST_PLACEHOLDER_SLUR_WORD_HERE';
|
||||
let bannedWordErrorMessage = t('bannedWordUsed').split('.');
|
||||
bannedWordErrorMessage[0] += ` (${removePunctuationFromString(testBannedWordMessage.toLowerCase())})`;
|
||||
bannedWordErrorMessage = bannedWordErrorMessage.join('.');
|
||||
let testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE';
|
||||
let testBannedWordMessage1 = 'TESTPLACEHOLDERSWEARWORDHERE1';
|
||||
let testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE';
|
||||
let testSlurMessage1 = 'TESTPLACEHOLDERSLURWORDHERE1';
|
||||
let bannedWordErrorMessage = t('bannedWordUsed', {swearWordsUsed: testBannedWordMessage});
|
||||
|
||||
before(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
@@ -39,6 +39,7 @@ describe('POST /chat', () => {
|
||||
members: 2,
|
||||
});
|
||||
user = groupLeader;
|
||||
await user.update({'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL}); // prevent tests accidentally throwing messageGroupChatSpam
|
||||
groupWithChat = group;
|
||||
member = members[0];
|
||||
additionalMember = members[1];
|
||||
@@ -136,9 +137,19 @@ describe('POST /chat', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('checks error message has the banned words used', async () => {
|
||||
let randIndex = Math.floor(Math.random() * (bannedWords.length + 1));
|
||||
let testBannedWords = bannedWords.slice(randIndex, randIndex + 2).map((w) => w.replace(/\\/g, ''));
|
||||
it('errors when word is typed in mixed case', async () => {
|
||||
let substrLength = Math.floor(testBannedWordMessage.length / 2);
|
||||
let chatMessage = testBannedWordMessage.substring(0, substrLength).toLowerCase() + testBannedWordMessage.substring(substrLength).toUpperCase();
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedWordUsed', {swearWordsUsed: chatMessage}),
|
||||
});
|
||||
});
|
||||
|
||||
it('checks error message has all the banned words used, regardless of case', async () => {
|
||||
let testBannedWords = [testBannedWordMessage.toUpperCase(), testBannedWordMessage1.toLowerCase()];
|
||||
let chatMessage = `Mixing ${testBannedWords[0]} and ${testBannedWords[1]} is bad for you.`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage}))
|
||||
.to.eventually.be.rejected
|
||||
@@ -320,6 +331,17 @@ describe('POST /chat', () => {
|
||||
members[0].flags.chatRevoked = false;
|
||||
await members[0].update({'flags.chatRevoked': false});
|
||||
});
|
||||
|
||||
it('errors when slur is typed in mixed case', async () => {
|
||||
let substrLength = Math.floor(testSlurMessage1.length / 2);
|
||||
let chatMessage = testSlurMessage1.substring(0, substrLength).toLowerCase() + testSlurMessage1.substring(substrLength).toUpperCase();
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not error when sending a message to a private guild with a user with revoked chat', async () => {
|
||||
@@ -359,9 +381,11 @@ describe('POST /chat', () => {
|
||||
});
|
||||
|
||||
it('creates a chat', async () => {
|
||||
let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
|
||||
const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
|
||||
const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`);
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(newMessage.message.id).to.exist;
|
||||
expect(groupMessages[0].id).to.exist;
|
||||
});
|
||||
|
||||
it('creates a chat with user styles', async () => {
|
||||
|
||||
@@ -2,13 +2,12 @@ import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import xml2js from 'xml2js';
|
||||
import Bluebird from 'bluebird';
|
||||
import util from 'util';
|
||||
|
||||
let parseStringAsync = Bluebird.promisify(xml2js.parseString, {context: xml2js});
|
||||
let parseStringAsync = util.promisify(xml2js.parseString).bind(xml2js);
|
||||
|
||||
describe('GET /export/userdata.xml', () => {
|
||||
// TODO disabled because it randomly causes the build to fail
|
||||
xit('should return a valid XML file with user data', async () => {
|
||||
it('should return a valid XML file with user data', async () => {
|
||||
let user = await generateUser();
|
||||
let tasks = await user.post('/tasks/user', [
|
||||
{type: 'habit', text: 'habit 1'},
|
||||
@@ -31,13 +30,21 @@ describe('GET /export/userdata.xml', () => {
|
||||
expect(res).to.contain.all.keys(['tasks', 'flags', 'tasksOrder', 'auth']);
|
||||
expect(res.auth.local).not.to.have.keys(['salt', 'hashed_password']);
|
||||
expect(res.tasks).to.have.all.keys(['dailys', 'habits', 'todos', 'rewards']);
|
||||
|
||||
expect(res.tasks.habits.length).to.equal(2);
|
||||
expect(res.tasks.habits[0]._id).to.equal(tasks[0]._id);
|
||||
let habitIds = _.map(res.tasks.habits, '_id');
|
||||
expect(habitIds).to.have.deep.members([tasks[0]._id, tasks[4]._id]);
|
||||
|
||||
expect(res.tasks.dailys.length).to.equal(2);
|
||||
expect(res.tasks.dailys[0]._id).to.equal(tasks[1]._id);
|
||||
let dailyIds = _.map(res.tasks.dailys, '_id');
|
||||
expect(dailyIds).to.have.deep.members([tasks[1]._id, tasks[5]._id]);
|
||||
|
||||
expect(res.tasks.rewards.length).to.equal(2);
|
||||
expect(res.tasks.rewards[0]._id).to.equal(tasks[2]._id);
|
||||
let rewardIds = _.map(res.tasks.rewards, '_id');
|
||||
expect(rewardIds).to.have.deep.members([tasks[2]._id, tasks[6]._id]);
|
||||
|
||||
expect(res.tasks.todos.length).to.equal(3);
|
||||
expect(res.tasks.todos[1]._id).to.equal(tasks[3]._id);
|
||||
let todoIds = _.map(res.tasks.todos, '_id');
|
||||
expect(todoIds).to.deep.include.members([tasks[3]._id, tasks[7]._id]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,8 +201,8 @@ describe('GET /groups', () => {
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
let page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
|
||||
.to.eventually.have.a.lengthOf(1 + 3); // 1 created now, 3 by other tests
|
||||
expect(page2[3].name).to.equal('guild with less members');
|
||||
.to.eventually.have.a.lengthOf(1 + 4); // 1 created now, 4 by other tests
|
||||
expect(page2[4].name).to.equal('guild with less members');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -220,4 +220,18 @@ describe('GET /groups', () => {
|
||||
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
|
||||
});
|
||||
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
let group = await generateGroup(user, {
|
||||
name: 'c++ coders',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
// search for 'c++ coders'
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=0&search=c%2B%2B+coders'))
|
||||
.to.eventually.have.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]')
|
||||
.and.to.have.property('_id', group._id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
@@ -93,7 +93,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
|
||||
@@ -136,6 +136,22 @@ describe('POST /group', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when a user with no chat privileges attempts to create a public guild', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
|
||||
await expect(
|
||||
user.post('/groups', {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
})
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotCreatePublicGuildWhenMuted'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('private guild', () => {
|
||||
@@ -163,6 +179,17 @@ describe('POST /group', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a private guild when the user has no chat privileges', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
let privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild._id).to.exist;
|
||||
});
|
||||
|
||||
it('deducts gems from user and adds them to guild bank', async () => {
|
||||
let privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
@@ -201,6 +228,16 @@ describe('POST /group', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a party when the user has no chat privileges', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
let party = await user.post('/groups', {
|
||||
name: partyName,
|
||||
type: partyType,
|
||||
});
|
||||
|
||||
expect(party._id).to.exist;
|
||||
});
|
||||
|
||||
it('does not require gems to create a party', async () => {
|
||||
await user.update({ balance: 0 });
|
||||
|
||||
|
||||
@@ -44,12 +44,12 @@ describe('POST /group/:groupId/join', () => {
|
||||
expect(res.leader.profile.name).to.eql(user.profile.name);
|
||||
});
|
||||
|
||||
it('returns an error is user was already a member', async () => {
|
||||
it('returns an error if user was already a member', async () => {
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
await expect(joiningUser.post(`/groups/${publicGuild._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('userAlreadyInGroup'),
|
||||
message: t('youAreAlreadyInGroup'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -262,6 +262,30 @@ describe('POST /group/:groupId/join', () => {
|
||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
it('does not allow user to leave a party if a quest was active and they were the only member', async () => {
|
||||
let userToInvite = await generateUser();
|
||||
let oldParty = await userToInvite.post('/groups', { // add user to a party
|
||||
name: 'Another Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
await userToInvite.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
});
|
||||
await userToInvite.post(`/groups/${oldParty._id}/quests/invite/${PET_QUEST}`);
|
||||
|
||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
|
||||
await user.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
await expect(userToInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageCannotLeaveWhileQuesting'),
|
||||
});
|
||||
});
|
||||
|
||||
it('invites joining member to active quest', async () => {
|
||||
await user.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
each,
|
||||
} from 'lodash';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import * as payments from '../../../../../website/server/libs/payments';
|
||||
import * as payments from '../../../../../website/server/libs/payments/payments';
|
||||
|
||||
describe('POST /groups/:groupId/leave', () => {
|
||||
let typesOfGroups = {
|
||||
|
||||
@@ -24,6 +24,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
describe('user id invites', () => {
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
|
||||
let userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user is not found', async () => {
|
||||
let fakeID = generateUUID();
|
||||
|
||||
@@ -160,6 +173,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
describe('email invites', () => {
|
||||
let testInvite = {name: 'test', email: 'test@habitica.com'};
|
||||
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
|
||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
emails: [testInvite],
|
||||
inviter: 'inviter name',
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invite is missing an email', async () => {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
emails: [{name: 'test'}],
|
||||
@@ -321,6 +347,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
describe('guild invites', () => {
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
|
||||
let userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user is already invited to the group', async () => {
|
||||
let userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
@@ -398,6 +437,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
|
||||
let userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user has a pending invitation to the party', async () => {
|
||||
let userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('GET /members/:memberId', () => {
|
||||
let memberRes = await user.get(`/members/${member._id}`);
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import amzLib from '../../../../../../website/server/libs/amazonPayments';
|
||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||
|
||||
describe('payments : amazon #subscribeCancel', () => {
|
||||
let endpoint = '/amazon/subscribe/cancel?noRedirect=true';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import amzLib from '../../../../../../website/server/libs/amazonPayments';
|
||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||
|
||||
describe('payments - amazon - #checkout', () => {
|
||||
let endpoint = '/amazon/checkout';
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import amzLib from '../../../../../../website/server/libs/amazonPayments';
|
||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||
|
||||
describe('payments - amazon - #subscribe', () => {
|
||||
let endpoint = '/amazon/subscribe';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||
import applePayments from '../../../../../../website/server/libs/applePayments';
|
||||
import applePayments from '../../../../../../website/server/libs/payments/apple';
|
||||
|
||||
describe('payments : apple #cancelSubscribe', () => {
|
||||
let endpoint = '/iap/ios/subscribe/cancel?noRedirect=true';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||
import applePayments from '../../../../../../website/server/libs/applePayments';
|
||||
import applePayments from '../../../../../../website/server/libs/payments/apple';
|
||||
|
||||
describe('payments : apple #verify', () => {
|
||||
let endpoint = '/iap/ios/verify';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import applePayments from '../../../../../../website/server/libs/applePayments';
|
||||
import applePayments from '../../../../../../website/server/libs/payments/apple';
|
||||
|
||||
describe('payments : apple #subscribe', () => {
|
||||
let endpoint = '/iap/ios/subscribe';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||
import googlePayments from '../../../../../../website/server/libs/googlePayments';
|
||||
import googlePayments from '../../../../../../website/server/libs/payments/google';
|
||||
|
||||
describe('payments : google #cancelSubscribe', () => {
|
||||
let endpoint = '/iap/android/subscribe/cancel?noRedirect=true';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import googlePayments from '../../../../../../website/server/libs/googlePayments';
|
||||
import googlePayments from '../../../../../../website/server/libs/payments/google';
|
||||
|
||||
describe('payments : google #subscribe', () => {
|
||||
let endpoint = '/iap/android/subscribe';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||
import googlePayments from '../../../../../../website/server/libs/googlePayments';
|
||||
import googlePayments from '../../../../../../website/server/libs/payments/google';
|
||||
|
||||
describe('payments : google #verify', () => {
|
||||
let endpoint = '/iap/android/verify';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
|
||||
describe('payments : paypal #checkout', () => {
|
||||
let endpoint = '/paypal/checkout';
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
|
||||
describe('payments : paypal #checkoutSuccess', () => {
|
||||
let endpoint = '/paypal/checkout/success';
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
import shared from '../../../../../../website/common';
|
||||
|
||||
describe('payments : paypal #subscribe', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
|
||||
describe('payments : paypal #subscribeCancel', () => {
|
||||
let endpoint = '/paypal/subscribe/cancel';
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
|
||||
describe('payments : paypal #subscribeSuccess', () => {
|
||||
let endpoint = '/paypal/subscribe/success';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
|
||||
describe('payments - paypal - #ipn', () => {
|
||||
let endpoint = '/paypal/ipn';
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/stripePayments';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
describe('payments - stripe - #subscribeCancel', () => {
|
||||
let endpoint = '/stripe/subscribe/cancel?redirect=none';
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/stripePayments';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
describe('payments - stripe - #checkout', () => {
|
||||
let endpoint = '/stripe/checkout';
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/stripePayments';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
describe('payments - stripe - #subscribeEdit', () => {
|
||||
let endpoint = '/stripe/subscribe/edit';
|
||||
|
||||
@@ -2,8 +2,9 @@ import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
generateUser,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import Bluebird from 'bluebird';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
|
||||
describe('POST /groups/:groupId/quests/accept', () => {
|
||||
const PET_QUEST = 'whale';
|
||||
@@ -140,7 +141,7 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
||||
// quest will start after everyone has accepted
|
||||
await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
|
||||
await Bluebird.delay(500);
|
||||
await sleep(0.5);
|
||||
|
||||
await rejectingMember.sync();
|
||||
|
||||
@@ -155,10 +156,11 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
||||
// quest will start after everyone has accepted
|
||||
await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
|
||||
await questingGroup.sync();
|
||||
expect(questingGroup.chat[0].text).to.exist;
|
||||
expect(questingGroup.chat[0]._meta).to.exist;
|
||||
expect(questingGroup.chat[0]._meta).to.have.all.keys(['participatingMembers']);
|
||||
const groupChat = await Chat.find({ groupId: questingGroup._id }).exec();
|
||||
|
||||
expect(groupChat[0].text).to.exist;
|
||||
expect(groupChat[0]._meta).to.exist;
|
||||
expect(groupChat[0]._meta).to.have.all.keys(['participatingMembers']);
|
||||
|
||||
let returnedGroup = await leader.get(`/groups/${questingGroup._id}`);
|
||||
expect(returnedGroup.chat[0]._meta).to.be.undefined;
|
||||
|
||||
@@ -2,8 +2,9 @@ import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
generateUser,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import Bluebird from 'bluebird';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
|
||||
describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
const PET_QUEST = 'whale';
|
||||
@@ -135,7 +136,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
||||
|
||||
await Bluebird.delay(500);
|
||||
await sleep(0.5);
|
||||
|
||||
await Promise.all([
|
||||
partyMemberThatRejects.sync(),
|
||||
@@ -161,7 +162,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
||||
|
||||
await Bluebird.delay(500);
|
||||
await sleep(0.5);
|
||||
|
||||
await questingGroup.sync();
|
||||
|
||||
@@ -184,7 +185,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
||||
|
||||
await Bluebird.delay(500);
|
||||
await sleep(0.5);
|
||||
|
||||
await questingGroup.sync();
|
||||
|
||||
@@ -201,7 +202,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
||||
|
||||
await Bluebird.delay(500);
|
||||
await sleep(0.5);
|
||||
|
||||
await questingGroup.sync();
|
||||
|
||||
@@ -222,7 +223,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
||||
|
||||
await Bluebird.delay(500);
|
||||
await sleep(0.5);
|
||||
|
||||
await questingGroup.sync();
|
||||
|
||||
@@ -241,11 +242,13 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
|
||||
await questingGroup.sync();
|
||||
|
||||
expect(questingGroup.chat[0].text).to.exist;
|
||||
expect(questingGroup.chat[0]._meta).to.exist;
|
||||
expect(questingGroup.chat[0]._meta).to.have.all.keys(['participatingMembers']);
|
||||
const groupChat = await Chat.find({ groupId: questingGroup._id }).exec();
|
||||
|
||||
let returnedGroup = await leader.get(`/groups/${questingGroup._id}`);
|
||||
expect(groupChat[0].text).to.exist;
|
||||
expect(groupChat[0]._meta).to.exist;
|
||||
expect(groupChat[0]._meta).to.have.all.keys(['participatingMembers']);
|
||||
|
||||
const returnedGroup = await leader.get(`/groups/${questingGroup._id}`);
|
||||
expect(returnedGroup.chat[0]._meta).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { quests as questScrolls } from '../../../../../website/common/script/content';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
|
||||
describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
let questingGroup;
|
||||
@@ -199,11 +200,11 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
|
||||
await groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`);
|
||||
|
||||
await group.sync();
|
||||
const groupChat = await Chat.find({ groupId: group._id }).exec();
|
||||
|
||||
expect(group.chat[0].text).to.exist;
|
||||
expect(group.chat[0]._meta).to.exist;
|
||||
expect(group.chat[0]._meta).to.have.all.keys(['participatingMembers']);
|
||||
expect(groupChat[0].text).to.exist;
|
||||
expect(groupChat[0]._meta).to.exist;
|
||||
expect(groupChat[0]._meta).to.have.all.keys(['participatingMembers']);
|
||||
|
||||
let returnedGroup = await groupLeader.get(`/groups/${group._id}`);
|
||||
expect(returnedGroup.chat[0]._meta).to.be.undefined;
|
||||
|
||||
@@ -90,7 +90,7 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
|
||||
let stub = sandbox.stub(Group.prototype, 'sendChat');
|
||||
let stub = sandbox.spy(Group.prototype, 'sendChat');
|
||||
|
||||
let res = await leader.post(`/groups/${questingGroup._id}/quests/abort`);
|
||||
await Promise.all([
|
||||
|
||||
@@ -2,9 +2,10 @@ import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
generateUser,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import Bluebird from 'bluebird';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
|
||||
describe('POST /groups/:groupId/quests/reject', () => {
|
||||
let questingGroup;
|
||||
@@ -168,7 +169,7 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
||||
// quest will start after everyone has accepted or rejected
|
||||
await rejectingMember.post(`/groups/${questingGroup._id}/quests/reject`);
|
||||
|
||||
await Bluebird.delay(500);
|
||||
await sleep(0.5);
|
||||
|
||||
await questingGroup.sync();
|
||||
|
||||
@@ -185,11 +186,12 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
await partyMembers[1].post(`/groups/${questingGroup._id}/quests/reject`);
|
||||
await questingGroup.sync();
|
||||
|
||||
expect(questingGroup.chat[0].text).to.exist;
|
||||
expect(questingGroup.chat[0]._meta).to.exist;
|
||||
expect(questingGroup.chat[0]._meta).to.have.all.keys(['participatingMembers']);
|
||||
const groupChat = await Chat.find({ groupId: questingGroup._id }).exec();
|
||||
|
||||
expect(groupChat[0].text).to.exist;
|
||||
expect(groupChat[0]._meta).to.exist;
|
||||
expect(groupChat[0]._meta).to.have.all.keys(['participatingMembers']);
|
||||
|
||||
let returnedGroup = await leader.get(`/groups/${questingGroup._id}`);
|
||||
expect(returnedGroup.chat[0]._meta).to.be.undefined;
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
describe('GET /shops/market', () => {
|
||||
let user;
|
||||
@@ -42,13 +41,13 @@ describe('GET /shops/market', () => {
|
||||
return array;
|
||||
}, []);
|
||||
|
||||
let results = await Bluebird.each(items, (item) => {
|
||||
let results = await Promise.all(items.map((item) => {
|
||||
let { purchaseType, key } = item;
|
||||
return user.post(`/user/purchase/${purchaseType}/${key}`);
|
||||
});
|
||||
}));
|
||||
|
||||
expect(results.length).to.be.greaterThan(0);
|
||||
results.forEach((item) => {
|
||||
items.forEach((item) => {
|
||||
expect(item).to.include.keys('key', 'text', 'notes', 'class', 'value', 'currency');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,6 +81,49 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
expect(body.direction).to.eql('up');
|
||||
expect(body.delta).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
context('sending user activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('sends user activity webhook when the user levels up', async () => {
|
||||
let uuid = generateUUID();
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'userActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
leveledUp: true,
|
||||
},
|
||||
});
|
||||
|
||||
const initialLvl = user.stats.lvl;
|
||||
|
||||
await user.update({
|
||||
'stats.exp': 3000,
|
||||
});
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task.id}/score/up`);
|
||||
await user.sync();
|
||||
await sleep();
|
||||
|
||||
let body = server.getWebhookData(uuid);
|
||||
|
||||
expect(body.type).to.eql('leveledUp');
|
||||
expect(body.initialLvl).to.eql(initialLvl);
|
||||
expect(body.finalLvl).to.eql(user.stats.lvl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('todos', () => {
|
||||
|
||||
@@ -296,6 +296,16 @@ describe('PUT /tasks/:id', () => {
|
||||
|
||||
expect(fetchedDaily.text).to.eql('saved');
|
||||
});
|
||||
|
||||
// This is a special case for iOS requests
|
||||
it('will round a priority (difficulty)', async () => {
|
||||
daily = await user.put(`/tasks/${daily._id}`, {
|
||||
alias: 'alias',
|
||||
priority: 0.10000000000005,
|
||||
});
|
||||
|
||||
expect(daily.priority).to.eql(0.1);
|
||||
});
|
||||
});
|
||||
|
||||
context('habits', () => {
|
||||
|
||||
@@ -2,8 +2,8 @@ import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
generateChallenge,
|
||||
sleep,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import Bluebird from 'bluebird';
|
||||
import { find } from 'lodash';
|
||||
|
||||
describe('POST /tasks/:id/score/:direction', () => {
|
||||
@@ -27,7 +27,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
await Bluebird.delay(1000);
|
||||
await sleep(1);
|
||||
let updatedUser = await user.get('/user');
|
||||
usersChallengeTaskId = updatedUser.tasksOrder.habits[0];
|
||||
});
|
||||
@@ -65,7 +65,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
});
|
||||
await Bluebird.delay(1000);
|
||||
await sleep(1);
|
||||
let updatedUser = await user.get('/user');
|
||||
usersChallengeTaskId = updatedUser.tasksOrder.dailys[0];
|
||||
});
|
||||
@@ -109,7 +109,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
});
|
||||
await Bluebird.delay(1000);
|
||||
await sleep(1);
|
||||
let updatedUser = await user.get('/user');
|
||||
usersChallengeTaskId = updatedUser.tasksOrder.todos[0];
|
||||
});
|
||||
@@ -134,7 +134,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
});
|
||||
await Bluebird.delay(1000);
|
||||
await sleep(1);
|
||||
let updatedUser = await user.get('/user');
|
||||
usersChallengeTaskId = updatedUser.tasksOrder.todos[0];
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
server,
|
||||
sleep,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
@@ -94,4 +96,49 @@ describe('POST /tasks/:taskId/checklist/:itemId/score', () => {
|
||||
message: t('checklistItemNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
context('sending task activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('sends task activity webhooks', async () => {
|
||||
let uuid = generateUUID();
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'taskActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
checklistScored: true,
|
||||
updated: false,
|
||||
},
|
||||
});
|
||||
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
});
|
||||
|
||||
let updatedTask = await user.post(`/tasks/${task.id}/checklist`, {
|
||||
text: 'checklist item text',
|
||||
});
|
||||
|
||||
let checklistItem = updatedTask.checklist[0];
|
||||
|
||||
let scoredItemTask = await user.post(`/tasks/${task.id}/checklist/${checklistItem.id}/score`);
|
||||
|
||||
await sleep();
|
||||
|
||||
let body = server.getWebhookData(uuid);
|
||||
|
||||
expect(body.type).to.eql('checklistScored');
|
||||
expect(body.task).to.eql(scoredItemTask);
|
||||
expect(body.item).to.eql(scoredItemTask.checklist[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
each,
|
||||
map,
|
||||
} from 'lodash';
|
||||
import Bluebird from 'bluebird';
|
||||
import {
|
||||
sha1MakeSalt,
|
||||
sha1Encrypt as sha1EncryptPassword,
|
||||
@@ -104,7 +103,7 @@ describe('DELETE /user', () => {
|
||||
password,
|
||||
});
|
||||
|
||||
await Bluebird.all(map(ids, id => {
|
||||
await Promise.all(map(ids, id => {
|
||||
return expect(checkExistence('tasks', id)).to.eventually.eql(false);
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -34,6 +34,8 @@ describe('GET /user', () => {
|
||||
expect(returnedUser._id).to.equal(user._id);
|
||||
expect(returnedUser.achievements).to.exist;
|
||||
expect(returnedUser.items.mounts).to.exist;
|
||||
// Notifications are always returned
|
||||
expect(returnedUser.notifications).to.exist;
|
||||
expect(returnedUser.stats).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
24
test/api/v3/integration/user/GET-user_inAppRewards.test.js
Normal file
24
test/api/v3/integration/user/GET-user_inAppRewards.test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /user/in-app-rewards', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns the reward items available for purchase', async () => {
|
||||
let buyList = await user.get('/user/in-app-rewards');
|
||||
|
||||
expect(_.find(buyList, item => {
|
||||
return item.text === t('armorWarrior1Text');
|
||||
})).to.exist;
|
||||
|
||||
expect(_.find(buyList, item => {
|
||||
return item.text === t('armorWarrior2Text');
|
||||
})).to.not.exist;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /user/toggle-pinned-item', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('cannot unpin potion', async () => {
|
||||
await expect(user.get('/user/toggle-pinned-item/potion/potion'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('cannotUnpinArmoirPotion'),
|
||||
});
|
||||
});
|
||||
|
||||
it('can pin shield_rogue_5', async () => {
|
||||
let result = await user.get('/user/toggle-pinned-item/marketGear/gear.flat.shield_rogue_5');
|
||||
|
||||
expect(result.pinnedItems.length).to.be.eql(user.pinnedItems.length + 1);
|
||||
});
|
||||
});
|
||||
148
test/api/v3/integration/user/POST-move-pinned-item.js
Normal file
148
test/api/v3/integration/user/POST-move-pinned-item.js
Normal file
@@ -0,0 +1,148 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
import getOfficialPinnedItems from '../../../../../website/common/script/libs/getOfficialPinnedItems.js';
|
||||
|
||||
describe('POST /user/move-pinned-item/:path/move/to/:position', () => {
|
||||
let user;
|
||||
let officialPinnedItems;
|
||||
let officialPinnedItemPaths;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
officialPinnedItems = getOfficialPinnedItems(user);
|
||||
|
||||
officialPinnedItemPaths = [];
|
||||
// officialPinnedItems are returned in { type: ..., path:... } format but we just need the paths for testPinnedItemsOrder
|
||||
if (officialPinnedItems.length > 0) {
|
||||
officialPinnedItemPaths = officialPinnedItems.map(item => item.path);
|
||||
}
|
||||
});
|
||||
|
||||
it('adjusts the order of pinned items with no order mismatch', async () => {
|
||||
let testPinnedItems = [
|
||||
{ type: 'armoire', path: 'armoire' },
|
||||
{ type: 'potion', path: 'potion' },
|
||||
{ type: 'marketGear', path: 'gear.flat.weapon_warrior_1' },
|
||||
{ type: 'marketGear', path: 'gear.flat.head_warrior_1' },
|
||||
{ type: 'marketGear', path: 'gear.flat.armor_warrior_1' },
|
||||
{ type: 'hatchingPotions', path: 'hatchingPotions.Golden' },
|
||||
{ type: 'marketGear', path: 'gear.flat.shield_warrior_1' },
|
||||
{ type: 'card', path: 'cardTypes.greeting' },
|
||||
{ type: 'potion', path: 'hatchingPotions.Golden' },
|
||||
{ type: 'card', path: 'cardTypes.thankyou' },
|
||||
{ type: 'food', path: 'food.Saddle' },
|
||||
];
|
||||
|
||||
let testPinnedItemsOrder = [
|
||||
'hatchingPotions.Golden',
|
||||
'cardTypes.greeting',
|
||||
'armoire',
|
||||
'gear.flat.weapon_warrior_1',
|
||||
'gear.flat.head_warrior_1',
|
||||
'cardTypes.thankyou',
|
||||
'gear.flat.armor_warrior_1',
|
||||
'food.Saddle',
|
||||
'gear.flat.shield_warrior_1',
|
||||
'potion',
|
||||
];
|
||||
|
||||
// For this test put seasonal items at the end so they stay out of the way
|
||||
testPinnedItemsOrder = testPinnedItemsOrder.concat(officialPinnedItemPaths);
|
||||
|
||||
await user.update({
|
||||
pinnedItems: testPinnedItems,
|
||||
pinnedItemsOrder: testPinnedItemsOrder,
|
||||
});
|
||||
|
||||
let res = await user.post('/user/move-pinned-item/armoire/move/to/5');
|
||||
await user.sync();
|
||||
|
||||
expect(user.pinnedItemsOrder[5]).to.equal('armoire');
|
||||
expect(user.pinnedItemsOrder[2]).to.equal('gear.flat.weapon_warrior_1');
|
||||
|
||||
// We have done nothing to change pinnedItems!
|
||||
expect(user.pinnedItems).to.deep.equal(testPinnedItems);
|
||||
|
||||
let expectedResponse = [
|
||||
'hatchingPotions.Golden',
|
||||
'cardTypes.greeting',
|
||||
'gear.flat.weapon_warrior_1',
|
||||
'gear.flat.head_warrior_1',
|
||||
'cardTypes.thankyou',
|
||||
'armoire',
|
||||
'gear.flat.armor_warrior_1',
|
||||
'food.Saddle',
|
||||
'gear.flat.shield_warrior_1',
|
||||
'potion',
|
||||
];
|
||||
expectedResponse = expectedResponse.concat(officialPinnedItemPaths);
|
||||
|
||||
expect(res).to.eql(expectedResponse);
|
||||
});
|
||||
|
||||
it('adjusts the order of pinned items with order mismatch', async () => {
|
||||
let testPinnedItems = [
|
||||
{ type: 'card', path: 'cardTypes.thankyou' },
|
||||
{ type: 'card', path: 'cardTypes.greeting' },
|
||||
{ type: 'potion', path: 'potion' },
|
||||
{ type: 'armoire', path: 'armoire' },
|
||||
];
|
||||
|
||||
let testPinnedItemsOrder = [
|
||||
'armoire',
|
||||
'potion',
|
||||
];
|
||||
|
||||
await user.update({
|
||||
pinnedItems: testPinnedItems,
|
||||
pinnedItemsOrder: testPinnedItemsOrder,
|
||||
});
|
||||
|
||||
let res = await user.post('/user/move-pinned-item/armoire/move/to/1');
|
||||
await user.sync();
|
||||
|
||||
// The basic test
|
||||
expect(user.pinnedItemsOrder[1]).to.equal('armoire');
|
||||
|
||||
// potion is now the last item because the 2 unacounted for cards show up
|
||||
// at the beginning of the order
|
||||
expect(user.pinnedItemsOrder[user.pinnedItemsOrder.length - 1]).to.equal('potion');
|
||||
|
||||
let expectedResponse = [
|
||||
'cardTypes.thankyou',
|
||||
'cardTypes.greeting',
|
||||
'potion',
|
||||
];
|
||||
// inAppRewards is used here and will by default put these seasonal items in the front like this:
|
||||
expectedResponse = officialPinnedItemPaths.concat(expectedResponse);
|
||||
// now put "armoire" in where we moved it:
|
||||
expectedResponse.splice(1, 0, 'armoire');
|
||||
|
||||
expect(res).to.eql(expectedResponse);
|
||||
});
|
||||
|
||||
it('cannot move pinned item that you do not have pinned', async () => {
|
||||
let testPinnedItems = [
|
||||
{ type: 'potion', path: 'potion' },
|
||||
{ type: 'armoire', path: 'armoire' },
|
||||
];
|
||||
|
||||
let testPinnedItemsOrder = [
|
||||
'armoire',
|
||||
'potion',
|
||||
];
|
||||
|
||||
await user.update({
|
||||
pinnedItems: testPinnedItems,
|
||||
pinnedItemsOrder: testPinnedItemsOrder,
|
||||
});
|
||||
|
||||
try {
|
||||
await user.post('/user/move-pinned-item/cardTypes.thankyou/move/to/1');
|
||||
} catch (err) {
|
||||
expect(err).to.exist;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -180,11 +180,42 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
members: 1,
|
||||
});
|
||||
await groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 13});
|
||||
|
||||
await groupLeader.post('/user/class/cast/earth');
|
||||
await sleep(1);
|
||||
await group.sync();
|
||||
expect(group.chat[0]).to.exist;
|
||||
expect(group.chat[0].uuid).to.equal('system');
|
||||
const groupMessages = await groupLeader.get(`/groups/${group._id}/chat`);
|
||||
|
||||
expect(groupMessages[0]).to.exist;
|
||||
expect(groupMessages[0].uuid).to.equal('system');
|
||||
});
|
||||
|
||||
it('Ethereal Surge does not recover mp of other mages', async () => {
|
||||
let group = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 4,
|
||||
});
|
||||
|
||||
let promises = [];
|
||||
promises.push(group.groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 20}));
|
||||
promises.push(group.members[0].update({'stats.mp': 0, 'stats.class': 'warrior', 'stats.lvl': 20}));
|
||||
promises.push(group.members[1].update({'stats.mp': 0, 'stats.class': 'wizard', 'stats.lvl': 20}));
|
||||
promises.push(group.members[2].update({'stats.mp': 0, 'stats.class': 'rogue', 'stats.lvl': 20}));
|
||||
promises.push(group.members[3].update({'stats.mp': 0, 'stats.class': 'healer', 'stats.lvl': 20}));
|
||||
await Promise.all(promises);
|
||||
|
||||
await group.groupLeader.post('/user/class/cast/mpheal');
|
||||
|
||||
promises = [];
|
||||
promises.push(group.members[0].sync());
|
||||
promises.push(group.members[1].sync());
|
||||
promises.push(group.members[2].sync());
|
||||
promises.push(group.members[3].sync());
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
|
||||
expect(group.members[1].stats.mp).to.equal(0); // wizard
|
||||
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
|
||||
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
|
||||
});
|
||||
|
||||
it('cast bulk', async () => {
|
||||
@@ -197,7 +228,7 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
await groupLeader.post('/user/class/cast/earth', {quantity: 2});
|
||||
|
||||
await sleep(1);
|
||||
await group.sync();
|
||||
group = await groupLeader.get(`/groups/${group._id}`);
|
||||
|
||||
expect(group.chat[0]).to.exist;
|
||||
expect(group.chat[0].uuid).to.equal('system');
|
||||
@@ -258,11 +289,31 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
expect(user.achievements.birthday).to.equal(1);
|
||||
});
|
||||
|
||||
it('passes correct target to spell when targetType === \'task\'', async () => {
|
||||
await user.update({'stats.class': 'wizard', 'stats.lvl': 11});
|
||||
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
let result = await user.post(`/user/class/cast/fireball?targetId=${task._id}`);
|
||||
|
||||
expect(result.task._id).to.equal(task._id);
|
||||
});
|
||||
|
||||
it('passes correct target to spell when targetType === \'self\'', async () => {
|
||||
await user.update({'stats.class': 'wizard', 'stats.lvl': 14, 'stats.mp': 50});
|
||||
|
||||
let result = await user.post('/user/class/cast/frost');
|
||||
|
||||
expect(result.user.stats.mp).to.equal(10);
|
||||
});
|
||||
|
||||
|
||||
// TODO find a way to have sinon working in integration tests
|
||||
// it doesn't work when tests are running separately from server
|
||||
it('passes correct target to spell when targetType === \'task\'');
|
||||
it('passes correct target to spell when targetType === \'tasks\'');
|
||||
it('passes correct target to spell when targetType === \'self\'');
|
||||
it('passes correct target to spell when targetType === \'party\'');
|
||||
it('passes correct target to spell when targetType === \'user\'');
|
||||
it('passes correct target to spell when targetType === \'party\' and user is not in a party');
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
server,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import content from '../../../../../website/common/script/content';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /user/feed/:pet/:food', () => {
|
||||
let user;
|
||||
@@ -37,4 +40,41 @@ describe('POST /user/feed/:pet/:food', () => {
|
||||
expect(user.items.food.Milk).to.equal(1);
|
||||
expect(user.items.pets['Wolf-Base']).to.equal(7);
|
||||
});
|
||||
|
||||
context('sending user activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('sends user activity webhook when a new mount is raised', async () => {
|
||||
let uuid = generateUUID();
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'userActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
mountRaised: true,
|
||||
},
|
||||
});
|
||||
|
||||
await user.update({
|
||||
'items.pets.Wolf-Base': 49,
|
||||
'items.food.Milk': 2,
|
||||
});
|
||||
let res = await user.post('/user/feed/Wolf-Base/Milk');
|
||||
|
||||
await sleep();
|
||||
|
||||
let body = server.getWebhookData(uuid);
|
||||
|
||||
expect(body.type).to.eql('mountRaised');
|
||||
expect(body.pet).to.eql('Wolf-Base');
|
||||
expect(body.message).to.eql(res.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
server,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /user/hatch/:egg/:hatchingPotion', () => {
|
||||
let user;
|
||||
@@ -28,4 +31,41 @@ describe('POST /user/hatch/:egg/:hatchingPotion', () => {
|
||||
data: JSON.parse(JSON.stringify(user.items)),
|
||||
});
|
||||
});
|
||||
|
||||
context('sending user activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('sends user activity webhook when a new pet is hatched', async () => {
|
||||
let uuid = generateUUID();
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'userActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
petHatched: true,
|
||||
},
|
||||
});
|
||||
|
||||
await user.update({
|
||||
'items.eggs.Wolf': 1,
|
||||
'items.hatchingPotions.Base': 1,
|
||||
});
|
||||
let res = await user.post('/user/hatch/Wolf/Base');
|
||||
|
||||
await sleep();
|
||||
|
||||
let body = server.getWebhookData(uuid);
|
||||
|
||||
expect(body.type).to.eql('petHatched');
|
||||
expect(body.pet).to.eql('Wolf-Base');
|
||||
expect(body.message).to.eql(res.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,10 +30,12 @@ describe('POST /user/release-both', () => {
|
||||
'items.currentPet': animal,
|
||||
'items.pets': loadPets(),
|
||||
'items.mounts': loadMounts(),
|
||||
'achievements.triadBingo': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when user balance is too low and user does not have triadBingo', async () => {
|
||||
// @TODO: Traid is now free. Add this back if we need
|
||||
xit('returns an error when user balance is too low and user does not have triadBingo', async () => {
|
||||
await expect(user.post('/user/release-both'))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
@@ -45,9 +47,7 @@ describe('POST /user/release-both', () => {
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('grants triad bingo with gems', async () => {
|
||||
await user.update({
|
||||
balance: 1.5,
|
||||
});
|
||||
await user.update();
|
||||
|
||||
let response = await user.post('/user/release-both');
|
||||
await user.sync();
|
||||
|
||||
@@ -27,6 +27,33 @@ describe('PUT /user', () => {
|
||||
expect(user.stats.hp).to.eql(14);
|
||||
});
|
||||
|
||||
it('tags must be an array', async () => {
|
||||
await expect(user.put('/user', {
|
||||
tags: {
|
||||
tag: true,
|
||||
},
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'mustBeArray',
|
||||
});
|
||||
});
|
||||
|
||||
it('update tags', async () => {
|
||||
let userTags = user.tags;
|
||||
|
||||
await user.put('/user', {
|
||||
tags: [...user.tags, {
|
||||
name: 'new tag',
|
||||
}],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.tags.length).to.be.eql(userTags.length + 1);
|
||||
});
|
||||
|
||||
|
||||
it('profile.name cannot be an empty string or null', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': ' ', // string should be trimmed
|
||||
|
||||
@@ -357,6 +357,21 @@ describe('POST /user/auth/local/register', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sanitizes email params to a lowercase string before creating the user', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = 'ISANEmAiL@ExAmPle.coM';
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.auth.local.email).to.equal(email.toLowerCase());
|
||||
});
|
||||
|
||||
it('fails on a habitica.com email', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@habitica.com`;
|
||||
|
||||
@@ -13,7 +13,7 @@ import nconf from 'nconf';
|
||||
const ENDPOINT = '/user/auth/update-email';
|
||||
|
||||
describe('PUT /user/auth/update-email', () => {
|
||||
let newEmail = 'some-new-email_2@example.net';
|
||||
let newEmail = 'SOmE-nEw-emAIl_2@example.net';
|
||||
let oldPassword = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
|
||||
|
||||
context('Local Authenticaion User', async () => {
|
||||
@@ -53,14 +53,15 @@ describe('PUT /user/auth/update-email', () => {
|
||||
});
|
||||
|
||||
it('changes email if new email and existing password are provided', async () => {
|
||||
let lowerCaseNewEmail = newEmail.toLowerCase();
|
||||
let response = await user.put(ENDPOINT, {
|
||||
newEmail,
|
||||
password: oldPassword,
|
||||
});
|
||||
expect(response).to.eql({ email: 'some-new-email_2@example.net' });
|
||||
expect(response.email).to.eql(lowerCaseNewEmail);
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.email).to.eql(newEmail);
|
||||
expect(user.auth.local.email).to.eql(lowerCaseNewEmail);
|
||||
});
|
||||
|
||||
it('rejects if email is already taken', async () => {
|
||||
|
||||
@@ -116,6 +116,7 @@ describe('POST /user/webhook', () => {
|
||||
let webhook = await user.post('/user/webhook', body);
|
||||
|
||||
expect(webhook.options).to.eql({
|
||||
checklistScored: false,
|
||||
created: false,
|
||||
updated: false,
|
||||
deleted: false,
|
||||
@@ -126,6 +127,7 @@ describe('POST /user/webhook', () => {
|
||||
it('can set taskActivity options', async () => {
|
||||
body.type = 'taskActivity';
|
||||
body.options = {
|
||||
checklistScored: true,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -135,6 +137,7 @@ describe('POST /user/webhook', () => {
|
||||
let webhook = await user.post('/user/webhook', body);
|
||||
|
||||
expect(webhook.options).to.eql({
|
||||
checklistScored: true,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -145,6 +148,7 @@ describe('POST /user/webhook', () => {
|
||||
it('discards extra properties in taskActivity options', async () => {
|
||||
body.type = 'taskActivity';
|
||||
body.options = {
|
||||
checklistScored: false,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -156,6 +160,7 @@ describe('POST /user/webhook', () => {
|
||||
|
||||
expect(webhook.options.foo).to.not.exist;
|
||||
expect(webhook.options).to.eql({
|
||||
checklistScored: false,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -218,4 +223,16 @@ describe('POST /user/webhook', () => {
|
||||
groupId: body.options.groupId,
|
||||
});
|
||||
});
|
||||
|
||||
it('discards extra properties in globalActivity options', async () => {
|
||||
body.type = 'globalActivity';
|
||||
body.options = {
|
||||
foo: 'bar',
|
||||
};
|
||||
|
||||
let webhook = await user.post('/user/webhook', body);
|
||||
|
||||
expect(webhook.options.foo).to.not.exist;
|
||||
expect(webhook.options).to.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,6 +95,7 @@ describe('PUT /user/webhook/:id', () => {
|
||||
let webhook = await user.put(`/user/webhook/${webhookToUpdate.id}`, {type, options});
|
||||
|
||||
expect(webhook.options).to.eql({
|
||||
checklistScored: false, // starting value
|
||||
created: true, // starting value
|
||||
updated: false,
|
||||
deleted: true,
|
||||
|
||||
@@ -32,4 +32,11 @@ describe('GET /world-state', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a string representing the current season for NPC sprites', async () => {
|
||||
const res = await requester().get('/world-state');
|
||||
|
||||
expect(res).to.have.nested.property('npcImageSuffix');
|
||||
expect(res.npcImageSuffix).to.be.a('string');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable global-require */
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import Bluebird from 'bluebird';
|
||||
import requireAgain from 'require-again';
|
||||
import { recoverCron, cron } from '../../../../../website/server/libs/cron';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
@@ -24,7 +23,7 @@ describe('cron', () => {
|
||||
local: {
|
||||
username: 'username',
|
||||
lowerCaseUsername: 'username',
|
||||
email: 'email@email.email',
|
||||
email: 'email@example.com',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
@@ -83,7 +82,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not reset plan.gemsBought within the month', () => {
|
||||
let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix());
|
||||
let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').toDate());
|
||||
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
||||
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
@@ -118,21 +117,6 @@ describe('cron', () => {
|
||||
expect(user.purchased.plan.consecutive.offset).to.equal(1);
|
||||
});
|
||||
|
||||
it('increments plan.consecutive.trinkets when user has reached a month that is a multiple of 3', () => {
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
user.purchased.plan.consecutive.offset = 1;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user.purchased.plan.consecutive.offset).to.equal(0);
|
||||
});
|
||||
|
||||
it('increments plan.consecutive.trinkets multiple times if user has been absent with continuous subscription', () => {
|
||||
user.purchased.plan.dateUpdated = moment().subtract(6, 'months').toDate();
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
});
|
||||
|
||||
it('does not award unearned plan.consecutive.trinkets if subscription ended during an absence', () => {
|
||||
user.purchased.plan.dateUpdated = moment().subtract(6, 'months').toDate();
|
||||
user.purchased.plan.dateTerminated = moment().subtract(3, 'months').toDate();
|
||||
@@ -144,21 +128,6 @@ describe('cron', () => {
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
});
|
||||
|
||||
it('increments plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', () => {
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
user.purchased.plan.consecutive.offset = 1;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
expect(user.purchased.plan.consecutive.offset).to.equal(0);
|
||||
});
|
||||
|
||||
it('increments plan.consecutive.gemCapExtra multiple times if user has been absent with continuous subscription', () => {
|
||||
user.purchased.plan.dateUpdated = moment().subtract(6, 'months').toDate();
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
});
|
||||
|
||||
it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', () => {
|
||||
user.purchased.plan.consecutive.gemCapExtra = 25;
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
@@ -185,6 +154,465 @@ describe('cron', () => {
|
||||
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
||||
expect(user.purchased.plan.consecutive.offset).to.equal(0);
|
||||
});
|
||||
|
||||
describe('for a 1-month recurring subscription', () => {
|
||||
let clock;
|
||||
// create a user that will be used for all of these tests without a reset before each
|
||||
let user1 = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username1',
|
||||
lowerCaseUsername: 'username1',
|
||||
email: 'email1@example.com',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
});
|
||||
// user1 has a 1-month recurring subscription starting today
|
||||
user1.purchased.plan.customerId = 'subscribedId';
|
||||
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||
user1.purchased.plan.planId = 'basic';
|
||||
user1.purchased.plan.consecutive.count = 0;
|
||||
user1.purchased.plan.consecutive.offset = 0;
|
||||
user1.purchased.plan.consecutive.trinkets = 0;
|
||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||
|
||||
it('does not increment consecutive benefits after the first month', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects e.g., from time zone oddness.
|
||||
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits after the second month', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months').add(2, 'days').toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects e.g., from time zone oddness.
|
||||
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(2);
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits after the third month', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months').add(2, 'days').toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects e.g., from time zone oddness.
|
||||
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(3);
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits after the fourth month', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months').add(2, 'days').toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects e.g., from time zone oddness.
|
||||
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(4);
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(10, 'months').add(2, 'days').toDate());
|
||||
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(10);
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 3-month recurring subscription', () => {
|
||||
let clock;
|
||||
let user3 = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username3',
|
||||
lowerCaseUsername: 'username3',
|
||||
email: 'email3@example.com',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
});
|
||||
// user3 has a 3-month recurring subscription starting today
|
||||
user3.purchased.plan.customerId = 'subscribedId';
|
||||
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||
user3.purchased.plan.planId = 'basic_3mo';
|
||||
user3.purchased.plan.consecutive.count = 0;
|
||||
user3.purchased.plan.consecutive.offset = 3;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the middle of the period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(3);
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(4);
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(5, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(5);
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(6, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(6);
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(7);
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(10, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(10);
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 6-month recurring subscription', () => {
|
||||
let clock;
|
||||
let user6 = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username6',
|
||||
lowerCaseUsername: 'username6',
|
||||
email: 'email6@example.com',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
});
|
||||
// user6 has a 6-month recurring subscription starting today
|
||||
user6.purchased.plan.customerId = 'subscribedId';
|
||||
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||
user6.purchased.plan.planId = 'google_6mo';
|
||||
user6.purchased.plan.consecutive.count = 0;
|
||||
user6.purchased.plan.consecutive.offset = 6;
|
||||
user6.purchased.plan.consecutive.trinkets = 2;
|
||||
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||
expect(user6.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(6, 'months').add(2, 'days').toDate());
|
||||
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||
expect(user6.purchased.plan.consecutive.count).to.equal(6);
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months').add(2, 'days').toDate());
|
||||
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||
expect(user6.purchased.plan.consecutive.count).to.equal(7);
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(13, 'months').add(2, 'days').toDate());
|
||||
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||
expect(user6.purchased.plan.consecutive.count).to.equal(13);
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(6);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(19, 'months').add(2, 'days').toDate());
|
||||
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||
expect(user6.purchased.plan.consecutive.count).to.equal(19);
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(8);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 12-month recurring subscription', () => {
|
||||
let clock;
|
||||
|
||||
let user12 = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username12',
|
||||
lowerCaseUsername: 'username12',
|
||||
email: 'email12@example.com',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
});
|
||||
// user12 has a 12-month recurring subscription starting today
|
||||
user12.purchased.plan.customerId = 'subscribedId';
|
||||
user12.purchased.plan.dateUpdated = moment().toDate();
|
||||
user12.purchased.plan.planId = 'basic_12mo';
|
||||
user12.purchased.plan.consecutive.count = 0;
|
||||
user12.purchased.plan.consecutive.offset = 12;
|
||||
user12.purchased.plan.consecutive.trinkets = 4;
|
||||
user12.purchased.plan.consecutive.gemCapExtra = 20;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||
expect(user12.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(12, 'months').add(2, 'days').toDate());
|
||||
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||
expect(user12.purchased.plan.consecutive.count).to.equal(12);
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(13, 'months').add(2, 'days').toDate());
|
||||
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||
expect(user12.purchased.plan.consecutive.count).to.equal(13);
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(8);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(25, 'months').add(2, 'days').toDate());
|
||||
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||
expect(user12.purchased.plan.consecutive.count).to.equal(25);
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(12);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(37, 'months').add(2, 'days').toDate());
|
||||
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||
expect(user12.purchased.plan.consecutive.count).to.equal(37);
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(16);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 3-month gift subscription (non-recurring)', () => {
|
||||
let clock;
|
||||
let user3g = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username3g',
|
||||
lowerCaseUsername: 'username3g',
|
||||
email: 'email3g@example.com',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
});
|
||||
// user3g has a 3-month gift subscription starting today
|
||||
user3g.purchased.plan.customerId = 'Gift';
|
||||
user3g.purchased.plan.dateUpdated = moment().toDate();
|
||||
user3g.purchased.plan.dateTerminated = moment().add(3, 'months').toDate();
|
||||
user3g.purchased.plan.planId = null;
|
||||
user3g.purchased.plan.consecutive.count = 0;
|
||||
user3g.purchased.plan.consecutive.offset = 3;
|
||||
user3g.purchased.plan.consecutive.trinkets = 1;
|
||||
user3g.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the gift subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3g, tasksByType, daysMissed, analytics});
|
||||
expect(user3g.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month of the gift subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3g, tasksByType, daysMissed, analytics});
|
||||
expect(user3g.purchased.plan.consecutive.count).to.equal(2);
|
||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the third month of the gift subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3g, tasksByType, daysMissed, analytics});
|
||||
expect(user3g.purchased.plan.consecutive.count).to.equal(3);
|
||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the month after the gift subscription has ended', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months').add(2, 'days').toDate());
|
||||
cron({user: user3g, tasksByType, daysMissed, analytics});
|
||||
expect(user3g.purchased.plan.consecutive.count).to.equal(0); // subscription has been erased by now
|
||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(0); // erased
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 6-month recurring subscription where the user has incorrect consecutive month data from prior bugs', () => {
|
||||
let clock;
|
||||
let user6x = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username6x',
|
||||
lowerCaseUsername: 'username6x',
|
||||
email: 'email6x@example.com',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
});
|
||||
// user6x has a 6-month recurring subscription starting 8 months in the past before issue #4819 was fixed
|
||||
user6x.purchased.plan.customerId = 'subscribedId';
|
||||
user6x.purchased.plan.dateUpdated = moment().toDate();
|
||||
user6x.purchased.plan.planId = 'basic_6mo';
|
||||
user6x.purchased.plan.consecutive.count = 8;
|
||||
user6x.purchased.plan.consecutive.offset = 0;
|
||||
user6x.purchased.plan.consecutive.trinkets = 3;
|
||||
user6x.purchased.plan.consecutive.gemCapExtra = 15;
|
||||
|
||||
it('increments consecutive benefits in the first month since the fix for #4819 goes live', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||
cron({user: user6x, tasksByType, daysMissed, analytics});
|
||||
expect(user6x.purchased.plan.consecutive.count).to.equal(9);
|
||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month after the fix goes live', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months').add(2, 'days').toDate());
|
||||
cron({user: user6x, tasksByType, daysMissed, analytics});
|
||||
expect(user6x.purchased.plan.consecutive.count).to.equal(10);
|
||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(4);
|
||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the third month after the fix goes live', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months').add(2, 'days').toDate());
|
||||
cron({user: user6x, tasksByType, daysMissed, analytics});
|
||||
expect(user6x.purchased.plan.consecutive.count).to.equal(11);
|
||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(3);
|
||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits in the seventh month after the fix goes live', () => {
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months').add(2, 'days').toDate());
|
||||
cron({user: user6x, tasksByType, daysMissed, analytics});
|
||||
expect(user6x.purchased.plan.consecutive.count).to.equal(15);
|
||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(7);
|
||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('end of the month perks when user is not subscribed', () => {
|
||||
@@ -1349,7 +1777,7 @@ describe('recoverCron', () => {
|
||||
local: {
|
||||
username: 'username',
|
||||
lowerCaseUsername: 'username',
|
||||
email: 'email@email.email',
|
||||
email: 'email@example.com',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
@@ -1363,7 +1791,7 @@ describe('recoverCron', () => {
|
||||
});
|
||||
|
||||
it('throws an error if user cannot be found', async () => {
|
||||
execStub.returns(Bluebird.resolve(null));
|
||||
execStub.returns(Promise.resolve(null));
|
||||
|
||||
try {
|
||||
await recoverCron(status, locals);
|
||||
@@ -1374,8 +1802,8 @@ describe('recoverCron', () => {
|
||||
});
|
||||
|
||||
it('increases status.times count and reruns up to 4 times', async () => {
|
||||
execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'}));
|
||||
execStub.onCall(4).returns(Bluebird.resolve({_cronSignature: 'NOT_RUNNING'}));
|
||||
execStub.returns(Promise.resolve({_cronSignature: 'RUNNING_CRON'}));
|
||||
execStub.onCall(4).returns(Promise.resolve({_cronSignature: 'NOT_RUNNING'}));
|
||||
|
||||
await recoverCron(status, locals);
|
||||
|
||||
@@ -1384,7 +1812,7 @@ describe('recoverCron', () => {
|
||||
});
|
||||
|
||||
it('throws an error if recoverCron runs 5 times', async () => {
|
||||
execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'}));
|
||||
execStub.returns(Promise.resolve({_cronSignature: 'RUNNING_CRON'}));
|
||||
|
||||
try {
|
||||
await recoverCron(status, locals);
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
generateGroup,
|
||||
} from '../../../../../../helpers/api-unit.helper.js';
|
||||
import { model as User } from '../../../../../../../website/server/models/user';
|
||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
||||
import payments from '../../../../../../../website/server/libs/payments';
|
||||
import amzLib from '../../../../../../../website/server/libs/payments/amazon';
|
||||
import payments from '../../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../../website/common';
|
||||
import { createNonLeaderGroupMember } from '../paymentHelpers';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { model as User } from '../../../../../../../website/server/models/user';
|
||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
||||
import payments from '../../../../../../../website/server/libs/payments';
|
||||
import amzLib from '../../../../../../../website/server/libs/payments/amazon';
|
||||
import payments from '../../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../../website/common';
|
||||
|
||||
const i18n = common.i18n;
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
} from '../../../../../../helpers/api-unit.helper.js';
|
||||
import { model as User } from '../../../../../../../website/server/models/user';
|
||||
import { model as Coupon } from '../../../../../../../website/server/models/coupon';
|
||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
||||
import payments from '../../../../../../../website/server/libs/payments';
|
||||
import amzLib from '../../../../../../../website/server/libs/payments/amazon';
|
||||
import payments from '../../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../../website/common';
|
||||
|
||||
const i18n = common.i18n;
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
} from '../../../../../../helpers/api-unit.helper.js';
|
||||
import { model as User } from '../../../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../../../website/server/models/group';
|
||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
||||
import payments from '../../../../../../../website/server/libs/payments';
|
||||
import amzLib from '../../../../../../../website/server/libs/payments/amazon';
|
||||
import payments from '../../../../../../../website/server/libs/payments/payments';
|
||||
|
||||
describe('#upgradeGroupPlan', () => {
|
||||
let spy, data, user, group, uuidString;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable camelcase */
|
||||
import iapModule from '../../../../../website/server/libs/inAppPurchases';
|
||||
import payments from '../../../../../website/server/libs/payments';
|
||||
import applePayments from '../../../../../website/server/libs/applePayments';
|
||||
import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import {model as User} from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import iapModule from '../../../../../../website/server/libs/inAppPurchases';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
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 moment from 'moment';
|
||||
|
||||
const i18n = common.i18n;
|
||||
@@ -57,6 +57,18 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if getPurchaseData is invalid', async () => {
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData').returns([]);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_NO_ITEM_PURCHASED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
@@ -69,27 +81,76 @@ describe('Apple Payments', () => {
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('purchases gems', async () => {
|
||||
it('errors if amount does not exist', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
.returns([{productId: 'badProduct',
|
||||
transactionId: token,
|
||||
}]);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_INVALID_ITEM,
|
||||
});
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
amount: 5.25,
|
||||
headers,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
const gemsCanPurchase = [
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.4gems',
|
||||
amount: 1,
|
||||
},
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.20gems',
|
||||
amount: 5.25,
|
||||
},
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||
amount: 5.25,
|
||||
},
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.42gems',
|
||||
amount: 10.5,
|
||||
},
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.84gems',
|
||||
amount: 21,
|
||||
},
|
||||
];
|
||||
|
||||
gemsCanPurchase.forEach(gemTest => {
|
||||
it(`purchases ${gemTest.productId} gems`, async () => {
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
.returns([{productId: gemTest.productId,
|
||||
transactionId: token,
|
||||
}]);
|
||||
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
amount: gemTest.amount,
|
||||
headers,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe', () => {
|
||||
@@ -133,7 +194,16 @@ describe('Apple Payments', () => {
|
||||
iapModule.validate.restore();
|
||||
iapModule.isValidated.restore();
|
||||
iapModule.getPurchaseData.restore();
|
||||
payments.createSubscription.restore();
|
||||
if (payments.createSubscription.restore) payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if sku is empty', async () => {
|
||||
await expect(applePayments.subscribe('', user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if receipt is invalid', async () => {
|
||||
@@ -149,26 +219,69 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a user subscription', async () => {
|
||||
const subOptions = [
|
||||
{
|
||||
sku: 'subscription1month',
|
||||
subKey: 'basic_earned',
|
||||
},
|
||||
{
|
||||
sku: 'com.habitrpg.ios.habitica.subscription.3month',
|
||||
subKey: 'basic_3mo',
|
||||
},
|
||||
{
|
||||
sku: 'com.habitrpg.ios.habitica.subscription.6month',
|
||||
subKey: 'basic_6mo',
|
||||
},
|
||||
{
|
||||
sku: 'com.habitrpg.ios.habitica.subscription.12month',
|
||||
subKey: 'basic_12mo',
|
||||
},
|
||||
];
|
||||
subOptions.forEach(option => {
|
||||
it(`creates a user subscription for ${option.sku}`, async () => {
|
||||
iapModule.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({day: 1}).toDate(),
|
||||
productId: option.sku,
|
||||
transactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[option.subKey];
|
||||
|
||||
await applePayments.subscribe(option.sku, user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: token,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a user is already subscribed', async () => {
|
||||
payments.createSubscription.restore();
|
||||
user = new User();
|
||||
|
||||
await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: token,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
});
|
||||
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable camelcase */
|
||||
import iapModule from '../../../../../website/server/libs/inAppPurchases';
|
||||
import payments from '../../../../../website/server/libs/payments';
|
||||
import googlePayments from '../../../../../website/server/libs/googlePayments';
|
||||
import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import {model as User} from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import iapModule from '../../../../../../website/server/libs/inAppPurchases';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
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 moment from 'moment';
|
||||
|
||||
const i18n = common.i18n;
|
||||
@@ -1,7 +1,7 @@
|
||||
import moment from 'moment';
|
||||
|
||||
import * as sender from '../../../../../../../website/server/libs/email';
|
||||
import * as api from '../../../../../../../website/server/libs/payments';
|
||||
import * as api from '../../../../../../../website/server/libs/payments/payments';
|
||||
import { model as User } from '../../../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../../../website/server/models/group';
|
||||
import {
|
||||
|
||||
@@ -3,10 +3,10 @@ import stripeModule from 'stripe';
|
||||
import nconf from 'nconf';
|
||||
|
||||
import * as sender from '../../../../../../../website/server/libs/email';
|
||||
import * as api from '../../../../../../../website/server/libs/payments';
|
||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
||||
import stripePayments from '../../../../../../../website/server/libs/stripePayments';
|
||||
import paypalPayments from '../../../../../../../website/server/libs/paypalPayments';
|
||||
import * as api from '../../../../../../../website/server/libs/payments/payments';
|
||||
import amzLib from '../../../../../../../website/server/libs/payments/amazon';
|
||||
import paypalPayments from '../../../../../../../website/server/libs/payments/paypal';
|
||||
import stripePayments from '../../../../../../../website/server/libs/payments/stripe';
|
||||
import { model as User } from '../../../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../../../website/server/models/group';
|
||||
import {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import moment from 'moment';
|
||||
|
||||
import * as sender from '../../../../../website/server/libs/email';
|
||||
import * as api from '../../../../../website/server/libs/payments';
|
||||
import analytics from '../../../../../website/server/libs/analyticsService';
|
||||
import notifications from '../../../../../website/server/libs/pushNotifications';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { translate as t } from '../../../../helpers/api-v3-integration.helper';
|
||||
import * as sender from '../../../../../../website/server/libs/email';
|
||||
import * as api from '../../../../../../website/server/libs/payments/payments';
|
||||
import analytics from '../../../../../../website/server/libs/analyticsService';
|
||||
import notifications from '../../../../../../website/server/libs/pushNotifications';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import { translate as t } from '../../../../../helpers/api-v3-integration.helper';
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../helpers/api-unit.helper.js';
|
||||
} from '../../../../../helpers/api-unit.helper.js';
|
||||
|
||||
describe('payments/index', () => {
|
||||
let user, group, data, plan;
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable camelcase */
|
||||
import payments from '../../../../../../../website/server/libs/payments';
|
||||
import paypalPayments from '../../../../../../../website/server/libs/paypalPayments';
|
||||
import paypalPayments from '../../../../../../../website/server/libs/payments/paypal';
|
||||
import payments from '../../../../../../../website/server/libs/payments/payments';
|
||||
import { model as User } from '../../../../../../../website/server/models/user';
|
||||
|
||||
describe('checkout success', () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user