mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-04-04 21:12:01 -05:00
Compare commits
242 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
121fd38bd1 | ||
|
|
36d72f5f7a | ||
|
|
f1b8bd80e7 | ||
|
|
84d2ce6a3f | ||
|
|
76010e6c8f | ||
|
|
c707b6c99b | ||
|
|
e4bd466cc7 | ||
|
|
001b8eb894 | ||
|
|
9abcfe8614 | ||
|
|
bc6102551d | ||
|
|
959a3ff85b | ||
|
|
518b874f64 | ||
|
|
6cc359a935 | ||
|
|
514d35c0be | ||
|
|
13da92ea68 | ||
|
|
03c4d82b7d | ||
|
|
d905ab7f86 | ||
|
|
c6560b6b1b | ||
|
|
c61f660255 | ||
|
|
2f1b683ec9 | ||
|
|
47bb217068 | ||
|
|
f49fd05da1 | ||
|
|
b0341aa06f | ||
|
|
b07ec18e33 | ||
|
|
12930a2bac | ||
|
|
91f5c47d9d | ||
|
|
fe7850d10f | ||
|
|
c5c2da75bf | ||
|
|
969607cd3b | ||
|
|
2a1f52a359 | ||
|
|
47d9594679 | ||
|
|
97e40c81f3 | ||
|
|
c8b61a2f7d | ||
|
|
e9543f0d28 | ||
|
|
77b88490e4 | ||
|
|
7fc2500bfd | ||
|
|
fb229acb58 | ||
|
|
6ce83d1fa4 | ||
|
|
2be4815aea | ||
|
|
1dbc42f48a | ||
|
|
89279c8aed | ||
|
|
faedb13598 | ||
|
|
c0c74659c5 | ||
|
|
bf5ad2db1f | ||
|
|
7d99873960 | ||
|
|
e02ef00397 | ||
|
|
23c5c4211c | ||
|
|
69cc134fff | ||
|
|
ffd9400cb7 | ||
|
|
5be91ef842 | ||
|
|
3123183e46 | ||
|
|
49cca7a601 | ||
|
|
7fbd38d18c | ||
|
|
1f95376d39 | ||
|
|
2da0a1e88c | ||
|
|
afacd3e1cf | ||
|
|
a69b9e6705 | ||
|
|
e4e5d10316 | ||
|
|
27c38bdf45 | ||
|
|
ea24eeb019 | ||
|
|
55a8eef3e1 | ||
|
|
92cbb4a07d | ||
|
|
3f96d05365 | ||
|
|
0b72f6a613 | ||
|
|
5e1e6be518 | ||
|
|
472ec99291 | ||
|
|
0284e9a4e3 | ||
|
|
1a0721c078 | ||
|
|
6b6b548ac5 | ||
|
|
30f3d786bb | ||
|
|
07bbba6789 | ||
|
|
6afb2bd0d4 | ||
|
|
f1a3bd5001 | ||
|
|
3f6a13d209 | ||
|
|
3658e41fec | ||
|
|
c69d5c7ae6 | ||
|
|
747f9e6a99 | ||
|
|
7755ab090b | ||
|
|
9ed17df1e3 | ||
|
|
faeb040a83 | ||
|
|
0a1ae1375e | ||
|
|
9756030fa2 | ||
|
|
c66172b74b | ||
|
|
281f6d1806 | ||
|
|
237095d109 | ||
|
|
fa788f49fc | ||
|
|
0817cf96e1 | ||
|
|
97e1d75dce | ||
|
|
52bf20c27d | ||
|
|
5dbaf39aba | ||
|
|
66d402c985 | ||
|
|
8048146223 | ||
|
|
e2c07e458d | ||
|
|
90a9e8e192 | ||
|
|
f8039f48a6 | ||
|
|
04337f8e83 | ||
|
|
45297f8bf9 | ||
|
|
6f112c29f2 | ||
|
|
4d1edb363c | ||
|
|
4e303cc592 | ||
|
|
798a975185 | ||
|
|
eb2b46fc5d | ||
|
|
29854d3bdb | ||
|
|
f8751b002c | ||
|
|
cd545e08d5 | ||
|
|
f69bb4f023 | ||
|
|
847081d2b2 | ||
|
|
8112d46ea4 | ||
|
|
d13bded647 | ||
|
|
1de4ab3612 | ||
|
|
f9f22f313f | ||
|
|
f57eed85a8 | ||
|
|
10dd3318ab | ||
|
|
cbef83c14a | ||
|
|
59709a8590 | ||
|
|
f85f2a0c6d | ||
|
|
605a5a1d5c | ||
|
|
2d5d786c8e | ||
|
|
5efe5b7b10 | ||
|
|
3e92bb22fa | ||
|
|
1249b9d410 | ||
|
|
197aafe092 | ||
|
|
79829ca128 | ||
|
|
adaa1d9a3e | ||
|
|
3e6691dbbb | ||
|
|
046761b9aa | ||
|
|
0b0466b960 | ||
|
|
f8d4a2bd6b | ||
|
|
1af59a3770 | ||
|
|
bbcb13c91b | ||
|
|
d27dc46c50 | ||
|
|
679459b83b | ||
|
|
5a619773d5 | ||
|
|
ad76ab1315 | ||
|
|
15eb8db925 | ||
|
|
a0e92c5605 | ||
|
|
eac3e36c07 | ||
|
|
0b8def555b | ||
|
|
5f5fa5c2eb | ||
|
|
1eac8bbbbe | ||
|
|
49c7580cd4 | ||
|
|
dca958f2e2 | ||
|
|
eae5f0d605 | ||
|
|
6ab091645c | ||
|
|
d66041c280 | ||
|
|
de070a450a | ||
|
|
eaaab35f31 | ||
|
|
6a63f080ad | ||
|
|
c42f81b629 | ||
|
|
9a78a7b896 | ||
|
|
8b70721137 | ||
|
|
44ffbd716d | ||
|
|
5bfc3a5ff4 | ||
|
|
0ba5df4164 | ||
|
|
52a59c8192 | ||
|
|
c1a860494d | ||
|
|
395dafa127 | ||
|
|
bab41647f5 | ||
|
|
8582a67308 | ||
|
|
0d58fb0fd3 | ||
|
|
1d2482f8bc | ||
|
|
f4cf906127 | ||
|
|
559f9b1825 | ||
|
|
c7039bc9ea | ||
|
|
f929d36e1a | ||
|
|
254d1a3465 | ||
|
|
442aae8a35 | ||
|
|
bcb0ed0a5c | ||
|
|
a48b8f0e34 | ||
|
|
7eeeda2aae | ||
|
|
a5ad9c30f0 | ||
|
|
ac732b2c85 | ||
|
|
a56b2d68fb | ||
|
|
25b0ff38c4 | ||
|
|
dcc06931cc | ||
|
|
bc3ebbd095 | ||
|
|
e5b9581743 | ||
|
|
4b9fe49e3a | ||
|
|
ab4c8b0a46 | ||
|
|
f6c26fe869 | ||
|
|
80e9735b28 | ||
|
|
aa6f188bd9 | ||
|
|
e8b7660376 | ||
|
|
7d76622410 | ||
|
|
928e5f66c4 | ||
|
|
6a343535c0 | ||
|
|
f58f6acb44 | ||
|
|
64754777ed | ||
|
|
3b5e4b6d84 | ||
|
|
9383578cb8 | ||
|
|
474672ec64 | ||
|
|
25c6691793 | ||
|
|
3ea7b72024 | ||
|
|
2d6f05a9a4 | ||
|
|
28637286d6 | ||
|
|
874887b790 | ||
|
|
c977e5ebb5 | ||
|
|
f040e668f3 | ||
|
|
55a15f938c | ||
|
|
8c4f35daf4 | ||
|
|
8f38ce3424 | ||
|
|
b8f57a74d0 | ||
|
|
7ed26c0dbe | ||
|
|
e8f5b26d4d | ||
|
|
0273648b6b | ||
|
|
b6fdac8885 | ||
|
|
00e6389672 | ||
|
|
e02c669b61 | ||
|
|
f0cb7c6bf3 | ||
|
|
571ef0b309 | ||
|
|
74328d1bcc | ||
|
|
d34a9d828c | ||
|
|
2fd35b3a40 | ||
|
|
e27512f626 | ||
|
|
dbf9cb3b4e | ||
|
|
34c1245519 | ||
|
|
f602bfe438 | ||
|
|
9aa4b8aa64 | ||
|
|
5a150ebc5b | ||
|
|
cbe1892b50 | ||
|
|
13df60e0dd | ||
|
|
3ff7692528 | ||
|
|
111bba84dc | ||
|
|
b0d2b72b88 | ||
|
|
696317ea8a | ||
|
|
593178a46a | ||
|
|
f8fe16482d | ||
|
|
5108480ec5 | ||
|
|
95968b1b1c | ||
|
|
566569af98 | ||
|
|
6693e9fca9 | ||
|
|
431bde56d2 | ||
|
|
7cf17c0e63 | ||
|
|
49561bfc8c | ||
|
|
8cbbb58e78 | ||
|
|
905549e379 | ||
|
|
5d45c7209a | ||
|
|
371cddfe17 | ||
|
|
fcfac30caa | ||
|
|
b094fb1e52 | ||
|
|
a2dd82b6db | ||
|
|
e6071610e4 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
.git
|
||||
website
|
||||
@@ -14,7 +14,7 @@ files:
|
||||
owner: root
|
||||
group: users
|
||||
content: |
|
||||
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@3
|
||||
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@4
|
||||
container_commands:
|
||||
01_makeBabel:
|
||||
command: "touch /tmp/.babel.json"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"node": true,
|
||||
},
|
||||
"extends": [
|
||||
"habitrpg/es6",
|
||||
"habitrpg"
|
||||
"habitrpg",
|
||||
"habitrpg/esnext"
|
||||
],
|
||||
}
|
||||
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,5 +9,6 @@ Fixes put_issue_url_here
|
||||
|
||||
|
||||
[//]: # (Put User ID in here - found in Settings -> API)
|
||||
|
||||
----
|
||||
UUID:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '4.3.1'
|
||||
- '6'
|
||||
before_install:
|
||||
- npm install -g npm@3
|
||||
- npm install -g npm@4
|
||||
- if [ $REQUIRES_SERVER ]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10; echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list; sudo apt-get update; sudo apt-get install mongodb-org-server; fi
|
||||
before_script:
|
||||
- npm run test:build
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -17,20 +17,22 @@ RUN apt-get install -y \
|
||||
python
|
||||
|
||||
# Install NodeJS
|
||||
RUN curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
|
||||
RUN curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Install npm@latest
|
||||
RUN curl -sL https://www.npmjs.org/install.sh | sh
|
||||
|
||||
# Clean up package management
|
||||
RUN apt-get clean
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g npm@3
|
||||
RUN npm install -g gulp grunt-cli bower
|
||||
RUN npm install -g gulp grunt-cli bower mocha
|
||||
|
||||
# Clone Habitica repo and install dependencies
|
||||
WORKDIR /habitrpg
|
||||
RUN git clone https://github.com/HabitRPG/habitrpg.git /habitrpg
|
||||
RUN git clone https://github.com/HabitRPG/habitica.git /habitrpg
|
||||
RUN npm install
|
||||
RUN bower install --allow-root
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ module.exports = function(grunt) {
|
||||
files: [
|
||||
{expand: true, cwd: 'website/client-old/', src: 'favicon.ico', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/client-old/', src: 'favicon_192x192.png', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/static/sprites'},
|
||||
{expand: true, cwd: 'website/assets/sprites/', src: 'backer-only/*.gif', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/assets/sprites/', src: 'npc_ian.gif', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/assets/sprites/', src: 'quest_*.gif', dest: 'website/build/'},
|
||||
@@ -78,6 +78,7 @@ module.exports = function(grunt) {
|
||||
'website/build/favicon.ico',
|
||||
'website/build/favicon_192x192.png',
|
||||
'website/build/*.png',
|
||||
'website/build/static/sprites/*.png',
|
||||
'website/build/*.gif',
|
||||
'website/build/bower_components/bootstrap/dist/fonts/*'
|
||||
],
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
},
|
||||
"SLACK": {
|
||||
"FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/"
|
||||
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||
"SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
web:
|
||||
volumes:
|
||||
- '.:/habitrpg'
|
||||
- '.:/habitrpg'
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3000:3000"
|
||||
links:
|
||||
- mongo
|
||||
- mongo
|
||||
environment:
|
||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||
|
||||
mongo:
|
||||
image: mongo
|
||||
ports:
|
||||
- "27017:27017"
|
||||
- "27017:27017"
|
||||
|
||||
@@ -12,6 +12,9 @@ import {each} from 'lodash';
|
||||
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
|
||||
const DIST_PATH = 'website/assets/sprites/dist/';
|
||||
|
||||
const IMG_DIST_PATH_NEW_CLIENT = 'website/static/sprites/';
|
||||
const CSS_DIST_PATH_NEW_CLIENT = 'website/client/assets/css/sprites/';
|
||||
|
||||
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
|
||||
|
||||
gulp.task('sprites:main', () => {
|
||||
@@ -25,7 +28,7 @@ gulp.task('sprites:largeSprites', () => {
|
||||
});
|
||||
|
||||
gulp.task('sprites:clean', (done) => {
|
||||
clean(`${DIST_PATH}spritesmith*`, done);
|
||||
clean(`{${DIST_PATH}spritesmith*,${IMG_DIST_PATH_NEW_CLIENT}spritesmith*,${CSS_DIST_PATH_NEW_CLIENT}spritesmith*}`, done);
|
||||
});
|
||||
|
||||
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
|
||||
@@ -66,14 +69,16 @@ function createSpritesStream (name, src) {
|
||||
algorithm: 'binary-tree',
|
||||
padding: 1,
|
||||
cssTemplate: 'website/assets/sprites/css/css.template.handlebars',
|
||||
cssVarMap: cssVarMap
|
||||
cssVarMap: cssVarMap,
|
||||
}));
|
||||
|
||||
let imgStream = spriteData.img
|
||||
.pipe(imagemin())
|
||||
.pipe(gulp.dest(IMG_DIST_PATH_NEW_CLIENT))
|
||||
.pipe(gulp.dest(DIST_PATH));
|
||||
|
||||
let cssStream = spriteData.css
|
||||
.pipe(gulp.dest(CSS_DIST_PATH_NEW_CLIENT))
|
||||
.pipe(gulp.dest(DIST_PATH));
|
||||
|
||||
stream.add(imgStream);
|
||||
@@ -148,4 +153,9 @@ function cssVarMap (sprite) {
|
||||
}
|
||||
if (~sprite.name.indexOf('shirt'))
|
||||
sprite.custom.px.offset_y = `-${ sprite.y + 30 }px`; // even more for shirts
|
||||
if (~sprite.name.indexOf('hair_base')) {
|
||||
let styleArray = sprite.name.split('_').slice(2,3);
|
||||
if (Number(styleArray[0]) > 14)
|
||||
sprite.custom.px.offset_y = `-${ sprite.y }px`; // don't crop updos
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ gulp.task('test:api-v3:integration:watch', () => {
|
||||
|
||||
gulp.task('test:api-v3:integration:separate-server', (done) => {
|
||||
let runner = exec(
|
||||
testBin('mocha test/api/v3/integration --recursive', 'LOAD_SERVER=0'),
|
||||
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
|
||||
{maxBuffer: 500 * 1024},
|
||||
(err, stdout, stderr) => done(err)
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
|
||||
var connectionString = 'mongodb://sabrecat:ELI54WzIwlNWCiQ@ds013393-a1.mlab.com:13393/habitica?auto_reconnect=true';
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
|
||||
74
migrations/20161122_turkey_ladder.js
Normal file
74
migrations/20161122_turkey_ladder.js
Normal file
@@ -0,0 +1,74 @@
|
||||
var migrationName = '20161122_turkey_ladder.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Yearly Turkey Day award. Turkey pet, Turkey mount, Gilded Turkey pet, Gilded Turkey mount
|
||||
*/
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'auth.timestamps.loggedin':{$gt:new Date('2016-10-31')} // Extend timeframe each run of migration
|
||||
};
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'migration': 1,
|
||||
'items.mounts': 1,
|
||||
'items.pets': 1,
|
||||
};
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
|
||||
// specify user data to change:
|
||||
var set = {};
|
||||
|
||||
if (user.items.pets['Turkey-Gilded']) {
|
||||
set = {'migration':migrationName, 'items.mounts.Turkey-Gilded':true};
|
||||
} else if (user.items.mounts['Turkey-Base']) {
|
||||
set = {'migration':migrationName, 'items.pets.Turkey-Gilded':5};
|
||||
} else if (user.items.pets['Turkey-Base']) {
|
||||
set = {'migration':migrationName, 'items.mounts.Turkey-Base':true};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.pets.Turkey-Base':5};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
73
migrations/20161230_nye_hats.js
Normal file
73
migrations/20161230_nye_hats.js
Normal file
@@ -0,0 +1,73 @@
|
||||
var migrationName = '20161230_nye_hats.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Yearly New Year's party hat award
|
||||
*/
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'auth.timestamps.loggedin':{$gt:new Date('2016-11-30')} // Remove after first run
|
||||
};
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'items.gear.owned': 1,
|
||||
};
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
|
||||
// specify user data to change:
|
||||
var set = {};
|
||||
|
||||
if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2016':false};
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2015':false};
|
||||
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2014':false};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_nye':false};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ var _id = '';
|
||||
var update = {
|
||||
$addToSet: {
|
||||
'purchased.plan.mysteryItems':{
|
||||
$each:['head_mystery_201610','armor_mystery_201610']
|
||||
$each:['head_mystery_201612','armor_mystery_201612']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,14 +6,11 @@ var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
* Remove flag stating that the Enchanted Armoire is empty, for when new equipment is added
|
||||
*/
|
||||
|
||||
var dbserver = 'localhost:27017'; // FOR TEST DATABASE
|
||||
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE
|
||||
var dbname = 'habitrpg';
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
@@ -22,7 +19,6 @@ var query = {
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'flags.armoireEmpty':1
|
||||
};
|
||||
|
||||
console.warn('Updating users...');
|
||||
@@ -32,7 +28,8 @@ dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
return displayData();
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
|
||||
|
||||
115
migrations/restore-profile-data.js
Normal file
115
migrations/restore-profile-data.js
Normal file
@@ -0,0 +1,115 @@
|
||||
var migrationName = 'restore_profile_data.js';
|
||||
var authorName = 'ThehollidayInn'; // in case script author needs to know when their ...
|
||||
var authorUuid = ''; //... own data is done
|
||||
|
||||
/*
|
||||
* Check if users have empty profile data in new database and update it with old database info
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = ''; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
var monk2 = require('monk');
|
||||
var oldDbConnectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var olDbUsers = monk2(oldDbConnectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId)
|
||||
{
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
// 'profile.name': 'profile name not found',
|
||||
'profile.blurb': null,
|
||||
// 'auth.timestamps.loggedin': {$gt: new Date('11/30/2016')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: ['_id', 'profile', 'auth.timestamps.loggedin'] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.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.');
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
|
||||
var userPaymentPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPaymentPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
if (!user.profile.name || user.profile.name === 'profile name not found' || !user.profile.imageUrl || !user.profile.blurb) {
|
||||
return olDbUsers.findOne({_id: user._id}, '_id profile')
|
||||
.then((oldUserData) => {
|
||||
if (!oldUserData) return;
|
||||
// specify user data to change:
|
||||
var set = {};
|
||||
|
||||
if (oldUserData.profile.name === 'profile name not found') return;
|
||||
|
||||
var userNeedsProfileName = !user.profile.name || user.profile.name === 'profile name not found';
|
||||
if (userNeedsProfileName && oldUserData.profile.name) {
|
||||
set['profile.name'] = oldUserData.profile.name;
|
||||
}
|
||||
|
||||
if (!user.profile.imageUrl && oldUserData.profile.imageUrl) {
|
||||
set['profile.imageUrl'] = oldUserData.profile.imageUrl;
|
||||
}
|
||||
|
||||
if (!user.profile.blurb && oldUserData.profile.blurb) {
|
||||
set['profile.blurb'] = oldUserData.profile.blurb;
|
||||
}
|
||||
|
||||
if (Object.keys(set).length !== 0 && set.constructor === Object) {
|
||||
console.log(set)
|
||||
return 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);
|
||||
}
|
||||
|
||||
|
||||
processUsers()
|
||||
80
migrations/takeThis.js
Normal file
80
migrations/takeThis.js
Normal file
@@ -0,0 +1,80 @@
|
||||
var migrationName = '20170103_takeThis.js'; // Update per month
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Award Take This ladder items to participants in this month's challenge
|
||||
*/
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'challenges':{$in:['ff674aba-a114-4a6f-8ebc-1de27ffb646e']}
|
||||
};
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'items.gear.owned': 1
|
||||
};
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
|
||||
// specify user data to change:
|
||||
var set = {};
|
||||
|
||||
if (typeof user.items.gear.owned.back_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName};
|
||||
{ else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.back_special_takeThis':false};
|
||||
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
|
||||
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
|
||||
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
|
||||
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
5323
npm-shrinkwrap.json
generated
5323
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "3.53.0",
|
||||
"version": "3.66.2",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "3.6.0",
|
||||
@@ -15,8 +15,10 @@
|
||||
"aws-sdk": "^2.0.25",
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-loader": "^6.0.0",
|
||||
"babel-plugin-syntax-async-functions": "^6.13.0",
|
||||
"babel-plugin-transform-async-to-module-method": "^6.8.0",
|
||||
"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",
|
||||
@@ -29,7 +31,7 @@
|
||||
"compression": "^1.6.1",
|
||||
"connect-ratelimit": "0.0.7",
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.3",
|
||||
"coupon-code": "^0.4.5",
|
||||
"css-loader": "^0.23.1",
|
||||
"csv-stringify": "^1.0.2",
|
||||
"cwait": "^1.0.0",
|
||||
@@ -64,17 +66,18 @@
|
||||
"image-size": "~0.3.2",
|
||||
"in-app-purchase": "^1.1.6",
|
||||
"jade": "~1.11.0",
|
||||
"jquery": "https://registry.npmjs.org/jquery/-/jquery-3.1.1.tgz",
|
||||
"js2xmlparser": "~1.0.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.7.1",
|
||||
"less-loader": "^2.2.3",
|
||||
"lodash": "^3.10.1",
|
||||
"lodash.setwith": "^4.2.0",
|
||||
"lodash.pickby": "^4.2.0",
|
||||
"lodash.setwith": "^4.2.0",
|
||||
"merge-stream": "^1.0.0",
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.13.0",
|
||||
"mongoose": "^4.4.16",
|
||||
"mongoose": "^4.7.1",
|
||||
"mongoose-id-autoinc": "~2013.7.14-4",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "~0.8.2",
|
||||
@@ -90,6 +93,7 @@
|
||||
"passport-google-oauth20": "1.0.0",
|
||||
"paypal-ipn": "3.0.0",
|
||||
"paypal-rest-sdk": "^1.2.1",
|
||||
"postcss-easy-import": "^1.0.1",
|
||||
"pretty-data": "^0.40.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"pug": "^2.0.0-beta6",
|
||||
@@ -111,11 +115,12 @@
|
||||
"validator": "^4.9.0",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"vue": "^2.0.0-rc.6",
|
||||
"vue": "^2.1.0",
|
||||
"vue-hot-reload-api": "^1.2.0",
|
||||
"vue-loader": "^9.4.0",
|
||||
"vue-loader": "^10.0.0",
|
||||
"vue-resource": "^1.0.2",
|
||||
"vue-router": "^2.0.0-rc.5",
|
||||
"vue-template-compiler": "^2.1.0",
|
||||
"webpack": "^1.12.2",
|
||||
"webpack-merge": "^0.8.3",
|
||||
"winston": "^2.1.0",
|
||||
@@ -123,8 +128,8 @@
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "^4.3.1",
|
||||
"npm": "^3.8.9"
|
||||
"node": "^6.9.1",
|
||||
"npm": "^4.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.vue .",
|
||||
@@ -155,7 +160,6 @@
|
||||
"postinstall": "bower --config.interactive=false install -f; gulp build; npm run client:build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^6.0.0",
|
||||
"chai": "^3.4.0",
|
||||
"chai-as-promised": "^5.1.0",
|
||||
"chalk": "^1.1.3",
|
||||
@@ -165,13 +169,12 @@
|
||||
"cross-spawn": "^2.1.5",
|
||||
"csv": "~0.3.6",
|
||||
"deep-diff": "~0.1.4",
|
||||
"eslint": "~2.12.0",
|
||||
"eslint-config-habitrpg": "^1.0.0",
|
||||
"eslint": "^3.0.0",
|
||||
"eslint-config-habitrpg": "^2.0.0",
|
||||
"eslint-friendly-formatter": "^2.0.5",
|
||||
"eslint-loader": "^1.3.0",
|
||||
"eslint-plugin-babel": "^3.0.0",
|
||||
"eslint-plugin-html": "^1.3.0",
|
||||
"eslint-plugin-mocha": "^2.1.0",
|
||||
"eslint-plugin-mocha": "^4.7.0",
|
||||
"event-stream": "^3.2.2",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"expect.js": "~0.2.0",
|
||||
@@ -196,6 +199,7 @@
|
||||
"mocha": "^2.3.3",
|
||||
"mongodb": "^2.0.46",
|
||||
"mongoskin": "~2.1.0",
|
||||
"monk": "^3.1.3",
|
||||
"nightwatch": "^0.8.18",
|
||||
"phantomjs-prebuilt": "^2.1.12",
|
||||
"protractor": "^3.1.1",
|
||||
@@ -204,6 +208,7 @@
|
||||
"selenium-server": "2.53.0",
|
||||
"sinon": "^1.17.2",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"superagent-defaults": "^0.1.13",
|
||||
"vinyl-transform": "^1.0.0",
|
||||
"webpack-dev-middleware": "^1.4.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": [
|
||||
"habitrpg/mocha",
|
||||
"habitrpg/babel"
|
||||
"habitrpg/esnext"
|
||||
],
|
||||
"env": {
|
||||
"node": true,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('GET challenges/group/:groupId', () => {
|
||||
describe('GET challenges/groups/:groupId', () => {
|
||||
context('Public Guild', () => {
|
||||
let publicGuild, user, nonMember, challenge, challenge2;
|
||||
|
||||
|
||||
@@ -35,6 +35,24 @@ describe('POST /chat', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when an empty message is provided', async () => {
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: ' '}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when an message containing only newlines is provided', async () => {
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: '\n\n'}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when group is not found', async () => {
|
||||
await expect(user.post('/groups/invalidID/chat', { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
|
||||
@@ -7,15 +7,21 @@ import {
|
||||
import moment from 'moment';
|
||||
|
||||
describe('GET /export/history.csv', () => {
|
||||
it('should return a valid CSV file with tasks history data', async () => {
|
||||
// TODO disabled because it randomly causes the build to fail
|
||||
xit('should return a valid CSV file with tasks history data', async () => {
|
||||
let user = await generateUser();
|
||||
let tasks = await user.post('/tasks/user', [
|
||||
{type: 'habit', text: 'habit 1'},
|
||||
{type: 'daily', text: 'daily 1'},
|
||||
{type: 'habit', text: 'habit 1'},
|
||||
{type: 'habit', text: 'habit 2'},
|
||||
{type: 'todo', text: 'todo 1'},
|
||||
]);
|
||||
|
||||
// to handle occasional inconsistency in task creation order
|
||||
tasks.sort(function (a, b) {
|
||||
return a.text.localeCompare(b.text);
|
||||
});
|
||||
|
||||
// score all the tasks twice
|
||||
await user.post(`/tasks/${tasks[0]._id}/score/up`);
|
||||
await user.post(`/tasks/${tasks[1]._id}/score/up`);
|
||||
@@ -28,7 +34,7 @@ describe('GET /export/history.csv', () => {
|
||||
await user.post(`/tasks/${tasks[3]._id}/score/up`);
|
||||
|
||||
// adding an history entry to daily 1 manually because cron didn't run yet
|
||||
await updateDocument('tasks', tasks[1], {
|
||||
await updateDocument('tasks', tasks[0], {
|
||||
history: [{value: 3.2, date: Number(new Date())}],
|
||||
});
|
||||
|
||||
@@ -41,9 +47,9 @@ describe('GET /export/history.csv', () => {
|
||||
let splitRes = res.split('\n');
|
||||
|
||||
expect(splitRes[0]).to.equal('Task Name,Task ID,Task Type,Date,Value');
|
||||
expect(splitRes[1]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`);
|
||||
expect(splitRes[2]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[1].value}`);
|
||||
expect(splitRes[3]).to.equal(`daily 1,${tasks[1]._id},daily,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`);
|
||||
expect(splitRes[1]).to.equal(`daily 1,${tasks[0]._id},daily,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`);
|
||||
expect(splitRes[2]).to.equal(`habit 1,${tasks[1]._id},habit,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`);
|
||||
expect(splitRes[3]).to.equal(`habit 1,${tasks[1]._id},habit,${moment(tasks[1].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[1].value}`);
|
||||
expect(splitRes[4]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[0].value}`);
|
||||
expect(splitRes[5]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[1].value}`);
|
||||
expect(splitRes[6]).to.equal('');
|
||||
|
||||
@@ -7,7 +7,8 @@ import Bluebird from 'bluebird';
|
||||
let parseStringAsync = Bluebird.promisify(xml2js.parseString, {context: xml2js});
|
||||
|
||||
describe('GET /export/userdata.xml', () => {
|
||||
it('should return a valid XML file with user data', async () => {
|
||||
// TODO disabled because it randomly causes the build to fail
|
||||
xit('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'},
|
||||
|
||||
@@ -82,8 +82,10 @@ describe('GET /groups/:groupId/members', () => {
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background'].sort());
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
'size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background',
|
||||
].sort());
|
||||
|
||||
expect(memberRes.stats.maxMP).to.exist;
|
||||
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
|
||||
|
||||
@@ -134,6 +134,22 @@ describe('POST /group/:groupId/join', () => {
|
||||
|
||||
await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 1);
|
||||
});
|
||||
|
||||
it('notifies inviting user that their invitation was accepted', async () => {
|
||||
await invitedUser.post(`/groups/${guild._id}/join`);
|
||||
|
||||
let inviter = await user.get('/user');
|
||||
let expectedData = {
|
||||
headerText: t('invitationAcceptedHeader'),
|
||||
bodyText: t('invitationAcceptedBody', {
|
||||
username: invitedUser.auth.local.username,
|
||||
groupName: guild.name,
|
||||
}),
|
||||
};
|
||||
|
||||
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
|
||||
expect(inviter.notifications[0].data).to.eql(expectedData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -172,6 +188,23 @@ describe('POST /group/:groupId/join', () => {
|
||||
await expect(invitedUser.get('/user')).to.eventually.have.deep.property('party._id', party._id);
|
||||
});
|
||||
|
||||
it('notifies inviting user that their invitation was accepted', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
let inviter = await user.get('/user');
|
||||
|
||||
let expectedData = {
|
||||
headerText: t('invitationAcceptedHeader'),
|
||||
bodyText: t('invitationAcceptedBody', {
|
||||
username: invitedUser.auth.local.username,
|
||||
groupName: party.name,
|
||||
}),
|
||||
};
|
||||
|
||||
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
|
||||
expect(inviter.notifications[0].data).to.eql(expectedData);
|
||||
});
|
||||
|
||||
it('clears invitation from user when joining party', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
let leader;
|
||||
@@ -60,6 +61,14 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
});
|
||||
|
||||
context('Guilds', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('can remove other members', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||
let memberRemoved = await member.get('/user');
|
||||
@@ -80,6 +89,22 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
|
||||
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, {id: guild._id})).eql(-1);
|
||||
});
|
||||
|
||||
it('sends email to user with rescinded invite', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(invitedUser._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('guild-invite-rescinded');
|
||||
});
|
||||
|
||||
it('sends email to removed user', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(member._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('kicked-from-guild');
|
||||
});
|
||||
});
|
||||
|
||||
context('Party', () => {
|
||||
@@ -105,6 +130,11 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
partyInvitedUser = invitees[0];
|
||||
partyMember = members[0];
|
||||
removedMember = members[1];
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('can remove other members', async () => {
|
||||
@@ -187,5 +217,21 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
expect(party.quest.members[partyLeader._id]).to.be.true;
|
||||
expect(party.quest.members[partyMember._id]).to.not.exist;
|
||||
});
|
||||
|
||||
it('sends email to user with rescinded invite', async () => {
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(partyInvitedUser._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('party-invite-rescinded');
|
||||
});
|
||||
|
||||
it('sends email to removed user', async () => {
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(partyMember._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('kicked-from-party');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -300,6 +300,26 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
message: t('userAlreadyInGroup'),
|
||||
});
|
||||
});
|
||||
|
||||
// @TODO: Add this after we are able to mock the group plan route
|
||||
xit('returns an error when a non-leader invites to a group plan', async () => {
|
||||
let userToInvite = await generateUser();
|
||||
|
||||
let nonGroupLeader = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [nonGroupLeader._id],
|
||||
});
|
||||
await nonGroupLeader.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(nonGroupLeader.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanInviteToGroupPlan'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('party invites', () => {
|
||||
|
||||
44
test/api/v3/integration/members/GET-achievements.test.js
Normal file
44
test/api/v3/integration/members/GET-achievements.test.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('GET /members/:memberId/achievements', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('validates req.params.memberId', async () => {
|
||||
await expect(user.get('/members/invalidUUID/achievements')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns achievements based on given user', async () => {
|
||||
let member = await generateUser({
|
||||
contributor: {level: 1},
|
||||
backer: {tier: 3},
|
||||
});
|
||||
let achievementsRes = await user.get(`/members/${member._id}/achievements`);
|
||||
|
||||
expect(achievementsRes.special.achievements.contributor.earned).to.equal(true);
|
||||
expect(achievementsRes.special.achievements.contributor.value).to.equal(1);
|
||||
|
||||
expect(achievementsRes.special.achievements.kickstarter.earned).to.equal(true);
|
||||
expect(achievementsRes.special.achievements.kickstarter.value).to.equal(3);
|
||||
});
|
||||
|
||||
it('handles non-existing members', async () => {
|
||||
let dummyId = generateUUID();
|
||||
await expect(user.get(`/members/${dummyId}/achievements`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', {userId: dummyId}),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -35,8 +35,10 @@ describe('GET /members/:memberId', () => {
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background'].sort());
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
'size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background',
|
||||
].sort());
|
||||
|
||||
expect(memberRes.stats.maxMP).to.exist;
|
||||
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
|
||||
|
||||
@@ -4,6 +4,14 @@ import {
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
function findMessage (messages, receiverId) {
|
||||
let message = _.find(messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === receiverId;
|
||||
});
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
describe('POST /members/transfer-gems', () => {
|
||||
let userToSendMessage;
|
||||
let receiver;
|
||||
@@ -116,19 +124,14 @@ describe('POST /members/transfer-gems', () => {
|
||||
let updatedReceiver = await receiver.get('/user');
|
||||
let updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === userToSendMessage._id;
|
||||
});
|
||||
let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id);
|
||||
let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||
|
||||
let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === receiver._id;
|
||||
});
|
||||
|
||||
let messageSentContent = t('privateMessageGiftIntro', {
|
||||
let messageSentContent = t('privateMessageGiftGemsMessage', {
|
||||
receiverName: receiver.profile.name,
|
||||
senderName: userToSendMessage.profile.name,
|
||||
gemAmount,
|
||||
});
|
||||
messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount});
|
||||
messageSentContent = `\`${messageSentContent}\` `;
|
||||
messageSentContent += message;
|
||||
|
||||
@@ -150,19 +153,14 @@ describe('POST /members/transfer-gems', () => {
|
||||
let updatedReceiver = await receiver.get('/user');
|
||||
let updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === userToSendMessage._id;
|
||||
});
|
||||
let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id);
|
||||
let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||
|
||||
let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === receiver._id;
|
||||
});
|
||||
|
||||
let messageSentContent = t('privateMessageGiftIntro', {
|
||||
let messageSentContent = t('privateMessageGiftGemsMessage', {
|
||||
receiverName: receiver.profile.name,
|
||||
senderName: userToSendMessage.profile.name,
|
||||
gemAmount,
|
||||
});
|
||||
messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount});
|
||||
messageSentContent = `\`${messageSentContent}\` `;
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
@@ -173,4 +171,40 @@ describe('POST /members/transfer-gems', () => {
|
||||
expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent);
|
||||
expect(updatedSender.balance).to.equal(0);
|
||||
});
|
||||
|
||||
it('sends transfer gems message in each participant\'s language', async () => {
|
||||
await receiver.update({
|
||||
'preferences.language': 'es',
|
||||
});
|
||||
await userToSendMessage.update({
|
||||
'preferences.language': 'cs',
|
||||
});
|
||||
await userToSendMessage.post('/members/transfer-gems', {
|
||||
gemAmount,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let updatedReceiver = await receiver.get('/user');
|
||||
let updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id);
|
||||
let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||
|
||||
let [receieversMessageContent, sendersMessageContent] = ['es', 'cs'].map((lang) => {
|
||||
let messageContent = t('privateMessageGiftGemsMessage', {
|
||||
receiverName: receiver.profile.name,
|
||||
senderName: userToSendMessage.profile.name,
|
||||
gemAmount,
|
||||
}, lang);
|
||||
|
||||
return `\`${messageContent}\` `;
|
||||
});
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
expect(sendersMessageInReceiversInbox.text).to.equal(receieversMessageContent);
|
||||
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
expect(sendersMessageInSendersInbox.text).to.equal(sendersMessageContent);
|
||||
expect(updatedSender.balance).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /notifications/:notificationId/read', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('errors when notification is not found', async () => {
|
||||
let dummyId = generateUUID();
|
||||
await expect(user.post(`/notifications/${dummyId}/read`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
xit('removes a notification', async () => {
|
||||
});
|
||||
});
|
||||
@@ -10,13 +10,11 @@ describe('payments - amazon - #createOrderReferenceId', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies billingAgreementId', async (done) => {
|
||||
try {
|
||||
await user.post(endpoint);
|
||||
} catch (e) {
|
||||
// Parameter AWSAccessKeyId cannot be empty.
|
||||
expect(e.error).to.eql('BadRequest');
|
||||
done();
|
||||
}
|
||||
it('verifies billingAgreementId', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Missing req.body.billingAgreementId',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
|
||||
describe('POST /groups/:groupId/quests/abort', () => {
|
||||
let questingGroup;
|
||||
@@ -85,6 +86,7 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
});
|
||||
|
||||
it('aborts a quest', async () => {
|
||||
sandbox.stub(Group.prototype, 'sendChat');
|
||||
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/accept`);
|
||||
@@ -123,5 +125,8 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
},
|
||||
members: {},
|
||||
});
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/aborted the party quest Wail of the Whale.`/);
|
||||
Group.prototype.sendChat.restore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('GET /shops/seasonal', () => {
|
||||
|
||||
expect(shop.identifier).to.equal('seasonalShop');
|
||||
expect(shop.text).to.eql(t('seasonalShop'));
|
||||
expect(shop.notes).to.eql(t('seasonalShopFallText'));
|
||||
expect(shop.notes).to.be.a('string');
|
||||
expect(shop.imageName).to.be.a('string');
|
||||
expect(shop.categories).to.be.an('array');
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('GET /tasks/user', () => {
|
||||
for (let i = 0; i < numberOfTodos; i++) {
|
||||
let id = todos[i]._id;
|
||||
|
||||
await user.post(`/tasks/${id}/score/up`); // eslint-disable-line babel/no-await-in-loop
|
||||
await user.post(`/tasks/${id}/score/up`); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
await user.sync();
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('POST /tasks/clearCompletedTodos', () => {
|
||||
|
||||
for (let task of tasks) {
|
||||
if (['todo 2', 'todo 3', 'todo 6'].indexOf(task.text) !== -1) {
|
||||
await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line babel/no-await-in-loop
|
||||
await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,17 @@ import {
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { find } from 'lodash';
|
||||
|
||||
describe('POST /tasks/challenge/:challengeId', () => {
|
||||
let user;
|
||||
let guild;
|
||||
let challenge;
|
||||
|
||||
function findUserChallengeTask (memberTask) {
|
||||
return memberTask.challenge.id === challenge._id;
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({balance: 1});
|
||||
guild = await generateGroup(user);
|
||||
@@ -88,6 +93,9 @@ describe('POST /tasks/challenge/:challengeId', () => {
|
||||
});
|
||||
let challengeWithTask = await user.get(`/challenges/${challenge._id}`);
|
||||
|
||||
let memberTasks = await user.get('/tasks/user');
|
||||
let userChallengeTask = find(memberTasks, findUserChallengeTask);
|
||||
|
||||
expect(challengeWithTask.tasksOrder.habits.indexOf(task._id)).to.be.above(-1);
|
||||
expect(task.challenge.id).to.equal(challenge._id);
|
||||
expect(task.text).to.eql('test habit');
|
||||
@@ -95,6 +103,8 @@ describe('POST /tasks/challenge/:challengeId', () => {
|
||||
expect(task.type).to.eql('habit');
|
||||
expect(task.up).to.eql(false);
|
||||
expect(task.down).to.eql(true);
|
||||
|
||||
expect(userChallengeTask.notes).to.eql(task.notes);
|
||||
});
|
||||
|
||||
it('creates a todo', async () => {
|
||||
@@ -105,11 +115,16 @@ describe('POST /tasks/challenge/:challengeId', () => {
|
||||
});
|
||||
let challengeWithTask = await user.get(`/challenges/${challenge._id}`);
|
||||
|
||||
let memberTasks = await user.get('/tasks/user');
|
||||
let userChallengeTask = find(memberTasks, findUserChallengeTask);
|
||||
|
||||
expect(challengeWithTask.tasksOrder.todos.indexOf(task._id)).to.be.above(-1);
|
||||
expect(task.challenge.id).to.equal(challenge._id);
|
||||
expect(task.text).to.eql('test todo');
|
||||
expect(task.notes).to.eql('1976');
|
||||
expect(task.type).to.eql('todo');
|
||||
|
||||
expect(userChallengeTask.notes).to.eql(task.notes);
|
||||
});
|
||||
|
||||
it('creates a daily', async () => {
|
||||
@@ -124,6 +139,9 @@ describe('POST /tasks/challenge/:challengeId', () => {
|
||||
});
|
||||
let challengeWithTask = await user.get(`/challenges/${challenge._id}`);
|
||||
|
||||
let memberTasks = await user.get('/tasks/user');
|
||||
let userChallengeTask = find(memberTasks, findUserChallengeTask);
|
||||
|
||||
expect(challengeWithTask.tasksOrder.dailys.indexOf(task._id)).to.be.above(-1);
|
||||
expect(task.challenge.id).to.equal(challenge._id);
|
||||
expect(task.text).to.eql('test daily');
|
||||
@@ -132,5 +150,7 @@ describe('POST /tasks/challenge/:challengeId', () => {
|
||||
expect(task.frequency).to.eql('daily');
|
||||
expect(task.everyX).to.eql(5);
|
||||
expect(new Date(task.startDate)).to.eql(now);
|
||||
|
||||
expect(userChallengeTask.notes).to.eql(task.notes);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,4 +69,48 @@ describe('DELETE /tasks/:id', () => {
|
||||
expect(syncedTask.group.broken).to.equal('TASK_DELETED');
|
||||
expect(member2SyncedTask.group.broken).to.equal('TASK_DELETED');
|
||||
});
|
||||
|
||||
it('prevents a user from deleting a task they are assigned to', async () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.del(`/tasks/${syncedTask._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cantDeleteAssignedGroupTasks'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a user to delete a broken task', async () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await user.del(`/tasks/${task._id}`);
|
||||
|
||||
await member.del(`/tasks/${syncedTask._id}`);
|
||||
|
||||
await expect(member.get(`/tasks/${syncedTask._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: 'Task not found.',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a user to delete a task after leaving a group', async () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await member.post(`/groups/${guild._id}/leave`);
|
||||
|
||||
await member.del(`/tasks/${syncedTask._id}`);
|
||||
|
||||
await expect(member.get(`/tasks/${syncedTask._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: 'Task not found.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,12 @@ describe('GET /approvals/group/:groupId', () => {
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
|
||||
try {
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-empty
|
||||
}
|
||||
});
|
||||
|
||||
it('errors when user is not the group leader', async () => {
|
||||
|
||||
@@ -59,9 +59,11 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.length).to.equal(1);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved'));
|
||||
expect(member.notifications.length).to.equal(2);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[1].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
|
||||
expect(syncedTask.group.approval.approved).to.be.true;
|
||||
expect(syncedTask.group.approval.approvingUser).to.equal(user._id);
|
||||
|
||||
@@ -37,7 +37,12 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
let response = await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
let updatedTask = await member.get(`/tasks/${syncedTask._id}`);
|
||||
|
||||
await user.sync();
|
||||
@@ -48,8 +53,8 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
user: member.auth.local.username,
|
||||
taskName: updatedTask.text,
|
||||
}));
|
||||
expect(user.notifications[0].data.groupId).to.equal(guild._id);
|
||||
|
||||
expect(response.message).to.equal(t('taskApprovalHasBeenRequested'));
|
||||
expect(updatedTask.group.approval.requested).to.equal(true);
|
||||
expect(updatedTask.group.approval.requestedDate).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
|
||||
});
|
||||
@@ -58,7 +63,12 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('POST /tasks/:taskId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('allows user to assign themselves', async () => {
|
||||
it('allows user to assign themselves (claim)', async () => {
|
||||
await member.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let groupTask = await user.get(`/tasks/group/${guild._id}`);
|
||||
@@ -93,6 +93,15 @@ describe('POST /tasks/:taskId', () => {
|
||||
expect(syncedTask).to.exist;
|
||||
});
|
||||
|
||||
it('sends a message to the group when a user claims a task', async () => {
|
||||
await member.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let updateGroup = await user.get(`/groups/${guild._id}`);
|
||||
|
||||
expect(updateGroup.chat[0].text).to.equal(t('userIsClamingTask', {username: member.profile.name, task: task.text}));
|
||||
expect(updateGroup.chat[0].uuid).to.equal('system');
|
||||
});
|
||||
|
||||
it('assigns a task to a user', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
|
||||
@@ -62,15 +62,6 @@ describe('POST /user/buy/:key', () => {
|
||||
await user.post(`/user/buy/${key}`);
|
||||
await user.sync();
|
||||
|
||||
expect(user.items.gear.owned).to.eql({
|
||||
armor_warrior_1: true,
|
||||
eyewear_special_blackTopFrame: true,
|
||||
eyewear_special_blueTopFrame: true,
|
||||
eyewear_special_greenTopFrame: true,
|
||||
eyewear_special_pinkTopFrame: true,
|
||||
eyewear_special_redTopFrame: true,
|
||||
eyewear_special_whiteTopFrame: true,
|
||||
eyewear_special_yellowTopFrame: true,
|
||||
});
|
||||
expect(user.items.gear.owned.armor_warrior_1).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,15 +31,6 @@ describe('POST /user/buy-gear/:key', () => {
|
||||
await user.post(`/user/buy-gear/${key}`);
|
||||
await user.sync();
|
||||
|
||||
expect(user.items.gear.owned).to.eql({
|
||||
armor_warrior_1: true,
|
||||
eyewear_special_blackTopFrame: true,
|
||||
eyewear_special_blueTopFrame: true,
|
||||
eyewear_special_greenTopFrame: true,
|
||||
eyewear_special_pinkTopFrame: true,
|
||||
eyewear_special_redTopFrame: true,
|
||||
eyewear_special_whiteTopFrame: true,
|
||||
eyewear_special_yellowTopFrame: true,
|
||||
});
|
||||
expect(user.items.gear.owned.armor_warrior_1).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,6 @@ describe('POST /user/purchase-hourglass/:type/:key', () => {
|
||||
|
||||
expect(response.message).to.eql(t('hourglassPurchase'));
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(user.items.pets).to.eql({'MantisShrimp-Base': 5});
|
||||
expect(user.items.pets['MantisShrimp-Base']).to.eql(5);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,32 @@ describe('PUT /user', () => {
|
||||
expect(user.preferences.costume).to.eql(true);
|
||||
expect(user.stats.hp).to.eql(14);
|
||||
});
|
||||
|
||||
it('profile.name cannot be an empty string or null', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': ' ', // string should be trimmed
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': null,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Top Level Protected Operations', () => {
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('POST /user/auth/local/register', () => {
|
||||
expect(user._id).to.exist;
|
||||
expect(user.apiToken).to.exist;
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.profile.name).to.eql(username);
|
||||
});
|
||||
|
||||
it('provides default tags and tasks', async () => {
|
||||
@@ -66,6 +67,7 @@ describe('POST /user/auth/local/register', () => {
|
||||
});
|
||||
|
||||
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string');
|
||||
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
||||
});
|
||||
|
||||
it('requires password and confirmPassword to match', async () => {
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('POST /user/auth/social', () => {
|
||||
|
||||
describe('facebook', () => {
|
||||
before(async () => {
|
||||
let expectedResult = {id: facebookId};
|
||||
let expectedResult = {id: facebookId, displayName: 'a facebook user'};
|
||||
sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult);
|
||||
network = 'facebook';
|
||||
});
|
||||
@@ -47,6 +47,7 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.apiToken).to.exist;
|
||||
expect(response.id).to.exist;
|
||||
expect(response.newUser).to.be.true;
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a facebook user');
|
||||
});
|
||||
|
||||
it('logs an existing user in', async () => {
|
||||
@@ -88,7 +89,7 @@ describe('POST /user/auth/social', () => {
|
||||
|
||||
describe('google', () => {
|
||||
before(async () => {
|
||||
let expectedResult = {id: googleId};
|
||||
let expectedResult = {id: googleId, displayName: 'a google user'};
|
||||
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
|
||||
network = 'google';
|
||||
});
|
||||
@@ -102,6 +103,7 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.apiToken).to.exist;
|
||||
expect(response.id).to.exist;
|
||||
expect(response.newUser).to.be.true;
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
|
||||
});
|
||||
|
||||
it('logs an existing user in', async () => {
|
||||
|
||||
@@ -278,6 +278,7 @@ describe('analyticsService', () => {
|
||||
todos: [{_id: 'todo'}],
|
||||
rewards: [{_id: 'reward'}],
|
||||
balance: 12,
|
||||
loginIncentives: 1,
|
||||
};
|
||||
|
||||
data.user = user;
|
||||
@@ -302,6 +303,8 @@ describe('analyticsService', () => {
|
||||
contributorLevel: 1,
|
||||
subscription: 'foo-plan',
|
||||
balance: 12,
|
||||
balanceGemAmount: 48,
|
||||
loginIncentives: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -351,7 +354,8 @@ describe('analyticsService', () => {
|
||||
purchaseType: 'checkout',
|
||||
gift: false,
|
||||
quantity: 1,
|
||||
headers: {'x-client': 'habitica-web',
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import { model as User } from '../../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../../website/server/models/task';
|
||||
import { clone } from 'lodash';
|
||||
import common from '../../../../../website/common';
|
||||
import analytics from '../../../../../website/server/libs/analyticsService';
|
||||
|
||||
// const scoreTask = common.ops.scoreTask;
|
||||
|
||||
@@ -17,9 +18,6 @@ describe('cron', () => {
|
||||
let user;
|
||||
let tasksByType = {habits: [], dailys: [], todos: [], rewards: []};
|
||||
let daysMissed = 0;
|
||||
let analytics = {
|
||||
track: sinon.spy(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User({
|
||||
@@ -34,11 +32,17 @@ describe('cron', () => {
|
||||
},
|
||||
});
|
||||
|
||||
sinon.spy(analytics, 'track');
|
||||
|
||||
user._statsComputed = {
|
||||
mp: 10,
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
it('updates user.preferences.timezoneOffsetAtLastCron', () => {
|
||||
let timezoneOffsetFromUserPrefs = 1;
|
||||
|
||||
@@ -59,6 +63,11 @@ describe('cron', () => {
|
||||
expect(user.flags.cronCount).to.be.greaterThan(cronCountBefore);
|
||||
});
|
||||
|
||||
it('calls analytics', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
describe('end of the month perks', () => {
|
||||
beforeEach(() => {
|
||||
user.purchased.plan.customerId = 'subscribedId';
|
||||
@@ -257,6 +266,11 @@ describe('cron', () => {
|
||||
user.preferences.sleep = true;
|
||||
});
|
||||
|
||||
it('calls analytics', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('clears user buffs', () => {
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
@@ -286,7 +300,7 @@ describe('cron', () => {
|
||||
startDate: new Date(),
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[0].completed = true;
|
||||
|
||||
@@ -307,7 +321,7 @@ describe('cron', () => {
|
||||
value: 0,
|
||||
};
|
||||
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
tasksByType.todos.push(task);
|
||||
});
|
||||
|
||||
@@ -333,7 +347,7 @@ describe('cron', () => {
|
||||
type: 'daily',
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys = [];
|
||||
tasksByType.dailys.push(task);
|
||||
|
||||
@@ -435,7 +449,7 @@ describe('cron', () => {
|
||||
type: 'habit',
|
||||
};
|
||||
|
||||
let task = new Tasks.habit(Tasks.Task.sanitize(habit)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.habit(Tasks.Task.sanitize(habit)); // eslint-disable-line new-cap
|
||||
tasksByType.habits = [];
|
||||
tasksByType.habits.push(task);
|
||||
});
|
||||
@@ -476,7 +490,7 @@ describe('cron', () => {
|
||||
type: 'daily',
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys = [];
|
||||
tasksByType.dailys.push(task);
|
||||
|
||||
@@ -619,7 +633,7 @@ describe('cron', () => {
|
||||
type: 'daily',
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys = [];
|
||||
tasksByType.dailys.push(task);
|
||||
|
||||
@@ -656,9 +670,9 @@ describe('cron', () => {
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].type).to.equal('CRON');
|
||||
expect(user.notifications[0].data).to.eql({
|
||||
expect(user.notifications.length).to.be.greaterThan(0);
|
||||
expect(user.notifications[1].type).to.equal('CRON');
|
||||
expect(user.notifications[1].data).to.eql({
|
||||
hp: user.stats.hp - hpBefore,
|
||||
mp: user.stats.mp - mpBefore,
|
||||
});
|
||||
@@ -675,13 +689,14 @@ describe('cron', () => {
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].type).to.equal('CRON');
|
||||
expect(user.notifications[0].data).to.eql({
|
||||
expect(user.notifications.length).to.be.greaterThan(0);
|
||||
expect(user.notifications[1].type).to.equal('CRON');
|
||||
expect(user.notifications[1].data).to.eql({
|
||||
hp: user.stats.hp - hpBefore1,
|
||||
mp: user.stats.mp - mpBefore1,
|
||||
});
|
||||
|
||||
let notifsBefore2 = user.notifications.length;
|
||||
let hpBefore2 = user.stats.hp;
|
||||
let mpBefore2 = user.stats.mp;
|
||||
|
||||
@@ -689,12 +704,14 @@ describe('cron', () => {
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].type).to.equal('CRON');
|
||||
expect(user.notifications[0].data).to.eql({
|
||||
expect(user.notifications.length - notifsBefore2).to.equal(0);
|
||||
expect(user.notifications[0].type).to.not.equal('CRON');
|
||||
expect(user.notifications[1].type).to.equal('CRON');
|
||||
expect(user.notifications[1].data).to.eql({
|
||||
hp: user.stats.hp - hpBefore2 - (hpBefore2 - hpBefore1),
|
||||
mp: user.stats.mp - mpBefore2 - (mpBefore2 - mpBefore1),
|
||||
});
|
||||
expect(user.notifications[0].type).to.not.equal('CRON');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -747,6 +764,188 @@ describe('cron', () => {
|
||||
expect(user.inbox.messages[messageId]).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('login incentives', () => {
|
||||
it('increments incentive counter each cron', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
user.lastCron = moment(new Date()).subtract({days: 1});
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(2);
|
||||
});
|
||||
|
||||
it('pushes a notification of the day\'s incentive each cron', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.notifications.length).to.be.greaterThan(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('replaces previous notifications', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
let filteredNotifications = user.notifications.filter(n => n.type === 'LOGIN_INCENTIVE');
|
||||
|
||||
expect(filteredNotifications.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('increments loginIncentives by 1 even if days are skipped in between', () => {
|
||||
daysMissed = 3;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
});
|
||||
|
||||
it('increments loginIncentives by 1 even if user has Dailies paused', () => {
|
||||
user.preferences.sleep = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
});
|
||||
|
||||
it('awards user bard robes if login incentive is 1', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
expect(user.items.gear.owned.armor_special_bardRobes).to.eql(true);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user incentive backgrounds if login incentive is 2', () => {
|
||||
user.loginIncentives = 1;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(2);
|
||||
expect(user.purchased.background.blue).to.eql(true);
|
||||
expect(user.purchased.background.green).to.eql(true);
|
||||
expect(user.purchased.background.purple).to.eql(true);
|
||||
expect(user.purchased.background.red).to.eql(true);
|
||||
expect(user.purchased.background.yellow).to.eql(true);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user Bard Hat if login incentive is 3', () => {
|
||||
user.loginIncentives = 2;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(3);
|
||||
expect(user.items.gear.owned.head_special_bardHat).to.eql(true);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user RoyalPurple Hatching Potion if login incentive is 4', () => {
|
||||
user.loginIncentives = 3;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(4);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a Chocolate, Meat and Pink Contton Candy if login incentive is 5', () => {
|
||||
user.loginIncentives = 4;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(5);
|
||||
|
||||
expect(user.items.food.Chocolate).to.eql(1);
|
||||
expect(user.items.food.Meat).to.eql(1);
|
||||
expect(user.items.food.CottonCandyPink).to.eql(1);
|
||||
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user moon quest if login incentive is 7', () => {
|
||||
user.loginIncentives = 6;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(7);
|
||||
expect(user.items.quests.moon1).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user RoyalPurple Hatching Potion if login incentive is 10', () => {
|
||||
user.loginIncentives = 9;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(10);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a Strawberry, Patato and Blue Contton Candy if login incentive is 14', () => {
|
||||
user.loginIncentives = 13;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(14);
|
||||
|
||||
expect(user.items.food.Strawberry).to.eql(1);
|
||||
expect(user.items.food.Potatoe).to.eql(1);
|
||||
expect(user.items.food.CottonCandyBlue).to.eql(1);
|
||||
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a bard instrument if login incentive is 18', () => {
|
||||
user.loginIncentives = 17;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(18);
|
||||
expect(user.items.gear.owned.weapon_special_bardInstrument).to.eql(true);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user second moon quest if login incentive is 22', () => {
|
||||
user.loginIncentives = 21;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(22);
|
||||
expect(user.items.quests.moon2).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a RoyalPurple hatching potion if login incentive is 26', () => {
|
||||
user.loginIncentives = 25;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(26);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user Fish, Milk, Rotten Meat and Honey if login incentive is 30', () => {
|
||||
user.loginIncentives = 29;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(30);
|
||||
|
||||
expect(user.items.food.Fish).to.eql(1);
|
||||
expect(user.items.food.Milk).to.eql(1);
|
||||
expect(user.items.food.RottenMeat).to.eql(1);
|
||||
expect(user.items.food.Honey).to.eql(1);
|
||||
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a RoyalPurple hatching potion if login incentive is 35', () => {
|
||||
user.loginIncentives = 34;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(35);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user the third moon quest if login incentive is 40', () => {
|
||||
user.loginIncentives = 39;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(40);
|
||||
expect(user.items.quests.moon3).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a RoyalPurple hatching potion if login incentive is 45', () => {
|
||||
user.loginIncentives = 44;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(45);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a saddle if login incentive is 50', () => {
|
||||
user.loginIncentives = 49;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(50);
|
||||
expect(user.items.food.Saddle).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('recoverCron', () => {
|
||||
|
||||
@@ -4,14 +4,20 @@ import analytics from '../../../../../website/server/libs/analyticsService';
|
||||
import notifications from '../../../../../website/server/libs/pushNotifications';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import stripeModule from 'stripe';
|
||||
import moment from 'moment';
|
||||
import { translate as t } from '../../../../helpers/api-v3-integration.helper';
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../helpers/api-unit.helper.js';
|
||||
import i18n from '../../../../../website/common/script/i18n';
|
||||
import amzLib from '../../../../../website/server/libs/amazonPayments';
|
||||
|
||||
describe('payments/index', () => {
|
||||
let user, group, data, plan;
|
||||
|
||||
let stripe = stripeModule('test');
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
@@ -145,6 +151,14 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCreated if it did not previously exist', async () => {
|
||||
expect(recipient.purchased.plan.dateCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('does not change plan.customerId if it already exists', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
data.customerId = 'purchaserCustomerId';
|
||||
@@ -173,9 +187,10 @@ describe('payments/index', () => {
|
||||
|
||||
it('sends a private message about the gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledOnce;
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, '\`Hello recipient, sender has sent you 3 months of subscription!\`');
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg });
|
||||
});
|
||||
|
||||
it('sends an email about the gift', async () => {
|
||||
@@ -615,7 +630,40 @@ describe('payments/index', () => {
|
||||
await api.cancelSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledOnce;
|
||||
expect(sender.sendTxn).to.be.calledWith(user, 'cancel-subscription');
|
||||
expect(sender.sendTxn).to.be.calledWith(user, 'group-cancel-subscription');
|
||||
});
|
||||
|
||||
it('prevents non group leader from manging subscription', async () => {
|
||||
let groupMember = new User();
|
||||
data.user = groupMember;
|
||||
data.groupId = group._id;
|
||||
|
||||
await expect(api.cancelSubscription(data))
|
||||
.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows old group leader to cancel if they created the subscription', async () => {
|
||||
data.groupId = group._id;
|
||||
data.sub = {
|
||||
key: 'group_monthly',
|
||||
};
|
||||
data.paymentMethod = 'Payment Method';
|
||||
await api.createSubscription(data);
|
||||
|
||||
let updatedGroup = await Group.findById(group._id).exec();
|
||||
let newLeader = new User();
|
||||
updatedGroup.leader = newLeader._id;
|
||||
await updatedGroup.save();
|
||||
|
||||
await api.cancelSubscription(data);
|
||||
|
||||
updatedGroup = await Group.findById(group._id).exec();
|
||||
|
||||
expect(updatedGroup.purchased.plan.dateTerminated).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -689,14 +737,190 @@ describe('payments/index', () => {
|
||||
|
||||
it('sends a message from purchaser to recipient', async () => {
|
||||
await api.buyGems(data);
|
||||
let msg = '\`Hello recipient, sender has sent you 4 gems!\`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, '\`Hello recipient, sender has sent you 4 gems!\`');
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg });
|
||||
});
|
||||
|
||||
it('sends a push notification if user did not gift to self', async () => {
|
||||
await api.buyGems(data);
|
||||
expect(notifications.sendNotification).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('sends gem donation message in each participant\'s language', async () => {
|
||||
// TODO using english for both users because other languages are not loaded
|
||||
// for api.buyGems
|
||||
await recipient.update({
|
||||
'preferences.language': 'en',
|
||||
});
|
||||
await user.update({
|
||||
'preferences.language': 'en',
|
||||
});
|
||||
await api.buyGems(data);
|
||||
|
||||
let [recipientsMessageContent, sendersMessageContent] = ['en', 'en'].map((lang) => {
|
||||
let messageContent = t('giftedGemsFull', {
|
||||
username: recipient.profile.name,
|
||||
sender: user.profile.name,
|
||||
gemAmount: data.gift.gems.amount,
|
||||
}, lang);
|
||||
|
||||
return `\`${messageContent}\``;
|
||||
});
|
||||
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: recipientsMessageContent, senderMsg: sendersMessageContent });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#upgradeGroupPlan', () => {
|
||||
let spy;
|
||||
|
||||
beforeEach(function () {
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.returnsPromise().resolves([]);
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore(stripe.subscriptions.update);
|
||||
});
|
||||
|
||||
it('updates a group plan quantity', async () => {
|
||||
data.paymentMethod = 'Stripe';
|
||||
await api.createSubscription(data);
|
||||
|
||||
let updatedGroup = await Group.findById(group._id).exec();
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(3);
|
||||
|
||||
updatedGroup.memberCount += 1;
|
||||
await updatedGroup.save();
|
||||
|
||||
await api.updateStripeGroupPlan(updatedGroup, stripe);
|
||||
|
||||
expect(spy.calledOnce).to.be.true;
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(4);
|
||||
});
|
||||
|
||||
it('does not update a group plan quantity that has a payment method other than stripe', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
let updatedGroup = await Group.findById(group._id).exec();
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(3);
|
||||
|
||||
updatedGroup.memberCount += 1;
|
||||
await updatedGroup.save();
|
||||
|
||||
await api.updateStripeGroupPlan(updatedGroup, stripe);
|
||||
|
||||
expect(spy.calledOnce).to.be.false;
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('payWithStripe', () => {
|
||||
let spy;
|
||||
let stripeCreateCustomerSpy;
|
||||
let createSubSpy;
|
||||
|
||||
beforeEach(function () {
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.returnsPromise().resolves;
|
||||
|
||||
stripeCreateCustomerSpy = sinon.stub(stripe.customers, 'create');
|
||||
let stripCustomerResponse = {
|
||||
subscriptions: {
|
||||
data: [{id: 'test-id'}],
|
||||
},
|
||||
};
|
||||
stripeCreateCustomerSpy.returnsPromise().resolves(stripCustomerResponse);
|
||||
|
||||
createSubSpy = sinon.stub(api, 'createSubscription');
|
||||
createSubSpy.returnsPromise().resolves({});
|
||||
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore(stripe.subscriptions.update);
|
||||
stripe.customers.create.restore();
|
||||
api.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('subscribes with stripe', async () => {
|
||||
let token = 'test-token';
|
||||
let gift;
|
||||
let sub = data.sub;
|
||||
let groupId = group._id;
|
||||
let email = 'test@test.com';
|
||||
let headers = {};
|
||||
let coupon;
|
||||
|
||||
await api.payWithStripe({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy.calledOnce).to.be.true;
|
||||
expect(createSubSpy.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribeWithAmazon', () => {
|
||||
let amazonSetBillingAgreementDetailsSpy;
|
||||
let amazonConfirmBillingAgreementSpy;
|
||||
let amazongAuthorizeOnBillingAgreementSpy;
|
||||
let createSubSpy;
|
||||
|
||||
beforeEach(function () {
|
||||
amazonSetBillingAgreementDetailsSpy = sinon.stub(amzLib, 'setBillingAgreementDetails');
|
||||
amazonSetBillingAgreementDetailsSpy.returnsPromise().resolves({});
|
||||
|
||||
amazonConfirmBillingAgreementSpy = sinon.stub(amzLib, 'confirmBillingAgreement');
|
||||
amazonConfirmBillingAgreementSpy.returnsPromise().resolves({});
|
||||
|
||||
amazongAuthorizeOnBillingAgreementSpy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
|
||||
amazongAuthorizeOnBillingAgreementSpy.returnsPromise().resolves({});
|
||||
|
||||
createSubSpy = sinon.stub(api, 'createSubscription');
|
||||
createSubSpy.returnsPromise().resolves({});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
amzLib.setBillingAgreementDetails.restore();
|
||||
amzLib.confirmBillingAgreement.restore();
|
||||
amzLib.authorizeOnBillingAgreement.restore();
|
||||
api.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('subscribes with amazon', async () => {
|
||||
let billingAgreementId = 'billingAgreementId';
|
||||
let sub = data.sub;
|
||||
let coupon;
|
||||
let groupId = group._id;
|
||||
let headers = {};
|
||||
|
||||
await api.subscribeWithAmazon({
|
||||
billingAgreementId,
|
||||
sub,
|
||||
coupon,
|
||||
user,
|
||||
groupId,
|
||||
headers,
|
||||
});
|
||||
|
||||
expect(amazonSetBillingAgreementDetailsSpy.calledOnce).to.be.true;
|
||||
expect(amazonConfirmBillingAgreementSpy.calledOnce).to.be.true;
|
||||
expect(amazongAuthorizeOnBillingAgreementSpy.calledOnce).to.be.true;
|
||||
expect(createSubSpy.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,7 +98,6 @@ describe('response middleware', () => {
|
||||
{
|
||||
type: notification.type,
|
||||
id: notification.id,
|
||||
createdAt: notification.createdAt,
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -6,32 +6,36 @@ import common from '../../../../../website/common/';
|
||||
import { each, find } from 'lodash';
|
||||
|
||||
describe('Challenge Model', () => {
|
||||
let guild, leader, challenge, task, tasksToTest;
|
||||
let guild, leader, challenge, task;
|
||||
let tasksToTest = {
|
||||
habit: {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
notes: 'test notes',
|
||||
},
|
||||
todo: {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
notes: 'test notes',
|
||||
},
|
||||
daily: {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
notes: 'test notes',
|
||||
},
|
||||
reward: {
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
notes: 'test notes',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
tasksToTest = {
|
||||
habit: {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
},
|
||||
todo: {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
},
|
||||
daily: {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
},
|
||||
reward: {
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
},
|
||||
};
|
||||
guild = new Group({
|
||||
name: 'test party',
|
||||
type: 'guild',
|
||||
@@ -77,6 +81,7 @@ describe('Challenge Model', () => {
|
||||
});
|
||||
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.notes).to.eql(task.notes);
|
||||
});
|
||||
|
||||
it('syncs a challenge to a user', async () => {
|
||||
@@ -96,8 +101,8 @@ describe('Challenge Model', () => {
|
||||
});
|
||||
|
||||
expect(updatedNewMember.challenges).to.contain(challenge._id);
|
||||
expect(updatedNewMember.tags[3].id).to.equal(challenge._id);
|
||||
expect(updatedNewMember.tags[3].name).to.equal(challenge.shortName);
|
||||
expect(updatedNewMember.tags[7].id).to.equal(challenge._id);
|
||||
expect(updatedNewMember.tags[7].name).to.equal(challenge.shortName);
|
||||
expect(syncedTask).to.exist;
|
||||
});
|
||||
|
||||
@@ -161,7 +166,7 @@ describe('Challenge Model', () => {
|
||||
|
||||
context('type specific updates', () => {
|
||||
it('updates habit specific field to challenge and challenge members', async () => {
|
||||
task = new Tasks.habit(Tasks.Task.sanitize(tasksToTest.habit)); // eslint-disable-line babel/new-cap
|
||||
task = new Tasks.habit(Tasks.Task.sanitize(tasksToTest.habit)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
@@ -180,7 +185,7 @@ describe('Challenge Model', () => {
|
||||
});
|
||||
|
||||
it('updates todo specific field to challenge and challenge members', async () => {
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line babel/new-cap
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
@@ -196,7 +201,7 @@ describe('Challenge Model', () => {
|
||||
});
|
||||
|
||||
it('does not update checklists on the user task', async () => {
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line babel/new-cap
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
@@ -214,7 +219,7 @@ describe('Challenge Model', () => {
|
||||
});
|
||||
|
||||
it('updates daily specific field to challenge and challenge members', async () => {
|
||||
task = new Tasks.daily(Tasks.Task.sanitize(tasksToTest.daily)); // eslint-disable-line babel/new-cap
|
||||
task = new Tasks.daily(Tasks.Task.sanitize(tasksToTest.daily)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { sleep } from '../../../../helpers/api-unit.helper';
|
||||
import { model as Group, INVITES_LIMIT } from '../../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { BadRequest } from '../../../../../website/server/libs/errors';
|
||||
import {
|
||||
BadRequest,
|
||||
} from '../../../../../website/server/libs/errors';
|
||||
import { quests as questScrolls } from '../../../../../website/common/script/content';
|
||||
import { groupChatReceivedWebhook } from '../../../../../website/server/libs/webhook';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
import validator from 'validator';
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import shared from '../../../../../website/common';
|
||||
|
||||
describe('Group Model', () => {
|
||||
let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember;
|
||||
@@ -628,24 +631,105 @@ describe('Group Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes a private group when the last member leaves', async () => {
|
||||
party.memberCount = 1;
|
||||
|
||||
it('deletes a private party when the last member leaves', async () => {
|
||||
await party.leave(participatingMember);
|
||||
await party.leave(questLeader);
|
||||
await party.leave(nonParticipatingMember);
|
||||
await party.leave(undecidedMember);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party).to.not.exist;
|
||||
});
|
||||
|
||||
it('does not delete a public group when the last member leaves', async () => {
|
||||
it('does not delete a private group when the last member leaves and a subscription is active', async () => {
|
||||
party.memberCount = 1;
|
||||
party.purchased.plan.customerId = '110002222333';
|
||||
|
||||
await expect(party.leave(participatingMember))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
name: 'NotAuthorized',
|
||||
httpCode: 401,
|
||||
message: shared.i18n.t('cannotDeleteActiveGroup'),
|
||||
});
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party).to.exist;
|
||||
expect(party.memberCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('does not delete a public group when the last member leaves', async () => {
|
||||
party.privacy = 'public';
|
||||
|
||||
await party.leave(participatingMember);
|
||||
await party.leave(questLeader);
|
||||
await party.leave(nonParticipatingMember);
|
||||
await party.leave(undecidedMember);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party).to.exist;
|
||||
});
|
||||
|
||||
it('does not delete a private party when the member count reaches zero if there are still members', async () => {
|
||||
party.memberCount = 1;
|
||||
|
||||
await party.leave(participatingMember);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party).to.exist;
|
||||
});
|
||||
|
||||
it('deletes a private guild when the last member leaves', async () => {
|
||||
let guild = new Group({
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
let leader = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
|
||||
guild.leader = leader._id;
|
||||
|
||||
await Promise.all([
|
||||
guild.save(),
|
||||
leader.save(),
|
||||
]);
|
||||
|
||||
await guild.leave(leader);
|
||||
|
||||
guild = await Group.findOne({_id: guild._id});
|
||||
expect(guild).to.not.exist;
|
||||
});
|
||||
|
||||
it('does not delete a private guild when the member count reaches zero if there are still members', async () => {
|
||||
let guild = new Group({
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
let leader = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
|
||||
let member = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
|
||||
guild.leader = leader._id;
|
||||
|
||||
await Promise.all([
|
||||
guild.save(),
|
||||
leader.save(),
|
||||
member.save(),
|
||||
]);
|
||||
|
||||
await guild.leave(member);
|
||||
|
||||
guild = await Group.findOne({_id: guild._id});
|
||||
expect(guild).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#sendChat', () => {
|
||||
@@ -1061,8 +1145,45 @@ describe('Group Model', () => {
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
});
|
||||
|
||||
sandbox.spy(User, 'update');
|
||||
describe('user update retry failures', () => {
|
||||
let successfulMock = {
|
||||
exec: () => {
|
||||
return Promise.resolve({raw: 'sucess'});
|
||||
},
|
||||
};
|
||||
let failedMock = {
|
||||
exec: () => {
|
||||
return Promise.reject(new Error('error'));
|
||||
},
|
||||
};
|
||||
|
||||
it('doesn\'t retry successful operations', async () => {
|
||||
sandbox.stub(User, 'update').returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledTwice;
|
||||
});
|
||||
|
||||
it('stops retrying when a successful update has occurred', async () => {
|
||||
let updateStub = sandbox.stub(User, 'update');
|
||||
updateStub.onCall(0).returns(failedMock);
|
||||
updateStub.returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledThrice;
|
||||
});
|
||||
|
||||
it('retries failed updates at most five times per user', async () => {
|
||||
sandbox.stub(User, 'update').returns(failedMock);
|
||||
|
||||
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
||||
|
||||
expect(User.update.callCount).to.eql(10);
|
||||
});
|
||||
});
|
||||
|
||||
it('gives out achievements', async () => {
|
||||
@@ -1138,14 +1259,34 @@ describe('Group Model', () => {
|
||||
expect(updatedParticipatingMember.items.hatchingPotions.Shade).to.eql(2);
|
||||
});
|
||||
|
||||
it('awards quests', async () => {
|
||||
it('awards quest scrolls to owner', async () => {
|
||||
let questAwardQuest = questScrolls.vice2;
|
||||
|
||||
await party.finishQuest(questAwardQuest);
|
||||
|
||||
let updatedLeader = await User.findById(questLeader._id);
|
||||
|
||||
expect(updatedLeader.items.quests.vice3).to.eql(1);
|
||||
});
|
||||
|
||||
it('awards non quest leader rewards to quest leader', async () => {
|
||||
let gearQuest = questScrolls.vice3;
|
||||
|
||||
await party.finishQuest(gearQuest);
|
||||
|
||||
let updatedLeader = await User.findById(questLeader._id);
|
||||
|
||||
expect(updatedLeader.items.gear.owned.weapon_special_2).to.eql(true);
|
||||
});
|
||||
|
||||
it('doesn\'t award quest owner rewards to all participants', async () => {
|
||||
let questAwardQuest = questScrolls.vice2;
|
||||
|
||||
await party.finishQuest(questAwardQuest);
|
||||
|
||||
let updatedParticipatingMember = await User.findById(participatingMember._id);
|
||||
|
||||
expect(updatedParticipatingMember.items.quests.vice3).to.eql(1);
|
||||
expect(updatedParticipatingMember.items.quests.vice3).to.not.exist;
|
||||
});
|
||||
|
||||
it('awards pets', async () => {
|
||||
@@ -1171,13 +1312,15 @@ describe('Group Model', () => {
|
||||
|
||||
context('Party quests', () => {
|
||||
it('updates participating members with rewards', async () => {
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledTwice;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: {
|
||||
$in: [questLeader._id, participatingMember._id],
|
||||
},
|
||||
_id: questLeader._id,
|
||||
});
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: participatingMember._id,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1204,6 +1347,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates all users with rewards', async () => {
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(tavernQuest);
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { model as Challenge } from '../../../../../website/server/models/challen
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../../website/server/models/task';
|
||||
import { each, find } from 'lodash';
|
||||
import { each, find, findIndex } from 'lodash';
|
||||
|
||||
describe('Group Task Methods', () => {
|
||||
let guild, leader, challenge, task;
|
||||
@@ -68,11 +68,29 @@ describe('Group Task Methods', () => {
|
||||
task = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue));
|
||||
task.group.id = guild._id;
|
||||
await task.save();
|
||||
if (task.checklist) {
|
||||
task.checklist.push({
|
||||
text: 'Checklist Item 1',
|
||||
completed: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('syncs an assigned task to a user', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let tagIndex = findIndex(updatedLeader.tags, {id: guild._id});
|
||||
let newTag = updatedLeader.tags[tagIndex];
|
||||
|
||||
expect(newTag.id).to.equal(guild._id);
|
||||
expect(newTag.name).to.equal(guild.name);
|
||||
expect(newTag.group).to.equal(guild._id);
|
||||
});
|
||||
|
||||
it('create tags for a user when task is synced', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
@@ -96,35 +114,124 @@ describe('Group Task Methods', () => {
|
||||
expect(syncedTask.text).to.equal(task.text);
|
||||
});
|
||||
|
||||
it('syncs updated info for assigned task to all users', async () => {
|
||||
let newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
|
||||
it('syncs checklist items to an assigned user', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
await guild.syncTask(task, newMember);
|
||||
|
||||
let updatedTaskName = 'Update Task name';
|
||||
task.text = updatedTaskName;
|
||||
|
||||
await guild.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(leader._id);
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.text).to.equal(task.text);
|
||||
expect(syncedTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedTask.checklist[0].text).to.equal(task.checklist[0].text);
|
||||
});
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(newMember._id);
|
||||
expect(syncedMemberTask).to.exist;
|
||||
expect(syncedMemberTask.text).to.equal(task.text);
|
||||
describe('syncs updated info', async() => {
|
||||
let newMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
|
||||
await guild.syncTask(task, leader);
|
||||
await guild.syncTask(task, newMember);
|
||||
});
|
||||
|
||||
it('syncs updated info for assigned task to all users', async () => {
|
||||
let updatedTaskName = 'Update Task name';
|
||||
task.text = updatedTaskName;
|
||||
task.group.approval.required = true;
|
||||
|
||||
await guild.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(leader._id);
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.text).to.equal(task.text);
|
||||
expect(syncedTask.group.approval.required).to.equal(true);
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(newMember._id);
|
||||
expect(syncedMemberTask).to.exist;
|
||||
expect(syncedMemberTask.text).to.equal(task.text);
|
||||
expect(syncedMemberTask.group.approval.required).to.equal(true);
|
||||
});
|
||||
|
||||
it('syncs a new checklist item to all assigned users', async () => {
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
let newCheckListItem = {
|
||||
text: 'Checklist Item 1',
|
||||
completed: false,
|
||||
};
|
||||
|
||||
task.checklist.push(newCheckListItem);
|
||||
|
||||
await guild.updateTask(task, {newCheckListItem});
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedTask.checklist[1].text).to.equal(task.checklist[1].text);
|
||||
expect(syncedMemberTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedMemberTask.checklist[1].text).to.equal(task.checklist[1].text);
|
||||
});
|
||||
|
||||
it('syncs updated info for checklist in assigned task to all users when flag is passed', async () => {
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
let updateCheckListText = 'Updated checklist item';
|
||||
if (task.checklist) {
|
||||
task.checklist[0].text = updateCheckListText;
|
||||
}
|
||||
|
||||
await guild.updateTask(task, {updateCheckListItems: [task.checklist[0]]});
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedTask.checklist[0].text).to.equal(updateCheckListText);
|
||||
expect(syncedMemberTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedMemberTask.checklist[0].text).to.equal(updateCheckListText);
|
||||
});
|
||||
|
||||
it('removes a checklist item in assigned task to all users when flag is passed with checklist id', async () => {
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
await guild.updateTask(task, {removedCheckListItemId: task.checklist[0].id});
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(0);
|
||||
expect(syncedMemberTask.checklist.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('removes an assigned task and unlinks assignees', async () => {
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('Task Model', () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
taskWithAlias = new Tasks.todo({ // eslint-disable-line babel/new-cap
|
||||
taskWithAlias = new Tasks.todo({ // eslint-disable-line new-cap
|
||||
text: 'some text',
|
||||
alias: 'short-name',
|
||||
userId: user.id,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
describe('User Model', () => {
|
||||
it('keeps user._tmp when calling .toJSON', () => {
|
||||
@@ -48,28 +49,100 @@ describe('User Model', () => {
|
||||
});
|
||||
|
||||
context('notifications', () => {
|
||||
it('can add notifications with data', () => {
|
||||
it('can add notifications without data', () => {
|
||||
let user = new User();
|
||||
|
||||
user.addNotification('CRON');
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'createdAt']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
});
|
||||
|
||||
it('can add notifications without data', () => {
|
||||
it('can add notifications with data', () => {
|
||||
let user = new User();
|
||||
|
||||
user.addNotification('CRON', {field: 1});
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'createdAt']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
});
|
||||
|
||||
context('static push method', () => {
|
||||
it('adds notifications for a single member via static method', async() => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
|
||||
await User.pushNotification({_id: user._id}, 'CRON');
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
});
|
||||
|
||||
it('validates notifications via static method', async() => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
|
||||
expect(User.pushNotification({_id: user._id}, 'BAD_TYPE')).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('adds notifications without data for all given users via static method', async() => {
|
||||
let user = new User();
|
||||
let otherUser = new User();
|
||||
await Bluebird.all([user.save(), otherUser.save()]);
|
||||
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON');
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
});
|
||||
|
||||
it('adds notifications with data for all given users via static method', async() => {
|
||||
let user = new User();
|
||||
let otherUser = new User();
|
||||
await Bluebird.all([user.save(), otherUser.save()]);
|
||||
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1});
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -275,7 +275,8 @@ describe('Challenges Controller', function() {
|
||||
describe('editTask', function() {
|
||||
it('is Tasks.editTask', function() {
|
||||
inject(function(Tasks) {
|
||||
expect(scope.editTask).to.eql(Tasks.editTask);
|
||||
// @TODO: Currently we override the task function in the challenge ctrl, but we should abstract it again
|
||||
// expect(scope.editTask).to.eql(Tasks.editTask);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,10 @@ describe('Inventory Controller', function() {
|
||||
},
|
||||
preferences: {
|
||||
suppressModals: {}
|
||||
}
|
||||
},
|
||||
purchased: {
|
||||
plan: {}
|
||||
},
|
||||
});
|
||||
|
||||
Shared.wrap(user);
|
||||
@@ -450,4 +453,84 @@ describe('Inventory Controller', function() {
|
||||
expect(scope.hasAllTimeTravelerItemsOfType('mounts')).to.eql(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Gear search filter', function() {
|
||||
var wrap = function(text) {
|
||||
return {'text': function() {return text;}};
|
||||
}
|
||||
|
||||
var toText = function(list) {
|
||||
return _.map(list, function(ele) { return ele.text(); });
|
||||
}
|
||||
|
||||
var gearByClass, gearByType;
|
||||
|
||||
beforeEach(function() {
|
||||
scope.$digest();
|
||||
gearByClass = {'raw': [wrap('kale'), wrap('sashimi')],
|
||||
'cooked': [wrap('chicken'), wrap('potato')]};
|
||||
|
||||
gearByType = {'veg': [wrap('kale'), wrap('potato')],
|
||||
'not': [wrap('chicken'), wrap('sashimi')]};
|
||||
scope.gearByClass = gearByClass;
|
||||
scope.gearByType = gearByType;
|
||||
scope.equipmentQuery.query = 'a';
|
||||
});
|
||||
|
||||
it('filters nothing if equipmentQuery is nothing', function() {
|
||||
scope.equipmentQuery.query = '';
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByClass['raw'])).to.eql(['kale', 'sashimi']);
|
||||
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['chicken', 'potato']);
|
||||
expect(toText(scope.filteredGearByType['veg'])).to.eql(['kale', 'potato']);
|
||||
expect(toText(scope.filteredGearByType['not'])).to.eql(['chicken', 'sashimi']);
|
||||
});
|
||||
|
||||
it('filters out gear if class gear changes', function() {
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByClass['raw'])).to.eql(['kale', 'sashimi']);
|
||||
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['potato']);
|
||||
|
||||
scope.gearByClass['raw'].push(wrap('zucchini'));
|
||||
scope.gearByClass['cooked'].push(wrap('pizza'));
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByClass['raw'])).to.eql(['kale', 'sashimi']);
|
||||
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['potato', 'pizza']);
|
||||
});
|
||||
|
||||
it('filters out gear if typed gear changes', function() {
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByType['veg'])).to.eql(['kale', 'potato']);
|
||||
expect(toText(scope.filteredGearByType['not'])).to.eql(['sashimi']);
|
||||
|
||||
scope.gearByType['veg'].push(wrap('zucchini'));
|
||||
scope.gearByType['not'].push(wrap('pizza'));
|
||||
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByType['veg'])).to.eql(['kale', 'potato']);
|
||||
expect(toText(scope.filteredGearByType['not'])).to.eql(['sashimi', 'pizza']);
|
||||
});
|
||||
|
||||
it('filters out gear if filter query changes', function() {
|
||||
scope.equipmentQuery.query = 'c';
|
||||
scope.$digest();
|
||||
|
||||
expect(toText(scope.filteredGearByClass['raw'])).to.eql([]);
|
||||
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['chicken']);
|
||||
expect(toText(scope.filteredGearByType['veg'])).to.eql([]);
|
||||
expect(toText(scope.filteredGearByType['not'])).to.eql(['chicken']);
|
||||
});
|
||||
|
||||
it('returns the right filtered gear', function() {
|
||||
var equipment = [wrap('spicy tuna'), wrap('dragon'), wrap('rainbow'), wrap('caterpillar')];
|
||||
expect(toText(scope.equipmentSearch(equipment, 'ra'))).to.eql(['dragon', 'rainbow']);
|
||||
});
|
||||
|
||||
it('returns the right filtered gear if the source gear has unicode', function() {
|
||||
// blue hat, red hat, red shield
|
||||
var equipment = [wrap('藍色軟帽'), wrap('紅色軟帽'), wrap('紅色盾牌')];
|
||||
// searching for 'red' gives red hat, red shield
|
||||
expect(toText(scope.equipmentSearch(equipment, '紅色'))).to.eql(['紅色軟帽', '紅色盾牌']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('Notification Controller', function() {
|
||||
let User = {
|
||||
user,
|
||||
readNotification: function noop () {},
|
||||
readNotifications: function noop () {},
|
||||
sync: userSync
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ describe('Tasks Controller', function() {
|
||||
describe('editTask', function() {
|
||||
it('is Tasks.editTask', function() {
|
||||
inject(function(Tasks) {
|
||||
expect(scope.editTask).to.eql(Tasks.editTask);
|
||||
// @TODO: Currently we override the task function in the challenge ctrl, but we should abstract it again
|
||||
// expect(scope.editTask).to.eql(Tasks.editTask);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -194,6 +194,7 @@ describe('Analytics Service', function () {
|
||||
rewards: 1
|
||||
};
|
||||
expectedProperties.balance = 12;
|
||||
expectedProperties.balanceGemAmount = 48;
|
||||
|
||||
beforeEach(function() {
|
||||
user._id = 'unique-user-id';
|
||||
@@ -243,7 +244,8 @@ describe('Analytics Service', function () {
|
||||
habits: 1,
|
||||
rewards: 1
|
||||
},
|
||||
balance: 12
|
||||
balance: 12,
|
||||
balanceGemAmount: 48
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
@@ -16,6 +16,8 @@ describe('Tasks Service', function() {
|
||||
rootScope.charts = {};
|
||||
tasks = Tasks;
|
||||
});
|
||||
|
||||
rootScope.openModal = function () {};
|
||||
});
|
||||
|
||||
it('calls get user tasks endpoint', function() {
|
||||
@@ -151,7 +153,6 @@ describe('Tasks Service', function() {
|
||||
});
|
||||
|
||||
describe('editTask', function() {
|
||||
|
||||
var task;
|
||||
|
||||
beforeEach(function(){
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": ["transform-object-rest-spread"],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread",
|
||||
"syntax-async-functions",
|
||||
"transform-regenerator",
|
||||
],
|
||||
"comments": false
|
||||
}
|
||||
@@ -36,7 +36,10 @@ webpackConfig.module.preLoaders = webpackConfig.module.preLoaders || [];
|
||||
webpackConfig.module.preLoaders.unshift({
|
||||
test: /\.js$/,
|
||||
loader: 'isparta',
|
||||
include: path.resolve(projectRoot, 'website/client'),
|
||||
include: [
|
||||
path.resolve(projectRoot, 'website/client'),
|
||||
path.resolve(projectRoot, 'website/common'),
|
||||
],
|
||||
});
|
||||
|
||||
// only apply babel for test files when using isparta
|
||||
|
||||
8
test/client/unit/specs/filters/floor.spec.js
Normal file
8
test/client/unit/specs/filters/floor.spec.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import floorFilter from 'client/filters/floor';
|
||||
|
||||
describe('floor filter', () => {
|
||||
it('can floor a decimal number', () => {
|
||||
expect(floorFilter(4.567)).to.equal(4.56);
|
||||
expect(floorFilter(4.562)).to.equal(4.56);
|
||||
});
|
||||
});
|
||||
8
test/client/unit/specs/filters/round.spec.js
Normal file
8
test/client/unit/specs/filters/round.spec.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import roundFilter from 'client/filters/round';
|
||||
|
||||
describe('round filter', () => {
|
||||
it('can round a decimal number', () => {
|
||||
expect(roundFilter(4.567)).to.equal(4.57);
|
||||
expect(roundFilter(4.562)).to.equal(4.56);
|
||||
});
|
||||
});
|
||||
13
test/client/unit/specs/getters/userGems.spec.js
Normal file
13
test/client/unit/specs/getters/userGems.spec.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { gems as userGems } from 'client/store/getters/user';
|
||||
|
||||
describe('userGems getter', () => {
|
||||
it('returns the user\'s gems', () => {
|
||||
expect(userGems({
|
||||
state: {
|
||||
user: {
|
||||
balance: 4.5,
|
||||
},
|
||||
},
|
||||
})).to.equal(18);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
import storeInjector from 'inject?-vue!client/store';
|
||||
import { mapState, mapGetters, mapActions } from 'client/store';
|
||||
import { flattenAndNamespace } from 'client/store/helpers/internals';
|
||||
|
||||
describe('Store', () => {
|
||||
let injectedStore;
|
||||
@@ -14,11 +15,25 @@ describe('Store', () => {
|
||||
computedName ({ state }) {
|
||||
return `${state.name} computed!`;
|
||||
},
|
||||
...flattenAndNamespace({
|
||||
nested: {
|
||||
computedName ({ state }) {
|
||||
return `${state.name} computed!`;
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
'./actions': {
|
||||
getName ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
...flattenAndNamespace({
|
||||
nested: {
|
||||
getName ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}).default;
|
||||
});
|
||||
@@ -41,17 +56,29 @@ describe('Store', () => {
|
||||
injectedStore.state.name = 'test updated';
|
||||
});
|
||||
|
||||
it('supports getters', () => {
|
||||
expect(injectedStore.getters.computedName).to.equal('test computed!');
|
||||
injectedStore.state.name = 'test updated';
|
||||
expect(injectedStore.getters.computedName).to.equal('test updated computed!');
|
||||
describe('getters', () => {
|
||||
it('supports getters', () => {
|
||||
expect(injectedStore.getters.computedName).to.equal('test computed!');
|
||||
injectedStore.state.name = 'test updated';
|
||||
expect(injectedStore.getters.computedName).to.equal('test updated computed!');
|
||||
});
|
||||
|
||||
it('supports nested getters', () => {
|
||||
expect(injectedStore.getters['nested:computedName']).to.equal('test computed!');
|
||||
injectedStore.state.name = 'test updated';
|
||||
expect(injectedStore.getters['nested:computedName']).to.equal('test updated computed!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
it('can be dispatched', () => {
|
||||
it('can dispatch an action', () => {
|
||||
expect(injectedStore.dispatch('getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
|
||||
});
|
||||
|
||||
it('can dispatch a nested action', () => {
|
||||
expect(injectedStore.dispatch('nested:getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
|
||||
});
|
||||
|
||||
it('throws an error is the action doesn\'t exists', () => {
|
||||
expect(() => injectedStore.dispatched('wrong')).to.throw;
|
||||
});
|
||||
@@ -116,5 +143,26 @@ describe('Store', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('flattenAndNamespace', () => {
|
||||
let result = flattenAndNamespace({
|
||||
nested: {
|
||||
computed ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
getName ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
},
|
||||
nested2: {
|
||||
getName ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(Object.keys(result).length).to.equal(3);
|
||||
expect(Object.keys(result).sort()).to.deep.equal(['nested2:getName', 'nested:computed', 'nested:getName']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
365
test/common/libs/achievements.test.js
Normal file
365
test/common/libs/achievements.test.js
Normal file
@@ -0,0 +1,365 @@
|
||||
import shared from '../../../website/common';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
|
||||
describe('achievements', () => {
|
||||
describe('general well-formedness', () => {
|
||||
let user = generateUser();
|
||||
let achievements = shared.achievements.getAchievementsForProfile(user);
|
||||
|
||||
it('each category has \'label\' and \'achievements\' fields', () => {
|
||||
_.each(achievements, (category) => {
|
||||
expect(category).to.have.property('label')
|
||||
.that.is.a('string');
|
||||
expect(category).to.have.property('achievements')
|
||||
.that.is.a('object');
|
||||
});
|
||||
});
|
||||
|
||||
it('each achievement has all required fields of correct types', () => {
|
||||
_.each(achievements, (category) => {
|
||||
_.each(category.achievements, (achiev) => {
|
||||
// May have additional fields (such as 'value' and 'optionalCount').
|
||||
expect(achiev).to.contain.all.keys(['title', 'text', 'icon', 'earned', 'index']);
|
||||
expect(achiev.title).to.be.a('string');
|
||||
expect(achiev.text).to.be.a('string');
|
||||
expect(achiev.icon).to.be.a('string');
|
||||
expect(achiev.earned).to.be.a('boolean');
|
||||
expect(achiev.index).to.be.a('number');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('categories have unique labels', () => {
|
||||
let achievementsArray = _.values(achievements).map(cat => cat.label);
|
||||
let labels = _.uniq(achievementsArray);
|
||||
|
||||
expect(labels.length).to.be.greaterThan(0);
|
||||
expect(labels.length).to.eql(_.size(achievements));
|
||||
});
|
||||
|
||||
it('achievements have unique keys', () => {
|
||||
let keysSoFar = {};
|
||||
|
||||
_.each(achievements, (category) => {
|
||||
_.keys(category.achievements).forEach((key) => {
|
||||
expect(keysSoFar[key]).to.be.undefined;
|
||||
keysSoFar[key] = key;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('achievements have unique indices', () => {
|
||||
let indicesSoFar = {};
|
||||
|
||||
_.each(achievements, (category) => {
|
||||
_.each(category.achievements, (achiev) => {
|
||||
let i = achiev.index;
|
||||
expect(indicesSoFar[i]).to.be.undefined;
|
||||
indicesSoFar[i] = i;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('all categories have at least 1 achievement', () => {
|
||||
_.each(achievements, (category) => {
|
||||
expect(_.size(category.achievements)).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unearned basic achievements', () => {
|
||||
let user = generateUser();
|
||||
let basicAchievs = shared.achievements.getAchievementsForProfile(user).basic.achievements;
|
||||
|
||||
it('streak and perfect day achievements exist with counts', () => {
|
||||
let streak = basicAchievs.streak;
|
||||
let perfect = basicAchievs.perfect;
|
||||
|
||||
expect(streak).to.exist;
|
||||
expect(streak).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
expect(perfect).to.exist;
|
||||
expect(perfect).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('party up/on achievements exist with no counts', () => {
|
||||
let partyUp = basicAchievs.partyUp;
|
||||
let partyOn = basicAchievs.partyOn;
|
||||
|
||||
expect(partyUp).to.exist;
|
||||
expect(partyUp.optionalCount).to.be.undefined;
|
||||
expect(partyOn).to.exist;
|
||||
expect(partyOn.optionalCount).to.be.undefined;
|
||||
});
|
||||
|
||||
it('pet/mount master and triad bingo achievements exist with counts', () => {
|
||||
let beastMaster = basicAchievs.beastMaster;
|
||||
let mountMaster = basicAchievs.mountMaster;
|
||||
let triadBingo = basicAchievs.triadBingo;
|
||||
|
||||
expect(beastMaster).to.exist;
|
||||
expect(beastMaster).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
expect(mountMaster).to.exist;
|
||||
expect(mountMaster).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
expect(triadBingo).to.exist;
|
||||
expect(triadBingo).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('ultimate gear achievements exist with no counts', () => {
|
||||
let gearTypes = ['healer', 'rogue', 'warrior', 'mage'];
|
||||
gearTypes.forEach((gear) => {
|
||||
let gearAchiev = basicAchievs[`${gear}UltimateGear`];
|
||||
|
||||
expect(gearAchiev).to.exist;
|
||||
expect(gearAchiev.optionalCount).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
it('rebirth achievement exists with no count', () => {
|
||||
let rebirth = basicAchievs.rebirth;
|
||||
|
||||
expect(rebirth).to.exist;
|
||||
expect(rebirth.optionalCount).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('unearned seasonal achievements', () => {
|
||||
let user = generateUser();
|
||||
let seasonalAchievs = shared.achievements.getAchievementsForProfile(user).seasonal.achievements;
|
||||
|
||||
it('habiticaDays and habitBirthdays achievements exist with counts', () => {
|
||||
let habiticaDays = seasonalAchievs.habiticaDays;
|
||||
let habitBirthdays = seasonalAchievs.habitBirthdays;
|
||||
|
||||
expect(habiticaDays).to.exist;
|
||||
expect(habiticaDays).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
expect(habitBirthdays).to.exist;
|
||||
expect(habitBirthdays).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('spell achievements exist with counts', () => {
|
||||
let spellTypes = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
|
||||
spellTypes.forEach((spell) => {
|
||||
let spellAchiev = seasonalAchievs[spell];
|
||||
|
||||
expect(spellAchiev).to.exist;
|
||||
expect(spellAchiev).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
});
|
||||
|
||||
it('quest achievements do not exist', () => {
|
||||
let quests = ['dilatory', 'stressbeast', 'burnout', 'bewilder'];
|
||||
quests.forEach((quest) => {
|
||||
let questAchiev = seasonalAchievs[`${quest}Quest`];
|
||||
|
||||
expect(questAchiev).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('costumeContests achievement exists with count', () => {
|
||||
let costumeContests = seasonalAchievs.costumeContests;
|
||||
|
||||
expect(costumeContests).to.exist;
|
||||
expect(costumeContests).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('card achievements exist with counts', () => {
|
||||
let cardTypes = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday'];
|
||||
cardTypes.forEach((card) => {
|
||||
let cardAchiev = seasonalAchievs[`${card}Cards`];
|
||||
|
||||
expect(cardAchiev).to.exist;
|
||||
expect(cardAchiev).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unearned special achievements', () => {
|
||||
let user = generateUser();
|
||||
let specialAchievs = shared.achievements.getAchievementsForProfile(user).special.achievements;
|
||||
|
||||
it('habitSurveys achievement exists with count', () => {
|
||||
let habitSurveys = specialAchievs.habitSurveys;
|
||||
|
||||
expect(habitSurveys).to.exist;
|
||||
expect(habitSurveys).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('contributor achievement exists with value and no count', () => {
|
||||
let contributor = specialAchievs.contributor;
|
||||
|
||||
expect(contributor).to.exist;
|
||||
expect(contributor).to.have.property('value')
|
||||
.that.is.a('number');
|
||||
expect(contributor.optionalCount).to.be.undefined;
|
||||
});
|
||||
|
||||
it('npc achievement is hidden if unachieved', () => {
|
||||
let npc = specialAchievs.npc;
|
||||
expect(npc).to.not.exist;
|
||||
});
|
||||
|
||||
it('kickstarter achievement is hidden if unachieved', () => {
|
||||
let kickstarter = specialAchievs.kickstarter;
|
||||
expect(kickstarter).to.not.exist;
|
||||
});
|
||||
|
||||
it('veteran achievement is hidden if unachieved', () => {
|
||||
let veteran = specialAchievs.veteran;
|
||||
expect(veteran).to.not.exist;
|
||||
});
|
||||
|
||||
it('originalUser achievement is hidden if unachieved', () => {
|
||||
let originalUser = specialAchievs.originalUser;
|
||||
expect(originalUser).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('earned seasonal achievements', () => {
|
||||
let user = generateUser();
|
||||
let quests = ['dilatory', 'stressbeast', 'burnout', 'bewilder'];
|
||||
quests.forEach((quest) => {
|
||||
user.achievements.quests[quest] = 1;
|
||||
});
|
||||
let seasonalAchievs = shared.achievements.getAchievementsForProfile(user).seasonal.achievements;
|
||||
|
||||
it('quest achievements exist', () => {
|
||||
quests.forEach((quest) => {
|
||||
let questAchiev = seasonalAchievs[`${quest}Quest`];
|
||||
|
||||
expect(questAchiev).to.exist;
|
||||
expect(questAchiev.optionalCount).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('earned special achievements', () => {
|
||||
let user = generateUser({
|
||||
achievements: {
|
||||
habitSurveys: 2,
|
||||
veteran: true,
|
||||
originalUser: true,
|
||||
},
|
||||
backer: {tier: 3},
|
||||
contributor: {level: 1},
|
||||
});
|
||||
let specialAchievs = shared.achievements.getAchievementsForProfile(user).special.achievements;
|
||||
|
||||
it('habitSurveys achievement is earned with correct value', () => {
|
||||
let habitSurveys = specialAchievs.habitSurveys;
|
||||
|
||||
expect(habitSurveys).to.exist;
|
||||
expect(habitSurveys.earned).to.eql(true);
|
||||
expect(habitSurveys.value).to.eql(2);
|
||||
});
|
||||
|
||||
it('contributor achievement is earned with correct value', () => {
|
||||
let contributor = specialAchievs.contributor;
|
||||
|
||||
expect(contributor).to.exist;
|
||||
expect(contributor.earned).to.eql(true);
|
||||
expect(contributor.value).to.eql(1);
|
||||
});
|
||||
|
||||
it('npc achievement is earned with correct value', () => {
|
||||
let npcUser = generateUser({
|
||||
backer: {npc: 'test'},
|
||||
});
|
||||
let npc = shared.achievements.getAchievementsForProfile(npcUser).special.achievements.npc;
|
||||
|
||||
expect(npc).to.exist;
|
||||
expect(npc.earned).to.eql(true);
|
||||
expect(npc.value).to.eql('test');
|
||||
});
|
||||
|
||||
it('kickstarter achievement is earned with correct value', () => {
|
||||
let kickstarter = specialAchievs.kickstarter;
|
||||
|
||||
expect(kickstarter).to.exist;
|
||||
expect(kickstarter.earned).to.eql(true);
|
||||
expect(kickstarter.value).to.eql(3);
|
||||
});
|
||||
|
||||
it('veteran achievement is earned', () => {
|
||||
let veteran = specialAchievs.veteran;
|
||||
|
||||
expect(veteran).to.exist;
|
||||
expect(veteran.earned).to.eql(true);
|
||||
});
|
||||
|
||||
it('originalUser achievement is earned', () => {
|
||||
let originalUser = specialAchievs.originalUser;
|
||||
|
||||
expect(originalUser).to.exist;
|
||||
expect(originalUser.earned).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mountMaster, beastMaster, and triadBingo achievements', () => {
|
||||
it('master and triad bingo achievements do not include *Text2 strings if no keys have been used', () => {
|
||||
let user = generateUser();
|
||||
let basicAchievs = shared.achievements.getAchievementsForProfile(user).basic.achievements;
|
||||
|
||||
let beastMaster = basicAchievs.beastMaster;
|
||||
let mountMaster = basicAchievs.mountMaster;
|
||||
let triadBingo = basicAchievs.triadBingo;
|
||||
|
||||
expect(beastMaster.text).to.not.match(/released/);
|
||||
expect(beastMaster.text).to.not.match(/0 time\(s\)/);
|
||||
expect(mountMaster.text).to.not.match(/released/);
|
||||
expect(mountMaster.text).to.not.match(/0 time\(s\)/);
|
||||
expect(triadBingo.text).to.not.match(/released/);
|
||||
expect(triadBingo.text).to.not.match(/0 time\(s\)/);
|
||||
});
|
||||
|
||||
it('master and triad bingo achievements includes *Text2 strings if keys have been used', () => {
|
||||
let user = generateUser({
|
||||
achievements: {
|
||||
beastMasterCount: 1,
|
||||
mountMasterCount: 2,
|
||||
triadBingoCount: 3,
|
||||
},
|
||||
});
|
||||
let basicAchievs = shared.achievements.getAchievementsForProfile(user).basic.achievements;
|
||||
|
||||
let beastMaster = basicAchievs.beastMaster;
|
||||
let mountMaster = basicAchievs.mountMaster;
|
||||
let triadBingo = basicAchievs.triadBingo;
|
||||
|
||||
expect(beastMaster.text).to.match(/released/);
|
||||
expect(beastMaster.text).to.match(/1 time\(s\)/);
|
||||
expect(mountMaster.text).to.match(/released/);
|
||||
expect(mountMaster.text).to.match(/2 time\(s\)/);
|
||||
expect(triadBingo.text).to.match(/released/);
|
||||
expect(triadBingo.text).to.match(/3 time\(s\)/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ultimateGear achievements', () => {
|
||||
it('title and text contain localized class info', () => {
|
||||
let user = generateUser();
|
||||
let basicAchievs = shared.achievements.getAchievementsForProfile(user).basic.achievements;
|
||||
let gearTypes = ['healer', 'rogue', 'warrior', 'mage'];
|
||||
|
||||
gearTypes.forEach((gear) => {
|
||||
let gearAchiev = basicAchievs[`${gear}UltimateGear`];
|
||||
let classNameRegex = new RegExp(gear.charAt(0).toUpperCase() + gear.slice(1));
|
||||
|
||||
expect(gearAchiev.title).to.match(classNameRegex);
|
||||
expect(gearAchiev.text).to.match(classNameRegex);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -18,7 +18,7 @@ describe('shared.ops.blockUser', () => {
|
||||
|
||||
it('validates uuid', (done) => {
|
||||
try {
|
||||
blockUser(user, { params: { uuid: 1 } });
|
||||
blockUser(user, { params: { uuid: '1' } });
|
||||
} catch (error) {
|
||||
expect(error.message).to.eql(i18n.t('invalidUUID'));
|
||||
done();
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '../../helpers/common.helper';
|
||||
|
||||
describe('shared.ops.purchase', () => {
|
||||
const SEASONAL_FOOD = 'Candy_Base';
|
||||
const SEASONAL_FOOD = 'Meat';
|
||||
let user;
|
||||
let goldPoints = 40;
|
||||
let gemsBought = 40;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import revive from '../../../website/common/script/ops/revive';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
import {
|
||||
@@ -53,7 +55,21 @@ describe('shared.ops.revive', () => {
|
||||
expect(user.stats.str).to.equal(1);
|
||||
});
|
||||
|
||||
it('TODO: test actual ways stats are affected');
|
||||
it('it decreases a random stat from str, con, per, int by one', () => {
|
||||
let stats = ['str', 'con', 'per', 'int'];
|
||||
|
||||
_.each(stats, (s) => {
|
||||
user.stats[s] = 1;
|
||||
});
|
||||
|
||||
revive(user);
|
||||
|
||||
let statSum = _.reduce(stats, (m, k) => {
|
||||
return m + user.stats[k];
|
||||
}, 0);
|
||||
|
||||
expect(statSum).to.equal(3);
|
||||
});
|
||||
|
||||
it('removes a random item from user gear owned', () => {
|
||||
let weaponKey = 'weapon_warrior_0';
|
||||
@@ -65,15 +81,58 @@ describe('shared.ops.revive', () => {
|
||||
expect(user.items.gear.owned[weaponKey]).to.be.false;
|
||||
});
|
||||
|
||||
it('does not remove 0 value items');
|
||||
it('does not remove 0 value items', () => {
|
||||
user.items.gear.owned = {
|
||||
eyewear_special_yellowTopFrame: true,
|
||||
};
|
||||
|
||||
it('allows removing warrior sword (0 value item)');
|
||||
revive(user);
|
||||
|
||||
it('does not remove items of a different class');
|
||||
expect(user.items.gear.owned.eyewear_special_yellowTopFrame).to.be.true;
|
||||
});
|
||||
|
||||
it('removes "special" items');
|
||||
it('allows removing warrior sword (0 value item)', () => {
|
||||
user.items.gear.owned = {
|
||||
weapon_warrior_0: true,
|
||||
};
|
||||
|
||||
it('removes "armoire" items');
|
||||
let weaponKey = 'weapon_warrior_0';
|
||||
|
||||
let [, message] = revive(user);
|
||||
|
||||
expect(message).to.equal(i18n.t('messageLostItem', { itemText: content.gear.flat[weaponKey].text()}));
|
||||
expect(user.items.gear.owned[weaponKey]).to.be.false;
|
||||
});
|
||||
|
||||
it('does not remove items of a different class', () => {
|
||||
let weaponKey = 'weapon_wizard_1';
|
||||
user.items.gear.owned[weaponKey] = true;
|
||||
|
||||
let [, message] = revive(user);
|
||||
|
||||
expect(message).to.equal('');
|
||||
expect(user.items.gear.owned[weaponKey]).to.be.true;
|
||||
});
|
||||
|
||||
it('removes "special" items', () => {
|
||||
let weaponKey = 'weapon_special_1';
|
||||
user.items.gear.owned[weaponKey] = true;
|
||||
|
||||
let [, message] = revive(user);
|
||||
|
||||
expect(message).to.equal(i18n.t('messageLostItem', { itemText: content.gear.flat[weaponKey].text()}));
|
||||
expect(user.items.gear.owned[weaponKey]).to.be.false;
|
||||
});
|
||||
|
||||
it('removes "armoire" items', () => {
|
||||
let weaponKey = 'armor_armoire_goldenToga';
|
||||
user.items.gear.owned[weaponKey] = true;
|
||||
|
||||
let [, message] = revive(user);
|
||||
|
||||
expect(message).to.equal(i18n.t('messageLostItem', { itemText: content.gear.flat[weaponKey].text()}));
|
||||
expect(user.items.gear.owned[weaponKey]).to.be.false;
|
||||
});
|
||||
|
||||
it('dequips lost item from user if user had it equipped', () => {
|
||||
let weaponKey = 'weapon_warrior_0';
|
||||
|
||||
@@ -157,6 +157,16 @@ describe('shared.ops.scoreTask', () => {
|
||||
expect(ref.afterUser._tmp.quest.progressDelta).to.eql(secondTaskDelta);
|
||||
});
|
||||
|
||||
it('does not modify stats when task need approval', () => {
|
||||
todo.group.approval.required = true;
|
||||
options = { user: ref.afterUser, task: todo, direction: 'up', times: 5, cron: false };
|
||||
scoreTask(options);
|
||||
|
||||
expect(ref.afterUser.stats.hp).to.eql(50);
|
||||
expect(ref.afterUser.stats.exp).to.equal(ref.beforeUser.stats.exp);
|
||||
expect(ref.afterUser.stats.gp).to.equal(ref.beforeUser.stats.gp);
|
||||
});
|
||||
|
||||
context('habits', () => {
|
||||
it('up', () => {
|
||||
options = { user: ref.afterUser, task: habit, direction: 'up', times: 5, cron: false };
|
||||
|
||||
29
test/content/time-travelers.test.js
Normal file
29
test/content/time-travelers.test.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../helpers/common.helper';
|
||||
|
||||
import timeTravelers from '../../website/common/script/content/time-travelers'
|
||||
|
||||
describe('time-travelers store', () => {
|
||||
let user;
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
});
|
||||
|
||||
it('removes owned sets from the time travelers store', () => {
|
||||
user.items.gear.owned['head_mystery_201602'] = true;
|
||||
expect(timeTravelers.timeTravelerStore(user)['201602']).to.not.exist;
|
||||
expect(timeTravelers.timeTravelerStore(user)['201603']).to.exist;
|
||||
});
|
||||
|
||||
it('removes unopened mystery item sets from the time travelers store', () => {
|
||||
user.purchased = {
|
||||
plan: {
|
||||
mysteryItems: ['head_mystery_201602'],
|
||||
},
|
||||
};
|
||||
expect(timeTravelers.timeTravelerStore(user)['201602']).to.not.exist;
|
||||
expect(timeTravelers.timeTravelerStore(user)['201603']).to.exist;
|
||||
});
|
||||
});
|
||||
@@ -86,7 +86,7 @@ export function generateTodo (user) {
|
||||
completed: false,
|
||||
};
|
||||
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
task.userId = user._id;
|
||||
task.save();
|
||||
|
||||
@@ -101,7 +101,7 @@ export function generateDaily (user) {
|
||||
completed: false,
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
task.userId = user._id;
|
||||
task.save();
|
||||
|
||||
|
||||
@@ -13,5 +13,7 @@ chai.use(require('sinon-chai'));
|
||||
chai.use(require('chai-as-promised'));
|
||||
global.expect = chai.expect;
|
||||
global.sinon = require('sinon');
|
||||
let sinonStubPromise = require('sinon-stub-promise');
|
||||
sinonStubPromise(global.sinon);
|
||||
global.sandbox = sinon.sandbox.create();
|
||||
global.Promise = Bluebird;
|
||||
|
||||
@@ -7,8 +7,8 @@ const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/;
|
||||
// Use this to verify error messages returned by the server
|
||||
// That way, if the translated string changes, the test
|
||||
// will not break. NOTE: it checks against errors with string as well.
|
||||
export function translate (key, variables) {
|
||||
let translatedString = i18n.t(key, variables);
|
||||
export function translate (key, variables, language) {
|
||||
let translatedString = i18n.t(key, variables, language);
|
||||
|
||||
expect(translatedString).to.not.be.empty;
|
||||
expect(translatedString).to.not.eql(STRING_ERROR_MSG);
|
||||
|
||||
@@ -2,6 +2,7 @@ var path = require('path');
|
||||
var config = require('./config');
|
||||
var utils = require('./utils');
|
||||
var projectRoot = path.resolve(__dirname, '../');
|
||||
var webpack = require('webpack');
|
||||
|
||||
var IS_PROD = process.env.NODE_ENV === 'production';
|
||||
var baseConfig = {
|
||||
@@ -17,6 +18,7 @@ var baseConfig = {
|
||||
extensions: ['', '.js', '.vue'],
|
||||
fallback: [path.join(__dirname, '../node_modules')],
|
||||
alias: {
|
||||
jquery: 'jquery/src/jquery',
|
||||
client: path.resolve(__dirname, '../website/client'),
|
||||
assets: path.resolve(__dirname, '../website/client/assets'),
|
||||
components: path.resolve(__dirname, '../website/client/components'),
|
||||
@@ -25,6 +27,12 @@ var baseConfig = {
|
||||
resolveLoader: {
|
||||
fallback: [path.join(__dirname, '../node_modules')],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
jQuery: 'jquery',
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
preLoaders: !IS_PROD ? [
|
||||
{
|
||||
@@ -79,6 +87,9 @@ var baseConfig = {
|
||||
require('autoprefixer')({
|
||||
browsers: ['last 2 versions'],
|
||||
}),
|
||||
require('postcss-easy-import')({
|
||||
glob: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
BIN
website/assets/audio/airuTheme/Achievement_Unlocked.mp3
Normal file
BIN
website/assets/audio/airuTheme/Achievement_Unlocked.mp3
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Achievement_Unlocked.ogg
Normal file
BIN
website/assets/audio/airuTheme/Achievement_Unlocked.ogg
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Chat.mp3
Normal file
BIN
website/assets/audio/airuTheme/Chat.mp3
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Chat.ogg
Normal file
BIN
website/assets/audio/airuTheme/Chat.ogg
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Daily.mp3
Normal file
BIN
website/assets/audio/airuTheme/Daily.mp3
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Daily.ogg
Normal file
BIN
website/assets/audio/airuTheme/Daily.ogg
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Death.mp3
Normal file
BIN
website/assets/audio/airuTheme/Death.mp3
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Death.ogg
Normal file
BIN
website/assets/audio/airuTheme/Death.ogg
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Item_Drop.mp3
Normal file
BIN
website/assets/audio/airuTheme/Item_Drop.mp3
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Item_Drop.ogg
Normal file
BIN
website/assets/audio/airuTheme/Item_Drop.ogg
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Level_Up.mp3
Normal file
BIN
website/assets/audio/airuTheme/Level_Up.mp3
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Level_Up.ogg
Normal file
BIN
website/assets/audio/airuTheme/Level_Up.ogg
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Minus_Habit.mp3
Normal file
BIN
website/assets/audio/airuTheme/Minus_Habit.mp3
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Minus_Habit.ogg
Normal file
BIN
website/assets/audio/airuTheme/Minus_Habit.ogg
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Plus_Habit.mp3
Normal file
BIN
website/assets/audio/airuTheme/Plus_Habit.mp3
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Plus_Habit.ogg
Normal file
BIN
website/assets/audio/airuTheme/Plus_Habit.ogg
Normal file
Binary file not shown.
BIN
website/assets/audio/airuTheme/Reward.mp3
Normal file
BIN
website/assets/audio/airuTheme/Reward.mp3
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user