mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-05 11:21:59 -05:00
Merge branch 'develop' into greenkeeper/update-to-node-10
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
|
||||
|
||||
[//]: # (Put Issue # or URL here, if applicable. This will automatically close the issue if your PR is merged in)
|
||||
Fixes put_issue_url_here
|
||||
[//]: # (Put Issue # here, if applicable. This will automatically close the issue if your PR is merged in)
|
||||
Fixes put_#_and_issue_numer_here
|
||||
|
||||
### Changes
|
||||
[//]: # (Describe the changes that were made in detail here. Include pictures if necessary)
|
||||
|
||||
@@ -17,7 +17,7 @@ RUN npm install -g gulp-cli mocha
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone --branch v4.37.2 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN git clone --branch v4.45.1 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN gulp build:prod --force
|
||||
|
||||
|
||||
@@ -10,4 +10,3 @@ We need more programmers! Your assistance will be greatly appreciated.
|
||||
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths).
|
||||
|
||||
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
|
||||
|
||||
|
||||
+1
-2
@@ -112,6 +112,5 @@
|
||||
"CLOUDKARAFKA_USERNAME": "",
|
||||
"CLOUDKARAFKA_PASSWORD": "",
|
||||
"CLOUDKARAFKA_TOPIC_PREFIX": ""
|
||||
},
|
||||
"STACK_IMPACT_KEY": "aaaabbbbccccddddeeeeffffgggg111100002222"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import max from 'lodash/max';
|
||||
import mean from 'lodash/mean';
|
||||
import monk from 'monk';
|
||||
import round from 'lodash/round';
|
||||
import sum from 'lodash/sum';
|
||||
|
||||
/*
|
||||
* Output data on subscribers' task histories, formatted for CSV.
|
||||
* User ID,Count of Dailies,Count of Habits,Total History Size,Max History Size,Mean History Size,Median History Size
|
||||
*/
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
let dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
let dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
||||
|
||||
function usersReport () {
|
||||
let allHistoryLengths = [];
|
||||
|
||||
console.info('User ID,Count of Dailies,Count of Habits,Total History Size,Max History Size,Mean History Size,Median History Size');
|
||||
|
||||
dbUsers.find(
|
||||
{
|
||||
$and:
|
||||
[
|
||||
{'purchased.plan.planId': {$ne:null}},
|
||||
{'purchased.plan.planId': {$ne:''}},
|
||||
],
|
||||
$or:
|
||||
[
|
||||
{'purchased.plan.dateTerminated': null},
|
||||
{'purchased.plan.dateTerminated': ''},
|
||||
{'purchased.plan.dateTerminated': {$gt:new Date()}},
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: {_id: 1},
|
||||
}
|
||||
).each((user, {close, pause, resume}) => {
|
||||
let historyLengths = [];
|
||||
let habitCount = 0;
|
||||
let dailyCount = 0;
|
||||
|
||||
pause();
|
||||
return dbTasks.find(
|
||||
{
|
||||
userId: user._id,
|
||||
$or:
|
||||
[
|
||||
{type: 'habit'},
|
||||
{type: 'daily'},
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
type: 1,
|
||||
history: 1,
|
||||
},
|
||||
}
|
||||
).each((task) => {
|
||||
if (task.type === 'habit') {
|
||||
habitCount++;
|
||||
}
|
||||
if (task.type === 'daily') {
|
||||
dailyCount++;
|
||||
}
|
||||
if (task.history.length > 0) {
|
||||
allHistoryLengths.push(task.history.length);
|
||||
historyLengths.push(task.history.length);
|
||||
}
|
||||
}).then(() => {
|
||||
const totalHistory = sum(historyLengths);
|
||||
const maxHistory = historyLengths.length > 0 ? max(historyLengths) : 0;
|
||||
const meanHistory = historyLengths.length > 0 ? round(mean(historyLengths)) : 0;
|
||||
const medianHistory = historyLengths.length > 0 ? median(historyLengths) : 0;
|
||||
console.info(`${user._id},${dailyCount},${habitCount},${totalHistory},${maxHistory},${meanHistory},${medianHistory}`);
|
||||
resume();
|
||||
});
|
||||
}).then(() => {
|
||||
console.info(`Total Subscriber History Entries: ${sum(allHistoryLengths)}`);
|
||||
console.info(`Largest History Size: ${max(allHistoryLengths)}`);
|
||||
console.info(`Mean History Size: ${round(mean(allHistoryLengths))}`);
|
||||
console.info(`Median History Size: ${median(allHistoryLengths)}`);
|
||||
return process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
function median(values) { // https://gist.github.com/caseyjustus/1166258
|
||||
values.sort( function(a,b) {return a - b;} );
|
||||
|
||||
var half = Math.floor(values.length/2);
|
||||
|
||||
if (values.length % 2) {
|
||||
return values[half];
|
||||
}
|
||||
else {
|
||||
return (values[half-1] + values[half]) / 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = usersReport;
|
||||
+1
-1
@@ -25,7 +25,7 @@ services:
|
||||
- mongo
|
||||
|
||||
mongo:
|
||||
image: mongo
|
||||
image: mongo:3.4
|
||||
ports:
|
||||
- "27017:27017"
|
||||
networks:
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', (cb) => {
|
||||
|
||||
gulp.task('test:api-v3:unit', (done) => {
|
||||
let runner = exec(
|
||||
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-unit --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/unit --recursive --require ./test/helpers/start-server'),
|
||||
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-unit node_modules/mocha/bin/_mocha -- test/api/v3/unit --recursive --require ./test/helpers/start-server'),
|
||||
(err) => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
|
||||
@@ -17,5 +17,5 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
const processUsers = require('./groups/migrate-chat.js');
|
||||
const processUsers = require('./users/mystery-items.js');
|
||||
processUsers();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const migrationName = 'mystery-items-201802.js'; // Update per month
|
||||
const migrationName = 'mystery-items-201805.js'; // Update per month
|
||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Award this month's mystery items to subscribers
|
||||
*/
|
||||
const MYSTERY_ITEMS = ['back_mystery_201804', 'headAccessory_mystery_201804'];
|
||||
const MYSTERY_ITEMS = ['back_mystery_201805', 'head_mystery_201805'];
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
let monk = require('monk');
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
const migrationName = 'remove-social-users-extra-data.js';
|
||||
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Remove not needed data from social profiles
|
||||
*/
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
const monk = require('monk');
|
||||
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
migration: {$ne: migrationName},
|
||||
$or: [
|
||||
{ 'auth.facebook.id': { $exists: true } },
|
||||
{ 'auth.google.id': { $exists: true } },
|
||||
],
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const isFacebook = user.auth.facebook && user.auth.facebook.id;
|
||||
const isGoogle = user.auth.google && user.auth.google.id;
|
||||
|
||||
const update = { $set: {} };
|
||||
|
||||
if (isFacebook) {
|
||||
update.$set['auth.facebook'] = {
|
||||
id: user.auth.facebook.id,
|
||||
emails: user.auth.facebook.emails,
|
||||
};
|
||||
}
|
||||
|
||||
if (isGoogle) {
|
||||
update.$set['auth.google'] = {
|
||||
id: user.auth.google.id,
|
||||
emails: user.auth.google.emails,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.update({
|
||||
_id: user._id,
|
||||
}, update);
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
Generated
+8829
-2626
File diff suppressed because it is too large
Load Diff
+28
-27
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.40.0",
|
||||
"version": "4.46.2",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.6",
|
||||
"amazon-payments": "^0.2.7",
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"autoprefixer": "^8.3.0",
|
||||
"aws-sdk": "^2.229.1",
|
||||
"autoprefixer": "^8.5.0",
|
||||
"aws-sdk": "^2.239.1",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.1.8",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-loader": "^7.1.4",
|
||||
@@ -26,23 +26,23 @@
|
||||
"babel-register": "^6.6.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"bcrypt": "^2.0.0",
|
||||
"body-parser": "^1.15.0",
|
||||
"bootstrap": "^4.1.0",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^4.1.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.9",
|
||||
"compression": "^1.7.2",
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.1.4",
|
||||
"cross-env": "^5.1.5",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^2.1.0",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"express": "^4.16.3",
|
||||
"express-basic-auth": "^1.1.5",
|
||||
"express-validator": "^5.1.2",
|
||||
"express-validator": "^5.2.0",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"glob": "^7.1.2",
|
||||
"got": "^8.3.0",
|
||||
"got": "^8.3.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^7.0.1",
|
||||
"gulp-imagemin": "^4.1.0",
|
||||
@@ -52,7 +52,7 @@
|
||||
"hellojs": "^1.15.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"image-size": "^0.6.2",
|
||||
"in-app-purchase": "^1.9.2",
|
||||
"in-app-purchase": "^1.9.4",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": ">=3.0.0",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
@@ -61,13 +61,13 @@
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.0.16",
|
||||
"mongoose": "^5.1.2",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^0.14.4",
|
||||
"node-sass": "^4.9.0",
|
||||
"nodemailer": "^4.6.4",
|
||||
"ora": "^2.0.0",
|
||||
"ora": "^2.1.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.0.0",
|
||||
@@ -82,15 +82,16 @@
|
||||
"pusher": "^1.3.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"sass-loader": "^7.0.0",
|
||||
"shelljs": "^0.8.1",
|
||||
"stackimpact": "^1.3.0",
|
||||
"stripe": "^5.8.0",
|
||||
"superagent": "^3.4.3",
|
||||
"shelljs": "^0.8.2",
|
||||
"stripe": "^5.9.0",
|
||||
"superagent": "^3.8.3",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"svgo": "^1.0.5",
|
||||
"svgo-loader": "^2.1.0",
|
||||
"universal-analytics": "^0.4.16",
|
||||
"update": "^0.7.4",
|
||||
"upgrade": "^1.1.0",
|
||||
"url-loader": "^1.0.0",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^3.0.1",
|
||||
@@ -104,7 +105,7 @@
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack": "^3.12.0",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"winston": "^2.4.2",
|
||||
"winston-loggly-bulk": "^2.0.2",
|
||||
@@ -140,15 +141,15 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/test-utils": "^1.0.0-beta.15",
|
||||
"@vue/test-utils": "^1.0.0-beta.16",
|
||||
"babel-plugin-istanbul": "^4.1.6",
|
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chalk": "^2.4.1",
|
||||
"chromedriver": "^2.38.2",
|
||||
"chromedriver": "^2.38.3",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^3.0.0",
|
||||
"coveralls": "^3.0.1",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-habitrpg": "^4.0.0",
|
||||
@@ -164,7 +165,7 @@
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
"karma-chai-plugins": "^0.9.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-coverage": "^1.1.2",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-sinon-chai": "^1.3.4",
|
||||
@@ -174,17 +175,17 @@
|
||||
"karma-webpack": "^3.0.0",
|
||||
"lcov-result-merger": "^2.0.0",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^6.0.5",
|
||||
"monk": "^6.0.6",
|
||||
"nightwatch": "^0.9.21",
|
||||
"puppeteer": "^1.3.0",
|
||||
"puppeteer": "^1.4.0",
|
||||
"require-again": "^2.0.0",
|
||||
"selenium-server": "^3.11.0",
|
||||
"selenium-server": "^3.12.0",
|
||||
"sinon": "^4.5.0",
|
||||
"sinon-chai": "^3.0.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"webpack-bundle-analyzer": "^2.11.1",
|
||||
"webpack-bundle-analyzer": "^2.12.0",
|
||||
"webpack-dev-middleware": "^2.0.5",
|
||||
"webpack-hot-middleware": "^2.22.1"
|
||||
"webpack-hot-middleware": "^2.22.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"memwatch-next": "^0.3.0",
|
||||
|
||||
@@ -41,6 +41,7 @@ describe('DELETE /challenges/:challengeId', () => {
|
||||
group = populatedGroup.group;
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
|
||||
{type: 'habit', text: taskText},
|
||||
|
||||
@@ -33,9 +33,11 @@ describe('GET /challenges/:challengeId', () => {
|
||||
group = populatedGroup.group;
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('should return challenge data', async () => {
|
||||
await challenge.sync();
|
||||
let chal = await user.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.memberCount).to.equal(challenge.memberCount);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
@@ -80,6 +82,7 @@ describe('GET /challenges/:challengeId', () => {
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await members[0].post(`/challenges/${challenge._id}/join`);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('fails if user doesn\'t have access to the challenge', async () => {
|
||||
@@ -134,6 +137,7 @@ describe('GET /challenges/:challengeId', () => {
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await members[0].post(`/challenges/${challenge._id}/join`);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('fails if user doesn\'t have access to the challenge', async () => {
|
||||
|
||||
@@ -24,6 +24,7 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
||||
members = populatedGroup.members;
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
await members[0].post(`/challenges/${challenge._id}/join`);
|
||||
await members[1].post(`/challenges/${challenge._id}/join`);
|
||||
await members[2].post(`/challenges/${challenge._id}/join`);
|
||||
@@ -60,9 +61,9 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
||||
});
|
||||
|
||||
it('should return a valid CSV file with export data', async () => {
|
||||
let res = await members[0].get(`/challenges/${challenge._id}/export/csv`);
|
||||
let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
|
||||
let splitRes = res.split('\n');
|
||||
const res = await members[0].get(`/challenges/${challenge._id}/export/csv`);
|
||||
const sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
|
||||
const splitRes = res.split('\n');
|
||||
|
||||
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
|
||||
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
@@ -71,4 +72,16 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
||||
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[5]).to.equal('');
|
||||
});
|
||||
|
||||
it('should successfully return when it contains erroneous residue user data', async () => {
|
||||
await members[0].update({challenges: []});
|
||||
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
|
||||
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
|
||||
const splitRes = res.split('\n');
|
||||
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
|
||||
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[4]).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
let leader = await generateUser({balance: 4});
|
||||
let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
|
||||
let challenge = await generateChallenge(leader, group);
|
||||
await leader.post(`/challenges/${challenge._id}/join`);
|
||||
let res = await user.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: leader._id,
|
||||
@@ -59,6 +60,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
let anotherUser = await generateUser({balance: 3});
|
||||
let group = await generateGroup(anotherUser, {type: 'guild', privacy: 'public', name: generateUUID()});
|
||||
let challenge = await generateChallenge(anotherUser, group);
|
||||
await anotherUser.post(`/challenges/${challenge._id}/join`);
|
||||
let res = await user.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: anotherUser._id,
|
||||
@@ -72,6 +74,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
it('returns only first 30 members if req.query.includeAllMembers is not true', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
let usersToGenerate = [];
|
||||
for (let i = 0; i < 31; i++) {
|
||||
@@ -90,6 +93,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
it('returns only first 30 members if req.query.includeAllMembers is not defined', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
let usersToGenerate = [];
|
||||
for (let i = 0; i < 31; i++) {
|
||||
@@ -108,6 +112,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
it('returns all members if req.query.includeAllMembers is true', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
let usersToGenerate = [];
|
||||
for (let i = 0; i < 31; i++) {
|
||||
@@ -123,9 +128,11 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('supports using req.query.lastId to get more members', async () => {
|
||||
it('supports using req.query.lastId to get more members', async function () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
let usersToGenerate = [];
|
||||
for (let i = 0; i < 57; i++) {
|
||||
@@ -146,6 +153,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
it('supports using req.query.search to get search members', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
let usersToGenerate = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
|
||||
@@ -50,6 +50,7 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
it('fails if user doesn\'t have access to the challenge', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
let anotherUser = await generateUser();
|
||||
let member = await generateUser();
|
||||
await expect(anotherUser.get(`/challenges/${challenge._id}/members/${member._id}`)).to.eventually.be.rejected.and.eql({
|
||||
@@ -62,6 +63,7 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
it('fails if member is not part of the challenge', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
let member = await generateUser();
|
||||
await expect(user.get(`/challenges/${challenge._id}/members/${member._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
@@ -74,6 +76,7 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
let groupLeader = await generateUser({balance: 4});
|
||||
let group = await generateGroup(groupLeader, {type: 'guild', privacy: 'public', name: generateUUID()});
|
||||
let challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
let taskText = 'Test Text';
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]);
|
||||
|
||||
@@ -86,6 +89,7 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
it('returns the member tasks for the challenges', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: 'Test Text'}]);
|
||||
|
||||
let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`);
|
||||
@@ -98,6 +102,7 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
it('returns the tasks without the tags and checklist', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
let taskText = 'Test Text';
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, [{
|
||||
type: 'todo',
|
||||
|
||||
@@ -25,7 +25,9 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should return group challenges for non member with populated leader', async () => {
|
||||
@@ -73,6 +75,7 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
|
||||
let newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
@@ -99,7 +102,9 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should prevent non-member from seeing challenges', async () => {
|
||||
@@ -156,9 +161,12 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
slug: 'habitica_official',
|
||||
}],
|
||||
});
|
||||
await user.post(`/challenges/${officialChallenge._id}/join`);
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
@@ -178,6 +186,7 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
|
||||
let newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
@@ -203,7 +212,9 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should prevent non-member from seeing challenges', async () => {
|
||||
@@ -263,7 +274,9 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
||||
|
||||
challenge = await generateChallenge(user, tavern, {prize: 1});
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, tavern, {prize: 1});
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should return tavern challenges with populated leader', async () => {
|
||||
|
||||
@@ -24,7 +24,9 @@ describe('GET challenges/user', () => {
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should return challenges user has joined', async () => {
|
||||
@@ -146,6 +148,7 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
|
||||
let newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user');
|
||||
|
||||
@@ -164,6 +167,7 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
|
||||
let privateChallenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
let challenges = await nonMember.get('/challenges/user');
|
||||
|
||||
@@ -198,9 +202,12 @@ describe('GET challenges/user', () => {
|
||||
slug: 'habitica_official',
|
||||
}],
|
||||
});
|
||||
await user.post(`/challenges/${officialChallenge._id}/join`);
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
@@ -220,6 +227,7 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
|
||||
let newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user');
|
||||
|
||||
@@ -252,12 +260,14 @@ describe('GET challenges/user', () => {
|
||||
await user.update({balance: 20});
|
||||
|
||||
for (let i = 0; i < 11; i += 1) {
|
||||
await generateChallenge(user, group); // eslint-disable-line
|
||||
let challenge = await generateChallenge(user, group); // eslint-disable-line
|
||||
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
|
||||
}
|
||||
});
|
||||
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
const categoryChallenge = await generateChallenge(user, guild, {categories});
|
||||
await user.post(`/challenges/${categoryChallenge._id}/join`);
|
||||
const challenges = await user.get(`/challenges/user?categories=${categories[0].slug}`);
|
||||
|
||||
expect(challenges[0]._id).to.eql(categoryChallenge._id);
|
||||
|
||||
@@ -242,7 +242,6 @@ describe('POST /challenges', () => {
|
||||
it('returns an error when challenge validation fails; doesn\'s save user or group', async () => {
|
||||
let oldChallengeCount = group.challengeCount;
|
||||
let oldUserBalance = groupLeader.balance;
|
||||
let oldUserChallenges = groupLeader.challenges;
|
||||
let oldGroupBalance = group.balance;
|
||||
|
||||
await expect(groupLeader.post('/challenges', {
|
||||
@@ -260,7 +259,6 @@ describe('POST /challenges', () => {
|
||||
expect(group.challengeCount).to.eql(oldChallengeCount);
|
||||
expect(group.balance).to.eql(oldGroupBalance);
|
||||
expect(groupLeader.balance).to.eql(oldUserBalance);
|
||||
expect(groupLeader.challenges).to.eql(oldUserChallenges);
|
||||
});
|
||||
|
||||
it('sets all properites of the challenge as passed', async () => {
|
||||
@@ -291,18 +289,19 @@ describe('POST /challenges', () => {
|
||||
name: group.name,
|
||||
type: group.type,
|
||||
});
|
||||
expect(challenge.memberCount).to.eql(1);
|
||||
expect(challenge.memberCount).to.eql(0);
|
||||
expect(challenge.prize).to.eql(prize);
|
||||
});
|
||||
|
||||
it('adds challenge to creator\'s challenges', async () => {
|
||||
let challenge = await groupLeader.post('/challenges', {
|
||||
it('does not add challenge to creator\'s challenges', async () => {
|
||||
await groupLeader.post('/challenges', {
|
||||
group: group._id,
|
||||
name: 'Test Challenge',
|
||||
shortName: 'TC Label',
|
||||
});
|
||||
|
||||
await expect(groupLeader.sync()).to.eventually.have.property('challenges').to.include(challenge._id);
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.challenges.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('awards achievement if this is creator\'s first challenge', async () => {
|
||||
|
||||
@@ -43,6 +43,7 @@ describe('POST /challenges/:challengeId/join', () => {
|
||||
authorizedUser = populatedGroup.members[0];
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('returns an error when user doesn\'t have permissions to access the challenge', async () => {
|
||||
@@ -91,6 +92,7 @@ describe('POST /challenges/:challengeId/join', () => {
|
||||
});
|
||||
|
||||
it('increases memberCount of challenge', async () => {
|
||||
await challenge.sync();
|
||||
let oldMemberCount = challenge.memberCount;
|
||||
|
||||
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
@@ -48,6 +48,7 @@ describe('POST /challenges/:challengeId/leave', () => {
|
||||
notInGroupLeavingUser = populatedGroup.members[2];
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
taskText = 'A challenge task text';
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
||||
challenge = await generateChallenge(groupLeader, group, {
|
||||
prize: 1,
|
||||
});
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
|
||||
{type: 'habit', text: taskText},
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('PUT /challenges/:challengeId', () => {
|
||||
member = members[0];
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
generateUser,
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('GET /coupons/', () => {
|
||||
let user;
|
||||
@@ -19,7 +19,7 @@ describe('GET /coupons/', () => {
|
||||
await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: apiMessages('noSudoAccess'),
|
||||
message: apiError('noSudoAccess'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import couponCode from 'coupon-code';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('POST /coupons/generate/:event', () => {
|
||||
let user;
|
||||
@@ -26,7 +26,7 @@ describe('POST /coupons/generate/:event', () => {
|
||||
await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: apiMessages('noSudoAccess'),
|
||||
message: apiError('noSudoAccess'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import {
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('GET /groups', () => {
|
||||
let user;
|
||||
@@ -167,7 +167,7 @@ describe('GET /groups', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: apiMessages('guildsOnlyPaginate'),
|
||||
message: apiError('guildsOnlyPaginate'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -81,7 +81,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('supports using req.query.lastId to get more invites', async () => {
|
||||
it('supports using req.query.lastId to get more invites', async function () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
let leader = await generateUser({balance: 4});
|
||||
let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
|
||||
|
||||
|
||||
@@ -142,7 +142,8 @@ describe('GET /groups/:groupId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('supports using req.query.lastId to get more members', async () => {
|
||||
it('supports using req.query.lastId to get more members', async function () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
let leader = await generateUser({balance: 4});
|
||||
let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await leader.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('payments : paypal #checkoutSuccess', () => {
|
||||
let endpoint = '/paypal/checkout/success';
|
||||
@@ -17,7 +17,7 @@ describe('payments : paypal #checkoutSuccess', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPaymentId'),
|
||||
message: apiError('missingPaymentId'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('payments : paypal #checkoutSuccess', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingCustomerId'),
|
||||
message: apiError('missingCustomerId'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
import shared from '../../../../../../website/common';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('payments : paypal #subscribe', () => {
|
||||
let endpoint = '/paypal/subscribe';
|
||||
@@ -17,7 +17,7 @@ describe('payments : paypal #subscribe', () => {
|
||||
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingSubKey'),
|
||||
message: apiError('missingSubKey'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
|
||||
describe('payments : paypal #subscribeSuccess', () => {
|
||||
@@ -16,7 +16,7 @@ describe('payments : paypal #subscribeSuccess', () => {
|
||||
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPaypalBlock'),
|
||||
message: apiError('missingPaypalBlock'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { quests as questScrolls } from '../../../../../website/common/script/content';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
let questingGroup;
|
||||
@@ -69,7 +70,7 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${FAKE_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('questNotFound', {key: FAKE_QUEST}),
|
||||
message: apiError('questNotFound', {key: FAKE_QUEST}),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ describe('DELETE /tasks/:id', () => {
|
||||
});
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
|
||||
@@ -40,6 +40,7 @@ describe('GET /tasks/:taskId', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('returns error when incorrect id is passed', async () => {
|
||||
|
||||
@@ -9,6 +9,7 @@ describe('POST /tasks/clearCompletedTodos', () => {
|
||||
let user = await generateUser({balance: 1});
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
let initialTodoCount = user.tasksOrder.todos.length;
|
||||
await user.post('/tasks/user', [
|
||||
|
||||
@@ -81,6 +81,49 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
expect(body.direction).to.eql('up');
|
||||
expect(body.delta).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
context('sending user activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('sends user activity webhook when the user levels up', async () => {
|
||||
let uuid = generateUUID();
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'userActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
leveledUp: true,
|
||||
},
|
||||
});
|
||||
|
||||
const initialLvl = user.stats.lvl;
|
||||
|
||||
await user.update({
|
||||
'stats.exp': 3000,
|
||||
});
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task.id}/score/up`);
|
||||
await user.sync();
|
||||
await sleep();
|
||||
|
||||
let body = server.getWebhookData(uuid);
|
||||
|
||||
expect(body.type).to.eql('leveledUp');
|
||||
expect(body.initialLvl).to.eql(initialLvl);
|
||||
expect(body.finalLvl).to.eql(user.stats.lvl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('todos', () => {
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('POST /tasks/unlink-all/:challengeId', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('fails if no keep query', async () => {
|
||||
|
||||
@@ -38,6 +38,7 @@ describe('POST /tasks/unlink-one/:taskId', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('fails if no keep query', async () => {
|
||||
|
||||
@@ -65,6 +65,7 @@ describe('PUT /tasks/:id', () => {
|
||||
fields for challenge tasks owned by a user`, async () => {
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
let challengeTask = await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||
type: 'daily',
|
||||
@@ -198,6 +199,7 @@ describe('PUT /tasks/:id', () => {
|
||||
});
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ describe('DELETE /tasks/:taskId/checklist/:itemId', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('fails on task not found', async () => {
|
||||
|
||||
@@ -17,6 +17,7 @@ describe('DELETE /tasks/:id', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -42,6 +42,7 @@ describe('GET /tasks/challenge/:challengeId', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('returns error when challenge is not found', async () => {
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ describe('POST /tasks/:taskId/checklist/', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('fails on task not found', async () => {
|
||||
|
||||
@@ -20,6 +20,7 @@ describe('POST /tasks/challenge/:challengeId', () => {
|
||||
user = await generateUser({balance: 1});
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('returns error when challenge is not found', async () => {
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
context('habits', () => {
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('PUT /tasks/:id', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
context('errors', () => {
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ describe('PUT /tasks/:taskId/checklist/:itemId', () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('fails on task not found', async () => {
|
||||
|
||||
+47
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
server,
|
||||
sleep,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
@@ -94,4 +96,49 @@ describe('POST /tasks/:taskId/checklist/:itemId/score', () => {
|
||||
message: t('checklistItemNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
context('sending task activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('sends task activity webhooks', async () => {
|
||||
let uuid = generateUUID();
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'taskActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
checklistScored: true,
|
||||
updated: false,
|
||||
},
|
||||
});
|
||||
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
});
|
||||
|
||||
let updatedTask = await user.post(`/tasks/${task.id}/checklist`, {
|
||||
text: 'checklist item text',
|
||||
});
|
||||
|
||||
let checklistItem = updatedTask.checklist[0];
|
||||
|
||||
let scoredItemTask = await user.post(`/tasks/${task.id}/checklist/${checklistItem.id}/score`);
|
||||
|
||||
await sleep();
|
||||
|
||||
let body = server.getWebhookData(uuid);
|
||||
|
||||
expect(body.type).to.eql('checklistScored');
|
||||
expect(body.task).to.eql(scoredItemTask);
|
||||
expect(body.item).to.eql(scoredItemTask.checklist[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,6 +117,7 @@ describe('DELETE /user', () => {
|
||||
let authorizedUser = populatedGroup.members[1];
|
||||
|
||||
let challenge = await generateChallenge(populatedGroup.groupLeader, group);
|
||||
await populatedGroup.groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await challenge.sync();
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { find } from 'lodash';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('POST /user/class/cast/:spellId', () => {
|
||||
let user;
|
||||
@@ -24,7 +25,7 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('spellNotFound', {spellId}),
|
||||
message: apiError('spellNotFound', {spellId}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +35,7 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('spellNotFound', {spellId}),
|
||||
message: apiError('spellNotFound', {spellId}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,6 +109,7 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
it('returns an error if a challenge task was targeted', async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup();
|
||||
let challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
|
||||
{type: 'habit', text: 'task text'},
|
||||
]);
|
||||
@@ -237,6 +239,7 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
it('searing brightness does not affect challenge or group tasks', async () => {
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test challenge habit',
|
||||
type: 'habit',
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
server,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import content from '../../../../../website/common/script/content';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /user/feed/:pet/:food', () => {
|
||||
let user;
|
||||
@@ -37,4 +40,41 @@ describe('POST /user/feed/:pet/:food', () => {
|
||||
expect(user.items.food.Milk).to.equal(1);
|
||||
expect(user.items.pets['Wolf-Base']).to.equal(7);
|
||||
});
|
||||
|
||||
context('sending user activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('sends user activity webhook when a new mount is raised', async () => {
|
||||
let uuid = generateUUID();
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'userActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
mountRaised: true,
|
||||
},
|
||||
});
|
||||
|
||||
await user.update({
|
||||
'items.pets.Wolf-Base': 49,
|
||||
'items.food.Milk': 2,
|
||||
});
|
||||
let res = await user.post('/user/feed/Wolf-Base/Milk');
|
||||
|
||||
await sleep();
|
||||
|
||||
let body = server.getWebhookData(uuid);
|
||||
|
||||
expect(body.type).to.eql('mountRaised');
|
||||
expect(body.pet).to.eql('Wolf-Base');
|
||||
expect(body.message).to.eql(res.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
server,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /user/hatch/:egg/:hatchingPotion', () => {
|
||||
let user;
|
||||
@@ -28,4 +31,41 @@ describe('POST /user/hatch/:egg/:hatchingPotion', () => {
|
||||
data: JSON.parse(JSON.stringify(user.items)),
|
||||
});
|
||||
});
|
||||
|
||||
context('sending user activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('sends user activity webhook when a new pet is hatched', async () => {
|
||||
let uuid = generateUUID();
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'userActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
petHatched: true,
|
||||
},
|
||||
});
|
||||
|
||||
await user.update({
|
||||
'items.eggs.Wolf': 1,
|
||||
'items.hatchingPotions.Base': 1,
|
||||
});
|
||||
let res = await user.post('/user/hatch/Wolf/Base');
|
||||
|
||||
await sleep();
|
||||
|
||||
let body = server.getWebhookData(uuid);
|
||||
|
||||
expect(body.type).to.eql('petHatched');
|
||||
expect(body.pet).to.eql('Wolf-Base');
|
||||
expect(body.message).to.eql(res.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -90,6 +90,7 @@ describe('POST /user/reset', () => {
|
||||
it('does not delete challenge or group tasks', async () => {
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test challenge habit',
|
||||
type: 'habit',
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../../website/common/script';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
|
||||
let content = shared.content;
|
||||
|
||||
@@ -24,7 +25,7 @@ describe('POST /user/buy/:key', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('itemNotFound', {key: 'notExisting'}),
|
||||
message: apiError('itemNotFound', {key: 'notExisting'}),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('POST /user/buy-gear/:key', () => {
|
||||
let user;
|
||||
@@ -21,7 +21,7 @@ describe('POST /user/buy-gear/:key', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('itemNotFound', {key: 'notExisting'}),
|
||||
message: apiError('itemNotFound', {key: 'notExisting'}),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../../website/common/script';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
|
||||
let content = shared.content;
|
||||
|
||||
@@ -20,7 +21,7 @@ describe('POST /user/buy-quest/:key', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('questNotFound', {key: 'notExisting'}),
|
||||
message: apiError('questNotFound', {key: 'notExisting'}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,4 +38,32 @@ describe('POST /user/buy-quest/:key', () => {
|
||||
itemText: item.text(),
|
||||
}));
|
||||
});
|
||||
|
||||
it('returns an error if quest prerequisites are not met', async () => {
|
||||
let key = 'dilatoryDistress2';
|
||||
|
||||
await expect(user.post(`/user/buy-quest/${key}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('mustComplete', {quest: 'dilatoryDistress1'}),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows purchase of a quest if prerequisites are met', async () => {
|
||||
const prerequisite = 'dilatoryDistress1';
|
||||
const key = 'dilatoryDistress2';
|
||||
const item = content.quests[key];
|
||||
|
||||
const achievementName = `achievements.quests.${prerequisite}`;
|
||||
|
||||
await user.update({[achievementName]: true, 'stats.gp': 9999});
|
||||
let res = await user.post(`/user/buy-quest/${key}`);
|
||||
await user.sync();
|
||||
|
||||
expect(res.data).to.eql(user.items.quests);
|
||||
expect(res.message).to.equal(t('messageBought', {
|
||||
itemText: item.text(),
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../../website/common/script';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
|
||||
let content = shared.content;
|
||||
|
||||
@@ -20,7 +21,7 @@ describe('POST /user/buy-special-spell/:key', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('spellNotFound', {spellId: 'notExisting'}),
|
||||
message: apiError('spellNotFound', {spellId: 'notExisting'}),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('POST /user/allocate', () => {
|
||||
let user;
|
||||
@@ -17,7 +18,7 @@ describe('POST /user/allocate', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidAttribute', {attr: 'invalid'}),
|
||||
message: apiError('invalidAttribute', {attr: 'invalid'}),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('POST /user/webhook', () => {
|
||||
let user, body;
|
||||
@@ -116,6 +117,7 @@ describe('POST /user/webhook', () => {
|
||||
let webhook = await user.post('/user/webhook', body);
|
||||
|
||||
expect(webhook.options).to.eql({
|
||||
checklistScored: false,
|
||||
created: false,
|
||||
updated: false,
|
||||
deleted: false,
|
||||
@@ -126,6 +128,7 @@ describe('POST /user/webhook', () => {
|
||||
it('can set taskActivity options', async () => {
|
||||
body.type = 'taskActivity';
|
||||
body.options = {
|
||||
checklistScored: true,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -135,6 +138,7 @@ describe('POST /user/webhook', () => {
|
||||
let webhook = await user.post('/user/webhook', body);
|
||||
|
||||
expect(webhook.options).to.eql({
|
||||
checklistScored: true,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -145,6 +149,7 @@ describe('POST /user/webhook', () => {
|
||||
it('discards extra properties in taskActivity options', async () => {
|
||||
body.type = 'taskActivity';
|
||||
body.options = {
|
||||
checklistScored: false,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -156,6 +161,7 @@ describe('POST /user/webhook', () => {
|
||||
|
||||
expect(webhook.options.foo).to.not.exist;
|
||||
expect(webhook.options).to.eql({
|
||||
checklistScored: false,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -200,7 +206,7 @@ describe('POST /user/webhook', () => {
|
||||
await expect(user.post('/user/webhook', body)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('groupIdRequired'),
|
||||
message: apiError('groupIdRequired'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -218,4 +224,16 @@ describe('POST /user/webhook', () => {
|
||||
groupId: body.options.groupId,
|
||||
});
|
||||
});
|
||||
|
||||
it('discards extra properties in globalActivity options', async () => {
|
||||
body.type = 'globalActivity';
|
||||
body.options = {
|
||||
foo: 'bar',
|
||||
};
|
||||
|
||||
let webhook = await user.post('/user/webhook', body);
|
||||
|
||||
expect(webhook.options.foo).to.not.exist;
|
||||
expect(webhook.options).to.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID} from 'uuid';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('PUT /user/webhook/:id', () => {
|
||||
let user, webhookToUpdate;
|
||||
@@ -95,6 +96,7 @@ describe('PUT /user/webhook/:id', () => {
|
||||
let webhook = await user.put(`/user/webhook/${webhookToUpdate.id}`, {type, options});
|
||||
|
||||
expect(webhook.options).to.eql({
|
||||
checklistScored: false, // starting value
|
||||
created: true, // starting value
|
||||
updated: false,
|
||||
deleted: true,
|
||||
@@ -126,7 +128,7 @@ describe('PUT /user/webhook/:id', () => {
|
||||
await expect(user.put(`/user/webhook/${webhookToUpdate.id}`, {type, options})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('groupIdRequired'),
|
||||
message: apiError('groupIdRequired'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('API Messages', () => {
|
||||
const message = 'Only public guilds support pagination.';
|
||||
it('returns an API message', () => {
|
||||
expect(apiMessages('guildsOnlyPaginate')).to.equal(message);
|
||||
expect(apiError('guildsOnlyPaginate')).to.equal(message);
|
||||
});
|
||||
|
||||
it('throws if the API message does not exist', () => {
|
||||
expect(() => apiMessages('iDoNotExist')).to.throw;
|
||||
expect(() => apiError('iDoNotExist')).to.throw;
|
||||
});
|
||||
|
||||
it('clones the passed variables', () => {
|
||||
let vars = {a: 1};
|
||||
sandbox.stub(_, 'clone').returns({});
|
||||
apiMessages('guildsOnlyPaginate', vars);
|
||||
apiError('guildsOnlyPaginate', vars);
|
||||
expect(_.clone).to.have.been.calledOnce;
|
||||
expect(_.clone).to.have.been.calledWith(vars);
|
||||
});
|
||||
@@ -22,7 +22,7 @@ describe('API Messages', () => {
|
||||
let vars = {a: 1};
|
||||
let stub = sinon.stub().returns('string');
|
||||
sandbox.stub(_, 'template').returns(stub);
|
||||
apiMessages('guildsOnlyPaginate', vars);
|
||||
apiError('guildsOnlyPaginate', vars);
|
||||
expect(_.template).to.have.been.calledOnce;
|
||||
expect(_.template).to.have.been.calledWith(message);
|
||||
expect(stub).to.have.been.calledOnce;
|
||||
@@ -13,6 +13,7 @@ import analytics from '../../../../../website/server/libs/analyticsService';
|
||||
let pathToCronLib = '../../../../../website/server/libs/cron';
|
||||
|
||||
describe('cron', () => {
|
||||
let clock = null;
|
||||
let user;
|
||||
let tasksByType = {habits: [], dailys: [], todos: [], rewards: []};
|
||||
let daysMissed = 0;
|
||||
@@ -34,6 +35,8 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (clock !== null)
|
||||
clock.restore();
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
@@ -82,14 +85,12 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not reset plan.gemsBought within the month', () => {
|
||||
let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').toDate());
|
||||
clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').toDate());
|
||||
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
||||
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(10);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('resets plan.dateUpdated on a new month', () => {
|
||||
@@ -156,7 +157,6 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
describe('for a 1-month recurring subscription', () => {
|
||||
let clock;
|
||||
// create a user that will be used for all of these tests without a reset before each
|
||||
let user1 = new User({
|
||||
auth: {
|
||||
@@ -187,7 +187,6 @@ describe('cron', () => {
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits after the second month', () => {
|
||||
@@ -199,7 +198,6 @@ describe('cron', () => {
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits after the third month', () => {
|
||||
@@ -211,7 +209,6 @@ describe('cron', () => {
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits after the fourth month', () => {
|
||||
@@ -223,7 +220,6 @@ describe('cron', () => {
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
@@ -233,12 +229,10 @@ describe('cron', () => {
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 3-month recurring subscription', () => {
|
||||
let clock;
|
||||
let user3 = new User({
|
||||
auth: {
|
||||
local: {
|
||||
@@ -266,7 +260,6 @@ describe('cron', () => {
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the middle of the period that they already have benefits for', () => {
|
||||
@@ -276,7 +269,6 @@ describe('cron', () => {
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||
@@ -286,7 +278,6 @@ describe('cron', () => {
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||
@@ -296,7 +287,6 @@ describe('cron', () => {
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', () => {
|
||||
@@ -306,7 +296,6 @@ describe('cron', () => {
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', () => {
|
||||
@@ -316,7 +305,6 @@ describe('cron', () => {
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||
@@ -326,7 +314,6 @@ describe('cron', () => {
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
@@ -336,12 +323,10 @@ describe('cron', () => {
|
||||
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 6-month recurring subscription', () => {
|
||||
let clock;
|
||||
let user6 = new User({
|
||||
auth: {
|
||||
local: {
|
||||
@@ -369,7 +354,6 @@ describe('cron', () => {
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||
@@ -379,7 +363,6 @@ describe('cron', () => {
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||
@@ -389,7 +372,6 @@ describe('cron', () => {
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||
@@ -399,7 +381,6 @@ describe('cron', () => {
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(6);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
@@ -409,13 +390,10 @@ describe('cron', () => {
|
||||
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(8);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 12-month recurring subscription', () => {
|
||||
let clock;
|
||||
|
||||
let user12 = new User({
|
||||
auth: {
|
||||
local: {
|
||||
@@ -443,7 +421,6 @@ describe('cron', () => {
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||
@@ -453,7 +430,6 @@ describe('cron', () => {
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||
@@ -463,7 +439,6 @@ describe('cron', () => {
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(8);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||
@@ -473,7 +448,6 @@ describe('cron', () => {
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(12);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
@@ -483,12 +457,10 @@ describe('cron', () => {
|
||||
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(16);
|
||||
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 3-month gift subscription (non-recurring)', () => {
|
||||
let clock;
|
||||
let user3g = new User({
|
||||
auth: {
|
||||
local: {
|
||||
@@ -503,7 +475,7 @@ describe('cron', () => {
|
||||
// user3g has a 3-month gift subscription starting today
|
||||
user3g.purchased.plan.customerId = 'Gift';
|
||||
user3g.purchased.plan.dateUpdated = moment().toDate();
|
||||
user3g.purchased.plan.dateTerminated = moment().add(3, 'months').toDate();
|
||||
user3g.purchased.plan.dateTerminated = moment().startOf('month').add(3, 'months').add(15, 'days').toDate();
|
||||
user3g.purchased.plan.planId = null;
|
||||
user3g.purchased.plan.consecutive.count = 0;
|
||||
user3g.purchased.plan.consecutive.offset = 3;
|
||||
@@ -517,7 +489,6 @@ describe('cron', () => {
|
||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(2);
|
||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month of the gift subscription', () => {
|
||||
@@ -527,7 +498,6 @@ describe('cron', () => {
|
||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the third month of the gift subscription', () => {
|
||||
@@ -537,7 +507,6 @@ describe('cron', () => {
|
||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the month after the gift subscription has ended', () => {
|
||||
@@ -547,12 +516,10 @@ describe('cron', () => {
|
||||
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(0); // erased
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 6-month recurring subscription where the user has incorrect consecutive month data from prior bugs', () => {
|
||||
let clock;
|
||||
let user6x = new User({
|
||||
auth: {
|
||||
local: {
|
||||
@@ -580,7 +547,6 @@ describe('cron', () => {
|
||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month after the fix goes live', () => {
|
||||
@@ -590,7 +556,6 @@ describe('cron', () => {
|
||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(4);
|
||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the third month after the fix goes live', () => {
|
||||
@@ -600,7 +565,6 @@ describe('cron', () => {
|
||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(3);
|
||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('increments consecutive benefits in the seventh month after the fix goes live', () => {
|
||||
@@ -610,7 +574,6 @@ describe('cron', () => {
|
||||
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
|
||||
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(7);
|
||||
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -627,14 +590,12 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not reset plan.gemsBought within the month', () => {
|
||||
let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix());
|
||||
clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix());
|
||||
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
||||
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(10);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('does not reset plan.dateUpdated on a new month', () => {
|
||||
@@ -1040,15 +1001,11 @@ describe('cron', () => {
|
||||
|
||||
describe('counters', () => {
|
||||
let notStartOfWeekOrMonth = new Date(2016, 9, 28).getTime(); // a Friday
|
||||
let clock;
|
||||
|
||||
beforeEach(() => {
|
||||
// Replace system clocks so we can get predictable results
|
||||
clock = sinon.useFakeTimers(notStartOfWeekOrMonth);
|
||||
});
|
||||
afterEach(() => {
|
||||
return clock.restore();
|
||||
});
|
||||
|
||||
it('should reset a daily habit counter each day', () => {
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
|
||||
@@ -19,7 +19,6 @@ function getUser () {
|
||||
emails: [{
|
||||
value: 'email@facebook',
|
||||
}],
|
||||
displayName: 'fb display name',
|
||||
},
|
||||
},
|
||||
profile: {
|
||||
@@ -100,7 +99,7 @@ describe('emails', () => {
|
||||
|
||||
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
||||
expect(data).to.have.property('name', user.auth.facebook.displayName);
|
||||
expect(data).to.have.property('name', user.profile.name);
|
||||
expect(data).to.have.property('email', user.auth.facebook.emails[0].value);
|
||||
expect(data).to.have.property('_id', user._id);
|
||||
expect(data).to.have.property('canSend', true);
|
||||
@@ -110,13 +109,12 @@ describe('emails', () => {
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
let getUserInfo = attachEmail.getUserInfo;
|
||||
let user = getUser();
|
||||
delete user.profile.name;
|
||||
delete user.auth.local.email;
|
||||
delete user.auth.facebook;
|
||||
|
||||
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
||||
expect(data).to.have.property('name', user.auth.local.username);
|
||||
expect(data).to.have.property('name', user.profile.name);
|
||||
expect(data).not.to.have.property('email');
|
||||
expect(data).to.have.property('_id', user._id);
|
||||
expect(data).to.have.property('canSend', true);
|
||||
|
||||
@@ -443,8 +443,7 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
let updatedUser = await User.findById(recipient._id).exec();
|
||||
|
||||
const updatedUser = await User.findById(recipient._id).exec();
|
||||
expect(updatedUser.purchased.plan.extraMonths).to.within(2, 3);
|
||||
});
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ describe('payments/index', () => {
|
||||
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, { receiverMsg: msg, senderMsg: msg });
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
|
||||
});
|
||||
|
||||
it('sends an email about the gift', async () => {
|
||||
@@ -629,7 +629,16 @@ describe('payments/index', () => {
|
||||
await api.buyGems(data);
|
||||
let msg = '\`Hello recipient, sender has sent you 4 gems!\`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg });
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
|
||||
});
|
||||
|
||||
it('sends a message from purchaser to recipient wtih custom message', async () => {
|
||||
data.gift.message = 'giftmessage';
|
||||
|
||||
await api.buyGems(data);
|
||||
|
||||
const msg = `\`Hello recipient, sender has sent you 4 gems!\` ${data.gift.message}`;
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
|
||||
});
|
||||
|
||||
it('sends a push notification if user did not gift to self', async () => {
|
||||
@@ -658,7 +667,7 @@ describe('payments/index', () => {
|
||||
return `\`${messageContent}\``;
|
||||
});
|
||||
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: recipientsMessageContent, senderMsg: sendersMessageContent });
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: recipientsMessageContent, senderMsg: sendersMessageContent, save: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,6 +37,22 @@ describe('checkout', () => {
|
||||
payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should error if there is no token', async () => {
|
||||
await expect(stripePayments.checkout({
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: 'Missing req.body.id',
|
||||
name: 'BadRequest',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if gem amount is too low', async () => {
|
||||
let receivingUser = new User();
|
||||
receivingUser.save();
|
||||
@@ -64,7 +80,6 @@ describe('checkout', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should error if user cannot get gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
|
||||
@@ -5,6 +5,7 @@ import slack from '../../../../../website/server/libs/slack';
|
||||
import logger from '../../../../../website/server/libs/logger';
|
||||
import { TAVERN_ID } from '../../../../../website/server/models/group';
|
||||
import nconf from 'nconf';
|
||||
import moment from 'moment';
|
||||
|
||||
describe('slack', () => {
|
||||
describe('sendFlagNotification', () => {
|
||||
@@ -45,13 +46,15 @@ describe('slack', () => {
|
||||
it('sends a slack webhook', () => {
|
||||
slack.sendFlagNotification(data);
|
||||
|
||||
const timestamp = `${moment(data.message.timestamp).utc().format('YYYY-MM-DD HH:mm')} UTC`;
|
||||
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: 'flagger (flagger-id; language: flagger-lang) flagged a message',
|
||||
attachments: [{
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: 'Author - author@example.com - author-id',
|
||||
author_name: `Author - author@example.com - author-id\n${timestamp}`,
|
||||
title: 'Flag in Some group - (private guild)',
|
||||
title_link: undefined,
|
||||
text: 'some text',
|
||||
@@ -97,9 +100,11 @@ describe('slack', () => {
|
||||
|
||||
slack.sendFlagNotification(data);
|
||||
|
||||
const timestamp = `${moment(data.message.timestamp).utc().format('YYYY-MM-DD HH:mm')} UTC`;
|
||||
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWithMatch({
|
||||
attachments: [sandbox.match({
|
||||
author_name: 'System Message',
|
||||
author_name: `System Message\n${timestamp}`,
|
||||
})],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,11 +4,19 @@ import {
|
||||
taskScoredWebhook,
|
||||
groupChatReceivedWebhook,
|
||||
taskActivityWebhook,
|
||||
questActivityWebhook,
|
||||
userActivityWebhook,
|
||||
} from '../../../../../website/server/libs/webhook';
|
||||
import {
|
||||
model as User,
|
||||
} from '../../../../../website/server/models/user';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-unit.helper.js';
|
||||
import { defer } from '../../../../helpers/api-unit.helper';
|
||||
|
||||
describe('webhooks', () => {
|
||||
let webhooks;
|
||||
let webhooks, user;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox.stub(got, 'post').returns(defer().promise);
|
||||
@@ -23,6 +31,26 @@ describe('webhooks', () => {
|
||||
updated: true,
|
||||
deleted: true,
|
||||
scored: true,
|
||||
checklistScored: true,
|
||||
},
|
||||
}, {
|
||||
id: 'questActivity',
|
||||
url: 'http://quest-activity.com',
|
||||
enabled: true,
|
||||
type: 'questActivity',
|
||||
options: {
|
||||
questStarted: true,
|
||||
questFinised: true,
|
||||
},
|
||||
}, {
|
||||
id: 'userActivity',
|
||||
url: 'http://user-activity.com',
|
||||
enabled: true,
|
||||
type: 'userActivity',
|
||||
options: {
|
||||
petHatched: true,
|
||||
mountRaised: true,
|
||||
leveledUp: true,
|
||||
},
|
||||
}, {
|
||||
id: 'groupChatReceived',
|
||||
@@ -33,6 +61,9 @@ describe('webhooks', () => {
|
||||
groupId: 'group-id',
|
||||
},
|
||||
}];
|
||||
|
||||
user = generateUser();
|
||||
user.webhooks = webhooks;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -57,7 +88,8 @@ describe('webhooks', () => {
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
|
||||
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(WebhookSender.defaultTransformData).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledOnce;
|
||||
@@ -67,6 +99,30 @@ describe('webhooks', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('adds default data (user and webhookType) to the body', () => {
|
||||
let sendWebhook = new WebhookSender({
|
||||
type: 'custom',
|
||||
});
|
||||
sandbox.spy(sendWebhook, 'attachDefaultData');
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(sendWebhook.attachDefaultData).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
||||
json: true,
|
||||
});
|
||||
|
||||
expect(body).to.eql({
|
||||
foo: 'bar',
|
||||
user: {_id: user._id},
|
||||
webhookType: 'custom',
|
||||
});
|
||||
});
|
||||
|
||||
it('can pass in a data transformation function', () => {
|
||||
sandbox.spy(WebhookSender, 'defaultTransformData');
|
||||
let sendWebhook = new WebhookSender({
|
||||
@@ -80,7 +136,8 @@ describe('webhooks', () => {
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
|
||||
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(WebhookSender.defaultTransformData).to.not.be.called;
|
||||
expect(got.post).to.be.calledOnce;
|
||||
@@ -93,7 +150,7 @@ describe('webhooks', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('provieds a default filter function', () => {
|
||||
it('provides a default filter function', () => {
|
||||
sandbox.spy(WebhookSender, 'defaultWebhookFilter');
|
||||
let sendWebhook = new WebhookSender({
|
||||
type: 'custom',
|
||||
@@ -101,7 +158,8 @@ describe('webhooks', () => {
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
|
||||
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(WebhookSender.defaultWebhookFilter).to.be.calledOnce;
|
||||
});
|
||||
@@ -117,7 +175,8 @@ describe('webhooks', () => {
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
|
||||
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(WebhookSender.defaultWebhookFilter).to.not.be.called;
|
||||
expect(got.post).to.not.be.called;
|
||||
@@ -134,10 +193,11 @@ describe('webhooks', () => {
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
sendWebhook.send([
|
||||
user.webhooks = [
|
||||
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom', options: { foo: 'bar' }},
|
||||
{ id: 'other-custom-webhook', url: 'http://other-custom-url.com', enabled: true, type: 'custom', options: { foo: 'foo' }},
|
||||
], body);
|
||||
];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch('http://custom-url.com');
|
||||
@@ -150,7 +210,8 @@ describe('webhooks', () => {
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: false, type: 'custom'}], body);
|
||||
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: false, type: 'custom'}];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(got.post).to.not.be.called;
|
||||
});
|
||||
@@ -162,7 +223,8 @@ describe('webhooks', () => {
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
sendWebhook.send([{id: 'custom-webhook', url: 'httxp://custom-url!!', enabled: true, type: 'custom'}], body);
|
||||
user.webhooks = [{id: 'custom-webhook', url: 'httxp://custom-url!!!', enabled: true, type: 'custom'}];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(got.post).to.not.be.called;
|
||||
});
|
||||
@@ -174,10 +236,30 @@ describe('webhooks', () => {
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
sendWebhook.send([
|
||||
user.webhooks = [
|
||||
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
|
||||
{ id: 'other-webhook', url: 'http://other-url.com', enabled: true, type: 'other'},
|
||||
], body);
|
||||
];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
||||
body,
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('sends every type of activity to global webhooks', () => {
|
||||
let sendWebhook = new WebhookSender({
|
||||
type: 'custom',
|
||||
});
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
user.webhooks = [
|
||||
{ id: 'global-webhook', url: 'http://custom-url.com', enabled: true, type: 'globalActivity'},
|
||||
];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
||||
@@ -193,10 +275,11 @@ describe('webhooks', () => {
|
||||
|
||||
let body = { foo: 'bar' };
|
||||
|
||||
sendWebhook.send([
|
||||
user.webhooks = [
|
||||
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
|
||||
{ id: 'other-custom-webhook', url: 'http://other-url.com', enabled: true, type: 'custom'},
|
||||
], body);
|
||||
];
|
||||
sendWebhook.send(user, body);
|
||||
|
||||
expect(got.post).to.be.calledTwice;
|
||||
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
||||
@@ -216,7 +299,6 @@ describe('webhooks', () => {
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
user: {
|
||||
_id: 'user-id',
|
||||
_tmp: {foo: 'bar'},
|
||||
stats: {
|
||||
lvl: 5,
|
||||
@@ -227,17 +309,6 @@ describe('webhooks', () => {
|
||||
return this;
|
||||
},
|
||||
},
|
||||
addComputedStatsToJSONObj () {
|
||||
let mockStats = Object.assign({
|
||||
maxHealth: 50,
|
||||
maxMP: 103,
|
||||
toNextLevel: 40,
|
||||
}, this.stats);
|
||||
|
||||
delete mockStats.toJSON;
|
||||
|
||||
return mockStats;
|
||||
},
|
||||
},
|
||||
task: {
|
||||
text: 'text',
|
||||
@@ -245,18 +316,66 @@ describe('webhooks', () => {
|
||||
direction: 'up',
|
||||
delta: 176,
|
||||
};
|
||||
|
||||
let mockStats = Object.assign({
|
||||
maxHealth: 50,
|
||||
maxMP: 103,
|
||||
toNextLevel: 40,
|
||||
}, data.user.stats);
|
||||
delete mockStats.toJSON;
|
||||
|
||||
sandbox.stub(User, 'addComputedStatsToJSONObj').returns(mockStats);
|
||||
});
|
||||
|
||||
it('sends task and stats data', () => {
|
||||
taskScoredWebhook.send(webhooks, data);
|
||||
taskScoredWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
|
||||
json: true,
|
||||
body: {
|
||||
type: 'scored',
|
||||
webhookType: 'taskActivity',
|
||||
user: {
|
||||
_id: 'user-id',
|
||||
_id: user._id,
|
||||
_tmp: {foo: 'bar'},
|
||||
stats: {
|
||||
lvl: 5,
|
||||
int: 10,
|
||||
str: 5,
|
||||
exp: 423,
|
||||
toNextLevel: 40,
|
||||
maxHealth: 50,
|
||||
maxMP: 103,
|
||||
},
|
||||
},
|
||||
task: {
|
||||
text: 'text',
|
||||
},
|
||||
direction: 'up',
|
||||
delta: 176,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sends task and stats data to globalActivity webhookd', () => {
|
||||
user.webhooks = [{
|
||||
id: 'globalActivity',
|
||||
url: 'http://global-activity.com',
|
||||
enabled: true,
|
||||
type: 'globalActivity',
|
||||
}];
|
||||
|
||||
taskScoredWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch('http://global-activity.com', {
|
||||
json: true,
|
||||
body: {
|
||||
type: 'scored',
|
||||
webhookType: 'taskActivity',
|
||||
user: {
|
||||
_id: user._id,
|
||||
_tmp: {foo: 'bar'},
|
||||
stats: {
|
||||
lvl: 5,
|
||||
@@ -280,7 +399,7 @@ describe('webhooks', () => {
|
||||
it('does not send task scored data if scored option is not true', () => {
|
||||
webhooks[0].options.scored = false;
|
||||
|
||||
taskScoredWebhook.send(webhooks, data);
|
||||
taskScoredWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.not.be.called;
|
||||
});
|
||||
@@ -301,13 +420,17 @@ describe('webhooks', () => {
|
||||
it(`sends ${type} tasks`, () => {
|
||||
data.type = type;
|
||||
|
||||
taskActivityWebhook.send(webhooks, data);
|
||||
taskActivityWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
|
||||
json: true,
|
||||
body: {
|
||||
type,
|
||||
webhookType: 'taskActivity',
|
||||
user: {
|
||||
_id: user._id,
|
||||
},
|
||||
task: data.task,
|
||||
},
|
||||
});
|
||||
@@ -317,7 +440,142 @@ describe('webhooks', () => {
|
||||
data.type = type;
|
||||
webhooks[0].options[type] = false;
|
||||
|
||||
taskActivityWebhook.send(webhooks, data);
|
||||
taskActivityWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.not.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('checklistScored', () => {
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
task: {
|
||||
text: 'text',
|
||||
},
|
||||
item: {
|
||||
text: 'item-text',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('sends \'checklistScored\' tasks', () => {
|
||||
data.type = 'checklistScored';
|
||||
|
||||
taskActivityWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
|
||||
json: true,
|
||||
body: {
|
||||
webhookType: 'taskActivity',
|
||||
user: {
|
||||
_id: user._id,
|
||||
},
|
||||
type: data.type,
|
||||
task: data.task,
|
||||
item: data.item,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not send task \'checklistScored\' data if \'checklistScored\' option is not true', () => {
|
||||
data.type = 'checklistScored';
|
||||
webhooks[0].options.checklistScored = false;
|
||||
|
||||
taskActivityWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('userActivityWebhook', () => {
|
||||
let data;
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
something: true,
|
||||
};
|
||||
});
|
||||
|
||||
['petHatched', 'mountRaised', 'leveledUp'].forEach((type) => {
|
||||
it(`sends ${type} webhooks`, () => {
|
||||
data.type = type;
|
||||
|
||||
userActivityWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch(webhooks[2].url, {
|
||||
json: true,
|
||||
body: {
|
||||
type,
|
||||
webhookType: 'userActivity',
|
||||
user: {
|
||||
_id: user._id,
|
||||
},
|
||||
something: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`does not send webhook ${type} data if ${type} option is not true`, () => {
|
||||
data.type = type;
|
||||
webhooks[2].options[type] = false;
|
||||
|
||||
userActivityWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('questActivityWebhook', () => {
|
||||
let data;
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
group: {
|
||||
id: 'group-id',
|
||||
name: 'some group',
|
||||
otherData: 'foo',
|
||||
},
|
||||
quest: {
|
||||
key: 'some-key',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
['questStarted', 'questFinised'].forEach((type) => {
|
||||
it(`sends ${type} webhooks`, () => {
|
||||
data.type = type;
|
||||
|
||||
questActivityWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch(webhooks[1].url, {
|
||||
json: true,
|
||||
body: {
|
||||
type,
|
||||
webhookType: 'questActivity',
|
||||
user: {
|
||||
_id: user._id,
|
||||
},
|
||||
group: {
|
||||
id: 'group-id',
|
||||
name: 'some group',
|
||||
},
|
||||
quest: {
|
||||
key: 'some-key',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`does not send webhook ${type} data if ${type} option is not true`, () => {
|
||||
data.type = type;
|
||||
webhooks[1].options[type] = false;
|
||||
|
||||
userActivityWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.not.be.called;
|
||||
});
|
||||
@@ -338,12 +596,16 @@ describe('webhooks', () => {
|
||||
},
|
||||
};
|
||||
|
||||
groupChatReceivedWebhook.send(webhooks, data);
|
||||
groupChatReceivedWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.be.calledOnce;
|
||||
expect(got.post).to.be.calledWithMatch(webhooks[webhooks.length - 1].url, {
|
||||
json: true,
|
||||
body: {
|
||||
webhookType: 'groupChatReceived',
|
||||
user: {
|
||||
_id: user._id,
|
||||
},
|
||||
group: {
|
||||
id: 'group-id',
|
||||
name: 'some group',
|
||||
@@ -369,7 +631,7 @@ describe('webhooks', () => {
|
||||
},
|
||||
};
|
||||
|
||||
groupChatReceivedWebhook.send(webhooks, data);
|
||||
groupChatReceivedWebhook.send(user, data);
|
||||
|
||||
expect(got.post).to.not.be.called;
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('auth middleware', () => {
|
||||
|
||||
describe('auth with headers', () => {
|
||||
it('allows to specify a list of user field that we do not want to load', (done) => {
|
||||
const authWithHeaders = authWithHeadersFactory(false, {
|
||||
const authWithHeaders = authWithHeadersFactory({
|
||||
userFieldsToExclude: ['items', 'flags', 'auth.timestamps'],
|
||||
});
|
||||
|
||||
|
||||
@@ -166,8 +166,11 @@ describe('cron middleware', () => {
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, (err) => {
|
||||
if (err) return reject(err);
|
||||
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
||||
resolve();
|
||||
User.findOne({_id: user._id}, function (secondErr, updatedUser) {
|
||||
if (secondErr) return reject(secondErr);
|
||||
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -176,7 +179,7 @@ describe('cron middleware', () => {
|
||||
user.lastCron = moment(new Date()).subtract({days: 2});
|
||||
let todo = generateTodo(user);
|
||||
let todoValueBefore = todo.value;
|
||||
await user.save();
|
||||
await Promise.all([todo.save(), user.save()]);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, (err) => {
|
||||
@@ -217,8 +220,11 @@ describe('cron middleware', () => {
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, (err) => {
|
||||
if (err) return reject(err);
|
||||
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
||||
resolve();
|
||||
User.findOne({_id: user._id}, function (secondErr, updatedUser) {
|
||||
if (secondErr) return reject(secondErr);
|
||||
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import i18n from '../../../../../website/common/script/i18n';
|
||||
import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/ensureAccessRight';
|
||||
import { NotAuthorized } from '../../../../../website/server/libs/errors';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('ensure access middlewares', () => {
|
||||
let res, req, next;
|
||||
@@ -46,7 +46,7 @@ describe('ensure access middlewares', () => {
|
||||
ensureSudo(req, res, next);
|
||||
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal(apiMessages('noSudoAccess'));
|
||||
expect(calledWith[0].message).to.equal(apiError('noSudoAccess'));
|
||||
expect(calledWith[0] instanceof NotAuthorized).to.equal(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@ import {
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { quests as questScrolls } from '../../../../../website/common/script/content';
|
||||
import { groupChatReceivedWebhook } from '../../../../../website/server/libs/webhook';
|
||||
import {
|
||||
groupChatReceivedWebhook,
|
||||
questActivityWebhook,
|
||||
} from '../../../../../website/server/libs/webhook';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/';
|
||||
import shared from '../../../../../website/common';
|
||||
@@ -21,6 +24,7 @@ describe('Group Model', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox.stub(email, 'sendTxn');
|
||||
sandbox.stub(questActivityWebhook, 'send');
|
||||
|
||||
party = new Group({
|
||||
name: 'test party',
|
||||
@@ -1189,6 +1193,47 @@ describe('Group Model', () => {
|
||||
expect(typeOfEmail).to.eql('quest-started');
|
||||
});
|
||||
|
||||
it('sends webhook to participating members that quest has started', async () => {
|
||||
// should receive webhook
|
||||
participatingMember.webhooks = [{
|
||||
type: 'questActivity',
|
||||
url: 'http://someurl.com',
|
||||
options: {
|
||||
questStarted: true,
|
||||
},
|
||||
}];
|
||||
questLeader.webhooks = [{
|
||||
type: 'questActivity',
|
||||
url: 'http://someurl.com',
|
||||
options: {
|
||||
questStarted: true,
|
||||
},
|
||||
}];
|
||||
|
||||
await Promise.all([participatingMember.save(), questLeader.save()]);
|
||||
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
expect(questActivityWebhook.send).to.be.calledTwice; // for 2 participating members
|
||||
|
||||
let args = questActivityWebhook.send.args[0];
|
||||
let webhooks = args[0].webhooks;
|
||||
let webhookOwner = args[0]._id;
|
||||
let options = args[1];
|
||||
|
||||
expect(webhooks).to.have.a.lengthOf(1);
|
||||
if (webhookOwner === questLeader._id) {
|
||||
expect(webhooks[0].id).to.eql(questLeader.webhooks[0].id);
|
||||
} else {
|
||||
expect(webhooks[0].id).to.eql(participatingMember.webhooks[0].id);
|
||||
}
|
||||
expect(webhooks[0].type).to.eql('questActivity');
|
||||
expect(options.group).to.eql(party);
|
||||
expect(options.quest.key).to.eql('whale');
|
||||
});
|
||||
|
||||
it('sends email only to members who have not opted out', async () => {
|
||||
participatingMember.preferences.emailNotifications.questStarted = false;
|
||||
questLeader.preferences.emailNotifications.questStarted = true;
|
||||
@@ -1570,6 +1615,42 @@ describe('Group Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sends webhook to participating members that quest has finished', async () => {
|
||||
// should receive webhook
|
||||
participatingMember.webhooks = [{
|
||||
type: 'questActivity',
|
||||
url: 'http://someurl.com',
|
||||
options: {
|
||||
questFinished: true,
|
||||
},
|
||||
}];
|
||||
questLeader.webhooks = [{
|
||||
type: 'questActivity',
|
||||
url: 'http://someurl.com',
|
||||
options: {
|
||||
questStarted: true, // will not receive the webhook
|
||||
},
|
||||
}];
|
||||
|
||||
await Promise.all([participatingMember.save(), questLeader.save()]);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
expect(questActivityWebhook.send).to.be.calledOnce;
|
||||
|
||||
let args = questActivityWebhook.send.args[0];
|
||||
let webhooks = args[0].webhooks;
|
||||
let options = args[1];
|
||||
|
||||
expect(webhooks).to.have.a.lengthOf(1);
|
||||
expect(webhooks[0].id).to.eql(participatingMember.webhooks[0].id);
|
||||
expect(webhooks[0].type).to.eql('questActivity');
|
||||
expect(options.group).to.eql(party);
|
||||
expect(options.quest.key).to.eql(quest.key);
|
||||
});
|
||||
|
||||
context('World quests in Tavern', () => {
|
||||
let tavernQuest;
|
||||
|
||||
@@ -1685,7 +1766,7 @@ describe('Group Model', () => {
|
||||
expect(groupChatReceivedWebhook.send).to.be.calledOnce;
|
||||
|
||||
let args = groupChatReceivedWebhook.send.args[0];
|
||||
let webhooks = args[0];
|
||||
let webhooks = args[0].webhooks;
|
||||
let options = args[1];
|
||||
|
||||
expect(webhooks).to.have.a.lengthOf(1);
|
||||
@@ -1749,9 +1830,9 @@ describe('Group Model', () => {
|
||||
expect(groupChatReceivedWebhook.send).to.be.calledThrice;
|
||||
|
||||
let args = groupChatReceivedWebhook.send.args;
|
||||
expect(args.find(arg => arg[0][0].id === memberWithWebhook.webhooks[0].id)).to.be.exist;
|
||||
expect(args.find(arg => arg[0][0].id === memberWithWebhook2.webhooks[0].id)).to.be.exist;
|
||||
expect(args.find(arg => arg[0][0].id === memberWithWebhook3.webhooks[0].id)).to.be.exist;
|
||||
expect(args.find(arg => arg[0].webhooks[0].id === memberWithWebhook.webhooks[0].id)).to.be.exist;
|
||||
expect(args.find(arg => arg[0].webhooks[0].id === memberWithWebhook2.webhooks[0].id)).to.be.exist;
|
||||
expect(args.find(arg => arg[0].webhooks[0].id === memberWithWebhook3.webhooks[0].id)).to.be.exist;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -42,13 +42,48 @@ describe('User Model', () => {
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
|
||||
user.addComputedStatsToJSONObj(userToJSON.stats);
|
||||
User.addComputedStatsToJSONObj(userToJSON.stats, userToJSON);
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
|
||||
});
|
||||
|
||||
it('can transform user object without mongoose helpers', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
let userToJSON = await User.findById(user._id).lean().exec();
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
expect(userToJSON.id).to.not.exist;
|
||||
|
||||
User.transformJSONUser(userToJSON);
|
||||
|
||||
expect(userToJSON.id).to.equal(userToJSON._id);
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
});
|
||||
|
||||
it('can transform user object without mongoose helpers (including computed stats)', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
let userToJSON = await User.findById(user._id).lean().exec();
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
|
||||
User.transformJSONUser(userToJSON, true);
|
||||
|
||||
expect(userToJSON.id).to.equal(userToJSON._id);
|
||||
expect(userToJSON.stats.maxMP).to.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
|
||||
});
|
||||
|
||||
context('notifications', () => {
|
||||
it('can add notifications without data', () => {
|
||||
let user = new User();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { model as Webhook } from '../../../../../website/server/models/webhook';
|
||||
import { BadRequest } from '../../../../../website/server/libs/errors';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('Webhook Model', () => {
|
||||
context('Instance Methods', () => {
|
||||
@@ -24,6 +25,7 @@ describe('Webhook Model', () => {
|
||||
updated: true,
|
||||
deleted: true,
|
||||
scored: true,
|
||||
checklistScored: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -36,6 +38,7 @@ describe('Webhook Model', () => {
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
checklistScored: false,
|
||||
created: false,
|
||||
updated: false,
|
||||
deleted: false,
|
||||
@@ -51,6 +54,7 @@ describe('Webhook Model', () => {
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
checklistScored: true,
|
||||
created: false,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -67,6 +71,7 @@ describe('Webhook Model', () => {
|
||||
|
||||
expect(wh.options.foo).to.not.exist;
|
||||
expect(wh.options).to.eql({
|
||||
checklistScored: true,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
@@ -74,7 +79,155 @@ describe('Webhook Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
['created', 'updated', 'deleted', 'scored'].forEach((option) => {
|
||||
['created', 'updated', 'deleted', 'scored', 'checklistScored'].forEach((option) => {
|
||||
it(`validates that ${option} is a boolean`, (done) => {
|
||||
config.options[option] = 'not a boolean';
|
||||
|
||||
try {
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceOf(BadRequest);
|
||||
expect(res.t).to.be.calledOnce;
|
||||
expect(res.t).to.be.calledWith('webhookBooleanOption', { option });
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('type is userActivity', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
type: 'userActivity',
|
||||
url: 'https//exmaple.com/endpoint',
|
||||
options: {
|
||||
petHatched: true,
|
||||
mountRaised: true,
|
||||
leveledUp: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('it provides default values for options', () => {
|
||||
delete config.options;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
petHatched: false,
|
||||
mountRaised: false,
|
||||
leveledUp: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('provides missing user options', () => {
|
||||
delete config.options.petHatched;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
petHatched: false,
|
||||
mountRaised: true,
|
||||
leveledUp: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('discards additional options', () => {
|
||||
config.options.foo = 'another option';
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options.foo).to.not.exist;
|
||||
expect(wh.options).to.eql({
|
||||
petHatched: true,
|
||||
mountRaised: true,
|
||||
leveledUp: true,
|
||||
});
|
||||
});
|
||||
|
||||
['petHatched', 'petHatched', 'leveledUp'].forEach((option) => {
|
||||
it(`validates that ${option} is a boolean`, (done) => {
|
||||
config.options[option] = 'not a boolean';
|
||||
|
||||
try {
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceOf(BadRequest);
|
||||
expect(res.t).to.be.calledOnce;
|
||||
expect(res.t).to.be.calledWith('webhookBooleanOption', { option });
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('type is questActivity', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
type: 'questActivity',
|
||||
url: 'https//exmaple.com/endpoint',
|
||||
options: {
|
||||
questStarted: true,
|
||||
questFinished: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('it provides default values for options', () => {
|
||||
delete config.options;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
questStarted: false,
|
||||
questFinished: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('provides missing user options', () => {
|
||||
delete config.options.questStarted;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
questStarted: false,
|
||||
questFinished: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('discards additional options', () => {
|
||||
config.options.foo = 'another option';
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options.foo).to.not.exist;
|
||||
expect(wh.options).to.eql({
|
||||
questStarted: true,
|
||||
questFinished: true,
|
||||
});
|
||||
});
|
||||
|
||||
['questStarted', 'questFinished'].forEach((option) => {
|
||||
it(`validates that ${option} is a boolean`, (done) => {
|
||||
config.options[option] = 'not a boolean';
|
||||
|
||||
@@ -135,12 +288,36 @@ describe('Webhook Model', () => {
|
||||
wh.formatOptions(res);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceOf(BadRequest);
|
||||
expect(res.t).to.be.calledOnce;
|
||||
expect(res.t).to.be.calledWith('groupIdRequired');
|
||||
|
||||
expect(err.message).to.eql(apiError('groupIdRequired'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
context('type is globalActivity', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
type: 'globalActivity',
|
||||
url: 'https//exmaple.com/endpoint',
|
||||
options: { },
|
||||
};
|
||||
});
|
||||
|
||||
it('discards additional objects', () => {
|
||||
config.options.foo = 'another thing';
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options.foo).to.not.exist;
|
||||
expect(wh.options).to.eql({});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
This folder contains the test files for the new client side that is being developed.
|
||||
|
||||
The old client side tests can be found in /test/client-old.
|
||||
@@ -0,0 +1,65 @@
|
||||
import {shallow} from '@vue/test-utils';
|
||||
|
||||
import CategoryTags from 'client/components/categories/categoryTags.vue';
|
||||
|
||||
describe('Category Tags', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(function () {
|
||||
wrapper = shallow(CategoryTags, {
|
||||
propsData: {
|
||||
categories: [],
|
||||
},
|
||||
slots: {
|
||||
default: '<p>This is a slot.</p>',
|
||||
},
|
||||
mocks: {
|
||||
$t: (string) => string,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('displays a category', () => {
|
||||
wrapper.setProps({
|
||||
categories: [
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(wrapper.contains('.category-label')).to.eq(true);
|
||||
expect(wrapper.find('.category-label').text()).to.eq('test');
|
||||
});
|
||||
|
||||
it('displays a habitica official in purple', () => {
|
||||
wrapper.setProps({
|
||||
categories: [
|
||||
{
|
||||
name: 'habitica_official',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(wrapper.contains('.category-label-purple')).to.eq(true);
|
||||
expect(wrapper.find('.category-label').text()).to.eq('habitica_official');
|
||||
});
|
||||
|
||||
it('displays owner label', () => {
|
||||
wrapper.setProps({
|
||||
owner: true,
|
||||
});
|
||||
expect(wrapper.contains('.category-label-blue')).to.eq(true);
|
||||
expect(wrapper.find('.category-label').text()).to.eq('owned');
|
||||
});
|
||||
|
||||
it('displays member label', () => {
|
||||
wrapper.setProps({
|
||||
member: true,
|
||||
});
|
||||
expect(wrapper.contains('.category-label-green')).to.eq(true);
|
||||
expect(wrapper.find('.category-label').text()).to.eq('joined');
|
||||
});
|
||||
|
||||
it('displays additional content at the end', () => {
|
||||
expect(wrapper.find('p').text()).to.eq('This is a slot.');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import {shallow} from '@vue/test-utils';
|
||||
|
||||
import SidebarSection from 'client/components/sidebarSection.vue';
|
||||
|
||||
describe('Sidebar Section', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(function () {
|
||||
wrapper = shallow(SidebarSection, {
|
||||
propsData: {
|
||||
title: 'Hello World',
|
||||
},
|
||||
slots: {
|
||||
default: '<p>This is a test.</p>',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('displays title', () => {
|
||||
expect(wrapper.find('h3').text()).to.eq('Hello World');
|
||||
});
|
||||
|
||||
it('displays contents', () => {
|
||||
expect(wrapper.find('.section-body').find('p').text()).to.eq('This is a test.');
|
||||
});
|
||||
|
||||
it('displays tooltip icon', () => {
|
||||
expect(wrapper.contains('.section-info')).to.eq(false);
|
||||
wrapper.setProps({tooltip: 'This is a test'});
|
||||
expect(wrapper.contains('.section-info')).to.eq(true);
|
||||
});
|
||||
|
||||
it('hides contents', () => {
|
||||
expect(wrapper.find('.section-body').element.style.display).to.not.eq('none');
|
||||
wrapper.find('.section-toggle').trigger('click');
|
||||
expect(wrapper.find('.section-body').element.style.display).to.eq('none');
|
||||
wrapper.find('.section-toggle').trigger('click');
|
||||
expect(wrapper.find('.section-body').element.style.display).to.not.eq('none');
|
||||
});
|
||||
|
||||
it('can hide contents by default', () => {
|
||||
wrapper = shallow(SidebarSection, {
|
||||
propsData: {
|
||||
title: 'Hello World',
|
||||
show: false,
|
||||
},
|
||||
slots: {
|
||||
default: '<p>This is a test.</p>',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.find('.section-body').element.style.display).to.eq('none');
|
||||
});
|
||||
});
|
||||
@@ -117,6 +117,17 @@ describe('common.fns.updateStats', () => {
|
||||
expect(user.addNotification).to.be.calledWith('DROPS_ENABLED');
|
||||
});
|
||||
|
||||
it('add user notification when the user levels up', () => {
|
||||
const initialLvl = user.stats.lvl;
|
||||
updateStats(user, {
|
||||
exp: 3000,
|
||||
});
|
||||
expect(user._tmp.leveledUp).to.eql([{
|
||||
initialLvl,
|
||||
newLvl: user.stats.lvl,
|
||||
}]);
|
||||
});
|
||||
|
||||
it('add user notification when rebirth is enabled', () => {
|
||||
user.stats.lvl = 51;
|
||||
updateStats(user, { });
|
||||
|
||||
@@ -59,6 +59,78 @@ describe('shops', () => {
|
||||
expect(specialCategory.items.find((item) => item.key === 'weapon_special_critical'));
|
||||
expect(specialCategory.items.find((item) => item.key === 'weapon_armoire_basicCrossbow'));// eslint-disable-line camelcase
|
||||
});
|
||||
|
||||
it('does not show gear when it is all owned', () => {
|
||||
let userWithItems = generateUser({
|
||||
stats: {
|
||||
class: 'wizard',
|
||||
},
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
weapon_wizard_0: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_1: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_2: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_3: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_4: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_5: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_6: true, // eslint-disable-line camelcase
|
||||
armor_wizard_1: true, // eslint-disable-line camelcase
|
||||
armor_wizard_2: true, // eslint-disable-line camelcase
|
||||
armor_wizard_3: true, // eslint-disable-line camelcase
|
||||
armor_wizard_4: true, // eslint-disable-line camelcase
|
||||
armor_wizard_5: true, // eslint-disable-line camelcase
|
||||
head_wizard_1: true, // eslint-disable-line camelcase
|
||||
head_wizard_2: true, // eslint-disable-line camelcase
|
||||
head_wizard_3: true, // eslint-disable-line camelcase
|
||||
head_wizard_4: true, // eslint-disable-line camelcase
|
||||
head_wizard_5: true, // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
let shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
|
||||
expect(shopWizardItems.length).to.eql(0);
|
||||
});
|
||||
|
||||
it('shows available gear not yet purchased and previously owned', () => {
|
||||
let userWithItems = generateUser({
|
||||
stats: {
|
||||
class: 'wizard',
|
||||
},
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
weapon_wizard_0: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_1: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_2: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_3: true, // eslint-disable-line camelcase
|
||||
weapon_wizard_4: true, // eslint-disable-line camelcase
|
||||
armor_wizard_1: true, // eslint-disable-line camelcase
|
||||
armor_wizard_2: true, // eslint-disable-line camelcase
|
||||
armor_wizard_3: false, // eslint-disable-line camelcase
|
||||
armor_wizard_4: false, // eslint-disable-line camelcase
|
||||
head_wizard_1: true, // eslint-disable-line camelcase
|
||||
head_wizard_2: false, // eslint-disable-line camelcase
|
||||
head_wizard_3: true, // eslint-disable-line camelcase
|
||||
head_wizard_4: false, // eslint-disable-line camelcase
|
||||
head_wizard_5: true, // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
let shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
|
||||
expect(shopWizardItems.find(item => item.key === 'weapon_wizard_5').locked).to.eql(false);
|
||||
expect(shopWizardItems.find(item => item.key === 'weapon_wizard_6').locked).to.eql(true);
|
||||
expect(shopWizardItems.find(item => item.key === 'armor_wizard_3').locked).to.eql(false);
|
||||
expect(shopWizardItems.find(item => item.key === 'armor_wizard_4').locked).to.eql(true);
|
||||
expect(shopWizardItems.find(item => item.key === 'head_wizard_2').locked).to.eql(false);
|
||||
expect(shopWizardItems.find(item => item.key === 'head_wizard_4').locked).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('questShop', () => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import content from '../../../../website/common/script/content/index';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
|
||||
describe('shared.ops.buy', () => {
|
||||
let user;
|
||||
@@ -40,7 +41,7 @@ describe('shared.ops.buy', () => {
|
||||
buy(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('missingKeyParam'));
|
||||
expect(err.message).to.equal(errorMessage('missingKeyParam'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -68,6 +69,13 @@ describe('shared.ops.buy', () => {
|
||||
eyewear_special_redTopFrame: true,
|
||||
eyewear_special_whiteTopFrame: true,
|
||||
eyewear_special_yellowTopFrame: true,
|
||||
headAccessory_special_blackHeadband: true,
|
||||
headAccessory_special_blueHeadband: true,
|
||||
headAccessory_special_greenHeadband: true,
|
||||
headAccessory_special_pinkHeadband: true,
|
||||
headAccessory_special_redHeadband: true,
|
||||
headAccessory_special_whiteHeadband: true,
|
||||
headAccessory_special_yellowHeadband: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
BadRequest, NotAuthorized, NotFound,
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
|
||||
function buyGear (user, req, analytics) {
|
||||
let buyOp = new BuyMarketGearOperation(user, req, analytics);
|
||||
@@ -63,6 +64,13 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
eyewear_special_redTopFrame: true,
|
||||
eyewear_special_whiteTopFrame: true,
|
||||
eyewear_special_yellowTopFrame: true,
|
||||
headAccessory_special_blackHeadband: true,
|
||||
headAccessory_special_blueHeadband: true,
|
||||
headAccessory_special_greenHeadband: true,
|
||||
headAccessory_special_pinkHeadband: true,
|
||||
headAccessory_special_redHeadband: true,
|
||||
headAccessory_special_whiteHeadband: true,
|
||||
headAccessory_special_yellowHeadband: true,
|
||||
});
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
@@ -190,7 +198,7 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
buyGear(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('missingKeyParam'));
|
||||
expect(err.message).to.equal(errorMessage('missingKeyParam'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -202,7 +210,7 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
buyGear(user, {params});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotFound);
|
||||
expect(err.message).to.equal(i18n.t('itemNotFound', params));
|
||||
expect(err.message).to.equal(errorMessage('itemNotFound', params));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
NotFound,
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
|
||||
describe('shared.ops.buyMysterySet', () => {
|
||||
let user;
|
||||
@@ -70,7 +71,7 @@ describe('shared.ops.buyMysterySet', () => {
|
||||
buyMysterySet(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('missingKeyParam'));
|
||||
expect(err.message).to.equal(errorMessage('missingKeyParam'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
NotFound,
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
|
||||
describe('shared.ops.buyQuest', () => {
|
||||
let user;
|
||||
@@ -106,7 +107,7 @@ describe('shared.ops.buyQuest', () => {
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotFound);
|
||||
expect(err.message).to.equal(i18n.t('questNotFound', {key: 'snarfblatter'}));
|
||||
expect(err.message).to.equal(errorMessage('questNotFound', {key: 'snarfblatter'}));
|
||||
expect(user.items.quests).to.eql({});
|
||||
expect(user.stats.gp).to.equal(9999);
|
||||
done();
|
||||
@@ -151,7 +152,22 @@ describe('shared.ops.buyQuest', () => {
|
||||
buyQuest(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('missingKeyParam'));
|
||||
expect(err.message).to.equal(errorMessage('missingKeyParam'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not buy a quest without completing previous quests', (done) => {
|
||||
try {
|
||||
buyQuest(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress3',
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('mustComplete', {quest: 'dilatoryDistress2'}));
|
||||
expect(user.items.quests).to.eql({});
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import buySpecialSpell from '../../../../website/common/script/ops/buy/buySpecialSpell';
|
||||
import {BuySpellOperation} from '../../../../website/common/script/ops/buy/buySpell';
|
||||
import {
|
||||
BadRequest,
|
||||
NotFound,
|
||||
@@ -9,11 +9,17 @@ import {
|
||||
generateUser,
|
||||
} from '../../../helpers/common.helper';
|
||||
import content from '../../../../website/common/script/content/index';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
|
||||
describe('shared.ops.buySpecialSpell', () => {
|
||||
let user;
|
||||
let analytics = {track () {}};
|
||||
|
||||
function buySpecialSpell (_user, _req, _analytics) {
|
||||
const buyOp = new BuySpellOperation(_user, _req, _analytics);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
sinon.stub(analytics, 'track');
|
||||
@@ -28,7 +34,7 @@ describe('shared.ops.buySpecialSpell', () => {
|
||||
buySpecialSpell(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('missingKeyParam'));
|
||||
expect(err.message).to.equal(errorMessage('missingKeyParam'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -42,7 +48,7 @@ describe('shared.ops.buySpecialSpell', () => {
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotFound);
|
||||
expect(err.message).to.equal(i18n.t('spellNotFound', {spellId: 'notExisting'}));
|
||||
expect(err.message).to.equal(errorMessage('spellNotFound', {spellId: 'notExisting'}));
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -8,6 +8,7 @@ import content from '../../../../website/common/script/content/index';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/common.helper';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
|
||||
describe('common.ops.hourglassPurchase', () => {
|
||||
let user;
|
||||
@@ -28,7 +29,7 @@ describe('common.ops.hourglassPurchase', () => {
|
||||
hourglassPurchase(user, {params: {}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.eql(i18n.t('missingKeyParam'));
|
||||
expect(err.message).to.eql(errorMessage('missingKeyParam'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -38,7 +39,7 @@ describe('common.ops.hourglassPurchase', () => {
|
||||
hourglassPurchase(user, {params: {key: 'Base'}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.eql(i18n.t('missingTypeParam'));
|
||||
expect(err.message).to.eql(errorMessage('missingTypeParam'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import i18n from '../../../website/common/script/i18n';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import errorMessage from '../../../website/common/script/libs/errorMessage';
|
||||
|
||||
describe('shared.ops.feed', () => {
|
||||
let user;
|
||||
@@ -23,7 +24,7 @@ describe('shared.ops.feed', () => {
|
||||
feed(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('missingPetFoodFeed'));
|
||||
expect(err.message).to.equal(errorMessage('missingPetFoodFeed'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -33,7 +34,7 @@ describe('shared.ops.feed', () => {
|
||||
feed(user, {params: {pet: 'invalid', food: 'food'}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidPetName'));
|
||||
expect(err.message).to.equal(errorMessage('invalidPetName'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -43,7 +44,7 @@ describe('shared.ops.feed', () => {
|
||||
feed(user, {params: {pet: 'Wolf-Red', food: 'invalid food name'}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotFound);
|
||||
expect(err.message).to.equal(i18n.t('messageFoodNotFound'));
|
||||
expect(err.message).to.equal(errorMessage('invalidFoodName'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import i18n from '../../../website/common/script/i18n';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import errorMessage from '../../../website/common/script/libs/errorMessage';
|
||||
|
||||
describe('shared.ops.hatch', () => {
|
||||
let user;
|
||||
@@ -24,7 +25,7 @@ describe('shared.ops.hatch', () => {
|
||||
hatch(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('missingEggHatchingPotionHatch'));
|
||||
expect(err.message).to.equal(errorMessage('missingEggHatchingPotion'));
|
||||
expect(user.items.pets).to.be.empty;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import i18n from '../../../../website/common/script/i18n';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/common.helper';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
|
||||
describe('shared.ops.allocate', () => {
|
||||
let user;
|
||||
@@ -22,7 +23,7 @@ describe('shared.ops.allocate', () => {
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidAttribute', {attr: 'notValid'}));
|
||||
expect(err.message).to.equal(errorMessage('invalidAttribute', {attr: 'notValid'}));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import i18n from '../../../../website/common/script/i18n';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/common.helper';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
|
||||
describe('shared.ops.allocateBulk', () => {
|
||||
let user;
|
||||
@@ -27,7 +28,7 @@ describe('shared.ops.allocateBulk', () => {
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidAttribute', {attr: 'invalid'}));
|
||||
expect(err.message).to.equal(errorMessage('invalidAttribute', {attr: 'invalid'}));
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -37,7 +38,7 @@ describe('shared.ops.allocateBulk', () => {
|
||||
allocateBulk(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('statsObjectRequired'));
|
||||
expect(err.message).to.equal(errorMessage('statsObjectRequired'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -966,7 +966,7 @@ describe('shouldDo', () => {
|
||||
m: false,
|
||||
};
|
||||
|
||||
let today = moment('2017-01-27');
|
||||
let today = moment('2017-01-27:00:00.000-00:00');
|
||||
let week = today.monthWeek();
|
||||
let dayOfWeek = today.day();
|
||||
dailyTask.startDate = today.toDate();
|
||||
@@ -974,7 +974,7 @@ describe('shouldDo', () => {
|
||||
dailyTask.repeat[DAY_MAPPING[dayOfWeek]] = true;
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.frequency = 'monthly';
|
||||
day = moment('2017-02-24');
|
||||
day = moment('2017-02-24:00:00.000-00:00');
|
||||
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as Tasks from '../../../../website/server/models/task';
|
||||
// If you need the user to have specific requirements,
|
||||
// such as a balance > 0, just pass in the adjustment
|
||||
// to the update object. If you want to adjust a nested
|
||||
// paramter, such as the number of wolf eggs the user has,
|
||||
// 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 = {}) {
|
||||
|
||||
@@ -94,7 +94,6 @@ export function generateTodo (user) {
|
||||
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
task.userId = user._id;
|
||||
task.save();
|
||||
|
||||
return task;
|
||||
}
|
||||
@@ -109,7 +108,6 @@ export function generateDaily (user) {
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
task.userId = user._id;
|
||||
task.save();
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ if (process.env.LOAD_SERVER === '0') { // when the server is in a different proc
|
||||
nconf.set('NODE_DB_URI', nconf.get('TEST_DB_URI'));
|
||||
nconf.set('NODE_ENV', 'test');
|
||||
nconf.set('IS_TEST', true);
|
||||
// We require src/server and npt src/index because
|
||||
// We require src/server and not src/index because
|
||||
// 1. nconf is already setup
|
||||
// 2. we don't need clustering
|
||||
require('../../website/server/server'); // eslint-disable-line global-require
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
`/website/client` contains the source files for the new client side that is being developed as part of the Habitica.com redesign. Static assets for the new client can be found in `website/static`.
|
||||
|
||||
The old client side files can be found in `/website/client-old`, they are still used on Habitica.com while the redesign is in progress.
|
||||
@@ -1,8 +1,7 @@
|
||||
#Running
|
||||
- Open a terminal and type `npm run client:dev`
|
||||
- Open a second terminal and type `npm start`
|
||||
# Running
|
||||
For information about installing Habitica locally, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally) and for information about running the local client, refer to the ["Run Habitica" section](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally#Run_Habitica) in that page.
|
||||
|
||||
#Preparation Reading
|
||||
# Preparation Reading
|
||||
- Vue 2 (https://vuejs.org)
|
||||
|
||||
- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/
|
||||
@@ -18,3 +17,5 @@ The API is almost the same except that we don’t use mutations but only actions
|
||||
The project is developed directly in the `develop` branch as long as we’ll be able to avoid splitting it into a different branch.
|
||||
|
||||
So far most of the work has been on the template, so there’s no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it’s basically a Flux implementation: there’s a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
|
||||
|
||||
For further resources, see [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths), and in particular the ["Website Technology Stack" section](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths#Website_Technology_Stack).
|
||||
|
||||
+39
-21
@@ -44,8 +44,6 @@ div
|
||||
router-view
|
||||
app-footer
|
||||
audio#sound(autoplay, ref="sound")
|
||||
source#oggSource(type="audio/ogg", :src="sound.oggSource")
|
||||
source#mp3Source(type="audio/mp3", :src="sound.mp3Source")
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@@ -108,7 +106,7 @@ div
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal, .modal-open {
|
||||
overflow-y: scroll !important;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
@@ -118,7 +116,7 @@ div
|
||||
|
||||
/* Push progress bar above modals */
|
||||
#nprogress .bar {
|
||||
z-index: 1043 !important; /* Must stay above nav bar */
|
||||
z-index: 1090 !important; /* Must stay above nav bar */
|
||||
}
|
||||
|
||||
.restingInn {
|
||||
@@ -127,7 +125,7 @@ div
|
||||
}
|
||||
|
||||
#app-header {
|
||||
margin-top: 96px !important;
|
||||
margin-top: 40px !important;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -220,10 +218,9 @@ export default {
|
||||
selectedItemToBuy: null,
|
||||
selectedSpellToBuy: null,
|
||||
|
||||
sound: {
|
||||
oggSource: '',
|
||||
mp3Source: '',
|
||||
},
|
||||
audioSource: null,
|
||||
audioSuffix: null,
|
||||
|
||||
loading: true,
|
||||
currentTipNumber: 0,
|
||||
bannerHidden: false,
|
||||
@@ -259,11 +256,22 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
let file = `/static/audio/${theme}/${sound}`;
|
||||
this.sound = {
|
||||
oggSource: `${file}.ogg`,
|
||||
mp3Source: `${file}.mp3`,
|
||||
};
|
||||
let file = `/static/audio/${theme}/${sound}`;
|
||||
|
||||
if (this.audioSuffix === null) {
|
||||
this.audioSource = document.createElement('source');
|
||||
if (this.$refs.sound.canPlayType('audio/ogg')) {
|
||||
this.audioSuffix = '.ogg';
|
||||
this.audioSource.type = 'audio/ogg';
|
||||
} else {
|
||||
this.audioSuffix = '.mp3';
|
||||
this.audioSource.type = 'audio/mp3';
|
||||
}
|
||||
this.audioSource.src = file + this.audioSuffix;
|
||||
this.$refs.sound.appendChild(this.audioSource);
|
||||
} else {
|
||||
this.audioSource.src = file + this.audioSuffix;
|
||||
}
|
||||
|
||||
this.$refs.sound.load();
|
||||
});
|
||||
@@ -295,12 +303,6 @@ export default {
|
||||
if (error.response.status >= 400) {
|
||||
this.checkForBannedUser(error);
|
||||
|
||||
// Check for conditions to reset the user auth
|
||||
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
|
||||
if (invalidUserMessage.indexOf(error.response.data) !== -1) {
|
||||
this.$store.dispatch('auth:logout');
|
||||
}
|
||||
|
||||
// Don't show errors from getting user details. These users have delete their account,
|
||||
// but their chat message still exists.
|
||||
let configExists = Boolean(error.response) && Boolean(error.response.config);
|
||||
@@ -313,11 +315,27 @@ export default {
|
||||
const errorData = error.response.data;
|
||||
const errorMessage = errorData.message || errorData;
|
||||
|
||||
// Check for conditions to reset the user auth
|
||||
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
|
||||
if (invalidUserMessage.indexOf(errorMessage) !== -1) {
|
||||
this.$store.dispatch('auth:logout');
|
||||
}
|
||||
|
||||
// Most server errors should return is click to dismiss errors, with some exceptions
|
||||
let snackbarTimeout = false;
|
||||
if (error.response.status === 502) snackbarTimeout = true;
|
||||
|
||||
const notificationNotFoundMessage = [
|
||||
this.$t('messageNotificationNotFound'),
|
||||
this.$t('messageNotificationNotFound', 'en'),
|
||||
];
|
||||
if (notificationNotFoundMessage.indexOf(errorMessage) !== -1) snackbarTimeout = true;
|
||||
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: errorMessage,
|
||||
type: 'error',
|
||||
timeout: true,
|
||||
timeout: snackbarTimeout,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,66 +1,48 @@
|
||||
.promo_armoire_background_201804 {
|
||||
.promo_armoire_backgrounds_201806 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -587px;
|
||||
background-position: -565px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_bundle_aquaticAmigos {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -534px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_earrings_headbands {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -707px 0px;
|
||||
width: 297px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_fairy_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 564px;
|
||||
height: 196px;
|
||||
}
|
||||
.promo_ios {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -532px 0px;
|
||||
background-position: 0px -197px;
|
||||
width: 325px;
|
||||
height: 336px;
|
||||
}
|
||||
.promo_mystery_201804 {
|
||||
.promo_mystery_201805 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -695px -337px;
|
||||
background-position: -707px -148px;
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_rainbow_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -284px -587px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_seasonalshop_spring {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -532px -337px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_shimmer_pastel {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -426px -735px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_shiny_seeds {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -426px -587px;
|
||||
width: 360px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_spring_fling_2018 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -587px;
|
||||
width: 141px;
|
||||
height: 588px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -532px -476px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
background-position: -822px -148px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.scene_positivity {
|
||||
.scene_perfect_day {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 531px;
|
||||
height: 243px;
|
||||
}
|
||||
.scene_video_games {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -244px;
|
||||
width: 339px;
|
||||
height: 342px;
|
||||
background-position: -326px -197px;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user