Compare commits
270 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3854c6f62f | |||
| 82cd53f8cb | |||
| 6f7cd96e9f | |||
| 9f09a0396b | |||
| f16e0bd044 | |||
| c91de90fff | |||
| 380fb0abf4 | |||
| 43b607aedd | |||
| 4c832ad36c | |||
| 76ae41875d | |||
| 7a5a856ac6 | |||
| 0bfd709116 | |||
| d7f6c3bc1b | |||
| 3eb164adcc | |||
| 7bd2cbcf04 | |||
| 087498760a | |||
| 0e26fcce98 | |||
| 8d0060d511 | |||
| 0af94b2b44 | |||
| a7b5b6e20e | |||
| 5f93aad925 | |||
| 4289becccc | |||
| eeddd3f366 | |||
| 6ce2caecf9 | |||
| 6b933914ef | |||
| 060e68ef95 | |||
| a486ded6dd | |||
| 7884f4ce9a | |||
| 1c82fa012d | |||
| 924723bce6 | |||
| b696dde6ba | |||
| e8d0557cb6 | |||
| cdb6acd4a0 | |||
| 128bd4b9cd | |||
| fc5f6a31ee | |||
| 1f0fa500bb | |||
| 36276d5d3f | |||
| 5c2c87f523 | |||
| c4f2dafc95 | |||
| f09b65e108 | |||
| a0f42b0e3e | |||
| e10655a5b4 | |||
| d519940931 | |||
| 58a43f51d8 | |||
| d25a5fcd57 | |||
| 4085d7a5bb | |||
| 6c4e1a326f | |||
| 9cf8c0a824 | |||
| e1f9643ffd | |||
| 1d5c1d5a5f | |||
| df7c0a005c | |||
| c60481ab34 | |||
| 13818b7634 | |||
| b2e834c74c | |||
| 4a9cfe8ce5 | |||
| 2419b219ba | |||
| 0b8ce63c76 | |||
| 95e541ae75 | |||
| 85c6c19235 | |||
| 07e4b2c463 | |||
| 7b2081ab03 | |||
| e749e42665 | |||
| 0b82722d27 | |||
| f35ef3a046 | |||
| 5656b9c6ca | |||
| 1e3d7acf06 | |||
| b7e391e074 | |||
| b2368e7804 | |||
| 85880d6bb5 | |||
| 352b8143f3 | |||
| 7fe3870297 | |||
| aff32b0e71 | |||
| 4ff56b17e7 | |||
| 03283d2e52 | |||
| 07909ac93a | |||
| 506b155cfa | |||
| 80bd41928c | |||
| be3bd25f00 | |||
| 3e365f2b4e | |||
| e1b08e3a20 | |||
| 01281b6414 | |||
| 0b65ac6c4f | |||
| 0d12686a10 | |||
| 8c4d1a67ac | |||
| e58fbcc34c | |||
| 1a66a680dc | |||
| fc08b753cd | |||
| 7a73f5bb83 | |||
| 6ce1f6f32e | |||
| 50278db1d6 | |||
| 1195560b0c | |||
| 33c639e28b | |||
| c2b106564f | |||
| fe636b9bd2 | |||
| 7835fe1deb | |||
| 8fb0d0899d | |||
| abb8dc4dc1 | |||
| 910a76db68 | |||
| 2f792d51f8 | |||
| 6cc925b535 | |||
| 87d86ee632 | |||
| b387d77128 | |||
| e644ae83fd | |||
| ae21680a5f | |||
| c1767eca1b | |||
| d20cd1bbf1 | |||
| 3e45f5af41 | |||
| 23cc2b9d21 | |||
| 58c4fcd506 | |||
| 2878abc130 | |||
| a8cb6e3409 | |||
| 2caa540006 | |||
| a2261e3591 | |||
| 90d498ff96 | |||
| 928327e02a | |||
| f454700722 | |||
| 83bce24e1f | |||
| e7979a99e6 | |||
| 5c648af2ea | |||
| 2ad4bee816 | |||
| e0291cf432 | |||
| bac9121153 | |||
| d178928c45 | |||
| 9a6aa5f443 | |||
| e277a088ee | |||
| e083df64e4 | |||
| dae37c17d6 | |||
| 1d81916674 | |||
| 2a225c2376 | |||
| bd9d9d31c3 | |||
| f1b5ce9c66 | |||
| c08b25101b | |||
| 9fd0e27c66 | |||
| 994082a1d8 | |||
| 0e6e7adb06 | |||
| 8486b9631f | |||
| 3f04c8abf5 | |||
| 89e1c69728 | |||
| 5e935230a4 | |||
| 9ad50be6ca | |||
| 1ff3f3d4e7 | |||
| 0b35aefdc9 | |||
| 0d374d817c | |||
| 034058301d | |||
| ae2d50c5b8 | |||
| fff16a86a5 | |||
| c7309ae179 | |||
| ef42fba049 | |||
| d75f926136 | |||
| 78612a91dd | |||
| 8bcd93075b | |||
| f318afb8cf | |||
| b954379f38 | |||
| e5c060a80b | |||
| 6668aae89b | |||
| 9b62804a5c | |||
| d21e29462c | |||
| 6bccd2a866 | |||
| caea222330 | |||
| 8ecbdc1448 | |||
| ee20b1eea8 | |||
| ff62c6eea0 | |||
| 57cd4d44cd | |||
| e989503cfa | |||
| 431cec7634 | |||
| 2a367ab3a7 | |||
| bc91dd81e9 | |||
| 201085651b | |||
| bb395a7ad8 | |||
| 264aad79ac | |||
| 9c797e6a54 | |||
| e6c3d00665 | |||
| 660928323e | |||
| 298a79b58d | |||
| 77f3bb53de | |||
| 17d8a7b706 | |||
| 1d8a5b1952 | |||
| f548103f4c | |||
| 2595ccb2b4 | |||
| ceb4288b17 | |||
| 3ebd37f7cb | |||
| ff3df55639 | |||
| 080c4b3e20 | |||
| e939799800 | |||
| b7797b3e6c | |||
| 55bd35d7d3 | |||
| b9ae03f795 | |||
| 75f6398de2 | |||
| 4004887ddd | |||
| ef4d761e0c | |||
| a1fb702d1e | |||
| 868759d3e8 | |||
| 45a9d6d17b | |||
| cc766d2260 | |||
| e710a00e74 | |||
| bad06ba449 | |||
| ccb088e127 | |||
| 4a4d48aed8 | |||
| da8006506b | |||
| d451d01b18 | |||
| 53555a0f16 | |||
| 962236846a | |||
| aae8f2923c | |||
| 61956336ea | |||
| 0be1f3eb7c | |||
| fe04b56ecc | |||
| 7e772924f6 | |||
| 901d11c61f | |||
| 8874558827 | |||
| ea5ce64db6 | |||
| 10b69986c0 | |||
| 2d35009bee | |||
| d267f09d04 | |||
| e956bbdf79 | |||
| 446154b97f | |||
| f137342d88 | |||
| 94db493974 | |||
| ee5c761680 | |||
| 0cbd6fb4d7 | |||
| 0ca3c1f94d | |||
| 2f699e24d7 | |||
| faa0611ab2 | |||
| c66d2cb469 | |||
| ee09c76c08 | |||
| c478748436 | |||
| 61606cb69d | |||
| 3d99a64e96 | |||
| 76f9204417 | |||
| fad59b9a8d | |||
| f218a432ec | |||
| 73ecdced01 | |||
| 1c17b415f0 | |||
| b912a83f22 | |||
| fb3a9740bd | |||
| 817c943860 | |||
| 0aba448c48 | |||
| 6b5173ecbf | |||
| 53d8d2fc6a | |||
| 52a7112591 | |||
| fd13771088 | |||
| c9d725ec20 | |||
| 3203bffeaa | |||
| 3f47cdd9a2 | |||
| 8b15d94ae1 | |||
| 8f2435c37c | |||
| 302bc899d7 | |||
| 8048cf9a97 | |||
| 20548daccf | |||
| 2c250bfcd9 | |||
| 75e3f15352 | |||
| 5670be26c7 | |||
| 9fc03cb91a | |||
| 7e80406181 | |||
| 60a6f6f2f6 | |||
| 92d68e5c6e | |||
| 6e7d8d93fe | |||
| bc861133e1 | |||
| 31ef21f25c | |||
| 97e1465899 | |||
| 7cb0f5145d | |||
| 5b6217a0bf | |||
| 09e4c88606 | |||
| 13bae96708 | |||
| 6241001eef | |||
| dc5722d0de | |||
| e3b2443029 | |||
| ca5927fe73 | |||
| 5a0eed7eae | |||
| 532881e679 | |||
| 27d763a46c |
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
import { sendTxn } from '../../../website/server/libs/email';
|
||||
import { sendTxn } from '../../website/server/libs/email';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
|
||||
@@ -1,67 +1,24 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = 'full-stable';
|
||||
import each from 'lodash/each';
|
||||
import keys from 'lodash/keys';
|
||||
import content from '../../website/common/script/content/index';
|
||||
const migrationName = 'full-stable.js';
|
||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
/*
|
||||
* Award users every extant pet and mount
|
||||
*/
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
let monk = require('monk');
|
||||
let dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
'profile.name': 'SabreCat',
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
let set = {
|
||||
migration: migrationName,
|
||||
};
|
||||
|
||||
const set = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
each(keys(content.pets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
@@ -88,30 +45,40 @@ function updateUser (user) {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set});
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
return await User.update({_id: user._id}, {$set: set}).exec();
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.local.username': 'olson22',
|
||||
};
|
||||
|
||||
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);
|
||||
const fields = {
|
||||
_id: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = 'mystery_items_201901';
|
||||
const MYSTERY_ITEMS = ['head_mystery_201901', 'body_mystery_201901'];
|
||||
const MIGRATION_NAME = 'mystery_items_201903';
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201903', 'head_mystery_201903'];
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
import { model as UserNotification } from '../../website/server/models/userNotification';
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20190314_pi_day';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const inc = {
|
||||
'items.food.Pie_Skeleton': 1,
|
||||
'items.food.Pie_Base': 1,
|
||||
'items.food.Pie_CottonCandyBlue': 1,
|
||||
'items.food.Pie_CottonCandyPink': 1,
|
||||
'items.food.Pie_Shade': 1,
|
||||
'items.food.Pie_White': 1,
|
||||
'items.food.Pie_Golden': 1,
|
||||
'items.food.Pie_Zombie': 1,
|
||||
'items.food.Pie_Desert': 1,
|
||||
'items.food.Pie_Red': 1,
|
||||
};
|
||||
const set = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
set['items.gear.owned.head_special_piDay'] = false;
|
||||
set['items.gear.owned.shield_special_piDay'] = false;
|
||||
const push = [
|
||||
{type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid()},
|
||||
{type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid()},
|
||||
];
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2019-02-15')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.84.7",
|
||||
"version": "4.92.6",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@google-cloud/trace-agent": "^3.5.2",
|
||||
"@google-cloud/trace-agent": "^3.6.0",
|
||||
"@slack/client": "^3.8.1",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.7",
|
||||
"amplitude": "^3.5.0",
|
||||
"amplitude-js": "^4.6.0-beta.2",
|
||||
"amplitude-js": "^5.0.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"autoprefixer": "^8.5.0",
|
||||
"aws-sdk": "^2.400.0",
|
||||
"autoprefixer": "^9.4.0",
|
||||
"aws-sdk": "^2.432.0",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
@@ -28,16 +28,16 @@
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.6.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"bcrypt": "^3.0.1",
|
||||
"bcrypt": "^3.0.5",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^4.1.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.13",
|
||||
"compression": "^1.7.2",
|
||||
"cookie-session": "^1.2.0",
|
||||
"bootstrap-vue": "^2.0.0-rc.18",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-session": "^1.3.3",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^4.3.1",
|
||||
"csv-stringify": "^5.1.0",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"express": "^4.16.3",
|
||||
@@ -52,10 +52,10 @@
|
||||
"gulp-nodemon": "^2.4.1",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.15.1",
|
||||
"hellojs": "^1.18.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"image-size": "^0.6.2",
|
||||
"in-app-purchase": "^1.10.2",
|
||||
"image-size": "^0.7.0",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": ">=3.0.0",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
@@ -64,13 +64,13 @@
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.4.11",
|
||||
"mongoose": "^5.4.19",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
"node-sass": "^4.9.0",
|
||||
"nodemailer": "^5.0.0",
|
||||
"ora": "^3.0.0",
|
||||
"nodemailer": "^6.0.0",
|
||||
"ora": "^3.2.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.0.0",
|
||||
@@ -85,12 +85,12 @@
|
||||
"sass-loader": "^7.0.3",
|
||||
"shelljs": "^0.8.2",
|
||||
"short-uuid": "^3.0.0",
|
||||
"smartbanner.js": "^1.9.1",
|
||||
"smartbanner.js": "^1.11.0",
|
||||
"stripe": "^5.9.0",
|
||||
"superagent": "^4.0.0",
|
||||
"superagent": "^5.0.2",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"svgo": "^1.0.5",
|
||||
"svgo": "^1.2.0",
|
||||
"svgo-loader": "^2.1.0",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"update": "^0.7.4",
|
||||
@@ -100,13 +100,13 @@
|
||||
"uuid": "^3.0.1",
|
||||
"validator": "^10.5.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vue": "^2.6.4",
|
||||
"vue": "^2.6.10",
|
||||
"vue-loader": "^14.2.2",
|
||||
"vue-mugen-scroll": "^0.2.1",
|
||||
"vue-router": "^3.0.0",
|
||||
"vue-style-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.6.4",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vuedraggable": "^2.20.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^3.12.0",
|
||||
"webpack-merge": "^4.1.3",
|
||||
@@ -154,7 +154,7 @@
|
||||
"chalk": "^2.4.1",
|
||||
"chromedriver": "^2.40.0",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^3.0.1",
|
||||
"coveralls": "^3.0.3",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-habitrpg": "^4.0.0",
|
||||
@@ -166,7 +166,7 @@
|
||||
"expect.js": "^0.3.1",
|
||||
"http-proxy-middleware": "^0.19.0",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"karma": "^3.1.3",
|
||||
"karma": "^4.0.1",
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
"karma-chai-plugins": "^0.9.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
@@ -181,11 +181,11 @@
|
||||
"lcov-result-merger": "^3.0.0",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^6.0.6",
|
||||
"nightwatch": "^0.9.21",
|
||||
"puppeteer": "^1.5.0",
|
||||
"nightwatch": "^1.0.16",
|
||||
"puppeteer": "^1.14.0",
|
||||
"require-again": "^2.0.0",
|
||||
"selenium-server": "^3.12.0",
|
||||
"sinon": "^6.3.5",
|
||||
"sinon": "^7.2.4",
|
||||
"sinon-chai": "^3.0.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"webpack-bundle-analyzer": "^2.12.0",
|
||||
|
||||
@@ -53,7 +53,7 @@ async function _deleteHabiticaData (user, email) {
|
||||
|
||||
if (response) {
|
||||
console.log(`${response.status} ${response.statusText}`);
|
||||
if (response.status === 200) console.log(`${user._id} removed. Last login: ${user.auth.timestamps.loggedin}`);
|
||||
if (response.status === 200) console.log(`${user._id} (${email}) removed. Last login: ${user.auth.timestamps.loggedin}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -335,14 +335,13 @@ describe('analyticsService', () => {
|
||||
let data, itemSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
Visitor.prototype.event.yields();
|
||||
|
||||
itemSpy = sandbox.stub().returnsThis();
|
||||
|
||||
Visitor.prototype.event.returns({
|
||||
send: sandbox.stub(),
|
||||
});
|
||||
Visitor.prototype.transaction.returns({
|
||||
item: itemSpy,
|
||||
send: sandbox.stub().returnsThis(),
|
||||
send: sandbox.stub().yields(),
|
||||
});
|
||||
|
||||
data = {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/* eslint-disable camelcase */
|
||||
import {
|
||||
validateItemPath,
|
||||
getDefaultOwnedGear,
|
||||
} from '../../../../../website/server/libs/items/utils';
|
||||
|
||||
describe('Items Utils', () => {
|
||||
describe('getDefaultOwnedGear', () => {
|
||||
it('clones the result object', () => {
|
||||
const res1 = getDefaultOwnedGear();
|
||||
res1.extraProperty = true;
|
||||
|
||||
const res2 = getDefaultOwnedGear();
|
||||
expect(res2).not.to.have.property('extraProperty');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateItemPath', () => {
|
||||
it('returns false if not an item path', () => {
|
||||
expect(validateItemPath('notitems.gear.owned.item')).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true if a valid schema path', () => {
|
||||
expect(validateItemPath('items.gear.equipped.weapon')).to.equal(true);
|
||||
expect(validateItemPath('items.currentPet')).to.equal(true);
|
||||
expect(validateItemPath('items.special.snowball')).to.equal(true);
|
||||
});
|
||||
|
||||
it('works with owned gear paths', () => {
|
||||
expect(validateItemPath('items.gear.owned.head_armoire_crownOfHearts')).to.equal(true);
|
||||
expect(validateItemPath('items.gear.owned.head_invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with pets paths', () => {
|
||||
expect(validateItemPath('items.pets.Wolf-CottonCandyPink')).to.equal(true);
|
||||
expect(validateItemPath('items.pets.Wolf-Invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with eggs paths', () => {
|
||||
expect(validateItemPath('items.eggs.LionCub')).to.equal(true);
|
||||
expect(validateItemPath('items.eggs.Armadillo')).to.equal(true);
|
||||
expect(validateItemPath('items.eggs.NotAnArmadillo')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with hatching potions paths', () => {
|
||||
expect(validateItemPath('items.hatchingPotions.Base')).to.equal(true);
|
||||
expect(validateItemPath('items.hatchingPotions.StarryNight')).to.equal(true);
|
||||
expect(validateItemPath('items.hatchingPotions.Invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with food paths', () => {
|
||||
expect(validateItemPath('items.food.Cake_Base')).to.equal(true);
|
||||
expect(validateItemPath('items.food.Cake_Invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with mounts paths', () => {
|
||||
expect(validateItemPath('items.mounts.Cactus-Base')).to.equal(true);
|
||||
expect(validateItemPath('items.mounts.Aether-Invisible')).to.equal(true);
|
||||
expect(validateItemPath('items.mounts.Aether-Invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with quests paths', () => {
|
||||
expect(validateItemPath('items.quests.atom3')).to.equal(true);
|
||||
expect(validateItemPath('items.quests.invalid')).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -32,6 +32,7 @@ describe('slack', () => {
|
||||
},
|
||||
message: {
|
||||
id: 'chat-id',
|
||||
username: 'author',
|
||||
user: 'Author',
|
||||
uuid: 'author-id',
|
||||
text: 'some text',
|
||||
@@ -50,11 +51,11 @@ describe('slack', () => {
|
||||
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: 'flagger (flagger-id; language: flagger-lang) flagged a message',
|
||||
text: 'flagger (flagger-id; language: flagger-lang) flagged a group message',
|
||||
attachments: [{
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `Author - author@example.com - author-id\n${timestamp}`,
|
||||
author_name: `@author Author (author@example.com; author-id)\n${timestamp}`,
|
||||
title: 'Flag in Some group - (private guild)',
|
||||
title_link: undefined,
|
||||
text: 'some text',
|
||||
|
||||
@@ -63,11 +63,11 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${user.profile.name} (${user.id}; language: en) flagged a message`,
|
||||
text: `${user.profile.name} (${user.id}; language: en) flagged a group message`,
|
||||
attachments: [{
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `${anotherUser.profile.name} - ${anotherUser.auth.local.email} - ${anotherUser._id}\n${timestamp}`,
|
||||
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
@@ -98,11 +98,11 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a message`,
|
||||
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a group message`,
|
||||
attachments: [{
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `${newUser.profile.name} - ${newUser.auth.local.email} - ${newUser._id}\n${timestamp}`,
|
||||
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
|
||||
@@ -257,7 +257,7 @@ describe('POST /chat', () => {
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
|
||||
author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`,
|
||||
title: 'Slur in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
|
||||
text: testSlurMessage,
|
||||
@@ -310,7 +310,7 @@ describe('POST /chat', () => {
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `${members[0].profile.name} - ${members[0].auth.local.email} - ${members[0]._id}`,
|
||||
author_name: `@${members[0].auth.local.username} ${members[0].profile.name} (${members[0].auth.local.email}; ${members[0]._id})`,
|
||||
title: 'Slur in Party - (private party)',
|
||||
title_link: undefined,
|
||||
text: testSlurMessage,
|
||||
|
||||
@@ -25,9 +25,9 @@ describe('GET /heroes/:heroId', () => {
|
||||
|
||||
it('validates req.params.heroId', async () => {
|
||||
await expect(user.get('/hall/heroes/invalidUUID')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', {userId: 'invalidUUID'}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('GET /heroes/:heroId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only necessary hero data', async () => {
|
||||
it('returns only necessary hero data given user id', async () => {
|
||||
let hero = await generateUser({
|
||||
contributor: {tier: 23},
|
||||
});
|
||||
@@ -53,4 +53,24 @@ describe('GET /heroes/:heroId', () => {
|
||||
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
|
||||
expect(heroRes.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('returns only necessary hero data given username', async () => {
|
||||
let hero = await generateUser({
|
||||
contributor: {tier: 23},
|
||||
});
|
||||
let heroRes = await user.get(`/hall/heroes/${hero.auth.local.username}`);
|
||||
|
||||
expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'balance', 'profile', 'purchased',
|
||||
'contributor', 'auth', 'items',
|
||||
]);
|
||||
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
|
||||
expect(heroRes.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('returns correct hero using search with difference case', async () => {
|
||||
await generateUser({}, { username: 'TestUpperCaseName123' });
|
||||
let heroRes = await user.get('/hall/heroes/TestuPPerCasEName123');
|
||||
expect(heroRes.auth.local.username).to.equal('TestUpperCaseName123');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,8 +27,6 @@ describe('GET /inbox/messages', () => {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
});
|
||||
|
||||
it('returns the user inbox messages as an array of ordered messages (from most to least recent)', async () => {
|
||||
@@ -45,4 +43,21 @@ describe('GET /inbox/messages', () => {
|
||||
expect(messages[2].text).to.equal('second');
|
||||
expect(messages[3].text).to.equal('first');
|
||||
});
|
||||
|
||||
it('returns four messages when using page-query ', async () => {
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(user.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const messages = await user.get('/inbox/messages?page=1');
|
||||
|
||||
expect(messages.length).to.equal(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
|
||||
describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
let questingGroup;
|
||||
@@ -99,6 +100,10 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
it('cancels a quest', async () => {
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
// partyMembers[1] hasn't accepted the invitation, because if he accepts, invitation phase ends.
|
||||
// The cancel command can be done only in the invitation phase.
|
||||
|
||||
let stub = sandbox.spy(Group.prototype, 'sendChat');
|
||||
|
||||
let res = await leader.post(`/groups/${questingGroup._id}/quests/cancel`);
|
||||
|
||||
@@ -135,5 +140,9 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
},
|
||||
members: {},
|
||||
});
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/cancelled the party quest Wail of the Whale.`/);
|
||||
|
||||
stub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,4 +110,22 @@ describe('POST /user/auth/local/login', () => {
|
||||
let isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
|
||||
expect(isValidPassword).to.equal(true);
|
||||
});
|
||||
|
||||
it('user uses social authentication and has no password', async () => {
|
||||
await user.unset({
|
||||
'auth.local.hashed_password': 1,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.hashed_password).to.be.undefined;
|
||||
|
||||
await expect(api.post(endpoint, {
|
||||
username: user.auth.local.username,
|
||||
password: 'any-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidLoginCredentialsLong'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('POST /members/flag-private-message/:messageId', () => {
|
||||
let userToSendMessage;
|
||||
let messageToSend = 'Test Private Message';
|
||||
|
||||
beforeEach(async () => {
|
||||
userToSendMessage = await generateUser();
|
||||
});
|
||||
|
||||
it('Allows players to flag their own private message', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let senderMessages = await userToSendMessage.get('/inbox/messages');
|
||||
|
||||
let sendersMessageInSendersInbox = _.find(senderMessages, (message) => {
|
||||
return message.uuid === receiver._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
await expect(userToSendMessage.post(`/members/flag-private-message/${sendersMessageInSendersInbox.id}`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('Flags a private message', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let receiversMessages = await receiver.get('/inbox/messages');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('Returns an error when user tries to flag a private message that is already flagged', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let receiversMessages = await receiver.get('/inbox/messages');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
|
||||
|
||||
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('messageGroupChatFlagAlreadyReported'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -62,6 +62,18 @@ describe('inAppRewards', () => {
|
||||
expect(result[9].path).to.eql('potion');
|
||||
});
|
||||
|
||||
it('ignores null/undefined entries', () => {
|
||||
user.pinnedItems = testPinnedItems;
|
||||
user.pinnedItems.push(null);
|
||||
user.pinnedItems.push(undefined);
|
||||
user.pinnedItemsOrder = testPinnedItemsOrder;
|
||||
|
||||
let result = inAppRewards(user);
|
||||
|
||||
expect(result[2].path).to.eql('armoire');
|
||||
expect(result[9].path).to.eql('potion');
|
||||
});
|
||||
|
||||
it('does not return seasonal items which have been unpinned', () => {
|
||||
if (officialPinnedItems.length === 0) {
|
||||
return; // if no seasonal items, this test is not applicable
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import content from '../../../../website/common/script/content/index';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
|
||||
describe('shared.ops.buy', () => {
|
||||
let user;
|
||||
@@ -16,6 +17,10 @@ describe('shared.ops.buy', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
defaultsDeep(user, {
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
@@ -26,7 +31,6 @@ describe('shared.ops.buy', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
sinon.stub(analytics, 'track');
|
||||
@@ -138,4 +142,52 @@ describe('shared.ops.buy', () => {
|
||||
buy(user, {params: {key: 'potion'}, quantity: 2});
|
||||
expect(user.stats.hp).to.eql(50);
|
||||
});
|
||||
|
||||
it('errors if user supplies a non-numeric quantity', (done) => {
|
||||
try {
|
||||
buy(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
type: 'quest',
|
||||
quantity: 'bogle',
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(errorMessage('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('errors if user supplies a negative quantity', (done) => {
|
||||
try {
|
||||
buy(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
type: 'quest',
|
||||
quantity: -3,
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(errorMessage('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('errors if user supplies a decimal quantity', (done) => {
|
||||
try {
|
||||
buy(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
type: 'quest',
|
||||
quantity: 1.83,
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(errorMessage('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
|
||||
function buyGear (user, req, analytics) {
|
||||
let buyOp = new BuyMarketGearOperation(user, req, analytics);
|
||||
@@ -24,6 +25,10 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
defaultsDeep(user, {
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
@@ -34,7 +39,6 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
sinon.stub(shared, 'randomVal');
|
||||
|
||||
@@ -108,6 +108,47 @@ describe('shared.ops.purchase', () => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns error when user supplies a non-numeric quantity', (done) => {
|
||||
let type = 'eggs';
|
||||
let key = 'Wolf';
|
||||
|
||||
try {
|
||||
purchase(user, {params: {type, key}, quantity: 'jamboree'}, analytics);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns error when user supplies a negative quantity', (done) => {
|
||||
let type = 'eggs';
|
||||
let key = 'Wolf';
|
||||
user.balance = 10;
|
||||
|
||||
try {
|
||||
purchase(user, {params: {type, key}, quantity: -2}, analytics);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns error when user supplies a decimal quantity', (done) => {
|
||||
let type = 'eggs';
|
||||
let key = 'Wolf';
|
||||
user.balance = 10;
|
||||
|
||||
try {
|
||||
purchase(user, {params: {type, key}, quantity: 2.9}, analytics);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('successful purchase', () => {
|
||||
|
||||
@@ -93,6 +93,22 @@ describe('shared.ops.hatch', () => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow hatching quest pet egg using wacky potion', (done) => {
|
||||
user.items.eggs = {Bunny: 1};
|
||||
user.items.hatchingPotions = {Veggie: 1};
|
||||
user.items.pets = {};
|
||||
try {
|
||||
hatch(user, {params: {egg: 'Bunny', hatchingPotion: 'Veggie'}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('messageInvalidEggPotionCombo'));
|
||||
expect(user.items.pets).to.be.empty;
|
||||
expect(user.items.eggs).to.eql({Bunny: 1});
|
||||
expect(user.items.hatchingPotions).to.eql({Veggie: 1});
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('successful hatching', () => {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import {addPinnedGear} from '../../../website/common/script/ops/pinnedGearUtils';
|
||||
|
||||
describe('shared.ops.pinnedGearUtils.addPinnedGear', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
});
|
||||
|
||||
it('not adds an item with empty properties to pinnedItems', () => {
|
||||
addPinnedGear(user, undefined, undefined);
|
||||
|
||||
expect(user.pinnedItems.length).to.be.eql(0);
|
||||
});
|
||||
});
|
||||
@@ -9,13 +9,14 @@ import hatchingPotions from '../../website/common/script/content/hatching-potion
|
||||
|
||||
describe('hatchingPotions', () => {
|
||||
describe('all', () => {
|
||||
it('is a combination of drop and premium potions', () => {
|
||||
it('is a combination of drop, premium, and wacky potions', () => {
|
||||
let dropNumber = Object.keys(hatchingPotions.drops).length;
|
||||
let premiumNumber = Object.keys(hatchingPotions.premium).length;
|
||||
let wackyNumber = Object.keys(hatchingPotions.wacky).length;
|
||||
let allNumber = Object.keys(hatchingPotions.all).length;
|
||||
|
||||
expect(allNumber).to.be.greaterThan(0);
|
||||
expect(allNumber).to.equal(dropNumber + premiumNumber);
|
||||
expect(allNumber).to.equal(dropNumber + premiumNumber + wackyNumber);
|
||||
});
|
||||
|
||||
it('contains basic information about each potion', () => {
|
||||
|
||||
@@ -47,6 +47,18 @@ describe('stable', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('wackyPets', () => {
|
||||
it('contains a pet for each wacky potion * each drop egg', () => {
|
||||
let numberOfWackyPotions = Object.keys(potions.wacky).length;
|
||||
let numberOfDropEggs = Object.keys(eggs.drops).length;
|
||||
let numberOfWackyPets = Object.keys(stable.wackyPets).length;
|
||||
let expectedTotal = numberOfWackyPotions * numberOfDropEggs;
|
||||
|
||||
expect(numberOfWackyPets).to.be.greaterThan(0);
|
||||
expect(numberOfWackyPets).to.equal(expectedTotal);
|
||||
});
|
||||
});
|
||||
|
||||
describe('specialPets', () => {
|
||||
it('each value is a valid translation string', () => {
|
||||
each(stable.specialPets, (pet) => {
|
||||
@@ -107,10 +119,11 @@ describe('stable', () => {
|
||||
let questNumber = Object.keys(stable.questPets).length;
|
||||
let specialNumber = Object.keys(stable.specialPets).length;
|
||||
let premiumNumber = Object.keys(stable.premiumPets).length;
|
||||
let wackyNumber = Object.keys(stable.wackyPets).length;
|
||||
let allNumber = Object.keys(stable.petInfo).length;
|
||||
|
||||
expect(allNumber).to.be.greaterThan(0);
|
||||
expect(allNumber).to.equal(dropNumber + questNumber + specialNumber + premiumNumber);
|
||||
expect(allNumber).to.equal(dropNumber + questNumber + specialNumber + premiumNumber + wackyNumber);
|
||||
});
|
||||
|
||||
it('contains basic information about each pet', () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { requester } from './requester';
|
||||
import {
|
||||
getDocument as getDocumentFromMongo,
|
||||
updateDocument as updateDocumentInMongo,
|
||||
unsetDocument as unsetDocumentInMongo,
|
||||
} from '../mongo';
|
||||
import {
|
||||
assign,
|
||||
@@ -29,6 +30,18 @@ class ApiObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
async unset (options) {
|
||||
if (isEmpty(options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await unsetDocumentInMongo(this._docType, this, options);
|
||||
|
||||
_updateLocalParameters((this, options));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async sync () {
|
||||
let updatedDoc = await getDocumentFromMongo(this._docType, this);
|
||||
|
||||
|
||||
@@ -13,10 +13,16 @@ import * as Tasks from '../../../../website/server/models/task';
|
||||
// parameter, such as the number of wolf eggs the user has,
|
||||
// , you can do so by passing in the full path as a string:
|
||||
// { 'items.eggs.Wolf': 10 }
|
||||
export async function generateUser (update = {}) {
|
||||
let username = (Date.now() + generateUUID()).substring(0, 20);
|
||||
let password = 'password';
|
||||
let email = `${username}@example.com`;
|
||||
//
|
||||
// To manually set a username, email or password pass it in as
|
||||
// an object for the second parameter. Only overrides need to be
|
||||
// added. Items that don't exist will be autogenerated.
|
||||
// Example: generateUser({}, { username: 'TestName' }) adds user
|
||||
// with the 'TestName' username.
|
||||
export async function generateUser (update = {}, overrides = {}) {
|
||||
let username = overrides.username || (Date.now() + generateUUID()).substring(0, 20);
|
||||
let password = overrides.password || 'password';
|
||||
let email = overrides.email || `${username}@example.com`;
|
||||
|
||||
let user = await requester().post('/user/auth/local/register', {
|
||||
username,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '../../website/server/models/task';
|
||||
export {translate} from './translate';
|
||||
|
||||
|
||||
export function generateUser (options = {}) {
|
||||
let user = new User(options).toObject();
|
||||
|
||||
|
||||
@@ -98,6 +98,19 @@ export async function updateDocument (collectionName, doc, update) {
|
||||
});
|
||||
}
|
||||
|
||||
// Unset a property in the database.
|
||||
// Useful for testing.
|
||||
export async function unsetDocument (collectionName, doc, update) {
|
||||
let collection = mongoose.connection.db.collection(collectionName);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
collection.updateOne({ _id: doc._id }, { $unset: update }, (updateErr) => {
|
||||
if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDocument (collectionName, doc) {
|
||||
let collection = mongoose.connection.db.collection(collectionName);
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Veggie {
|
||||
background: url("~assets/images/Pet_HatchingPotion_Veggie.gif") no-repeat;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
.Gems {
|
||||
display:inline-block;
|
||||
margin-right:5px;
|
||||
|
||||
@@ -1,54 +1,72 @@
|
||||
.promo_armoire_backgrounds_201902 {
|
||||
.promo_april_fools_2019 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -530px;
|
||||
background-position: 0px -840px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201901 {
|
||||
.promo_armoire_backgrounds_201904 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -689px -148px;
|
||||
width: 282px;
|
||||
background-position: -424px -840px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mythical_marvels_bundle {
|
||||
.promo_butterflies {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -689px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 676px;
|
||||
height: 676px;
|
||||
}
|
||||
.promo_celestial_rainbow_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -433px -677px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_classes_spring2019 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -677px;
|
||||
width: 432px;
|
||||
height: 162px;
|
||||
}
|
||||
.promo_egg_hunt {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px 0px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201903 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px -444px;
|
||||
width: 351px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_seasonalshop_spring {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px -592px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_shiny_seeds {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px -296px;
|
||||
width: 351px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_spring_avatar_customizations {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px -148px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -972px -148px;
|
||||
background-position: -1168px -592px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.scene_apollo {
|
||||
.scene_yesterdailies_repeatables {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -689px -296px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.scene_coding {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -689px -444px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_eating_healthy {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -277px;
|
||||
width: 333px;
|
||||
height: 252px;
|
||||
}
|
||||
.scene_office {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -328px 0px;
|
||||
width: 360px;
|
||||
height: 240px;
|
||||
}
|
||||
.scene_yesterdailies {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -677px 0px;
|
||||
width: 327px;
|
||||
height: 276px;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 915 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 560 KiB After Width: | Height: | Size: 556 KiB |
|
Before Width: | Height: | Size: 551 KiB After Width: | Height: | Size: 570 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 344 KiB |
|
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 322 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 146 KiB |