Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
539f0e33e2 | ||
|
|
405e053377 | ||
|
|
52fbb8f899 | ||
|
|
8682cf1cf7 | ||
|
|
6e922cfb44 | ||
|
|
cafabd93e1 | ||
|
|
1001d48eb7 | ||
|
|
b5c4618d56 | ||
|
|
92a4ba93d2 | ||
|
|
90d35d2f1f | ||
|
|
fead027cd2 | ||
|
|
5578426985 | ||
|
|
1c39fae127 | ||
|
|
45a757b589 | ||
|
|
8b610d771c | ||
|
|
bd81d27145 | ||
|
|
8eb430cbcb | ||
|
|
f218133d25 | ||
|
|
5f440d9097 | ||
|
|
0294868747 | ||
|
|
9c8d870d16 | ||
|
|
a7acd863f3 | ||
|
|
f32ef0a6ba | ||
|
|
ebf3b4aa47 | ||
|
|
5a8366468b | ||
|
|
df57518815 | ||
|
|
7d342b5115 | ||
|
|
388de9a97d | ||
|
|
28c79d9d20 | ||
|
|
85cf322b30 | ||
|
|
362ca73c94 | ||
|
|
90273362c4 | ||
|
|
7aadc10fab | ||
|
|
bfd45596b5 | ||
|
|
2eed4d38ae | ||
|
|
29bbe8534b | ||
|
|
9e008890b2 | ||
|
|
5505bf1e45 | ||
|
|
d40781ce07 | ||
|
|
d9719cdc05 | ||
|
|
8cc6a96be0 | ||
|
|
c5fb2d6506 | ||
|
|
afc336461e | ||
|
|
31376c8461 | ||
|
|
3a849bac18 | ||
|
|
563f3e2012 | ||
|
|
e24a024091 | ||
|
|
dc7d3816fd | ||
|
|
a094e13352 | ||
|
|
83376a38de | ||
|
|
db9c13a05d | ||
|
|
8c8aa78a1a | ||
|
|
6e3f7c005a | ||
|
|
1395380dfe | ||
|
|
833ceb3bf3 | ||
|
|
0522aa1551 | ||
|
|
58a9e4a439 | ||
|
|
84e2b2f45e | ||
|
|
71c0939a15 | ||
|
|
26c8323e70 | ||
|
|
bb7d447003 | ||
|
|
97ea510a34 | ||
|
|
99610b4916 | ||
|
|
9a43b85492 | ||
|
|
ecbf39cee4 | ||
|
|
4394772ee3 | ||
|
|
dd7fa73961 | ||
|
|
6ec23ce790 | ||
|
|
b953519e2d | ||
|
|
33a8072d23 | ||
|
|
213316d807 | ||
|
|
44cd4d0708 | ||
|
|
063b7a9af0 | ||
|
|
c08b5a4f1e | ||
|
|
90117625d7 | ||
|
|
1b3dad749e | ||
|
|
a622a3ebe3 | ||
|
|
2c83c16644 | ||
|
|
1034675184 | ||
|
|
a420876697 | ||
|
|
dc265e26b3 | ||
|
|
b5203dda61 | ||
|
|
80e92a8767 | ||
|
|
a265bfac9d | ||
|
|
92e4d5cd68 | ||
|
|
a18e9b3b18 | ||
|
|
c1a6ba6242 | ||
|
|
ed761a8b7b | ||
|
|
81d5971829 | ||
|
|
eb2d320d1f | ||
|
|
67538a368e | ||
|
|
d55b95834d | ||
|
|
9ff9cd3b35 | ||
|
|
b1f24de3c4 | ||
|
|
2ce2100f89 | ||
|
|
dbaae4183e | ||
|
|
2009bb97cb | ||
|
|
3fc9501bac | ||
|
|
2c2ded2b70 | ||
|
|
d689010e38 | ||
|
|
e173b7784c | ||
|
|
c3db59aae8 | ||
|
|
44e063c035 | ||
|
|
4e2c08cfed | ||
|
|
c845c337df | ||
|
|
418b57f9fb | ||
|
|
9725da258e | ||
|
|
4191ea1968 | ||
|
|
a9340ee60f | ||
|
|
c8d874d28a | ||
|
|
32a22f1545 | ||
|
|
8ffe302a49 | ||
|
|
5c50a40f39 | ||
|
|
84329e5fad | ||
|
|
64507a161e | ||
|
|
0f7fc27663 | ||
|
|
1545685a5b | ||
|
|
410355c3f1 | ||
|
|
ac27cabf6a | ||
|
|
972631e7ac | ||
|
|
d27ed7c406 | ||
|
|
031783b1d7 | ||
|
|
318aa7cbd9 | ||
|
|
f802a41f75 | ||
|
|
1d597039ca | ||
|
|
8153674dc0 | ||
|
|
0002148326 | ||
|
|
d198e23de6 | ||
|
|
4f4e141806 | ||
|
|
05e8d6f032 | ||
|
|
39847893d2 | ||
|
|
e4dbf09dda | ||
|
|
eb99b709e0 | ||
|
|
862b3453f8 | ||
|
|
7f48853d32 | ||
|
|
5c4f763bb1 | ||
|
|
bc9401b2f7 | ||
|
|
6fb9030b96 | ||
|
|
ba307af963 | ||
|
|
cf4b920a67 | ||
|
|
b0ff35a8f1 | ||
|
|
85b4c7825e | ||
|
|
5b7ea8ec5c | ||
|
|
5cfd0c863e | ||
|
|
10c6244c0c | ||
|
|
20e65be8bf | ||
|
|
8bac324ba7 | ||
|
|
2ee0288aaa | ||
|
|
b7ef4c50b2 | ||
|
|
52be9c750f | ||
|
|
b0200026aa | ||
|
|
e6c8b977c8 | ||
|
|
c78b5ecf7c | ||
|
|
f27e9b02d8 | ||
|
|
c06c19ca41 | ||
|
|
d5d894b8a9 | ||
|
|
7bd4e6a5a9 | ||
|
|
f13eed5663 | ||
|
|
a9a2fe6314 | ||
|
|
d6514bce8b | ||
|
|
603fc8c4dd | ||
|
|
3c602351f9 | ||
|
|
6aa204c3f5 | ||
|
|
eaaa5ad7f3 | ||
|
|
54468ff499 | ||
|
|
53405aa586 | ||
|
|
7630c02e13 | ||
|
|
ec444384f4 | ||
|
|
cce9b33844 | ||
|
|
b977d42402 | ||
|
|
2672cbd790 | ||
|
|
b7ca5be6ee | ||
|
|
5ae89761b0 | ||
|
|
696121fb24 |
@@ -17,7 +17,7 @@
|
||||
"NODE_DB_URI":"mongodb://localhost/habitrpg",
|
||||
"TEST_DB_URI":"mongodb://localhost/habitrpg_test",
|
||||
"NODE_ENV":"development",
|
||||
"ENABLE_CONSOLE_LOGS_IN_TEST": false,
|
||||
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
|
||||
"CRON_SAFE_MODE":"false",
|
||||
"CRON_SEMI_SAFE_MODE":"false",
|
||||
"MAINTENANCE_MODE": "false",
|
||||
|
||||
123
migrations/20180811_inboxOutsideUser.js
Normal file
@@ -0,0 +1,123 @@
|
||||
const migrationName = '20180811_inboxOutsideUser.js';
|
||||
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Move inbox messages from the user model to their own collection
|
||||
*/
|
||||
|
||||
const monk = require('monk');
|
||||
const nconf = require('nconf');
|
||||
|
||||
const Inbox = require('../website/server/models/message').inboxModel;
|
||||
const connectionString = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
||||
const dbInboxes = monk(connectionString).get('inboxes', { castIds: false });
|
||||
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
let query = {
|
||||
migration: {$ne: migrationName},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 1000,
|
||||
fields: ['_id', 'inbox'],
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
let msgCount = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users and their tasks found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let usersPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(usersPromises)
|
||||
.then(() => {
|
||||
return processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (msgCount % progressCount === 0) console.warn(`${msgCount } messages processed`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } being processed`);
|
||||
|
||||
const oldInboxMessages = user.inbox.messages || {};
|
||||
const oldInboxMessagesIds = Object.keys(oldInboxMessages);
|
||||
|
||||
msgCount += oldInboxMessagesIds.length;
|
||||
|
||||
const newInboxMessages = oldInboxMessagesIds.map(msgId => {
|
||||
const msg = oldInboxMessages[msgId];
|
||||
if (!msg || (!msg.id && !msg._id)) { // eslint-disable-line no-extra-parens
|
||||
console.log('missing message or message _id and id', msg);
|
||||
throw new Error('error!');
|
||||
}
|
||||
|
||||
if (msg.id && !msg._id) msg._id = msg.id;
|
||||
if (msg._id && !msg.id) msg.id = msg._id;
|
||||
|
||||
const newMsg = new Inbox(msg);
|
||||
newMsg.ownerId = user._id;
|
||||
return newMsg.toJSON();
|
||||
});
|
||||
|
||||
return dbInboxes.insert(newInboxMessages)
|
||||
.then(() => {
|
||||
return dbUsers.update({_id: user._id}, {
|
||||
$set: {
|
||||
migration: migrationName,
|
||||
'inbox.messages': {},
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
console.warn(`\n${ msgCount } messages 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;
|
||||
99
migrations/users/generate-usernames.js
Normal file
@@ -0,0 +1,99 @@
|
||||
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Generate usernames for users who lack them
|
||||
*/
|
||||
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
import { generateUsername } from '../../website/server/libs/auth/utils';
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
'auth.local.username': {$exists: false},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2018-04-01')}, // Initial coverage for users active within last 6 months
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
'auth',
|
||||
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
if (!user.auth.local.username) {
|
||||
const newName = generateUsername();
|
||||
dbUsers.update(
|
||||
{_id: user._id},
|
||||
{$set:
|
||||
{
|
||||
'auth.local.username': newName,
|
||||
'auth.local.lowerCaseUsername': newName,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
@@ -1,14 +1,14 @@
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
|
||||
const migrationName = 'mystery-items-201807.js'; // Update per month
|
||||
const migrationName = 'mystery-items-201808.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 = ['armor_mystery_201807', 'head_mystery_201807'];
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201809', 'head_mystery_201809'];
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
let migrationName = '20180801_takeThis.js'; // Update per month
|
||||
let migrationName = '20180904_takeThis.js'; // Update per month
|
||||
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
@@ -15,7 +15,7 @@ function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
migration: {$ne: migrationName},
|
||||
challenges: {$in: ['081f8912-3526-47d5-984f-f71bbeec77fc']}, // Update per month
|
||||
challenges: {$in: ['1044ec0c-4a85-48c5-9f36-d51c0c62c7d3']}, // Update per month
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
|
||||
4441
package-lock.json
generated
10
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.57.1",
|
||||
"version": "4.64.1",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -9,9 +9,9 @@
|
||||
"amazon-payments": "^0.2.7",
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"autoprefixer": "^8.5.0",
|
||||
"aws-sdk": "^2.239.1",
|
||||
"apn": "^2.2.0",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
@@ -26,7 +26,7 @@
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.6.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"bcrypt": "^3.0.0",
|
||||
"bcrypt": "^3.0.1",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^4.1.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.9",
|
||||
@@ -62,7 +62,7 @@
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.1.2",
|
||||
"mongoose": "^5.1.3",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^0.14.4",
|
||||
@@ -83,6 +83,8 @@
|
||||
"rimraf": "^2.4.3",
|
||||
"sass-loader": "^7.0.0",
|
||||
"shelljs": "^0.8.2",
|
||||
"short-uuid": "^3.0.0",
|
||||
"smartbanner.js": "^1.9.1",
|
||||
"stripe": "^5.9.0",
|
||||
"superagent": "^3.8.3",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
|
||||
@@ -65,6 +65,12 @@ describe('cron', () => {
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('calls analytics when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
describe('end of the month perks', () => {
|
||||
beforeEach(() => {
|
||||
user.purchased.plan.customerId = 'subscribedId';
|
||||
@@ -655,76 +661,6 @@ describe('cron', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('user is sleeping', () => {
|
||||
beforeEach(() => {
|
||||
user.preferences.sleep = true;
|
||||
});
|
||||
|
||||
it('calls analytics', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('clears user buffs', () => {
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
int: 1,
|
||||
per: 1,
|
||||
con: 1,
|
||||
stealth: 1,
|
||||
streaks: true,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
expect(user.stats.buffs.int).to.equal(0);
|
||||
expect(user.stats.buffs.per).to.equal(0);
|
||||
expect(user.stats.buffs.con).to.equal(0);
|
||||
expect(user.stats.buffs.stealth).to.equal(0);
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('resets all dailies without damaging user', () => {
|
||||
let daily = {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[0].completed = true;
|
||||
|
||||
let healthBefore = user.stats.hp;
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
expect(user.stats.hp).to.equal(healthBefore);
|
||||
});
|
||||
|
||||
it('sets isDue for daily', () => {
|
||||
let daily = {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[0].completed = true;
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.dailys[0].isDue).to.be.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('todos', () => {
|
||||
beforeEach(() => {
|
||||
let todo = {
|
||||
@@ -846,6 +782,15 @@ describe('cron', () => {
|
||||
expect(tasksByType.dailys[0].isDue).to.be.false;
|
||||
});
|
||||
|
||||
it('computes isDue when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].frequency = 'daily';
|
||||
tasksByType.dailys[0].everyX = 5;
|
||||
tasksByType.dailys[0].startDate = moment().toDate();
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(tasksByType.dailys[0].isDue).to.exist;
|
||||
});
|
||||
|
||||
it('computes nextDue', () => {
|
||||
tasksByType.dailys[0].frequency = 'daily';
|
||||
tasksByType.dailys[0].everyX = 5;
|
||||
@@ -865,6 +810,13 @@ describe('cron', () => {
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should set tasks completed to false when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should reset task checklist for completed dailys', () => {
|
||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||
tasksByType.dailys[0].completed = true;
|
||||
@@ -872,6 +824,14 @@ describe('cron', () => {
|
||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should reset task checklist for completed dailys when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||
tasksByType.dailys[0].completed = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should reset task checklist for dailys with scheduled misses', () => {
|
||||
daysMissed = 10;
|
||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||
@@ -884,12 +844,19 @@ describe('cron', () => {
|
||||
daysMissed = 1;
|
||||
let hpBefore = user.stats.hp;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
||||
});
|
||||
|
||||
it('should not do damage for missing a daily when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
let hpBefore = user.stats.hp;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.stats.hp).to.equal(hpBefore);
|
||||
});
|
||||
|
||||
it('should not do damage for missing a daily when CRON_SAFE_MODE is set', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||
@@ -930,7 +897,7 @@ describe('cron', () => {
|
||||
expect(hpDifferenceOfPartiallyIncompleteDaily).to.be.lessThan(hpDifferenceOfFullyIncompleteDaily);
|
||||
});
|
||||
|
||||
it('should decrement quest progress down for missing a daily', () => {
|
||||
it('should decrement quest.progress.down for missing a daily', () => {
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
@@ -939,6 +906,16 @@ describe('cron', () => {
|
||||
expect(progress.down).to.equal(-1);
|
||||
});
|
||||
|
||||
it('should not decrement quest.progress.down for missing a daily when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let progress = cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(progress.down).to.equal(0);
|
||||
});
|
||||
|
||||
it('should do damage for only yesterday\'s dailies', () => {
|
||||
daysMissed = 3;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
@@ -1017,7 +994,7 @@ describe('cron', () => {
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset habit counters even if user is resting in the Inn', () => {
|
||||
it('should reset habit counters even if user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
@@ -1278,7 +1255,23 @@ describe('cron', () => {
|
||||
expect(user.achievements.perfect).to.equal(0);
|
||||
});
|
||||
|
||||
it('increments user buffs if all (at least 1) due dailies were completed', () => {
|
||||
it('gives perfect day buff if all (at least 1) due dailies were completed', () => {
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
|
||||
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
|
||||
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
|
||||
});
|
||||
|
||||
it('gives perfect day buff if all (at least 1) due dailies were completed when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
@@ -1317,6 +1310,31 @@ describe('cron', () => {
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('clears buffs if user does not have a perfect day (no due dailys) when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).add({days: 1});
|
||||
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
int: 1,
|
||||
per: 1,
|
||||
con: 1,
|
||||
stealth: 0,
|
||||
streaks: true,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
expect(user.stats.buffs.int).to.equal(0);
|
||||
expect(user.stats.buffs.per).to.equal(0);
|
||||
expect(user.stats.buffs.con).to.equal(0);
|
||||
expect(user.stats.buffs.stealth).to.equal(0);
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('clears buffs if user does not have a perfect day (at least one due daily not completed)', () => {
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = false;
|
||||
@@ -1341,7 +1359,50 @@ describe('cron', () => {
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('still grants a perfect day when CRON_SAFE_MODE is set', () => {
|
||||
it('clears buffs if user does not have a perfect day (at least one due daily not completed) when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = false;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
int: 1,
|
||||
per: 1,
|
||||
con: 1,
|
||||
stealth: 0,
|
||||
streaks: true,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
expect(user.stats.buffs.int).to.equal(0);
|
||||
expect(user.stats.buffs.per).to.equal(0);
|
||||
expect(user.stats.buffs.con).to.equal(0);
|
||||
expect(user.stats.buffs.stealth).to.equal(0);
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('always grants a perfect day buff when CRON_SAFE_MODE is set', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = false;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
cronOverride({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
|
||||
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
|
||||
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
|
||||
});
|
||||
|
||||
it('always grants a perfect day buff when CRON_SAFE_MODE is set when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||
daysMissed = 1;
|
||||
@@ -1373,6 +1434,20 @@ describe('cron', () => {
|
||||
common.statsComputed.restore();
|
||||
});
|
||||
|
||||
it('should not add mp to user when user is sleeping', () => {
|
||||
const statsComputedRes = common.statsComputed(user);
|
||||
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
||||
|
||||
user.preferences.sleep = true;
|
||||
let mpBefore = user.stats.mp;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, {maxMP: 100}));
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.stats.mp).to.equal(mpBefore);
|
||||
|
||||
common.statsComputed.restore();
|
||||
});
|
||||
|
||||
it('set user\'s mp to statsComputed.maxMP when user.stats.mp is greater', () => {
|
||||
const statsComputedRes = common.statsComputed(user);
|
||||
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
||||
@@ -1514,27 +1589,6 @@ describe('cron', () => {
|
||||
flagCount: 0,
|
||||
};
|
||||
});
|
||||
|
||||
xit('does not clear pms under 200', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.inbox.messages[lastMessageId]).to.exist;
|
||||
});
|
||||
|
||||
xit('clears pms over 200', () => {
|
||||
let messageId = common.uuid();
|
||||
user.inbox.messages[messageId] = {
|
||||
id: messageId,
|
||||
text: `test ${messageId}`,
|
||||
timestamp: Number(new Date()),
|
||||
likes: {},
|
||||
flags: {},
|
||||
flagCount: 0,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.inbox.messages[messageId]).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('login incentives', () => {
|
||||
@@ -1568,7 +1622,7 @@ describe('cron', () => {
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
});
|
||||
|
||||
it('increments loginIncentives by 1 even if user has Dailies paused', () => {
|
||||
it('increments loginIncentives by 1 even if user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
|
||||
@@ -107,6 +107,25 @@ describe('Password Utilities', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('defaults to SHA1 encryption if salt is provided', async () => {
|
||||
let textPassword = 'mySecretPassword';
|
||||
let salt = sha1MakeSalt();
|
||||
let hashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||
|
||||
let user = {
|
||||
auth: {
|
||||
local: {
|
||||
hashed_password: hashedPassword,
|
||||
salt,
|
||||
passwordHashMethod: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let isValidPassword = await compare(user, textPassword);
|
||||
expect(isValidPassword).to.eql(true);
|
||||
});
|
||||
|
||||
it('throws an error if an invalid hashing method is used', async () => {
|
||||
try {
|
||||
await compare({
|
||||
|
||||
@@ -178,4 +178,12 @@ describe('taskManager', () => {
|
||||
|
||||
expect(order).to.eql(['task-id-2', 'task-id-1']);
|
||||
});
|
||||
|
||||
it('moves tasks to a specified position out of length', async () => {
|
||||
let order = ['task-id-1'];
|
||||
|
||||
moveTask(order, 'task-id-2', 2);
|
||||
|
||||
expect(order).to.eql(['task-id-1', 'task-id-2']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ import { TAVERN_ID } from '../../../../website/common/script/';
|
||||
import shared from '../../../../website/common';
|
||||
|
||||
describe('Group Model', () => {
|
||||
let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember;
|
||||
let party, questLeader, participatingMember, sleepingParticipatingMember, nonParticipatingMember, undecidedMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox.stub(email, 'sendTxn');
|
||||
@@ -48,6 +48,11 @@ describe('Group Model', () => {
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Participating Member' },
|
||||
});
|
||||
sleepingParticipatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Sleeping Participating Member' },
|
||||
preferences: { sleep: true },
|
||||
});
|
||||
nonParticipatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Non-Participating Member' },
|
||||
@@ -61,6 +66,7 @@ describe('Group Model', () => {
|
||||
party.save(),
|
||||
questLeader.save(),
|
||||
participatingMember.save(),
|
||||
sleepingParticipatingMember.save(),
|
||||
nonParticipatingMember.save(),
|
||||
undecidedMember.save(),
|
||||
]);
|
||||
@@ -80,6 +86,7 @@ describe('Group Model', () => {
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
@@ -175,6 +182,34 @@ describe('Group Model', () => {
|
||||
expect(party._processBossQuest).to.not.be.called;
|
||||
expect(Group.prototype._processCollectionQuest).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('does not call _processBossQuest when user is resting in the inn', async () => {
|
||||
party.quest.key = 'whale';
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(sleepingParticipatingMember, progress);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(party._processBossQuest).to.not.be.called;
|
||||
expect(party._processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
|
||||
it('does not call _processCollectionQuest when user is resting in the inn', async () => {
|
||||
party.quest.key = 'evilsanta2';
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(sleepingParticipatingMember, progress);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(party._processBossQuest).to.not.be.called;
|
||||
expect(party._processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
context('Boss Quests', () => {
|
||||
@@ -216,17 +251,20 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
updatedNonParticipatingMember,
|
||||
updatedUndecidedMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
User.findById(nonParticipatingMember._id),
|
||||
User.findById(undecidedMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.stats.hp).to.eql(42.5);
|
||||
expect(updatedParticipatingMember.stats.hp).to.eql(42.5);
|
||||
expect(updatedSleepingParticipatingMember.stats.hp).to.eql(42.5);
|
||||
expect(updatedNonParticipatingMember.stats.hp).to.eql(50);
|
||||
expect(updatedUndecidedMember.stats.hp).to.eql(50);
|
||||
});
|
||||
@@ -236,6 +274,7 @@ describe('Group Model', () => {
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
@@ -248,17 +287,20 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
updatedNonParticipatingMember,
|
||||
updatedUndecidedMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
User.findById(nonParticipatingMember._id),
|
||||
User.findById(undecidedMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.stats.hp).to.eql(42.5);
|
||||
expect(updatedParticipatingMember.stats.hp).to.eql(42.5);
|
||||
expect(updatedSleepingParticipatingMember.stats.hp).to.eql(42.5);
|
||||
expect(updatedNonParticipatingMember.stats.hp).to.eql(50);
|
||||
expect(updatedUndecidedMember.stats.hp).to.eql(50);
|
||||
});
|
||||
@@ -497,9 +539,11 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.achievements.quests[party.quest.key]).to.eql(1);
|
||||
@@ -508,6 +552,9 @@ describe('Group Model', () => {
|
||||
expect(updatedParticipatingMember.achievements.quests[party.quest.key]).to.eql(1);
|
||||
expect(updatedParticipatingMember.stats.exp).to.be.greaterThan(0);
|
||||
expect(updatedParticipatingMember.stats.gp).to.be.greaterThan(0);
|
||||
expect(updatedSleepingParticipatingMember.achievements.quests[party.quest.key]).to.eql(1);
|
||||
expect(updatedSleepingParticipatingMember.stats.exp).to.be.greaterThan(0);
|
||||
expect(updatedSleepingParticipatingMember.stats.gp).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -647,6 +694,7 @@ describe('Group Model', () => {
|
||||
it('returns an array of members whose quest status set to true', () => {
|
||||
party.quest.members = {
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[questLeader._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
@@ -654,6 +702,7 @@ describe('Group Model', () => {
|
||||
|
||||
expect(party.getParticipatingQuestMembers()).to.eql([
|
||||
participatingMember._id,
|
||||
sleepingParticipatingMember._id,
|
||||
questLeader._id,
|
||||
]);
|
||||
});
|
||||
@@ -756,11 +805,12 @@ describe('Group Model', () => {
|
||||
it('removes user from group quest', async () => {
|
||||
party.quest.members = {
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[questLeader._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
party.memberCount = 4;
|
||||
party.memberCount = 5;
|
||||
await party.save();
|
||||
|
||||
await party.leave(participatingMember);
|
||||
@@ -768,6 +818,7 @@ describe('Group Model', () => {
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party.quest.members).to.eql({
|
||||
[questLeader._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
});
|
||||
@@ -775,6 +826,7 @@ describe('Group Model', () => {
|
||||
|
||||
it('deletes a private party when the last member leaves', async () => {
|
||||
await party.leave(participatingMember);
|
||||
await party.leave(sleepingParticipatingMember);
|
||||
await party.leave(questLeader);
|
||||
await party.leave(nonParticipatingMember);
|
||||
await party.leave(undecidedMember);
|
||||
@@ -846,6 +898,7 @@ describe('Group Model', () => {
|
||||
party.privacy = 'public';
|
||||
|
||||
await party.leave(participatingMember);
|
||||
await party.leave(sleepingParticipatingMember);
|
||||
await party.leave(questLeader);
|
||||
await party.leave(nonParticipatingMember);
|
||||
await party.leave(undecidedMember);
|
||||
@@ -967,32 +1020,6 @@ describe('Group Model', () => {
|
||||
expect(chat.user).to.not.exist;
|
||||
});
|
||||
|
||||
it('cuts down chat to 200 messages', () => {
|
||||
for (let i = 0; i < 220; i++) {
|
||||
party.chat.push({ text: 'a message' });
|
||||
}
|
||||
|
||||
expect(party.chat).to.have.a.lengthOf(220);
|
||||
|
||||
party.sendChat('message');
|
||||
|
||||
expect(party.chat).to.have.a.lengthOf(200);
|
||||
});
|
||||
|
||||
it('cuts down chat to 400 messages when group is subcribed', () => {
|
||||
party.purchased.plan.customerId = 'test-customer-id';
|
||||
|
||||
for (let i = 0; i < 420; i++) {
|
||||
party.chat.push({ text: 'a message' });
|
||||
}
|
||||
|
||||
expect(party.chat).to.have.a.lengthOf(420);
|
||||
|
||||
party.sendChat('message');
|
||||
|
||||
expect(party.chat).to.have.a.lengthOf(400);
|
||||
});
|
||||
|
||||
it('updates users about new messages in party', () => {
|
||||
party.sendChat('message');
|
||||
|
||||
@@ -1074,6 +1101,7 @@ describe('Group Model', () => {
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
@@ -1130,6 +1158,7 @@ describe('Group Model', () => {
|
||||
let expectedQuestMembers = {};
|
||||
expectedQuestMembers[questLeader._id] = true;
|
||||
expectedQuestMembers[participatingMember._id] = true;
|
||||
expectedQuestMembers[sleepingParticipatingMember._id] = true;
|
||||
|
||||
expect(party.quest.members).to.eql(expectedQuestMembers);
|
||||
});
|
||||
@@ -1148,12 +1177,18 @@ describe('Group Model', () => {
|
||||
|
||||
questLeader = await User.findById(questLeader._id);
|
||||
participatingMember = await User.findById(participatingMember._id);
|
||||
sleepingParticipatingMember = await User.findById(sleepingParticipatingMember._id);
|
||||
|
||||
expect(participatingMember.party.quest.key).to.eql('whale');
|
||||
expect(participatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(participatingMember.party.quest.completed).to.eql(null);
|
||||
|
||||
expect(sleepingParticipatingMember.party.quest.key).to.eql('whale');
|
||||
expect(sleepingParticipatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(sleepingParticipatingMember.party.quest.completed).to.eql(null);
|
||||
|
||||
expect(questLeader.party.quest.key).to.eql('whale');
|
||||
expect(questLeader.party.quest.progress.down).to.eql(0);
|
||||
expect(questLeader.party.quest.progress.collectedItems).to.eql(0);
|
||||
@@ -1172,9 +1207,11 @@ describe('Group Model', () => {
|
||||
|
||||
it('sends email to participating members that quest has started', async () => {
|
||||
participatingMember.preferences.emailNotifications.questStarted = true;
|
||||
sleepingParticipatingMember.preferences.emailNotifications.questStarted = true;
|
||||
questLeader.preferences.emailNotifications.questStarted = true;
|
||||
await Promise.all([
|
||||
participatingMember.save(),
|
||||
sleepingParticipatingMember.save(),
|
||||
questLeader.save(),
|
||||
]);
|
||||
|
||||
@@ -1187,8 +1224,9 @@ describe('Group Model', () => {
|
||||
let memberIds = _.map(email.sendTxn.args[0][0], '_id');
|
||||
let typeOfEmail = email.sendTxn.args[0][1];
|
||||
|
||||
expect(memberIds).to.have.a.lengthOf(2);
|
||||
expect(memberIds).to.have.a.lengthOf(3);
|
||||
expect(memberIds).to.include(participatingMember._id);
|
||||
expect(memberIds).to.include(sleepingParticipatingMember._id);
|
||||
expect(memberIds).to.include(questLeader._id);
|
||||
expect(typeOfEmail).to.eql('quest-started');
|
||||
});
|
||||
@@ -1202,6 +1240,13 @@ describe('Group Model', () => {
|
||||
questStarted: true,
|
||||
},
|
||||
}];
|
||||
sleepingParticipatingMember.webhooks = [{
|
||||
type: 'questActivity',
|
||||
url: 'http://someurl.com',
|
||||
options: {
|
||||
questStarted: true,
|
||||
},
|
||||
}];
|
||||
questLeader.webhooks = [{
|
||||
type: 'questActivity',
|
||||
url: 'http://someurl.com',
|
||||
@@ -1210,13 +1255,13 @@ describe('Group Model', () => {
|
||||
},
|
||||
}];
|
||||
|
||||
await Promise.all([participatingMember.save(), questLeader.save()]);
|
||||
await Promise.all([participatingMember.save(), sleepingParticipatingMember.save(), questLeader.save()]);
|
||||
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
expect(questActivityWebhook.send).to.be.calledTwice; // for 2 participating members
|
||||
expect(questActivityWebhook.send).to.be.calledThrice; // for 3 participating members
|
||||
|
||||
let args = questActivityWebhook.send.args[0];
|
||||
let webhooks = args[0].webhooks;
|
||||
@@ -1226,6 +1271,8 @@ describe('Group Model', () => {
|
||||
expect(webhooks).to.have.a.lengthOf(1);
|
||||
if (webhookOwner === questLeader._id) {
|
||||
expect(webhooks[0].id).to.eql(questLeader.webhooks[0].id);
|
||||
} else if (webhookOwner === sleepingParticipatingMember._id) {
|
||||
expect(webhooks[0].id).to.eql(sleepingParticipatingMember.webhooks[0].id);
|
||||
} else {
|
||||
expect(webhooks[0].id).to.eql(participatingMember.webhooks[0].id);
|
||||
}
|
||||
@@ -1236,9 +1283,11 @@ describe('Group Model', () => {
|
||||
|
||||
it('sends email only to members who have not opted out', async () => {
|
||||
participatingMember.preferences.emailNotifications.questStarted = false;
|
||||
sleepingParticipatingMember.preferences.emailNotifications.questStarted = false;
|
||||
questLeader.preferences.emailNotifications.questStarted = true;
|
||||
await Promise.all([
|
||||
participatingMember.save(),
|
||||
sleepingParticipatingMember.save(),
|
||||
questLeader.save(),
|
||||
]);
|
||||
|
||||
@@ -1252,14 +1301,17 @@ describe('Group Model', () => {
|
||||
|
||||
expect(memberIds).to.have.a.lengthOf(1);
|
||||
expect(memberIds).to.not.include(participatingMember._id);
|
||||
expect(memberIds).to.not.include(sleepingParticipatingMember._id);
|
||||
expect(memberIds).to.include(questLeader._id);
|
||||
});
|
||||
|
||||
it('does not send email to initiating member', async () => {
|
||||
participatingMember.preferences.emailNotifications.questStarted = true;
|
||||
sleepingParticipatingMember.preferences.emailNotifications.questStarted = true;
|
||||
questLeader.preferences.emailNotifications.questStarted = true;
|
||||
await Promise.all([
|
||||
participatingMember.save(),
|
||||
sleepingParticipatingMember.save(),
|
||||
questLeader.save(),
|
||||
]);
|
||||
|
||||
@@ -1271,8 +1323,9 @@ describe('Group Model', () => {
|
||||
|
||||
let memberIds = _.map(email.sendTxn.args[0][0], '_id');
|
||||
|
||||
expect(memberIds).to.have.a.lengthOf(1);
|
||||
expect(memberIds).to.have.a.lengthOf(2);
|
||||
expect(memberIds).to.not.include(participatingMember._id);
|
||||
expect(memberIds).to.include(sleepingParticipatingMember._id);
|
||||
expect(memberIds).to.include(questLeader._id);
|
||||
});
|
||||
|
||||
@@ -1281,7 +1334,7 @@ describe('Group Model', () => {
|
||||
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
let members = [questLeader._id, participatingMember._id];
|
||||
let members = [questLeader._id, participatingMember._id, sleepingParticipatingMember._id];
|
||||
|
||||
expect(User.update).to.be.calledWith(
|
||||
{ _id: { $in: members } },
|
||||
@@ -1346,6 +1399,7 @@ describe('Group Model', () => {
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
@@ -1368,7 +1422,7 @@ describe('Group Model', () => {
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledTwice;
|
||||
expect(User.update).to.be.calledThrice;
|
||||
});
|
||||
|
||||
it('stops retrying when a successful update has occurred', async () => {
|
||||
@@ -1378,7 +1432,7 @@ describe('Group Model', () => {
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledThrice;
|
||||
expect(User.update.callCount).to.equal(4);
|
||||
});
|
||||
|
||||
it('retries failed updates at most five times per user', async () => {
|
||||
@@ -1386,7 +1440,7 @@ describe('Group Model', () => {
|
||||
|
||||
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
||||
|
||||
expect(User.update.callCount).to.eql(10);
|
||||
expect(User.update.callCount).to.eql(15); // for 3 users
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1396,17 +1450,19 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.achievements.quests[quest.key]).to.eql(1);
|
||||
expect(updatedParticipatingMember.achievements.quests[quest.key]).to.eql(1);
|
||||
expect(updatedSleepingParticipatingMember.achievements.quests[quest.key]).to.eql(1);
|
||||
});
|
||||
|
||||
// Disable test, it fails on TravisCI, but only there
|
||||
xit('gives out super awesome Masterclasser achievement to the deserving', async () => {
|
||||
it('gives out super awesome Masterclasser achievement to the deserving', async () => {
|
||||
quest = questScrolls.lostMasterclasser4;
|
||||
party.quest.key = quest.key;
|
||||
|
||||
@@ -1433,17 +1489,19 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id).exec(),
|
||||
User.findById(participatingMember._id).exec(),
|
||||
User.findById(sleepingParticipatingMember._id).exec(),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.achievements.lostMasterclasser).to.eql(true);
|
||||
expect(updatedParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
});
|
||||
|
||||
// Disable test, it fails on TravisCI, but only there
|
||||
xit('gives out super awesome Masterclasser achievement when quests done out of order', async () => {
|
||||
it('gives out super awesome Masterclasser achievement when quests done out of order', async () => {
|
||||
quest = questScrolls.lostMasterclasser1;
|
||||
party.quest.key = quest.key;
|
||||
|
||||
@@ -1470,13 +1528,16 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id).exec(),
|
||||
User.findById(participatingMember._id).exec(),
|
||||
User.findById(sleepingParticipatingMember._id).exec(),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.achievements.lostMasterclasser).to.eql(true);
|
||||
expect(updatedParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
});
|
||||
|
||||
it('gives xp and gold', async () => {
|
||||
@@ -1485,15 +1546,19 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.stats.exp).to.eql(quest.drop.exp);
|
||||
expect(updatedLeader.stats.gp).to.eql(quest.drop.gp);
|
||||
expect(updatedParticipatingMember.stats.exp).to.eql(quest.drop.exp);
|
||||
expect(updatedParticipatingMember.stats.gp).to.eql(quest.drop.gp);
|
||||
expect(updatedSleepingParticipatingMember.stats.exp).to.eql(quest.drop.exp);
|
||||
expect(updatedSleepingParticipatingMember.stats.gp).to.eql(quest.drop.gp);
|
||||
});
|
||||
|
||||
context('drops', () => {
|
||||
@@ -1593,13 +1658,16 @@ describe('Group Model', () => {
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledTwice;
|
||||
expect(User.update).to.be.calledThrice;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: questLeader._id,
|
||||
});
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: participatingMember._id,
|
||||
});
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: sleepingParticipatingMember._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('sets user quest object to a clean state', async () => {
|
||||
@@ -1632,7 +1700,7 @@ describe('Group Model', () => {
|
||||
},
|
||||
}];
|
||||
|
||||
await Promise.all([participatingMember.save(), questLeader.save()]);
|
||||
await Promise.all([participatingMember.save(), sleepingParticipatingMember.save(), questLeader.save()]);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
|
||||
@@ -63,45 +63,48 @@ describe('GET /challenges/:challengeId', () => {
|
||||
|
||||
context('private guild', () => {
|
||||
let groupLeader;
|
||||
let challengeLeader;
|
||||
let group;
|
||||
let challenge;
|
||||
let members;
|
||||
let user;
|
||||
let nonMember;
|
||||
let otherMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
nonMember = await generateUser();
|
||||
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: {type: 'guild', privacy: 'private'},
|
||||
members: 1,
|
||||
members: 2,
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
members = populatedGroup.members;
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await members[0].post(`/challenges/${challenge._id}/join`);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
challengeLeader = members[0];
|
||||
otherMember = members[1];
|
||||
|
||||
challenge = await generateChallenge(challengeLeader, group);
|
||||
});
|
||||
|
||||
it('fails if user doesn\'t have access to the challenge', async () => {
|
||||
await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
|
||||
it('fails if user isn\'t in the guild and isn\'t challenge leader', async () => {
|
||||
await expect(nonMember.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('challengeNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return challenge data', async () => {
|
||||
let chal = await members[0].get(`/challenges/${challenge._id}`);
|
||||
it('returns challenge data for any user in the guild', async () => {
|
||||
let chal = await otherMember.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader._id,
|
||||
profile: {name: groupLeader.profile.name},
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
});
|
||||
expect(chal.group).to.eql({
|
||||
_id: group._id,
|
||||
@@ -114,53 +117,72 @@ describe('GET /challenges/:challengeId', () => {
|
||||
leader: groupLeader.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns challenge data if challenge leader isn\'t in the guild or challenge', async () => {
|
||||
await challengeLeader.post(`/groups/${group._id}/leave`);
|
||||
await challengeLeader.sync();
|
||||
expect(challengeLeader.guilds).to.be.empty; // check that leaving worked
|
||||
|
||||
let chal = await challengeLeader.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('party', () => {
|
||||
let groupLeader;
|
||||
let challengeLeader;
|
||||
let group;
|
||||
let challenge;
|
||||
let members;
|
||||
let user;
|
||||
let nonMember;
|
||||
let otherMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
nonMember = await generateUser();
|
||||
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: {type: 'party'},
|
||||
members: 1,
|
||||
groupDetails: {type: 'party', privacy: 'private'},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
members = populatedGroup.members;
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await members[0].post(`/challenges/${challenge._id}/join`);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
challengeLeader = members[0];
|
||||
otherMember = members[1];
|
||||
|
||||
challenge = await generateChallenge(challengeLeader, group);
|
||||
});
|
||||
|
||||
it('fails if user doesn\'t have access to the challenge', async () => {
|
||||
await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
|
||||
it('fails if user isn\'t in the party and isn\'t challenge leader', async () => {
|
||||
await expect(nonMember.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('challengeNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return challenge data', async () => {
|
||||
let chal = await members[0].get(`/challenges/${challenge._id}`);
|
||||
it('returns challenge data for any user in the party', async () => {
|
||||
let chal = await otherMember.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader.id,
|
||||
profile: {name: groupLeader.profile.name},
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
});
|
||||
expect(chal.group).to.eql({
|
||||
_id: group._id,
|
||||
id: group.id,
|
||||
id: group._id,
|
||||
categories: [],
|
||||
name: group.name,
|
||||
summary: group.name,
|
||||
@@ -169,5 +191,21 @@ describe('GET /challenges/:challengeId', () => {
|
||||
leader: groupLeader.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns challenge data if challenge leader isn\'t in the party or challenge', async () => {
|
||||
await challengeLeader.post('/groups/party/leave');
|
||||
await challengeLeader.sync();
|
||||
expect(challengeLeader.party._id).to.be.undefined; // check that leaving worked
|
||||
|
||||
let chal = await challengeLeader.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
createAndPopulateGroup,
|
||||
generateChallenge,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
@@ -10,7 +11,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({ balance: 1 });
|
||||
});
|
||||
|
||||
it('validates optional req.query.lastId to be an UUID', async () => {
|
||||
@@ -21,7 +22,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if challenge doesn\'t exists', async () => {
|
||||
it('fails if challenge doesn\'t exist', async () => {
|
||||
await expect(user.get(`/challenges/${generateUUID()}/members`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
@@ -29,8 +30,8 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if user doesn\'t have access to the challenge', async () => {
|
||||
let group = await generateGroup(user);
|
||||
it('fails if user isn\'t in the private group and isn\'t challenge leader', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', privacy: 'private'});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
let anotherUser = await generateUser();
|
||||
|
||||
@@ -41,6 +42,27 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('works if user isn\'t in the private group but is challenge leader', async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: {type: 'party', privacy: 'private'},
|
||||
members: 1,
|
||||
});
|
||||
let groupLeader = populatedGroup.groupLeader;
|
||||
let challengeLeader = populatedGroup.members[0];
|
||||
let challenge = await generateChallenge(challengeLeader, populatedGroup.group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
await challengeLeader.post('/groups/party/leave');
|
||||
await challengeLeader.sync();
|
||||
expect(challengeLeader.party._id).to.be.undefined; // check that leaving worked
|
||||
|
||||
let res = await challengeLeader.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader._id,
|
||||
profile: {name: groupLeader.profile.name},
|
||||
});
|
||||
});
|
||||
|
||||
it('works with challenges belonging to public guild', async () => {
|
||||
let leader = await generateUser({balance: 4});
|
||||
let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
|
||||
|
||||
@@ -94,16 +94,6 @@ describe('POST /challenges', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when non-leader member creates a challenge in leaderOnly group', async () => {
|
||||
await expect(groupMember.post('/challenges', {
|
||||
group: group._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderChal'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows non-leader member to create a challenge', async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('POST /challenges/:challengeId/join', () => {
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('returns an error when user doesn\'t have permissions to access the challenge', async () => {
|
||||
it('returns an error when user isn\'t in the private group and isn\'t challenge leader', async () => {
|
||||
let unauthorizedUser = await generateUser();
|
||||
|
||||
await expect(unauthorizedUser.post(`/challenges/${challenge._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
@@ -56,6 +56,16 @@ describe('POST /challenges/:challengeId/join', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds when user isn\'t in the private group but is challenge leader', async () => {
|
||||
await groupLeader.post(`/challenges/${challenge._id}/leave`);
|
||||
await groupLeader.post(`/groups/${group._id}/leave`);
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.guilds).to.be.empty; // check that leaving worked
|
||||
|
||||
let res = await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
expect(res.name).to.equal(challenge.name);
|
||||
});
|
||||
|
||||
it('returns challenge data', async () => {
|
||||
let res = await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /inbox/messages', () => {
|
||||
let user;
|
||||
@@ -22,17 +22,27 @@ describe('GET /inbox/messages', () => {
|
||||
message: 'third',
|
||||
});
|
||||
|
||||
// message to yourself
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
});
|
||||
|
||||
it('returns the user inbox messages as an array of ordered messages (from most to least recent)', async () => {
|
||||
const messages = await user.get('/inbox/messages');
|
||||
|
||||
expect(messages.length).to.equal(3);
|
||||
expect(messages.length).to.equal(Object.keys(user.inbox.messages).length);
|
||||
expect(messages.length).to.equal(4);
|
||||
|
||||
expect(messages[0].text).to.equal('third');
|
||||
expect(messages[1].text).to.equal('second');
|
||||
expect(messages[2].text).to.equal('first');
|
||||
// message to yourself
|
||||
expect(messages[0].text).to.equal('fourth');
|
||||
expect(messages[0].sent).to.equal(false);
|
||||
expect(messages[0].uuid).to.equal(user._id);
|
||||
|
||||
expect(messages[1].text).to.equal('third');
|
||||
expect(messages[2].text).to.equal('second');
|
||||
expect(messages[3].text).to.equal('first');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -100,7 +100,7 @@ describe('POST /members/send-private-message', () => {
|
||||
let receiver = await generateUser();
|
||||
// const initialNotifications = receiver.notifications.length;
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
const response = await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
@@ -116,6 +116,9 @@ describe('POST /members/send-private-message', () => {
|
||||
return message.uuid === receiver._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(response.message.text).to.deep.equal(sendersMessageInSendersInbox.text);
|
||||
expect(response.message.uuid).to.deep.equal(sendersMessageInSendersInbox.uuid);
|
||||
|
||||
// @TODO waiting for mobile support
|
||||
// expect(updatedReceiver.notifications.length).to.equal(initialNotifications + 1);
|
||||
// const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
describe('payments - stripe - #subscribeCancel', () => {
|
||||
let endpoint = '/stripe/subscribe/cancel?redirect=none';
|
||||
let endpoint = '/stripe/subscribe/cancel?noRedirect=true';
|
||||
let user, group, stripeCancelSubscriptionStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
generateUser,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
import { chatModel as Chat } from '../../../../../website/server/models/message';
|
||||
|
||||
describe('POST /groups/:groupId/quests/accept', () => {
|
||||
const PET_QUEST = 'whale';
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
generateUser,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
import { chatModel as Chat } from '../../../../../website/server/models/message';
|
||||
|
||||
describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
const PET_QUEST = 'whale';
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
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 { chatModel as Chat } from '../../../../../website/server/models/message';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { model as Chat } from '../../../../../website/server/models/chat';
|
||||
import { chatModel as Chat } from '../../../../../website/server/models/message';
|
||||
|
||||
describe('POST /groups/:groupId/quests/reject', () => {
|
||||
let questingGroup;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
createAndPopulateGroup, translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { find } from 'lodash';
|
||||
import {find} from 'lodash';
|
||||
|
||||
describe('PUT /tasks/:id', () => {
|
||||
let user, guild, member, member2, task;
|
||||
@@ -38,16 +38,64 @@ describe('PUT /tasks/:id', () => {
|
||||
|
||||
it('updates a group task', async () => {
|
||||
let savedHabit = await user.put(`/tasks/${task._id}`, {
|
||||
text: 'some new text',
|
||||
up: false,
|
||||
down: false,
|
||||
notes: 'some new notes',
|
||||
});
|
||||
|
||||
expect(savedHabit.text).to.eql('some new text');
|
||||
expect(savedHabit.notes).to.eql('some new notes');
|
||||
expect(savedHabit.up).to.eql(false);
|
||||
expect(savedHabit.down).to.eql(false);
|
||||
});
|
||||
|
||||
it('updates a group task - approval is required', async () => {
|
||||
// allow to manage
|
||||
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||
managerId: member._id,
|
||||
});
|
||||
|
||||
// change the todo
|
||||
task = await member.put(`/tasks/${task._id}`, {
|
||||
text: 'new text!',
|
||||
requiresApproval: true,
|
||||
});
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, (memberTask) => memberTask.group.taskId === task._id);
|
||||
|
||||
// score up to trigger approval
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
});
|
||||
|
||||
it('updates a group task with checklist', async () => {
|
||||
// add a new todo
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'todo',
|
||||
type: 'todo',
|
||||
checklist: [
|
||||
{
|
||||
text: 'checklist 1',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
// change the checklist text
|
||||
task = await user.put(`/tasks/${task._id}`, {
|
||||
checklist: [
|
||||
{
|
||||
id: task.checklist[0].id,
|
||||
text: 'checklist 1 - edit',
|
||||
},
|
||||
{
|
||||
text: 'checklist 2 - edit',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(task.checklist.length).to.eql(2);
|
||||
});
|
||||
|
||||
it('updates the linked tasks', async () => {
|
||||
|
||||
@@ -3,25 +3,41 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('DELETE user message', () => {
|
||||
let user;
|
||||
let user, messagesId, otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({ inbox: { messages: { first: 'message', second: 'message' } } });
|
||||
expect(user.inbox.messages.first).to.eql('message');
|
||||
expect(user.inbox.messages.second).to.eql('message');
|
||||
before(async () => {
|
||||
[user, otherUser] = await Promise.all([generateUser(), generateUser()]);
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: otherUser.id,
|
||||
message: 'first',
|
||||
});
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: otherUser.id,
|
||||
message: 'second',
|
||||
});
|
||||
|
||||
let userRes = await user.get('/user');
|
||||
|
||||
messagesId = Object.keys(userRes.inbox.messages);
|
||||
expect(messagesId.length).to.eql(2);
|
||||
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('first');
|
||||
expect(userRes.inbox.messages[messagesId[1]].text).to.eql('second');
|
||||
});
|
||||
|
||||
it('one message', async () => {
|
||||
let result = await user.del('/user/messages/first');
|
||||
await user.sync();
|
||||
expect(result).to.eql({ second: 'message' });
|
||||
expect(user.inbox.messages).to.eql({ second: 'message' });
|
||||
let result = await user.del(`/user/messages/${messagesId[0]}`);
|
||||
messagesId = Object.keys(result);
|
||||
expect(messagesId.length).to.eql(1);
|
||||
|
||||
let userRes = await user.get('/user');
|
||||
expect(Object.keys(userRes.inbox.messages).length).to.eql(1);
|
||||
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('second');
|
||||
});
|
||||
|
||||
it('clear all', async () => {
|
||||
let result = await user.del('/user/messages');
|
||||
await user.sync();
|
||||
expect(user.inbox.messages).to.eql({});
|
||||
let userRes = await user.get('/user');
|
||||
expect(userRes.inbox.messages).to.eql({});
|
||||
expect(result).to.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,6 +58,21 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if use Healing Light spell with full health', async () => {
|
||||
await user.update({
|
||||
'stats.class': 'healer',
|
||||
'stats.lvl': 11,
|
||||
'stats.hp': 50,
|
||||
'stats.mp': 200,
|
||||
});
|
||||
await expect(user.post('/user/class/cast/heal'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageHealthAlreadyMax'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell.lvl > user.level', async () => {
|
||||
await user.update({'stats.mp': 200, 'stats.class': 'wizard'});
|
||||
await expect(user.post('/user/class/cast/earth'))
|
||||
|
||||
@@ -50,11 +50,24 @@ describe('POST /user/push-devices', () => {
|
||||
});
|
||||
|
||||
it('adds a push device to the user', async () => {
|
||||
let response = await user.post('/user/push-devices', {type, regId});
|
||||
const response = await user.post('/user/push-devices', {type, regId});
|
||||
await user.sync();
|
||||
|
||||
expect(response.message).to.equal(t('pushDeviceAdded'));
|
||||
expect(response.data[0].type).to.equal(type);
|
||||
expect(response.data[0].regId).to.equal(regId);
|
||||
expect(user.pushDevices[0].type).to.equal(type);
|
||||
expect(user.pushDevices[0].regId).to.equal(regId);
|
||||
});
|
||||
|
||||
it('removes a push device to the user', async () => {
|
||||
await user.post('/user/push-devices', {type, regId});
|
||||
|
||||
const response = await user.del(`/user/push-devices/${regId}`);
|
||||
await user.sync();
|
||||
|
||||
expect(response.message).to.equal(t('pushDeviceRemoved'));
|
||||
expect(response.data[0]).to.not.exist;
|
||||
expect(user.pushDevices[0]).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('PUT /user', () => {
|
||||
});
|
||||
|
||||
|
||||
it('profile.name cannot be an empty string or null', async () => {
|
||||
it('validates profile.name', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': ' ', // string should be trimmed
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
@@ -76,7 +76,23 @@ describe('PUT /user', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': 'this is a very long display name that will not be allowed due to length',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('displaynameIssueLength'),
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': 'TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('displaynameIssueSlur'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,23 @@ describe('POST /user/auth/local/register', () => {
|
||||
expect(user.newUser).to.eql(true);
|
||||
});
|
||||
|
||||
it('registers a new user and sets verifiedUsername to true', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user._id).to.exist;
|
||||
expect(user.apiToken).to.exist;
|
||||
expect(user.flags.verifiedUsername).to.eql(true);
|
||||
});
|
||||
|
||||
xit('remove spaces from username', async () => {
|
||||
// TODO can probably delete this test now
|
||||
let username = ' usernamewithspaces ';
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('POST /user/auth/social', () => {
|
||||
});
|
||||
|
||||
it('registers a new user', async () => {
|
||||
let response = await api.post(endpoint, {
|
||||
const response = await api.post(endpoint, {
|
||||
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||
network,
|
||||
});
|
||||
@@ -47,7 +47,10 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.apiToken).to.exist;
|
||||
expect(response.id).to.exist;
|
||||
expect(response.newUser).to.be.true;
|
||||
expect(response.username).to.exist;
|
||||
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a facebook user');
|
||||
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.exist;
|
||||
});
|
||||
|
||||
it('logs an existing user in', async () => {
|
||||
|
||||
@@ -8,7 +8,11 @@ describe('POST /user/allocate', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
@@ -31,6 +35,16 @@ describe('POST /user/allocate', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the user hasn\'t selected class', async () => {
|
||||
await user.update({'flags.classSelected': false});
|
||||
await expect(user.post('/user/allocate'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('classNotSelected'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allocates attribute points', async () => {
|
||||
await user.update({'stats.points': 1});
|
||||
let res = await user.post('/user/allocate?stat=con');
|
||||
|
||||
@@ -13,7 +13,11 @@ describe('POST /user/allocate-bulk', () => {
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
@@ -27,6 +31,16 @@ describe('POST /user/allocate-bulk', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user has not selected class', async () => {
|
||||
await user.update({'flags.classSelected': false});
|
||||
await expect(user.post('/user/allocate-bulk', statsUpdate))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('classNotSelected'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allocates attribute points', async () => {
|
||||
await user.update({'stats.points': 3});
|
||||
|
||||
|
||||
62
test/api/v4/coupon/POST-coupons_enter_code.test.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
resetHabiticaDB,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('POST /coupons/enter/:code', () => {
|
||||
let user;
|
||||
let sudoUser;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
sudoUser = await generateUser({
|
||||
'contributor.sudo': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if code is missing', async () => {
|
||||
await expect(user.post('/coupons/enter')).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: 'Not found.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if code is invalid', async () => {
|
||||
await expect(user.post('/coupons/enter/notValid')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidCoupon'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if coupon has been used', async () => {
|
||||
let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
|
||||
await user.post(`/coupons/enter/${coupon._id}`); // use coupon
|
||||
|
||||
await expect(user.post(`/coupons/enter/${coupon._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('couponUsed'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply the coupon to the user', async () => {
|
||||
let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
|
||||
let userRes = await user.post(`/coupons/enter/${coupon._id}`);
|
||||
expect(userRes._id).to.equal(user._id);
|
||||
expect(userRes.items.gear.owned.eyewear_special_wondercon_red).to.be.true;
|
||||
expect(userRes.items.gear.owned.eyewear_special_wondercon_black).to.be.true;
|
||||
expect(userRes.items.gear.owned.back_special_wondercon_black).to.be.true;
|
||||
expect(userRes.items.gear.owned.back_special_wondercon_red).to.be.true;
|
||||
expect(userRes.items.gear.owned.body_special_wondercon_red).to.be.true;
|
||||
expect(userRes.items.gear.owned.body_special_wondercon_black).to.be.true;
|
||||
expect(userRes.items.gear.owned.body_special_wondercon_gold).to.be.true;
|
||||
expect(userRes.extra).to.eql({signupEvent: 'wondercon'});
|
||||
});
|
||||
});
|
||||
30
test/api/v4/inbox/DELETE-inbox_clear.test.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('DELETE /inbox/clear', () => {
|
||||
it('removes all inbox messages for the user', async () => {
|
||||
const [user, otherUser] = await Promise.all([generateUser(), generateUser()]);
|
||||
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'first',
|
||||
});
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: otherUser.id,
|
||||
message: 'second',
|
||||
});
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'third',
|
||||
});
|
||||
|
||||
let messages = await user.get('/inbox/messages');
|
||||
expect(messages.length).to.equal(3);
|
||||
|
||||
await user.del('/inbox/clear/');
|
||||
|
||||
messages = await user.get('/inbox/messages');
|
||||
expect(messages.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
62
test/api/v4/inbox/DELETE-inbox_messages_messageId.test.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('DELETE /inbox/messages/:messageId', () => {
|
||||
let user;
|
||||
let otherUser;
|
||||
|
||||
before(async () => {
|
||||
[user, otherUser] = await Promise.all([generateUser(), generateUser()]);
|
||||
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'first',
|
||||
});
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: otherUser.id,
|
||||
message: 'second',
|
||||
});
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'third',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the messageId parameter is not an UUID', async () => {
|
||||
await expect(user.del('/inbox/messages/123'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the message does not exist', async () => {
|
||||
await expect(user.del(`/inbox/messages/${generateUUID()}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageGroupChatNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes one message', async () => {
|
||||
const messages = await user.get('/inbox/messages');
|
||||
|
||||
expect(messages.length).to.equal(3);
|
||||
|
||||
expect(messages[0].text).to.equal('third');
|
||||
expect(messages[1].text).to.equal('second');
|
||||
expect(messages[2].text).to.equal('first');
|
||||
|
||||
await user.del(`/inbox/messages/${messages[1]._id}`);
|
||||
const updatedMessages = await user.get('/inbox/messages');
|
||||
expect(updatedMessages.length).to.equal(2);
|
||||
|
||||
expect(updatedMessages[0].text).to.equal('third');
|
||||
expect(updatedMessages[1].text).to.equal('first');
|
||||
});
|
||||
});
|
||||
58
test/api/v4/user/GET-user.test.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
import common from '../../../../website/common';
|
||||
|
||||
describe('GET /user', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns the authenticated user with computed stats', async () => {
|
||||
let returnedUser = await user.get('/user');
|
||||
expect(returnedUser._id).to.equal(user._id);
|
||||
|
||||
expect(returnedUser.stats.maxMP).to.exist;
|
||||
expect(returnedUser.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(returnedUser.stats.toNextLevel).to.equal(common.tnl(returnedUser.stats.lvl));
|
||||
});
|
||||
|
||||
it('does not return private paths (and apiToken)', async () => {
|
||||
let returnedUser = await user.get('/user');
|
||||
|
||||
expect(returnedUser.auth.local.hashed_password).to.not.exist;
|
||||
expect(returnedUser.auth.local.passwordHashMethod).to.not.exist;
|
||||
expect(returnedUser.auth.local.salt).to.not.exist;
|
||||
expect(returnedUser.apiToken).to.not.exist;
|
||||
});
|
||||
|
||||
it('returns only user properties requested', async () => {
|
||||
let returnedUser = await user.get('/user?userFields=achievements,items.mounts');
|
||||
|
||||
expect(returnedUser._id).to.equal(user._id);
|
||||
expect(returnedUser.achievements).to.exist;
|
||||
expect(returnedUser.items.mounts).to.exist;
|
||||
// Notifications are always returned
|
||||
expect(returnedUser.notifications).to.exist;
|
||||
expect(returnedUser.stats).to.not.exist;
|
||||
});
|
||||
|
||||
it('does not return new inbox messages', async () => {
|
||||
const otherUser = await generateUser();
|
||||
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'first',
|
||||
});
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'second',
|
||||
});
|
||||
let returnedUser = await user.get('/user');
|
||||
|
||||
expect(returnedUser._id).to.equal(user._id);
|
||||
expect(returnedUser.inbox.messages).to.be.empty;
|
||||
});
|
||||
});
|
||||
324
test/api/v4/user/POST-user_class_cast_spellId.test.js
Normal file
@@ -0,0 +1,324 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
generateGroup,
|
||||
generateChallenge,
|
||||
sleep,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
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;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns an error if spell does not exist', async () => {
|
||||
await user.update({'stats.class': 'rogue'});
|
||||
let spellId = 'invalidSpell';
|
||||
await expect(user.post(`/user/class/cast/${spellId}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: apiError('spellNotFound', {spellId}),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell does not exist in user\'s class', async () => {
|
||||
let spellId = 'pickPocket';
|
||||
await expect(user.post(`/user/class/cast/${spellId}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: apiError('spellNotFound', {spellId}),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell.mana > user.mana', async () => {
|
||||
await user.update({'stats.class': 'rogue'});
|
||||
await expect(user.post('/user/class/cast/backStab'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notEnoughMana'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell.value > user.gold', async () => {
|
||||
await expect(user.post('/user/class/cast/birthday'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageNotEnoughGold'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell.lvl > user.level', async () => {
|
||||
await user.update({'stats.mp': 200, 'stats.class': 'wizard'});
|
||||
await expect(user.post('/user/class/cast/earth'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('spellLevelTooHigh', {level: 13}),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user doesn\'t own the spell', async () => {
|
||||
await expect(user.post('/user/class/cast/snowball'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('spellNotOwned'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if targetId is not an UUID', async () => {
|
||||
await expect(user.post('/user/class/cast/spellId?targetId=notAnUUID'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if targetId is required but missing', async () => {
|
||||
await user.update({'stats.class': 'rogue', 'stats.lvl': 11});
|
||||
await expect(user.post('/user/class/cast/pickPocket'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('targetIdUUID'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if targeted task doesn\'t exist', async () => {
|
||||
await user.update({'stats.class': 'rogue', 'stats.lvl': 11});
|
||||
await expect(user.post(`/user/class/cast/pickPocket?targetId=${generateUUID()}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
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'},
|
||||
]);
|
||||
await groupLeader.update({'stats.class': 'rogue', 'stats.lvl': 11});
|
||||
await sleep(0.5);
|
||||
await groupLeader.sync();
|
||||
await expect(groupLeader.post(`/user/class/cast/pickPocket?targetId=${groupLeader.tasksOrder.habits[0]}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('challengeTasksNoCast'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if a group task was targeted', async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup();
|
||||
|
||||
let groupTask = await groupLeader.post(`/tasks/group/${group._id}`, {
|
||||
text: 'todo group',
|
||||
type: 'todo',
|
||||
});
|
||||
await groupLeader.post(`/tasks/${groupTask._id}/assign/${groupLeader._id}`);
|
||||
let memberTasks = await groupLeader.get('/tasks/user');
|
||||
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === group._id;
|
||||
});
|
||||
|
||||
await groupLeader.update({'stats.class': 'rogue', 'stats.lvl': 11});
|
||||
await sleep(0.5);
|
||||
await groupLeader.sync();
|
||||
|
||||
await expect(groupLeader.post(`/user/class/cast/pickPocket?targetId=${syncedGroupTask._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('groupTasksNoCast'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if targeted party member doesn\'t exist', async () => {
|
||||
let {groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 1,
|
||||
});
|
||||
await groupLeader.update({'items.special.snowball': 3});
|
||||
|
||||
let target = generateUUID();
|
||||
await expect(groupLeader.post(`/user/class/cast/snowball?targetId=${target}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', {userId: target}),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if party does not exists', async () => {
|
||||
await user.update({'items.special.snowball': 3});
|
||||
|
||||
await expect(user.post(`/user/class/cast/snowball?targetId=${generateUUID()}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('partyNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('send message in party chat if party && !spell.silent', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 1,
|
||||
});
|
||||
await groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 13});
|
||||
|
||||
await groupLeader.post('/user/class/cast/earth');
|
||||
await sleep(1);
|
||||
const groupMessages = await groupLeader.get(`/groups/${group._id}/chat`);
|
||||
|
||||
expect(groupMessages[0]).to.exist;
|
||||
expect(groupMessages[0].uuid).to.equal('system');
|
||||
});
|
||||
|
||||
it('Ethereal Surge does not recover mp of other mages', async () => {
|
||||
let group = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 4,
|
||||
});
|
||||
|
||||
let promises = [];
|
||||
promises.push(group.groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 20}));
|
||||
promises.push(group.members[0].update({'stats.mp': 0, 'stats.class': 'warrior', 'stats.lvl': 20}));
|
||||
promises.push(group.members[1].update({'stats.mp': 0, 'stats.class': 'wizard', 'stats.lvl': 20}));
|
||||
promises.push(group.members[2].update({'stats.mp': 0, 'stats.class': 'rogue', 'stats.lvl': 20}));
|
||||
promises.push(group.members[3].update({'stats.mp': 0, 'stats.class': 'healer', 'stats.lvl': 20}));
|
||||
await Promise.all(promises);
|
||||
|
||||
await group.groupLeader.post('/user/class/cast/mpheal');
|
||||
|
||||
promises = [];
|
||||
promises.push(group.members[0].sync());
|
||||
promises.push(group.members[1].sync());
|
||||
promises.push(group.members[2].sync());
|
||||
promises.push(group.members[3].sync());
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
|
||||
expect(group.members[1].stats.mp).to.equal(0); // wizard
|
||||
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
|
||||
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
|
||||
});
|
||||
|
||||
it('cast bulk', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 13});
|
||||
await groupLeader.post('/user/class/cast/earth', {quantity: 2});
|
||||
|
||||
await sleep(1);
|
||||
group = await groupLeader.get(`/groups/${group._id}`);
|
||||
|
||||
expect(group.chat[0]).to.exist;
|
||||
expect(group.chat[0].uuid).to.equal('system');
|
||||
});
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
let groupTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'todo group',
|
||||
type: 'todo',
|
||||
});
|
||||
await user.update({'stats.class': 'healer', 'stats.mp': 200, 'stats.lvl': 15});
|
||||
await user.post(`/tasks/${groupTask._id}/assign/${user._id}`);
|
||||
|
||||
await user.post('/user/class/cast/brightness');
|
||||
await user.sync();
|
||||
|
||||
let memberTasks = await user.get('/tasks/user');
|
||||
|
||||
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === guild._id;
|
||||
});
|
||||
|
||||
let userChallengeTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.challenge.id === challenge._id;
|
||||
});
|
||||
|
||||
expect(userChallengeTask).to.exist;
|
||||
expect(syncedGroupTask).to.exist;
|
||||
expect(userChallengeTask.value).to.equal(0);
|
||||
expect(syncedGroupTask.value).to.equal(0);
|
||||
});
|
||||
|
||||
it('increases both user\'s achievement values', async () => {
|
||||
let party = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
});
|
||||
let leader = party.groupLeader;
|
||||
let recipient = party.members[0];
|
||||
await leader.update({'stats.gp': 10});
|
||||
await leader.post(`/user/class/cast/birthday?targetId=${recipient._id}`);
|
||||
await leader.sync();
|
||||
await recipient.sync();
|
||||
expect(leader.achievements.birthday).to.equal(1);
|
||||
expect(recipient.achievements.birthday).to.equal(1);
|
||||
});
|
||||
|
||||
it('only increases user\'s achievement one if target == caster', async () => {
|
||||
await user.update({'stats.gp': 10});
|
||||
await user.post(`/user/class/cast/birthday?targetId=${user._id}`);
|
||||
await user.sync();
|
||||
expect(user.achievements.birthday).to.equal(1);
|
||||
});
|
||||
|
||||
it('passes correct target to spell when targetType === \'task\'', async () => {
|
||||
await user.update({'stats.class': 'wizard', 'stats.lvl': 11});
|
||||
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
let result = await user.post(`/user/class/cast/fireball?targetId=${task._id}`);
|
||||
|
||||
expect(result.task._id).to.equal(task._id);
|
||||
});
|
||||
|
||||
it('passes correct target to spell when targetType === \'self\'', async () => {
|
||||
await user.update({'stats.class': 'wizard', 'stats.lvl': 14, 'stats.mp': 50});
|
||||
|
||||
let result = await user.post('/user/class/cast/frost');
|
||||
|
||||
expect(result.user.stats.mp).to.equal(10);
|
||||
});
|
||||
|
||||
|
||||
// TODO find a way to have sinon working in integration tests
|
||||
// it doesn't work when tests are running separately from server
|
||||
it('passes correct target to spell when targetType === \'tasks\'');
|
||||
it('passes correct target to spell when targetType === \'party\'');
|
||||
it('passes correct target to spell when targetType === \'user\'');
|
||||
it('passes correct target to spell when targetType === \'party\' and user is not in a party');
|
||||
it('passes correct target to spell when targetType === \'user\' and user is not in a party');
|
||||
});
|
||||
60
test/api/v4/user/POST-user_rebirth.test.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateDaily,
|
||||
generateReward,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('POST /user/rebirth', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns an error when user balance is too low', async () => {
|
||||
await expect(user.post('/user/rebirth'))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notEnoughGems'),
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('resets user\'s tasks', async () => {
|
||||
await user.update({
|
||||
balance: 1.5,
|
||||
});
|
||||
|
||||
let daily = await generateDaily({
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
value: 1,
|
||||
streak: 1,
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let reward = await generateReward({
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
value: 1,
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let response = await user.post('/user/rebirth');
|
||||
await user.sync();
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].type).to.equal('REBIRTH_ACHIEVEMENT');
|
||||
|
||||
let updatedDaily = await user.get(`/tasks/${daily._id}`);
|
||||
let updatedReward = await user.get(`/tasks/${reward._id}`);
|
||||
|
||||
expect(response.message).to.equal(t('rebirthComplete'));
|
||||
expect(updatedDaily.streak).to.equal(0);
|
||||
expect(updatedDaily.value).to.equal(0);
|
||||
expect(updatedReward.value).to.equal(1);
|
||||
});
|
||||
});
|
||||
54
test/api/v4/user/POST-user_reroll.test.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateDaily,
|
||||
generateReward,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('POST /user/reroll', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns an error when user balance is too low', async () => {
|
||||
await expect(user.post('/user/reroll'))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notEnoughGems'),
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('resets user\'s tasks', async () => {
|
||||
await user.update({
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
let daily = await generateDaily({
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let reward = await generateReward({
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
value: 1,
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
let response = await user.post('/user/reroll');
|
||||
await user.sync();
|
||||
|
||||
let updatedDaily = await user.get(`/tasks/${daily._id}`);
|
||||
let updatedReward = await user.get(`/tasks/${reward._id}`);
|
||||
|
||||
expect(response.message).to.equal(t('fortifyComplete'));
|
||||
expect(updatedDaily.value).to.equal(0);
|
||||
expect(updatedReward.value).to.equal(1);
|
||||
});
|
||||
});
|
||||
121
test/api/v4/user/POST-user_reset.test.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
generateChallenge,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
import { find } from 'lodash';
|
||||
|
||||
describe('POST /user/reset', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('resets user\'s habits', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('resets user\'s dailys', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.dailys).to.be.empty;
|
||||
});
|
||||
|
||||
it('resets user\'s todos', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.todos).to.be.empty;
|
||||
});
|
||||
|
||||
it('resets user\'s rewards', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.rewards).to.be.empty;
|
||||
});
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
let groupTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'todo group',
|
||||
type: 'todo',
|
||||
});
|
||||
await user.post(`/tasks/${groupTask._id}/assign/${user._id}`);
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
let memberTasks = await user.get('/tasks/user');
|
||||
|
||||
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === guild._id;
|
||||
});
|
||||
|
||||
let userChallengeTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.challenge.id === challenge._id;
|
||||
});
|
||||
|
||||
expect(userChallengeTask).to.exist;
|
||||
expect(syncedGroupTask).to.exist;
|
||||
});
|
||||
});
|
||||
256
test/api/v4/user/PUT-user.test.js
Normal file
@@ -0,0 +1,256 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
import { each, get } from 'lodash';
|
||||
|
||||
describe('PUT /user', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
context('Allowed Operations', () => {
|
||||
it('updates the user', async () => {
|
||||
await user.put('/user', {
|
||||
'profile.name': 'Frodo',
|
||||
'preferences.costume': true,
|
||||
'stats.hp': 14,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.profile.name).to.eql('Frodo');
|
||||
expect(user.preferences.costume).to.eql(true);
|
||||
expect(user.stats.hp).to.eql(14);
|
||||
});
|
||||
|
||||
it('tags must be an array', async () => {
|
||||
await expect(user.put('/user', {
|
||||
tags: {
|
||||
tag: true,
|
||||
},
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'mustBeArray',
|
||||
});
|
||||
});
|
||||
|
||||
it('update tags', async () => {
|
||||
let userTags = user.tags;
|
||||
|
||||
await user.put('/user', {
|
||||
tags: [...user.tags, {
|
||||
name: 'new tag',
|
||||
}],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.tags.length).to.be.eql(userTags.length + 1);
|
||||
});
|
||||
|
||||
|
||||
it('profile.name cannot be an empty string or null', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': ' ', // string should be trimmed
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': null,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Top Level Protected Operations', () => {
|
||||
let protectedOperations = {
|
||||
'gem balance': {balance: 100},
|
||||
auth: {'auth.blocked': true, 'auth.timestamps.created': new Date()},
|
||||
contributor: {'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text'},
|
||||
backer: {'backer.tier': 10, 'backer.npc': 'Bilbo'},
|
||||
subscriptions: {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000},
|
||||
'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true},
|
||||
notifications: [{type: 123}],
|
||||
webhooks: {webhooks: [{url: 'https://foobar.com'}]},
|
||||
};
|
||||
|
||||
each(protectedOperations, (data, testName) => {
|
||||
it(`does not allow updating ${testName}`, async () => {
|
||||
let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] });
|
||||
|
||||
await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: errorText,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Sub-Level Protected Operations', () => {
|
||||
let protectedOperations = {
|
||||
'class stat': {'stats.class': 'wizard'},
|
||||
'flags unless whitelisted': {'flags.dropsEnabled': true},
|
||||
webhooks: {'preferences.webhooks': [1, 2, 3]},
|
||||
sleep: {'preferences.sleep': true},
|
||||
'disable classes': {'preferences.disableClasses': true},
|
||||
};
|
||||
|
||||
each(protectedOperations, (data, testName) => {
|
||||
it(`does not allow updating ${testName}`, async () => {
|
||||
let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] });
|
||||
|
||||
await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: errorText,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Default Appearance Preferences', () => {
|
||||
let testCases = {
|
||||
shirt: 'yellow',
|
||||
skin: 'ddc994',
|
||||
'hair.color': 'blond',
|
||||
'hair.bangs': 2,
|
||||
'hair.base': 1,
|
||||
'hair.flower': 4,
|
||||
size: 'broad',
|
||||
};
|
||||
|
||||
each(testCases, (item, type) => {
|
||||
const update = {};
|
||||
update[`preferences.${type}`] = item;
|
||||
|
||||
it(`updates user with ${type} that is a default`, async () => {
|
||||
let dbUpdate = {};
|
||||
dbUpdate[`purchased.${type}.${item}`] = true;
|
||||
await user.update(dbUpdate);
|
||||
|
||||
// Sanity checks to make sure user is not already equipped with item
|
||||
expect(get(user.preferences, type)).to.not.eql(item);
|
||||
|
||||
let updatedUser = await user.put('/user', update);
|
||||
|
||||
expect(get(updatedUser.preferences, type)).to.eql(item);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user tries to update body size with invalid type', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'preferences.size': 'round',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('mustPurchaseToSet', { val: 'round', key: 'preferences.size' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('can set beard to default', async () => {
|
||||
await user.update({
|
||||
'purchased.hair.beard': 3,
|
||||
'preferences.hair.beard': 3,
|
||||
});
|
||||
|
||||
let updatedUser = await user.put('/user', {
|
||||
'preferences.hair.beard': 0,
|
||||
});
|
||||
|
||||
expect(updatedUser.preferences.hair.beard).to.eql(0);
|
||||
});
|
||||
|
||||
it('can set mustache to default', async () => {
|
||||
await user.update({
|
||||
'purchased.hair.mustache': 2,
|
||||
'preferences.hair.mustache': 2,
|
||||
});
|
||||
|
||||
let updatedUser = await user.put('/user', {
|
||||
'preferences.hair.mustache': 0,
|
||||
});
|
||||
|
||||
expect(updatedUser.preferences.hair.mustache).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('Purchasable Appearance Preferences', () => {
|
||||
let testCases = {
|
||||
background: 'volcano',
|
||||
shirt: 'convict',
|
||||
skin: 'cactus',
|
||||
'hair.base': 7,
|
||||
'hair.beard': 2,
|
||||
'hair.color': 'rainbow',
|
||||
'hair.mustache': 2,
|
||||
};
|
||||
|
||||
each(testCases, (item, type) => {
|
||||
const update = {};
|
||||
update[`preferences.${type}`] = item;
|
||||
|
||||
it(`returns an error if user tries to update ${type} with ${type} the user does not own`, async () => {
|
||||
await expect(user.put('/user', update)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('mustPurchaseToSet', {val: item, key: `preferences.${type}`}),
|
||||
});
|
||||
});
|
||||
|
||||
it(`updates user with ${type} user does own`, async () => {
|
||||
let dbUpdate = {};
|
||||
dbUpdate[`purchased.${type}.${item}`] = true;
|
||||
await user.update(dbUpdate);
|
||||
|
||||
// Sanity check to make sure user is not already equipped with item
|
||||
expect(get(user.preferences, type)).to.not.eql(item);
|
||||
|
||||
let updatedUser = await user.put('/user', update);
|
||||
|
||||
expect(get(updatedUser.preferences, type)).to.eql(item);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Improvement Categories', () => {
|
||||
it('sets valid categories', async () => {
|
||||
await user.put('/user', {
|
||||
'preferences.improvementCategories': ['work', 'school'],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.preferences.improvementCategories).to.eql(['work', 'school']);
|
||||
});
|
||||
|
||||
it('discards invalid categories', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'preferences.improvementCategories': ['work', 'procrastination', 'school'],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
739
test/api/v4/user/auth/POST-register_local.test.js
Normal file
@@ -0,0 +1,739 @@
|
||||
import {
|
||||
generateUser,
|
||||
requester,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
getProperty,
|
||||
} from '../../../../helpers/api-integration/v4';
|
||||
import { ApiUser } from '../../../../helpers/api-integration/api-classes';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { each } from 'lodash';
|
||||
import { encrypt } from '../../../../../website/server/libs/encryption';
|
||||
|
||||
function generateRandomUserName () {
|
||||
return (Date.now() + uuid()).substring(0, 20);
|
||||
}
|
||||
|
||||
describe('POST /user/auth/local/register', () => {
|
||||
context('username and email are free', () => {
|
||||
let api;
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester();
|
||||
});
|
||||
|
||||
it('registers a new user', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user._id).to.exist;
|
||||
expect(user.apiToken).to.exist;
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.profile.name).to.eql(username);
|
||||
expect(user.newUser).to.eql(true);
|
||||
});
|
||||
|
||||
xit('remove spaces from username', async () => {
|
||||
// TODO can probably delete this test now
|
||||
let username = ' usernamewithspaces ';
|
||||
let email = 'test@example.com';
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.auth.local.username).to.eql(username.trim());
|
||||
expect(user.profile.name).to.eql(username.trim());
|
||||
});
|
||||
|
||||
context('validates username', () => {
|
||||
const email = 'test@example.com';
|
||||
const password = 'password';
|
||||
|
||||
it('requires to username to be less than 20', async () => {
|
||||
const username = (Date.now() + uuid()).substring(0, 21);
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects chracters not in [-_a-zA-Z0-9]', async () => {
|
||||
const username = 'a-zA_Z09*';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows only [-_a-zA-Z0-9] characters', async () => {
|
||||
const username = 'a-zA_Z09';
|
||||
|
||||
const user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
});
|
||||
});
|
||||
|
||||
context('provides default tags and tasks', async () => {
|
||||
it('for a generic API consumer', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let dailys = await requests.get('/tasks/user?type=dailys');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
let rewards = await requests.get('/tasks/user?type=rewards');
|
||||
let tags = await requests.get('/tags');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(0);
|
||||
expect(dailys).to.have.a.lengthOf(0);
|
||||
expect(todos).to.have.a.lengthOf(1);
|
||||
expect(rewards).to.have.a.lengthOf(0);
|
||||
|
||||
expect(tags).to.have.a.lengthOf(7);
|
||||
expect(tags[0].name).to.eql(t('defaultTag1'));
|
||||
expect(tags[1].name).to.eql(t('defaultTag2'));
|
||||
expect(tags[2].name).to.eql(t('defaultTag3'));
|
||||
expect(tags[3].name).to.eql(t('defaultTag4'));
|
||||
expect(tags[4].name).to.eql(t('defaultTag5'));
|
||||
expect(tags[5].name).to.eql(t('defaultTag6'));
|
||||
expect(tags[6].name).to.eql(t('defaultTag7'));
|
||||
});
|
||||
|
||||
xit('for Web', async () => {
|
||||
api = requester(
|
||||
null,
|
||||
{'x-client': 'habitica-web'},
|
||||
);
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let dailys = await requests.get('/tasks/user?type=dailys');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
let rewards = await requests.get('/tasks/user?type=rewards');
|
||||
let tags = await requests.get('/tags');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(3);
|
||||
expect(habits[0].text).to.eql(t('defaultHabit1Text'));
|
||||
expect(habits[0].notes).to.eql('');
|
||||
expect(habits[1].text).to.eql(t('defaultHabit2Text'));
|
||||
expect(habits[1].notes).to.eql('');
|
||||
expect(habits[2].text).to.eql(t('defaultHabit3Text'));
|
||||
expect(habits[2].notes).to.eql('');
|
||||
|
||||
expect(dailys).to.have.a.lengthOf(0);
|
||||
|
||||
expect(todos).to.have.a.lengthOf(1);
|
||||
expect(todos[0].text).to.eql(t('defaultTodo1Text'));
|
||||
expect(todos[0].notes).to.eql(t('defaultTodoNotes'));
|
||||
|
||||
expect(rewards).to.have.a.lengthOf(1);
|
||||
expect(rewards[0].text).to.eql(t('defaultReward1Text'));
|
||||
expect(rewards[0].notes).to.eql('');
|
||||
|
||||
expect(tags).to.have.a.lengthOf(7);
|
||||
expect(tags[0].name).to.eql(t('defaultTag1'));
|
||||
expect(tags[1].name).to.eql(t('defaultTag2'));
|
||||
expect(tags[2].name).to.eql(t('defaultTag3'));
|
||||
expect(tags[3].name).to.eql(t('defaultTag4'));
|
||||
expect(tags[4].name).to.eql(t('defaultTag5'));
|
||||
expect(tags[5].name).to.eql(t('defaultTag6'));
|
||||
expect(tags[6].name).to.eql(t('defaultTag7'));
|
||||
});
|
||||
});
|
||||
|
||||
context('does not provide default tags and tasks', async () => {
|
||||
it('for Android', async () => {
|
||||
api = requester(
|
||||
null,
|
||||
{'x-client': 'habitica-android'},
|
||||
);
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let dailys = await requests.get('/tasks/user?type=dailys');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
let rewards = await requests.get('/tasks/user?type=rewards');
|
||||
let tags = await requests.get('/tags');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(0);
|
||||
expect(dailys).to.have.a.lengthOf(0);
|
||||
expect(todos).to.have.a.lengthOf(0);
|
||||
expect(rewards).to.have.a.lengthOf(0);
|
||||
expect(tags).to.have.a.lengthOf(0);
|
||||
});
|
||||
|
||||
it('for iOS', async () => {
|
||||
api = requester(
|
||||
null,
|
||||
{'x-client': 'habitica-ios'},
|
||||
);
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let dailys = await requests.get('/tasks/user?type=dailys');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
let rewards = await requests.get('/tasks/user?type=rewards');
|
||||
let tags = await requests.get('/tags');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(0);
|
||||
expect(dailys).to.have.a.lengthOf(0);
|
||||
expect(todos).to.have.a.lengthOf(0);
|
||||
expect(rewards).to.have.a.lengthOf(0);
|
||||
expect(tags).to.have.a.lengthOf(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('enrolls new users in an A/B test', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
||||
});
|
||||
|
||||
it('includes items awarded by default when creating a new user', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.items.quests.dustbunnies).to.equal(1);
|
||||
expect(user.purchased.background.violet).to.be.ok;
|
||||
expect(user.preferences.background).to.equal('violet');
|
||||
});
|
||||
|
||||
it('requires password and confirmPassword to match', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
let confirmPassword = 'not password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a username', async () => {
|
||||
let email = `${generateRandomUserName()}@example.com`;
|
||||
let password = 'password';
|
||||
let confirmPassword = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires an email', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a valid email', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = 'notanemail@sdf';
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('sanitizes email params to a lowercase string before creating the user', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = 'ISANEmAiL@ExAmPle.coM';
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.auth.local.email).to.equal(email.toLowerCase());
|
||||
});
|
||||
|
||||
it('fails on a habitica.com email', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@habitica.com`;
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on a habitrpg.com email', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@habitrpg.com`;
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a password', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let confirmPassword = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('attach to facebook user', () => {
|
||||
let user;
|
||||
let email = 'some@email.net';
|
||||
let username = 'some-username';
|
||||
let password = 'some-password';
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
it('checks onlySocialAttachLocal', async () => {
|
||||
await expect(user.post('/user/auth/local/register', {
|
||||
email,
|
||||
username,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlySocialAttachLocal'),
|
||||
});
|
||||
});
|
||||
it('succeeds', async () => {
|
||||
await user.update({ 'auth.facebook.id': 'some-fb-id', 'auth.local': { ok: true } });
|
||||
await user.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.auth.local.email).to.eql(email);
|
||||
});
|
||||
});
|
||||
|
||||
context('login is already taken', () => {
|
||||
let username, email, api;
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester();
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
|
||||
return generateUser({
|
||||
'auth.local.username': username,
|
||||
'auth.local.lowerCaseUsername': username,
|
||||
'auth.local.email': email,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects if username is already taken', async () => {
|
||||
let uniqueEmail = `${generateRandomUserName()}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email: uniqueEmail,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('usernameTaken'),
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects if email is already taken', async () => {
|
||||
let uniqueUsername = generateRandomUserName();
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username: uniqueUsername,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('emailTaken'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('req.query.groupInvite', () => {
|
||||
let api, username, email, password;
|
||||
|
||||
beforeEach(() => {
|
||||
api = requester();
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
password = 'password';
|
||||
});
|
||||
|
||||
it('does not crash the signup process when it\'s invalid', async () => {
|
||||
let user = await api.post('/user/auth/local/register?groupInvite=aaaaInvalid', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user._id).to.be.a('string');
|
||||
});
|
||||
|
||||
it('supports invite using req.query.groupInvite', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(), // so we can let it expire
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.parties[0].id).to.eql(group._id);
|
||||
expect(user.invitations.parties[0].name).to.eql(group.name);
|
||||
expect(user.invitations.parties[0].inviter).to.eql(groupLeader._id);
|
||||
});
|
||||
|
||||
it('awards achievement to inviter', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.achievements.invitedFriend).to.be.true;
|
||||
});
|
||||
|
||||
it('user not added to a party on expired invite', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now() - 6.912e8, // 8 days old
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.party).to.eql({});
|
||||
});
|
||||
|
||||
it('adds a user to a guild on an invite of type other than party', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.guilds[0]).to.eql({
|
||||
id: group._id,
|
||||
name: group.name,
|
||||
inviter: groupLeader._id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('successful login via api', () => {
|
||||
let api, username, email, password;
|
||||
|
||||
beforeEach(() => {
|
||||
api = requester();
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
password = 'password';
|
||||
});
|
||||
|
||||
it('sets all site tour values to -2 (already seen)', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.flags.tour).to.not.be.empty;
|
||||
|
||||
each(user.flags.tour, (value) => {
|
||||
expect(value).to.eql(-2);
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default todos, not no other task types', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.todos).to.not.be.empty;
|
||||
expect(user.tasksOrder.dailys).to.be.empty;
|
||||
expect(user.tasksOrder.habits).to.be.empty;
|
||||
expect(user.tasksOrder.rewards).to.be.empty;
|
||||
});
|
||||
|
||||
it('populates user with default tags', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.tags).to.not.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
context('successful login with habitica-web header', () => {
|
||||
let api, username, email, password;
|
||||
|
||||
beforeEach(() => {
|
||||
api = requester({}, {'x-client': 'habitica-web'});
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
password = 'password';
|
||||
});
|
||||
|
||||
it('sets all common tutorial flags to true', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.flags.tour).to.not.be.empty;
|
||||
|
||||
each(user.flags.tutorial.common, (value) => {
|
||||
expect(value).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default todos, habits, and rewards', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.todos).to.be.empty;
|
||||
expect(user.tasksOrder.dailys).to.be.empty;
|
||||
expect(user.tasksOrder.habits).to.be.empty;
|
||||
expect(user.tasksOrder.rewards).to.be.empty;
|
||||
});
|
||||
|
||||
it('populates user with default tags', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.tags).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('adds the correct tags to the correct tasks', async () => {
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let requests = new ApiUser(user);
|
||||
|
||||
let habits = await requests.get('/tasks/user?type=habits');
|
||||
let todos = await requests.get('/tasks/user?type=todos');
|
||||
|
||||
expect(habits).to.have.a.lengthOf(0);
|
||||
expect(todos).to.have.a.lengthOf(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
89
test/api/v4/user/auth/POST-user_verify_username.test.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v4';
|
||||
|
||||
const ENDPOINT = '/user/auth/verify-username';
|
||||
|
||||
describe('POST /user/auth/verify-username', async () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('successfully verifies username', async () => {
|
||||
let newUsername = 'new-username';
|
||||
let response = await user.post(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ isUsable: true });
|
||||
});
|
||||
|
||||
it('successfully verifies username with allowed characters', async () => {
|
||||
let newUsername = 'new-username_123';
|
||||
let response = await user.post(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ isUsable: true });
|
||||
});
|
||||
|
||||
context('errors', async () => {
|
||||
it('errors if username is not provided', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if username is a slur', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength'), t('usernameIssueSlur')] });
|
||||
});
|
||||
|
||||
it('errors if username contains a slur', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'TESTPLACEHOLDERSLURWORDHERE_otherword',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength'), t('usernameIssueSlur')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'something_TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength'), t('usernameIssueSlur')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'somethingTESTPLACEHOLDERSLURWORDHEREotherword',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength'), t('usernameIssueSlur')] });
|
||||
});
|
||||
|
||||
it('errors if username is not allowed', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'support',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueForbidden')] });
|
||||
});
|
||||
|
||||
it('errors if username is not allowed regardless of casing', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'SUppORT',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueForbidden')] });
|
||||
});
|
||||
|
||||
it('errors if username has incorrect length', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'thisisaverylongusernameover20characters',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueLength')] });
|
||||
});
|
||||
|
||||
it('errors if username contains invalid characters', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'Eichhörnchen',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueInvalidCharacters')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: 'test.name',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueInvalidCharacters')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
username: '🤬',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('usernameIssueInvalidCharacters')] });
|
||||
});
|
||||
});
|
||||
});
|
||||
224
test/api/v4/user/auth/PUT-user_update_username.test.js
Normal file
@@ -0,0 +1,224 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v4';
|
||||
import {
|
||||
bcryptCompare,
|
||||
sha1MakeSalt,
|
||||
sha1Encrypt as sha1EncryptPassword,
|
||||
} from '../../../../../website/server/libs/password';
|
||||
|
||||
const ENDPOINT = '/user/auth/update-username';
|
||||
|
||||
describe('PUT /user/auth/update-username', async () => {
|
||||
let user;
|
||||
let password = 'password'; // from habitrpg/test/helpers/api-integration/v4/object-generators.js
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('successfully changes username with password', async () => {
|
||||
let newUsername = 'new-username';
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
password,
|
||||
});
|
||||
expect(response).to.eql({ username: newUsername });
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(newUsername);
|
||||
});
|
||||
|
||||
it('successfully changes username without password', async () => {
|
||||
let newUsername = 'new-username-nopw';
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ username: newUsername });
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(newUsername);
|
||||
});
|
||||
|
||||
it('successfully changes username containing number and underscore', async () => {
|
||||
let newUsername = 'new_username9';
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ username: newUsername });
|
||||
await user.sync();
|
||||
expect(user.auth.local.username).to.eql(newUsername);
|
||||
});
|
||||
|
||||
it('sets verifiedUsername when changing username', async () => {
|
||||
user.flags.verifiedUsername = false;
|
||||
await user.sync();
|
||||
let newUsername = 'new-username-verify';
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
});
|
||||
expect(response).to.eql({ username: newUsername });
|
||||
await user.sync();
|
||||
expect(user.flags.verifiedUsername).to.eql(true);
|
||||
});
|
||||
|
||||
it('converts user with SHA1 encrypted password to bcrypt encryption', async () => {
|
||||
let myNewUsername = 'my-new-username';
|
||||
let textPassword = 'mySecretPassword';
|
||||
let salt = sha1MakeSalt();
|
||||
let sha1HashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||
|
||||
await user.update({
|
||||
'auth.local.hashed_password': sha1HashedPassword,
|
||||
'auth.local.passwordHashMethod': 'sha1',
|
||||
'auth.local.salt': salt,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.passwordHashMethod).to.equal('sha1');
|
||||
expect(user.auth.local.salt).to.equal(salt);
|
||||
expect(user.auth.local.hashed_password).to.equal(sha1HashedPassword);
|
||||
|
||||
// update email
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: myNewUsername,
|
||||
password: textPassword,
|
||||
});
|
||||
expect(response).to.eql({ username: myNewUsername });
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.auth.local.username).to.eql(myNewUsername);
|
||||
expect(user.auth.local.passwordHashMethod).to.equal('bcrypt');
|
||||
expect(user.auth.local.salt).to.be.undefined;
|
||||
expect(user.auth.local.hashed_password).not.to.equal(sha1HashedPassword);
|
||||
|
||||
let isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
|
||||
expect(isValidPassword).to.equal(true);
|
||||
});
|
||||
|
||||
context('errors', async () => {
|
||||
it('prevents username update if new username is already taken', async () => {
|
||||
let existingUsername = 'existing-username';
|
||||
await generateUser({'auth.local.username': existingUsername, 'auth.local.lowerCaseUsername': existingUsername });
|
||||
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: existingUsername,
|
||||
password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameTaken'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if password is wrong', async () => {
|
||||
let newUsername = 'new-username';
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
password: 'wrong-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('wrongPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username is not provided', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username is a slur', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username contains a slur', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'TESTPLACEHOLDERSLURWORDHERE_otherword',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
|
||||
});
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'something_TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
|
||||
});
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'somethingTESTPLACEHOLDERSLURWORDHEREotherword',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username is not allowed', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'support',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueForbidden'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username is not allowed regardless of casing', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'SUppORT',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueForbidden'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if username has incorrect length', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'thisisaverylongusernameover20characters',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueLength'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username contains invalid characters', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'Eichhörnchen',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueInvalidCharacters'),
|
||||
});
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: 'test.name',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueInvalidCharacters'),
|
||||
});
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: '🤬',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('usernameIssueInvalidCharacters'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
50
test/client/unit/specs/components/notifications.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import NotificationsComponent from 'client/components/notifications.vue';
|
||||
import Store from 'client/libs/store';
|
||||
import { hasClass } from 'client/store/getters/members';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Store);
|
||||
|
||||
describe('Notifications', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = new Store({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
stats: {
|
||||
lvl: 0,
|
||||
},
|
||||
flags: {},
|
||||
preferences: {},
|
||||
party: {
|
||||
quest: {
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
'user:fetch': () => {},
|
||||
'tasks:fetchUserTasks': () => {},
|
||||
},
|
||||
getters: {
|
||||
'members:hasClass': hasClass,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('set user has class computed prop', () => {
|
||||
const wrapper = shallowMount(NotificationsComponent, { store, localVue });
|
||||
|
||||
expect(wrapper.vm.userHasClass).to.be.false;
|
||||
|
||||
store.state.user.data.stats.lvl = 10;
|
||||
store.state.user.data.flags.classSelected = true;
|
||||
store.state.user.data.preferences.disableClasses = false;
|
||||
|
||||
expect(wrapper.vm.userHasClass).to.be.true;
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import {shallow} from '@vue/test-utils';
|
||||
import { shallow } from '@vue/test-utils';
|
||||
|
||||
import SidebarSection from 'client/components/sidebarSection.vue';
|
||||
|
||||
@@ -51,4 +51,4 @@ describe('Sidebar Section', () => {
|
||||
|
||||
expect(wrapper.find('.section-body').element.style.display).to.eq('none');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('Task Column', () => {
|
||||
expect(el).to.eq(taskListOverride[i]);
|
||||
});
|
||||
|
||||
wrapper.setProps({ isUser: false, taskListOverride });
|
||||
wrapper.setProps({ isUser: false });
|
||||
|
||||
wrapper.vm.taskList.forEach((el, i) => {
|
||||
expect(el).to.eq(taskListOverride[i]);
|
||||
|
||||
19
test/client/unit/specs/store/getters/tasks/canDelete.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import generateStore from 'client/store';
|
||||
|
||||
describe('canDelete getter', () => {
|
||||
it('cannot delete active challenge task', () => {
|
||||
const store = generateStore();
|
||||
|
||||
|
||||
const task = {userId: 1, challenge: {id: 2}};
|
||||
expect(store.getters['tasks:canDelete'](task)).to.equal(false);
|
||||
});
|
||||
|
||||
it('can delete broken challenge task', () => {
|
||||
const store = generateStore();
|
||||
|
||||
|
||||
const task = {userId: 1, challenge: {id: 2, broken: true}};
|
||||
expect(store.getters['tasks:canDelete'](task)).to.equal(true);
|
||||
});
|
||||
});
|
||||
141
test/client/unit/specs/store/getters/tasks/getTaskClasses.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import generateStore from 'client/store';
|
||||
|
||||
describe('getTaskClasses getter', () => {
|
||||
let store, getTaskClasses;
|
||||
|
||||
beforeEach(() => {
|
||||
store = generateStore();
|
||||
store.state.user.data = {
|
||||
preferences: {
|
||||
},
|
||||
};
|
||||
|
||||
getTaskClasses = store.getters['tasks:getTaskClasses'];
|
||||
});
|
||||
|
||||
it('returns reward edit-modal-bg class', () => {
|
||||
const task = {type: 'reward'};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-purple-modal-bg');
|
||||
});
|
||||
|
||||
it('returns worst task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: -21};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-worst-modal-bg');
|
||||
});
|
||||
|
||||
it('returns worse task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: -11};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-worse-modal-bg');
|
||||
});
|
||||
|
||||
it('returns bad task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: -6};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-bad-modal-bg');
|
||||
});
|
||||
|
||||
it('returns neutral task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: 0};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-neutral-modal-bg');
|
||||
});
|
||||
|
||||
it('returns good task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: 2};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-good-modal-bg');
|
||||
});
|
||||
|
||||
it('returns better task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: 6};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-better-modal-bg');
|
||||
});
|
||||
|
||||
|
||||
it('returns best task edit-modal-bg class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-bg')).to.equal('task-best-modal-bg');
|
||||
});
|
||||
|
||||
it('returns best task edit-modal-text class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-text')).to.equal('task-best-modal-text');
|
||||
});
|
||||
|
||||
it('returns best task edit-modal-icon class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-icon')).to.equal('task-best-modal-icon');
|
||||
});
|
||||
|
||||
it('returns best task edit-modal-option-disabled class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-option-disabled')).to.equal('task-best-modal-option-disabled');
|
||||
});
|
||||
|
||||
it('returns best task edit-modal-control-disabled class', () => {
|
||||
const task = {type: 'todo', value: 12};
|
||||
expect(getTaskClasses(task, 'edit-modal-habit-control-disabled')).to.equal('task-best-modal-habit-control-disabled');
|
||||
});
|
||||
|
||||
it('returns create-modal-bg class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-bg')).to.equal('task-purple-modal-bg');
|
||||
});
|
||||
|
||||
it('returns create-modal-text class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-text')).to.equal('task-purple-modal-text');
|
||||
});
|
||||
|
||||
it('returns create-modal-icon class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-icon')).to.equal('task-purple-modal-icon');
|
||||
});
|
||||
|
||||
it('returns create-modal-option-disabled class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-option-disabled')).to.equal('task-purple-modal-option-disabled');
|
||||
});
|
||||
|
||||
it('returns create-modal-habit-control-disabled class', () => {
|
||||
const task = {type: 'todo'};
|
||||
expect(getTaskClasses(task, 'create-modal-habit-control-disabled')).to.equal('task-purple-modal-habit-control-disabled');
|
||||
});
|
||||
|
||||
it('returns completed todo classes', () => {
|
||||
const task = {type: 'todo', value: 2, completed: true};
|
||||
expect(getTaskClasses(task, 'control')).to.deep.equal({
|
||||
bg: 'task-disabled-daily-todo-control-bg',
|
||||
checkbox: 'task-disabled-daily-todo-control-checkbox',
|
||||
inner: 'task-disabled-daily-todo-control-inner',
|
||||
content: 'task-disabled-daily-todo-control-content',
|
||||
});
|
||||
});
|
||||
|
||||
xit('returns good todo classes', () => {
|
||||
const task = {type: 'todo', value: 2};
|
||||
expect(getTaskClasses(task, 'control')).to.deep.equal({
|
||||
bg: 'task-good-control-bg',
|
||||
checkbox: 'task-good-control-checkbox',
|
||||
inner: 'task-good-control-inner-daily-todo`',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns reward classes', () => {
|
||||
const task = {type: 'reward'};
|
||||
expect(getTaskClasses(task, 'control')).to.deep.equal({
|
||||
bg: 'task-reward-control-bg',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns habit up classes', () => {
|
||||
const task = {type: 'habit', value: 2, up: true};
|
||||
expect(getTaskClasses(task, 'control')).to.deep.equal({
|
||||
up: {
|
||||
bg: 'task-good-control-bg',
|
||||
inner: 'task-good-control-inner-habit',
|
||||
},
|
||||
down: {
|
||||
bg: 'task-disabled-habit-control-bg',
|
||||
inner: 'task-disabled-habit-control-inner',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
52
test/common/libs/hasClass.test.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import hasClass from '../../../website/common/script/libs/hasClass';
|
||||
import { generateUser } from '../../helpers/common.helper';
|
||||
|
||||
describe('hasClass', () => {
|
||||
it('returns false for user with level below 10', () => {
|
||||
let userLvl9 = generateUser({
|
||||
'stats.lvl': 9,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
|
||||
let result = hasClass(userLvl9);
|
||||
|
||||
expect(result).to.eql(false);
|
||||
});
|
||||
|
||||
it('returns false for user with class not selected', () => {
|
||||
let userClassNotSelected = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': false,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
|
||||
let result = hasClass(userClassNotSelected);
|
||||
|
||||
expect(result).to.eql(false);
|
||||
});
|
||||
|
||||
it('returns false for user with classes disabled', () => {
|
||||
let userClassesDisabled = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': true,
|
||||
});
|
||||
|
||||
let result = hasClass(userClassesDisabled);
|
||||
|
||||
expect(result).to.eql(false);
|
||||
});
|
||||
|
||||
it('returns true for user with class', () => {
|
||||
let userClassSelected = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
|
||||
let result = hasClass(userClassSelected);
|
||||
|
||||
expect(result).to.eql(true);
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import clearPMs from '../../../website/common/script/ops/clearPMs';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
|
||||
describe('shared.ops.clearPMs', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user.inbox.messages = { first: 'message', second: 'message' };
|
||||
});
|
||||
|
||||
it('clears messages', () => {
|
||||
expect(user.inbox.messages).to.not.eql({});
|
||||
let [result] = clearPMs(user);
|
||||
expect(user.inbox.messages).to.eql({});
|
||||
expect(result).to.eql({});
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import deletePM from '../../../website/common/script/ops/deletePM';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
|
||||
describe('shared.ops.deletePM', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user.inbox.messages = { first: 'message', second: 'message' };
|
||||
});
|
||||
|
||||
it('delete message', () => {
|
||||
expect(user.inbox.messages).to.not.eql({ second: 'message' });
|
||||
let [response] = deletePM(user, { params: { id: 'first' } });
|
||||
expect(user.inbox.messages).to.eql({ second: 'message' });
|
||||
expect(response).to.eql({ second: 'message' });
|
||||
});
|
||||
});
|
||||
38
test/common/ops/spells.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import spells from '../../../website/common/script/content/spells';
|
||||
import {
|
||||
NotAuthorized,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
|
||||
// TODO complete the test suite...
|
||||
|
||||
describe('shared.ops.spells', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
});
|
||||
|
||||
it('returns an error when healer tries to cast Healing Light with full health', (done) => {
|
||||
user.stats.class = 'healer';
|
||||
user.stats.lvl = 11;
|
||||
user.stats.hp = 50;
|
||||
user.stats.mp = 200;
|
||||
|
||||
let spell = spells.healer.heal;
|
||||
|
||||
try {
|
||||
spell.cast(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('messageHealthAlreadyMax'));
|
||||
expect(user.stats.hp).to.eql(50);
|
||||
expect(user.stats.mp).to.eql(200);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,11 @@ describe('shared.ops.allocate', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if an invalid attribute is supplied', (done) => {
|
||||
@@ -28,6 +32,39 @@ describe('shared.ops.allocate', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user is below lvl 10', (done) => {
|
||||
user.stats.lvl = 9;
|
||||
try {
|
||||
allocate(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user hasn\'t selected class', (done) => {
|
||||
user.flags.classSelected = false;
|
||||
try {
|
||||
allocate(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user has disabled classes', (done) => {
|
||||
user.preferences.disableClasses = true;
|
||||
try {
|
||||
allocate(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user doesn\'t have attribute points', (done) => {
|
||||
try {
|
||||
allocate(user);
|
||||
|
||||
@@ -13,7 +13,11 @@ describe('shared.ops.allocateBulk', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user = generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if an invalid attribute is supplied', (done) => {
|
||||
@@ -43,6 +47,60 @@ describe('shared.ops.allocateBulk', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user is below lvl 10', (done) => {
|
||||
user.stats.lvl = 9;
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
int: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user hasn\'t selected class', (done) => {
|
||||
user.flags.classSelected = false;
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
int: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user has disabled classes', (done) => {
|
||||
user.preferences.disableClasses = true;
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
int: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('classNotSelected'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user doesn\'t have attribute points', (done) => {
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
|
||||
@@ -15,21 +15,21 @@ div
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
template(v-if="isUserLoaded")
|
||||
div.resting-banner(v-if="showRestingBanner")
|
||||
div.resting-banner(v-show="showRestingBanner", ref="restingBanner")
|
||||
span.content
|
||||
span.label {{ $t('innCheckOutBanner') }}
|
||||
span.separator |
|
||||
span.label.d-inline.d-sm-none {{ $t('innCheckOutBannerShort') }}
|
||||
span.label.d-none.d-sm-inline {{ $t('innCheckOutBanner') }}
|
||||
span.separator |
|
||||
span.resume(@click="resumeDamage()") {{ $t('resumeDamage') }}
|
||||
div.closepadding(@click="hideBanner()")
|
||||
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close")
|
||||
notifications-display
|
||||
app-menu(:class='{"restingInn": showRestingBanner}')
|
||||
app-menu(:class='{"restingInn": showRestingBanner}' :style="{ marginTop: bannerHeight + 'px' }")
|
||||
.container-fluid
|
||||
app-header(:class='{"restingInn": showRestingBanner}')
|
||||
buyModal(
|
||||
:item="selectedItemToBuy || {}",
|
||||
:withPin="true",
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="customPurchase($event)",
|
||||
:genericPurchase="genericPurchase(selectedItemToBuy)",
|
||||
|
||||
@@ -119,20 +119,9 @@ div
|
||||
z-index: 1600 !important; /* Must stay above nav bar */
|
||||
}
|
||||
|
||||
.restingInn {
|
||||
.navbar {
|
||||
top: 40px;
|
||||
}
|
||||
|
||||
#app-header {
|
||||
margin-top: 40px !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.resting-banner {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
background-color: $blue-10;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -140,11 +129,10 @@ div
|
||||
display: flex;
|
||||
|
||||
.content {
|
||||
height: 24px;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
color: $white;
|
||||
|
||||
padding: 8px 38px 8px 8px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -161,6 +149,13 @@ div
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.content {
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: $blue-100;
|
||||
margin: 0px 15px;
|
||||
@@ -169,6 +164,7 @@ div
|
||||
.resume {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
white-space:nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -224,6 +220,7 @@ export default {
|
||||
loading: true,
|
||||
currentTipNumber: 0,
|
||||
bannerHidden: false,
|
||||
bannerHeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -414,7 +411,6 @@ export default {
|
||||
this.$store.watch(state => state.title, (title) => {
|
||||
document.title = title;
|
||||
});
|
||||
|
||||
this.$nextTick(() => {
|
||||
// Load external scripts after the app has been rendered
|
||||
Analytics.load();
|
||||
@@ -432,6 +428,14 @@ export default {
|
||||
|
||||
this.hideLoadingScreen();
|
||||
|
||||
window.addEventListener('resize', this.setBannerOffset);
|
||||
// Adjust the positioning of the header banners
|
||||
this.$watch('showRestingBanner', () => {
|
||||
this.$nextTick(() => {
|
||||
this.setBannerOffset();
|
||||
});
|
||||
}, {immediate: true});
|
||||
|
||||
// Adjust the timezone offset
|
||||
if (this.user.preferences.timezoneOffset !== this.browserTimezoneOffset) {
|
||||
this.$store.dispatch('user:set', {
|
||||
@@ -458,6 +462,7 @@ export default {
|
||||
this.$root.$off('bv::show::modal');
|
||||
this.$root.$off('buyModal::showItem');
|
||||
this.$root.$off('selectMembersModal::showItem');
|
||||
window.removeEventListener('resize', this.setBannerOffset);
|
||||
},
|
||||
mounted () {
|
||||
// Remove the index.html loading screen and now show the inapp loading
|
||||
@@ -567,13 +572,6 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
resetItemToBuy ($event) {
|
||||
// @TODO: Do we need this? I think selecting a new item
|
||||
// overwrites. @negue might know
|
||||
if (!$event && this.selectedItemToBuy.purchaseType !== 'card') {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
itemSelected (item) {
|
||||
this.selectedItemToBuy = item;
|
||||
},
|
||||
@@ -623,10 +621,22 @@ export default {
|
||||
},
|
||||
hideBanner () {
|
||||
this.bannerHidden = true;
|
||||
this.setBannerOffset();
|
||||
},
|
||||
resumeDamage () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
setBannerOffset () {
|
||||
let contentPlacement = 0;
|
||||
if (this.showRestingBanner && this.$refs.restingBanner !== undefined) {
|
||||
contentPlacement = this.$refs.restingBanner.clientHeight;
|
||||
}
|
||||
this.bannerHeight = contentPlacement;
|
||||
let smartBanner = document.getElementsByClassName('smartbanner')[0];
|
||||
if (smartBanner !== undefined) {
|
||||
smartBanner.style.top = `${contentPlacement}px`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -659,3 +669,4 @@ export default {
|
||||
<style src="assets/css/sprites/spritesmith-main-21.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-22.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
<style src="smartbanner.js/dist/smartbanner.min.css"></style>
|
||||
|
||||
@@ -1,42 +1,84 @@
|
||||
.promo_armoire_backgrounds_201808 {
|
||||
.achievement-costumeContest6x {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -817px 0px;
|
||||
background-position: -1076px -214px;
|
||||
width: 144px;
|
||||
height: 156px;
|
||||
}
|
||||
.promo_animal_tails {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -934px -214px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_armoire_backgrounds_201810 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -910px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_fall_festival_2017 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -699px;
|
||||
width: 414px;
|
||||
height: 210px;
|
||||
}
|
||||
.promo_fall_festival_2018 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -934px 0px;
|
||||
width: 393px;
|
||||
height: 213px;
|
||||
}
|
||||
.promo_forest_friends_bundle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -421px -495px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_ghost_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -415px -699px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_ios {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -441px 0px;
|
||||
background-position: 0px -337px;
|
||||
width: 375px;
|
||||
height: 361px;
|
||||
}
|
||||
.promo_kangaroo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -327px;
|
||||
background-position: 0px 0px;
|
||||
width: 420px;
|
||||
height: 336px;
|
||||
}
|
||||
.promo_mystery_201807 {
|
||||
.promo_mystery_201809 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -817px -442px;
|
||||
width: 114px;
|
||||
height: 120px;
|
||||
background-position: -934px -656px;
|
||||
width: 306px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_seasonal_shop {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1076px -371px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -817px -563px;
|
||||
background-position: -1221px -214px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_unconventional_armor {
|
||||
.scene_nametag {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -441px -362px;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background-position: -421px 0px;
|
||||
width: 512px;
|
||||
height: 208px;
|
||||
}
|
||||
.scene_tavern {
|
||||
.scene_tools {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 440px;
|
||||
height: 326px;
|
||||
background-position: -421px -209px;
|
||||
width: 366px;
|
||||
height: 285px;
|
||||
}
|
||||
|
||||
114
website/client/assets/css/sprites/spritesmith-main-23.css
Normal file
@@ -0,0 +1,114 @@
|
||||
.Pet_HatchingPotion_Floral {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -69px 0px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Ghost {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -207px 0px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Glass {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: 0px -69px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Glow {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -69px -69px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -138px 0px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Holly {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -138px -69px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Peppermint {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: 0px -138px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Purple {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -69px -138px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Rainbow {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -138px -138px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: 0px 0px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_RoyalPurple {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -207px -69px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -207px -138px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shimmer {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: 0px -207px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -69px -207px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Spooky {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -138px -207px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_StarryNight {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -207px -207px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Thunderstorm {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -276px 0px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -276px -69px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -276px -138px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
BIN
website/client/assets/images/npc/fall/npc_bailey.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
website/client/assets/images/npc/fall/npc_justin.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
website/client/assets/images/npc/fall/npc_matt.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 177 KiB |
|
Before Width: | Height: | Size: 549 KiB After Width: | Height: | Size: 554 KiB |
|
Before Width: | Height: | Size: 449 KiB After Width: | Height: | Size: 483 KiB |
|
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 419 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 79 KiB |