mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Merge remote-tracking branch 'origin/develop' into negue/flagpm
# Conflicts: # website/client/components/chat/chatCard.vue # website/client/components/chat/chatMessages.vue # website/common/locales/en/messages.json
This commit is contained in:
@@ -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",
|
||||
@@ -39,6 +39,7 @@
|
||||
"NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY",
|
||||
"GA_ID": "GA_ID",
|
||||
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
|
||||
"AMPLITUDE_SECRET": "AMPLITUDE_SECRET",
|
||||
"AMAZON_PAYMENTS": {
|
||||
"SELLER_ID": "SELLER_ID",
|
||||
"CLIENT_ID": "CLIENT_ID",
|
||||
@@ -88,12 +89,6 @@
|
||||
"USERNAME": "admin",
|
||||
"PASSWORD": "password"
|
||||
},
|
||||
"PUSHER": {
|
||||
"ENABLED": "false",
|
||||
"APP_ID": "appId",
|
||||
"KEY": "key",
|
||||
"SECRET": "secret"
|
||||
},
|
||||
"SLACK": {
|
||||
"FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||
|
||||
48
database_reports/20181001_backtoschool_challenge.js
Normal file
48
database_reports/20181001_backtoschool_challenge.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
|
||||
/*
|
||||
* Output data on users who completed all the To-Do tasks in the 2018 Back-to-School Challenge.
|
||||
* User ID,Profile Name
|
||||
*/
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
const CHALLENGE_ID = '0acb1d56-1660-41a4-af80-9259f080b62b';
|
||||
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
let dbTasks = monk(CONNECTION_STRING).get('tasks', { castIds: false });
|
||||
|
||||
function usersReport() {
|
||||
console.info('User ID,Profile Name');
|
||||
let userCount = 0;
|
||||
|
||||
dbUsers.find(
|
||||
{challenges: CHALLENGE_ID},
|
||||
{fields:
|
||||
{_id: 1, 'profile.name': 1}
|
||||
},
|
||||
).each((user, {close, pause, resume}) => {
|
||||
pause();
|
||||
userCount++;
|
||||
let completedTodos = 0;
|
||||
return dbTasks.find(
|
||||
{
|
||||
userId: user._id,
|
||||
'challenge.id': CHALLENGE_ID,
|
||||
type: 'todo',
|
||||
},
|
||||
{fields: {completed: 1}}
|
||||
).each((task) => {
|
||||
if (task.completed) completedTodos++;
|
||||
}).then(() => {
|
||||
if (completedTodos >= 7) {
|
||||
console.info(`${user._id},${user.profile.name}`);
|
||||
}
|
||||
resume();
|
||||
});
|
||||
}).then(() => {
|
||||
console.info(`${userCount} users reviewed`);
|
||||
return process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = usersReport;
|
||||
@@ -8,6 +8,7 @@ const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is do
|
||||
|
||||
const monk = require('monk');
|
||||
const nconf = require('nconf');
|
||||
const uuid = require('uuid').v4;
|
||||
|
||||
const Inbox = require('../website/server/models/message').inboxModel;
|
||||
const connectionString = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
||||
@@ -84,16 +85,39 @@ function updateUser (user) {
|
||||
return newMsg.toJSON();
|
||||
});
|
||||
|
||||
return dbInboxes.insert(newInboxMessages)
|
||||
.then(() => {
|
||||
const promises = newInboxMessages.map(newMsg => {
|
||||
return (async function fn () {
|
||||
const existing = await dbInboxes.find({_id: newMsg._id});
|
||||
|
||||
if (existing.length > 0) {
|
||||
if (
|
||||
existing[0].ownerId === newMsg.ownerId &&
|
||||
existing[0].text === newMsg.text &&
|
||||
existing[0].uuid === newMsg.uuid &&
|
||||
existing[0].sent === newMsg.sent
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
newMsg.id = newMsg._id = uuid();
|
||||
}
|
||||
|
||||
return newMsg;
|
||||
})();
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then((filteredNewMsg) => {
|
||||
filteredNewMsg = filteredNewMsg.filter(m => Boolean(m && m.id && m._id && m.id == m._id));
|
||||
return dbInboxes.insert(filteredNewMsg);
|
||||
}).then(() => {
|
||||
return dbUsers.update({_id: user._id}, {
|
||||
$set: {
|
||||
migration: migrationName,
|
||||
'inbox.messages': {},
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
107
migrations/archive/2018/20181002_username_email.js
Normal file
107
migrations/archive/2018/20181002_username_email.js
Normal file
@@ -0,0 +1,107 @@
|
||||
const MIGRATION_NAME = '20181003_username_email.js';
|
||||
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Send emails to eligible users announcing upcoming username changes
|
||||
*/
|
||||
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
import { sendTxn } from '../../website/server/libs/email';
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
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 = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2018-04-01')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 100,
|
||||
fields: [
|
||||
'_id',
|
||||
'auth',
|
||||
'preferences',
|
||||
'profile',
|
||||
], // 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(() => delay(7000))
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}});
|
||||
|
||||
sendTxn(
|
||||
user,
|
||||
'username-change',
|
||||
[{name: 'UNSUB_EMAIL_TYPE_URL', content: '/user/settings/notifications?unsubFrom=importantAnnouncements'},
|
||||
{name: 'LOGIN_NAME', content: user.auth.local.username}]
|
||||
);
|
||||
|
||||
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 delay (t, v) {
|
||||
return new Promise(function batchPause (resolve) {
|
||||
setTimeout(resolve.bind(null, v), t);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
109
migrations/archive/2018/20181108_username_email.js
Normal file
109
migrations/archive/2018/20181108_username_email.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const MIGRATION_NAME = '20181108_username_email.js';
|
||||
const AUTHOR_NAME = 'Sabe'; // in case script author needs to know when their ...
|
||||
const AUTHOR_UUID = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Send emails to eligible users announcing upcoming username changes
|
||||
*/
|
||||
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
import { sendTxn } from '../../../website/server/libs/email';
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
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 = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'flags.verifiedUsername': {$ne: true},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2018-10-25')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 100,
|
||||
fields: [
|
||||
'_id',
|
||||
'auth',
|
||||
'preferences',
|
||||
'profile',
|
||||
], // 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(() => delay(7000))
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}});
|
||||
|
||||
sendTxn(
|
||||
user,
|
||||
'username-change-follow-up',
|
||||
[{name: 'LOGIN_NAME', content: user.auth.local.username},
|
||||
{name: 'BASE_URL', content: BASE_URL}]
|
||||
);
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
if (user._id === AUTHOR_UUID) console.warn(`${AUTHOR_NAME} processed`);
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function delay (t, v) {
|
||||
return new Promise(function batchPause (resolve) {
|
||||
setTimeout(resolve.bind(null, v), t);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -17,5 +17,12 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
const processUsers = require('./users/takeThis.js');
|
||||
processUsers();
|
||||
const processUsers = require('../scripts/gdpr-delete-users.js');
|
||||
processUsers()
|
||||
.then(function success () {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(function failure (err) {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
94
migrations/users/20181023_veteran_pet_ladder.js
Normal file
94
migrations/users/20181023_veteran_pet_ladder.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20181023_veteran_pet_ladder';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
function processUsers (lastId) {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'flags.verifiedUsername': true,
|
||||
};
|
||||
|
||||
const fields = {
|
||||
'items.pets': 1,
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
return User.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.exec()
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${err}`);
|
||||
});
|
||||
}
|
||||
|
||||
const 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(() => {
|
||||
return processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
user.migration = MIGRATION_NAME;
|
||||
|
||||
if (user.items.pets['Bear-Veteran']) {
|
||||
user.items.pets['Fox-Veteran'] = 5;
|
||||
} else if (user.items.pets['Lion-Veteran']) {
|
||||
user.items.pets['Bear-Veteran'] = 5;
|
||||
} else if (user.items.pets['Tiger-Veteran']) {
|
||||
user.items.pets['Lion-Veteran'] = 5;
|
||||
} else if (user.items.pets['Wolf-Veteran']) {
|
||||
user.items.pets['Tiger-Veteran'] = 5;
|
||||
} else {
|
||||
user.items.pets['Wolf-Veteran'] = 5;
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return user.save();
|
||||
}
|
||||
|
||||
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;
|
||||
116
migrations/users/20181030_habitoween_ladder.js
Normal file
116
migrations/users/20181030_habitoween_ladder.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Award Habitoween ladder items to participants in this month's Habitoween festivities
|
||||
*/
|
||||
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
const MIGRATION_NAME = '20181030_habitoween_ladder.js'; // Update when running in future years
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
const AUTHOR_NAME = 'Sabe'; // in case script author needs to know when their ...
|
||||
const AUTHOR_UUID = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
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 = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2018-10-01')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
'items.mounts',
|
||||
'items.pets',
|
||||
], // 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}`);
|
||||
});
|
||||
}
|
||||
|
||||
const PROGRESS_COUNT = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
let set = {};
|
||||
let inc = {
|
||||
'items.food.Candy_Skeleton': 1,
|
||||
'items.food.Candy_Base': 1,
|
||||
'items.food.Candy_CottonCandyBlue': 1,
|
||||
'items.food.Candy_CottonCandyPink': 1,
|
||||
'items.food.Candy_Shade': 1,
|
||||
'items.food.Candy_White': 1,
|
||||
'items.food.Candy_Golden': 1,
|
||||
'items.food.Candy_Zombie': 1,
|
||||
'items.food.Candy_Desert': 1,
|
||||
'items.food.Candy_Red': 1,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets && user.items.mounts['JackOLantern-Ghost']) {
|
||||
set['items.pets.JackOLantern-Glow'] = 5;
|
||||
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
|
||||
set['items.mounts.JackOLantern-Ghost'] = true;
|
||||
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
|
||||
set['items.pets.JackOLantern-Ghost'] = 5;
|
||||
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
|
||||
set['items.mounts.JackOLantern-Base'] = true;
|
||||
} else {
|
||||
set['items.pets.JackOLantern-Base'] = 5;
|
||||
}
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set, $inc: inc});
|
||||
|
||||
if (count % PROGRESS_COUNT === 0) console.warn(`${count} ${user._id}`);
|
||||
if (user._id === AUTHOR_UUID) console.warn(`${AUTHOR_NAME} 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;
|
||||
99
migrations/users/generate-usernames.js
Normal file
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;
|
||||
@@ -8,7 +8,7 @@ const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is do
|
||||
/*
|
||||
* Award this month's mystery items to subscribers
|
||||
*/
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201808', 'head_mystery_201808'];
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201810', 'head_mystery_201810'];
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
|
||||
17293
package-lock.json
generated
17293
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.61.0",
|
||||
"version": "4.71.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -11,7 +11,7 @@
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"autoprefixer": "^8.5.0",
|
||||
"aws-sdk": "^2.239.1",
|
||||
"aws-sdk": "^2.329.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": "github:MylesBorins/node.bcrypt.js#update-nan",
|
||||
"bcrypt": "^3.0.1",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^4.1.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.9",
|
||||
@@ -35,7 +35,7 @@
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.1.5",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^3.0.0",
|
||||
"csv-stringify": "^4.3.1",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"express": "^4.16.3",
|
||||
@@ -46,29 +46,29 @@
|
||||
"got": "^9.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^7.0.1",
|
||||
"gulp-imagemin": "^4.1.0",
|
||||
"gulp-nodemon": "^2.2.1",
|
||||
"gulp-imagemin": "^5.0.3",
|
||||
"gulp-nodemon": "^2.4.1",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.15.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"image-size": "^0.6.2",
|
||||
"in-app-purchase": "^1.9.4",
|
||||
"in-app-purchase": "^1.10.2",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": ">=3.0.0",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
"lodash": "^4.17.10",
|
||||
"merge-stream": "^1.0.0",
|
||||
"method-override": "^2.3.5",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.1.2",
|
||||
"mongoose": "^5.3.4",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^0.14.4",
|
||||
"node-gcm": "^1.0.2",
|
||||
"node-sass": "^4.9.0",
|
||||
"nodemailer": "^4.6.4",
|
||||
"ora": "^2.1.0",
|
||||
"ora": "^3.0.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.0.0",
|
||||
@@ -79,13 +79,13 @@
|
||||
"postcss-easy-import": "^3.0.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"pug": "^2.0.3",
|
||||
"pusher": "^1.3.0",
|
||||
"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",
|
||||
"superagent": "^4.0.0",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"svgo": "^1.0.5",
|
||||
@@ -162,7 +162,7 @@
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"expect.js": "^0.3.1",
|
||||
"http-proxy-middleware": "^0.18.0",
|
||||
"http-proxy-middleware": "^0.19.0",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"karma": "^3.0.0",
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
@@ -183,7 +183,7 @@
|
||||
"puppeteer": "^1.4.0",
|
||||
"require-again": "^2.0.0",
|
||||
"selenium-server": "^3.12.0",
|
||||
"sinon": "^4.5.0",
|
||||
"sinon": "^6.3.5",
|
||||
"sinon-chai": "^3.0.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"webpack-bundle-analyzer": "^2.12.0",
|
||||
|
||||
88
scripts/gdpr-delete-users.js
Normal file
88
scripts/gdpr-delete-users.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/* eslint-disable no-console */
|
||||
import axios from 'axios';
|
||||
import { model as User } from '../website/server/models/user';
|
||||
import nconf from 'nconf';
|
||||
|
||||
const AMPLITUDE_KEY = nconf.get('AMPLITUDE_KEY');
|
||||
const AMPLITUDE_SECRET = nconf.get('AMPLITUDE_SECRET');
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
async function _deleteAmplitudeData (userId, email) {
|
||||
const response = await axios.post(
|
||||
'https://amplitude.com/api/2/deletions/users',
|
||||
{
|
||||
user_ids: userId, // eslint-disable-line camelcase
|
||||
requester: email,
|
||||
},
|
||||
{
|
||||
auth: {
|
||||
username: AMPLITUDE_KEY,
|
||||
password: AMPLITUDE_SECRET,
|
||||
},
|
||||
}
|
||||
).catch((err) => {
|
||||
console.log(err.response.data);
|
||||
});
|
||||
|
||||
if (response) console.log(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
async function _deleteHabiticaData (user) {
|
||||
await User.update(
|
||||
{_id: user._id},
|
||||
{$set: {
|
||||
'auth.local.passwordHashMethod': 'bcrypt',
|
||||
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
|
||||
}}
|
||||
);
|
||||
const response = await axios.delete(
|
||||
`${BASE_URL}/api/v3/user`,
|
||||
{
|
||||
data: {
|
||||
password: 'test',
|
||||
},
|
||||
headers: {
|
||||
'x-api-user': user._id,
|
||||
'x-api-key': user.apiToken,
|
||||
},
|
||||
}
|
||||
).catch((err) => {
|
||||
console.log(err.response.data);
|
||||
});
|
||||
|
||||
if (response) {
|
||||
console.log(`${response.status} ${response.statusText}`);
|
||||
if (response.status === 200) console.log(`${user._id} removed. Last login: ${user.auth.timestamps.loggedin}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function _processEmailAddress (email) {
|
||||
const emailRegex = new RegExp(`^${email}`, 'i');
|
||||
const users = await User.find({
|
||||
$or: [
|
||||
{'auth.local.email': emailRegex},
|
||||
{'auth.facebook.emails.value': emailRegex},
|
||||
{'auth.google.emails.value': emailRegex},
|
||||
]},
|
||||
{
|
||||
_id: 1,
|
||||
apiToken: 1,
|
||||
auth: 1,
|
||||
}).exec();
|
||||
|
||||
if (users.length < 1) {
|
||||
console.log(`No users found with email address ${email}`);
|
||||
} else {
|
||||
for (const user of users) {
|
||||
await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
|
||||
await _deleteHabiticaData(user); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteUserData (emails) {
|
||||
const emailPromises = emails.map(_processEmailAddress);
|
||||
return Promise.all(emailPromises);
|
||||
}
|
||||
|
||||
module.exports = deleteUserData;
|
||||
@@ -5,10 +5,17 @@ describe('Base model plugin', () => {
|
||||
let schema;
|
||||
|
||||
beforeEach(() => {
|
||||
schema = new mongoose.Schema();
|
||||
schema = new mongoose.Schema({}, {
|
||||
typeKey: '$type',
|
||||
});
|
||||
sandbox.stub(schema, 'add');
|
||||
});
|
||||
|
||||
it('throws if "typeKey" is not set to $type', () => {
|
||||
const schemaWithoutTypeKey = new mongoose.Schema();
|
||||
expect(() => schemaWithoutTypeKey.plugin(baseModel)).to.throw;
|
||||
});
|
||||
|
||||
it('adds a _id field to the schema', () => {
|
||||
schema.plugin(baseModel);
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ describe('Amazon Payments - Cancel Subscription', () => {
|
||||
|
||||
function expectBillingAggreementDetailSpy () {
|
||||
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails')
|
||||
.returnsPromise()
|
||||
.resolves({
|
||||
BillingAgreementDetails: {
|
||||
BillingAgreementStatus: {State: 'Open'},
|
||||
@@ -80,14 +79,14 @@ describe('Amazon Payments - Cancel Subscription', () => {
|
||||
headers = {};
|
||||
|
||||
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails');
|
||||
getBillingAgreementDetailsSpy.returnsPromise().resolves({
|
||||
getBillingAgreementDetailsSpy.resolves({
|
||||
BillingAgreementDetails: {
|
||||
BillingAgreementStatus: {State: 'Closed'},
|
||||
},
|
||||
});
|
||||
|
||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription');
|
||||
paymentCancelSubscriptionSpy.returnsPromise().resolves({});
|
||||
paymentCancelSubscriptionSpy.resolves({});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@@ -118,7 +117,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
|
||||
it('should close a user subscription if amazon not closed', async () => {
|
||||
amzLib.getBillingAgreementDetails.restore();
|
||||
expectBillingAggreementDetailSpy();
|
||||
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').returnsPromise().resolves({});
|
||||
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({});
|
||||
billingAgreementId = user.purchased.plan.customerId;
|
||||
|
||||
await amzLib.cancelSubscription({user, headers});
|
||||
@@ -164,7 +163,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
|
||||
it('should close a group subscription if amazon not closed', async () => {
|
||||
amzLib.getBillingAgreementDetails.restore();
|
||||
expectBillingAggreementDetailSpy();
|
||||
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').returnsPromise().resolves({});
|
||||
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({});
|
||||
billingAgreementId = group.purchased.plan.customerId;
|
||||
|
||||
await amzLib.cancelSubscription({user, groupId: group._id, headers});
|
||||
|
||||
@@ -68,22 +68,22 @@ describe('Amazon Payments - Checkout', () => {
|
||||
orderReferenceId = 'orderReferenceId';
|
||||
|
||||
setOrderReferenceDetailsSpy = sinon.stub(amzLib, 'setOrderReferenceDetails');
|
||||
setOrderReferenceDetailsSpy.returnsPromise().resolves({});
|
||||
setOrderReferenceDetailsSpy.resolves({});
|
||||
|
||||
confirmOrderReferenceSpy = sinon.stub(amzLib, 'confirmOrderReference');
|
||||
confirmOrderReferenceSpy.returnsPromise().resolves({});
|
||||
confirmOrderReferenceSpy.resolves({});
|
||||
|
||||
authorizeSpy = sinon.stub(amzLib, 'authorize');
|
||||
authorizeSpy.returnsPromise().resolves({});
|
||||
authorizeSpy.resolves({});
|
||||
|
||||
closeOrderReferenceSpy = sinon.stub(amzLib, 'closeOrderReference');
|
||||
closeOrderReferenceSpy.returnsPromise().resolves({});
|
||||
closeOrderReferenceSpy.resolves({});
|
||||
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems');
|
||||
paymentBuyGemsStub.returnsPromise().resolves({});
|
||||
paymentBuyGemsStub.resolves({});
|
||||
|
||||
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription');
|
||||
paymentCreateSubscritionStub.returnsPromise().resolves({});
|
||||
paymentCreateSubscritionStub.resolves({});
|
||||
|
||||
sinon.stub(common, 'uuid').returns('uuid-generated');
|
||||
});
|
||||
@@ -111,7 +111,7 @@ describe('Amazon Payments - Checkout', () => {
|
||||
}
|
||||
|
||||
it('should purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await amzLib.checkout({user, orderReferenceId, headers});
|
||||
|
||||
expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD);
|
||||
@@ -140,7 +140,7 @@ describe('Amazon Payments - Checkout', () => {
|
||||
});
|
||||
|
||||
it('should error if user cannot get gems gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(amzLib.checkout({user, orderReferenceId, headers})).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
|
||||
@@ -46,16 +46,16 @@ describe('Amazon Payments - Subscribe', () => {
|
||||
headers = {};
|
||||
|
||||
amazonSetBillingAgreementDetailsSpy = sinon.stub(amzLib, 'setBillingAgreementDetails');
|
||||
amazonSetBillingAgreementDetailsSpy.returnsPromise().resolves({});
|
||||
amazonSetBillingAgreementDetailsSpy.resolves({});
|
||||
|
||||
amazonConfirmBillingAgreementSpy = sinon.stub(amzLib, 'confirmBillingAgreement');
|
||||
amazonConfirmBillingAgreementSpy.returnsPromise().resolves({});
|
||||
amazonConfirmBillingAgreementSpy.resolves({});
|
||||
|
||||
amazonAuthorizeOnBillingAgreementSpy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
|
||||
amazonAuthorizeOnBillingAgreementSpy.returnsPromise().resolves({});
|
||||
amazonAuthorizeOnBillingAgreementSpy.resolves({});
|
||||
|
||||
createSubSpy = sinon.stub(payments, 'createSubscription');
|
||||
createSubSpy.returnsPromise().resolves({});
|
||||
createSubSpy.resolves({});
|
||||
|
||||
sinon.stub(common, 'uuid').returns('uuid-generated');
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('#upgradeGroupPlan', () => {
|
||||
await group.save();
|
||||
|
||||
spy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
|
||||
spy.returnsPromise().resolves([]);
|
||||
spy.resolves([]);
|
||||
|
||||
uuidString = 'uuid-v4';
|
||||
sinon.stub(uuid, 'v4').returns(uuidString);
|
||||
|
||||
@@ -24,16 +24,16 @@ describe('Apple Payments', () => {
|
||||
headers = {};
|
||||
|
||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||
.returnsPromise().resolves();
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||
.returnsPromise().resolves({});
|
||||
.resolves({});
|
||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||
.returns(true);
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
.returns([{productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||
transactionId: token,
|
||||
}]);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -70,7 +70,7 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
@@ -82,7 +82,7 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
|
||||
it('errors if amount does not exist', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
.returns([{productId: 'badProduct',
|
||||
@@ -130,7 +130,7 @@ describe('Apple Payments', () => {
|
||||
transactionId: token,
|
||||
}]);
|
||||
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
@@ -167,9 +167,9 @@ describe('Apple Payments', () => {
|
||||
nextPaymentProcessing = moment.utc().add({days: 2});
|
||||
|
||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||
.returnsPromise().resolves();
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||
.returnsPromise().resolves({});
|
||||
.resolves({});
|
||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||
.returns(true);
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
@@ -186,7 +186,7 @@ describe('Apple Payments', () => {
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
}]);
|
||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -297,9 +297,9 @@ describe('Apple Payments', () => {
|
||||
expirationDate = moment.utc();
|
||||
|
||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||
.returnsPromise().resolves();
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
expirationDate,
|
||||
});
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
@@ -314,7 +314,7 @@ describe('Apple Payments', () => {
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.additionalData = receipt;
|
||||
|
||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
||||
@@ -24,12 +24,12 @@ describe('Google Payments', () => {
|
||||
headers = {};
|
||||
|
||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||
.returnsPromise().resolves();
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||
.returnsPromise().resolves({});
|
||||
.resolves({});
|
||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||
.returns(true);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -64,7 +64,7 @@ describe('Google Payments', () => {
|
||||
});
|
||||
|
||||
it('should throw an error if user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -77,7 +77,7 @@ describe('Google Payments', () => {
|
||||
});
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
@@ -116,12 +116,12 @@ describe('Google Payments', () => {
|
||||
nextPaymentProcessing = moment.utc().add({days: 2});
|
||||
|
||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||
.returnsPromise().resolves();
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||
.returnsPromise().resolves({});
|
||||
.resolves({});
|
||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||
.returns(true);
|
||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -193,9 +193,9 @@ describe('Google Payments', () => {
|
||||
expirationDate = moment.utc();
|
||||
|
||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||
.returnsPromise().resolves();
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
expirationDate,
|
||||
});
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
@@ -210,7 +210,7 @@ describe('Google Payments', () => {
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.additionalData = {data: receipt, signature};
|
||||
|
||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
||||
@@ -69,11 +69,11 @@ describe('Purchasing a group plan for group', () => {
|
||||
};
|
||||
|
||||
let subscriptionId = 'subId';
|
||||
sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
|
||||
sinon.stub(stripe.customers, 'del').resolves({});
|
||||
|
||||
let currentPeriodEndTimeStamp = moment().add(3, 'months').unix();
|
||||
sinon.stub(stripe.customers, 'retrieve')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
subscriptions: {
|
||||
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase
|
||||
},
|
||||
@@ -216,7 +216,6 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (Amazon)', async () => {
|
||||
sinon.stub(amzLib, 'getBillingAgreementDetails')
|
||||
.returnsPromise()
|
||||
.resolves({
|
||||
BillingAgreementDetails: {
|
||||
BillingAgreementStatus: {State: 'Closed'},
|
||||
@@ -251,9 +250,9 @@ describe('Purchasing a group plan for group', () => {
|
||||
});
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (PayPal)', async () => {
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').resolves({});
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
agreement_details: { // eslint-disable-line camelcase
|
||||
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
|
||||
cycles_completed: 1, // eslint-disable-line camelcase
|
||||
@@ -449,7 +448,6 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
it('adds months to members with existing recurring subscription (Amazon)', async () => {
|
||||
sinon.stub(amzLib, 'getBillingAgreementDetails')
|
||||
.returnsPromise()
|
||||
.resolves({
|
||||
BillingAgreementDetails: {
|
||||
BillingAgreementStatus: {State: 'Closed'},
|
||||
@@ -478,9 +476,9 @@ describe('Purchasing a group plan for group', () => {
|
||||
});
|
||||
|
||||
it('adds months to members with existing recurring subscription (Paypal)', async () => {
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').resolves({});
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
agreement_details: { // eslint-disable-line camelcase
|
||||
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
|
||||
cycles_completed: 1, // eslint-disable-line camelcase
|
||||
|
||||
@@ -446,6 +446,19 @@ describe('payments/index', () => {
|
||||
fakeClock.restore();
|
||||
});
|
||||
|
||||
it('does not add a notification for mystery items if none was awarded', async () => {
|
||||
const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016
|
||||
let fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe);
|
||||
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(0);
|
||||
expect(user.notifications.find(n => n.type === 'NEW_MYSTERY_ITEMS')).to.be.undefined;
|
||||
|
||||
fakeClock.restore();
|
||||
});
|
||||
|
||||
it('does not award mystery item when user already owns the item', async () => {
|
||||
let mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
|
||||
let fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
|
||||
|
||||
@@ -13,9 +13,9 @@ describe('checkout success', () => {
|
||||
customerId = 'customerId-test';
|
||||
paymentId = 'paymentId-test';
|
||||
|
||||
paypalPaymentExecuteStub = sinon.stub(paypalPayments, 'paypalPaymentExecute').returnsPromise().resolves({});
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
|
||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
||||
paypalPaymentExecuteStub = sinon.stub(paypalPayments, 'paypalPaymentExecute').resolves({});
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('checkout', () => {
|
||||
beforeEach(() => {
|
||||
approvalHerf = 'approval_href';
|
||||
paypalPaymentCreateStub = sinon.stub(paypalPayments, 'paypalPaymentCreate')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
links: [{ rel: 'approval_url', href: approvalHerf }],
|
||||
});
|
||||
});
|
||||
@@ -80,7 +80,7 @@ describe('checkout', () => {
|
||||
|
||||
it('should error if the user cannot get gems', async () => {
|
||||
let user = new User();
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(paypalPayments.checkout({user})).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
|
||||
@@ -34,8 +34,8 @@ describe('ipn', () => {
|
||||
group.purchased.plan.lastBillingDate = new Date();
|
||||
await group.save();
|
||||
|
||||
ipnVerifyAsyncStub = sinon.stub(paypalPayments, 'ipnVerifyAsync').returnsPromise().resolves({});
|
||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
||||
ipnVerifyAsyncStub = sinon.stub(paypalPayments, 'ipnVerifyAsync').resolves({});
|
||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
||||
@@ -38,15 +38,15 @@ describe('subscribeCancel', () => {
|
||||
|
||||
nextBillingDate = new Date();
|
||||
|
||||
paypalBillingAgreementCancelStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
|
||||
paypalBillingAgreementCancelStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').resolves({});
|
||||
paypalBillingAgreementGetStub = sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
agreement_details: {
|
||||
next_billing_date: nextBillingDate,
|
||||
cycles_completed: 1,
|
||||
},
|
||||
});
|
||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
||||
@@ -28,10 +28,10 @@ describe('subscribeSuccess', () => {
|
||||
customerId = 'test-customerId';
|
||||
|
||||
paypalBillingAgreementExecuteStub = sinon.stub(paypalPayments, 'paypalBillingAgreementExecute')
|
||||
.returnsPromise({}).resolves({
|
||||
.resolves({
|
||||
id: customerId,
|
||||
});
|
||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('subscribe', () => {
|
||||
sub = Object.assign({}, common.content.subscriptionBlocks[subKey]);
|
||||
|
||||
paypalBillingAgreementCreateStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCreate')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
links: [{ rel: 'approval_url', href: approvalHerf }],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,12 +82,12 @@ describe('cancel subscription', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
subscriptionId = 'subId';
|
||||
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
|
||||
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
||||
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').resolves({});
|
||||
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
|
||||
currentPeriodEndTimeStamp = (new Date()).getTime();
|
||||
stripeRetrieveStub = sinon.stub(stripe.customers, 'retrieve')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
subscriptions: {
|
||||
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('checkout with subscription', () => {
|
||||
token = 'test-token';
|
||||
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.returnsPromise().resolves;
|
||||
spy.resolves;
|
||||
|
||||
stripeCreateCustomerSpy = sinon.stub(stripe.customers, 'create');
|
||||
let stripCustomerResponse = {
|
||||
@@ -63,10 +63,10 @@ describe('checkout with subscription', () => {
|
||||
data: [{id: subscriptionId}],
|
||||
},
|
||||
};
|
||||
stripeCreateCustomerSpy.returnsPromise().resolves(stripCustomerResponse);
|
||||
stripeCreateCustomerSpy.resolves(stripCustomerResponse);
|
||||
|
||||
stripePaymentsCreateSubSpy = sinon.stub(payments, 'createSubscription');
|
||||
stripePaymentsCreateSubSpy.returnsPromise().resolves({});
|
||||
stripePaymentsCreateSubSpy.resolves({});
|
||||
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
|
||||
@@ -26,9 +26,9 @@ describe('checkout', () => {
|
||||
let stripCustomerResponse = {
|
||||
id: customerIdResponse,
|
||||
};
|
||||
stripeChargeStub = sinon.stub(stripe.charges, 'create').returnsPromise().resolves(stripCustomerResponse);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
|
||||
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
||||
stripeChargeStub = sinon.stub(stripe.charges, 'create').resolves(stripCustomerResponse);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -82,7 +82,7 @@ describe('checkout', () => {
|
||||
|
||||
it('should error if user cannot get gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
@@ -101,7 +101,7 @@ describe('checkout', () => {
|
||||
|
||||
it('should purchase gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
|
||||
@@ -98,11 +98,11 @@ describe('edit subscription', () => {
|
||||
beforeEach(() => {
|
||||
subscriptionId = 'subId';
|
||||
stripeListSubscriptionStub = sinon.stub(stripe.customers, 'listSubscriptions')
|
||||
.returnsPromise().resolves({
|
||||
.resolves({
|
||||
data: [{id: subscriptionId}],
|
||||
});
|
||||
|
||||
stripeUpdateSubscriptionStub = sinon.stub(stripe.customers, 'updateSubscription').returnsPromise().resolves({});
|
||||
stripeUpdateSubscriptionStub = sinon.stub(stripe.customers, 'updateSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('Stripe - Webhooks', () => {
|
||||
const eventRetrieved = {type: eventType};
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves(eventRetrieved);
|
||||
sinon.stub(stripe.events, 'retrieve').resolves(eventRetrieved);
|
||||
sinon.stub(logger, 'error');
|
||||
});
|
||||
|
||||
@@ -52,8 +52,8 @@ describe('Stripe - Webhooks', () => {
|
||||
const eventType = 'customer.subscription.deleted';
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
|
||||
sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
||||
sinon.stub(stripe.customers, 'del').resolves({});
|
||||
sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -62,7 +62,7 @@ describe('Stripe - Webhooks', () => {
|
||||
});
|
||||
|
||||
it('does not do anything if event.request is null (subscription cancelled manually)', async () => {
|
||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
request: 123,
|
||||
@@ -79,7 +79,7 @@ describe('Stripe - Webhooks', () => {
|
||||
describe('user subscription', () => {
|
||||
it('throws an error if the user is not found', async () => {
|
||||
const customerId = 456;
|
||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -113,7 +113,7 @@ describe('Stripe - Webhooks', () => {
|
||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||
await subscriber.save();
|
||||
|
||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -146,7 +146,7 @@ describe('Stripe - Webhooks', () => {
|
||||
describe('group plan subscription', () => {
|
||||
it('throws an error if the group is not found', async () => {
|
||||
const customerId = 456;
|
||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -185,7 +185,7 @@ describe('Stripe - Webhooks', () => {
|
||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||
await subscriber.save();
|
||||
|
||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -227,7 +227,7 @@ describe('Stripe - Webhooks', () => {
|
||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||
await subscriber.save();
|
||||
|
||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('Stripe - Upgrade Group Plan', () => {
|
||||
await group.save();
|
||||
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.returnsPromise().resolves([]);
|
||||
spy.resolves([]);
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
stripePayments.setStripeApi(stripe);
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -477,7 +477,7 @@ describe('Group Model', () => {
|
||||
party.quest.active = false;
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
Group.prototype.sendChat.reset();
|
||||
Group.prototype.sendChat.resetHistory();
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
@@ -496,7 +496,7 @@ describe('Group Model', () => {
|
||||
party.quest.active = false;
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
Group.prototype.sendChat.reset();
|
||||
Group.prototype.sendChat.resetHistory();
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
@@ -569,7 +569,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('throws an error if no uuids or emails are passed in', async () => {
|
||||
await expect(Group.validateInvitations(null, null, res)).to.eventually.be.rejected.and.eql({
|
||||
await expect(Group.validateInvitations({}, res)).to.eventually.be.rejected.and.eql({
|
||||
httpCode: 400,
|
||||
message: 'Bad request.',
|
||||
name: 'BadRequest',
|
||||
@@ -579,7 +579,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('throws an error if only uuids are passed in, but they are not an array', async () => {
|
||||
await expect(Group.validateInvitations({ uuid: 'user-id'}, null, res)).to.eventually.be.rejected.and.eql({
|
||||
await expect(Group.validateInvitations({ uuids: 'user-id'}, res)).to.eventually.be.rejected.and.eql({
|
||||
httpCode: 400,
|
||||
message: 'Bad request.',
|
||||
name: 'BadRequest',
|
||||
@@ -589,7 +589,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('throws an error if only emails are passed in, but they are not an array', async () => {
|
||||
await expect(Group.validateInvitations(null, { emails: 'user@example.com'}, res)).to.eventually.be.rejected.and.eql({
|
||||
await expect(Group.validateInvitations({emails: 'user@example.com'}, res)).to.eventually.be.rejected.and.eql({
|
||||
httpCode: 400,
|
||||
message: 'Bad request.',
|
||||
name: 'BadRequest',
|
||||
@@ -599,27 +599,27 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('throws an error if emails are not passed in, and uuid array is empty', async () => {
|
||||
await expect(Group.validateInvitations([], null, res)).to.eventually.be.rejected.and.eql({
|
||||
await expect(Group.validateInvitations({uuids: []}, res)).to.eventually.be.rejected.and.eql({
|
||||
httpCode: 400,
|
||||
message: 'Bad request.',
|
||||
name: 'BadRequest',
|
||||
});
|
||||
expect(res.t).to.be.calledOnce;
|
||||
expect(res.t).to.be.calledWith('inviteMissingUuid');
|
||||
expect(res.t).to.be.calledWith('inviteMustNotBeEmpty');
|
||||
});
|
||||
|
||||
it('throws an error if uuids are not passed in, and email array is empty', async () => {
|
||||
await expect(Group.validateInvitations(null, [], res)).to.eventually.be.rejected.and.eql({
|
||||
await expect(Group.validateInvitations({emails: []}, res)).to.eventually.be.rejected.and.eql({
|
||||
httpCode: 400,
|
||||
message: 'Bad request.',
|
||||
name: 'BadRequest',
|
||||
});
|
||||
expect(res.t).to.be.calledOnce;
|
||||
expect(res.t).to.be.calledWith('inviteMissingEmail');
|
||||
expect(res.t).to.be.calledWith('inviteMustNotBeEmpty');
|
||||
});
|
||||
|
||||
it('throws an error if uuids and emails are passed in as empty arrays', async () => {
|
||||
await expect(Group.validateInvitations([], [], res)).to.eventually.be.rejected.and.eql({
|
||||
await expect(Group.validateInvitations({emails: [], uuids: []}, res)).to.eventually.be.rejected.and.eql({
|
||||
httpCode: 400,
|
||||
message: 'Bad request.',
|
||||
name: 'BadRequest',
|
||||
@@ -639,7 +639,7 @@ describe('Group Model', () => {
|
||||
|
||||
uuids.push('one-more-uuid'); // to put it over the limit
|
||||
|
||||
await expect(Group.validateInvitations(uuids, emails, res)).to.eventually.be.rejected.and.eql({
|
||||
await expect(Group.validateInvitations({uuids, emails}, res)).to.eventually.be.rejected.and.eql({
|
||||
httpCode: 400,
|
||||
message: 'Bad request.',
|
||||
name: 'BadRequest',
|
||||
@@ -657,33 +657,33 @@ describe('Group Model', () => {
|
||||
emails.push(`user-${i}@example.com`);
|
||||
}
|
||||
|
||||
await Group.validateInvitations(uuids, emails, res);
|
||||
await Group.validateInvitations({uuids, emails}, res);
|
||||
expect(res.t).to.not.be.called;
|
||||
});
|
||||
|
||||
|
||||
it('does not throw an error if only user ids are passed in', async () => {
|
||||
await Group.validateInvitations(['user-id', 'user-id2'], null, res);
|
||||
await Group.validateInvitations({uuids: ['user-id', 'user-id2']}, res);
|
||||
expect(res.t).to.not.be.called;
|
||||
});
|
||||
|
||||
it('does not throw an error if only emails are passed in', async () => {
|
||||
await Group.validateInvitations(null, ['user1@example.com', 'user2@example.com'], res);
|
||||
await Group.validateInvitations({emails: ['user1@example.com', 'user2@example.com']}, res);
|
||||
expect(res.t).to.not.be.called;
|
||||
});
|
||||
|
||||
it('does not throw an error if both uuids and emails are passed in', async () => {
|
||||
await Group.validateInvitations(['user-id', 'user-id2'], ['user1@example.com', 'user2@example.com'], res);
|
||||
await Group.validateInvitations({uuids: ['user-id', 'user-id2'], emails: ['user1@example.com', 'user2@example.com']}, res);
|
||||
expect(res.t).to.not.be.called;
|
||||
});
|
||||
|
||||
it('does not throw an error if uuids are passed in and emails are an empty array', async () => {
|
||||
await Group.validateInvitations(['user-id', 'user-id2'], [], res);
|
||||
await Group.validateInvitations({uuids: ['user-id', 'user-id2'], emails: []}, res);
|
||||
expect(res.t).to.not.be.called;
|
||||
});
|
||||
|
||||
it('does not throw an error if emails are passed in and uuids are an empty array', async () => {
|
||||
await Group.validateInvitations([], ['user1@example.com', 'user2@example.com'], res);
|
||||
await Group.validateInvitations({uuids: [], emails: ['user1@example.com', 'user2@example.com']}, res);
|
||||
expect(res.t).to.not.be.called;
|
||||
});
|
||||
});
|
||||
@@ -1843,6 +1843,62 @@ describe('Group Model', () => {
|
||||
expect(options.chat).to.eql(chat);
|
||||
});
|
||||
|
||||
it('sends webhooks for users with webhooks triggered by system messages', async () => {
|
||||
let guild = new Group({
|
||||
name: 'some guild',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
let memberWithWebhook = new User({
|
||||
guilds: [guild._id],
|
||||
webhooks: [{
|
||||
type: 'groupChatReceived',
|
||||
url: 'http://someurl.com',
|
||||
options: {
|
||||
groupId: guild._id,
|
||||
},
|
||||
}],
|
||||
});
|
||||
let memberWithoutWebhook = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
let nonMemberWithWebhooks = new User({
|
||||
webhooks: [{
|
||||
type: 'groupChatReceived',
|
||||
url: 'http://a-different-url.com',
|
||||
options: {
|
||||
groupId: generateUUID(),
|
||||
},
|
||||
}],
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
memberWithWebhook.save(),
|
||||
memberWithoutWebhook.save(),
|
||||
nonMemberWithWebhooks.save(),
|
||||
]);
|
||||
|
||||
guild.leader = memberWithWebhook._id;
|
||||
|
||||
await guild.save();
|
||||
|
||||
const groupMessage = guild.sendChat('Test message.');
|
||||
await groupMessage.save();
|
||||
|
||||
await sleep();
|
||||
|
||||
expect(groupChatReceivedWebhook.send).to.be.calledOnce;
|
||||
|
||||
let args = groupChatReceivedWebhook.send.args[0];
|
||||
let webhooks = args[0].webhooks;
|
||||
let options = args[1];
|
||||
|
||||
expect(webhooks).to.have.a.lengthOf(1);
|
||||
expect(webhooks[0].id).to.eql(memberWithWebhook.webhooks[0].id);
|
||||
expect(options.group).to.eql(guild);
|
||||
expect(options.chat).to.eql(groupMessage);
|
||||
});
|
||||
|
||||
it('sends webhooks for each user with webhooks in group', async () => {
|
||||
let guild = new Group({
|
||||
name: 'some guild',
|
||||
@@ -1932,28 +1988,54 @@ describe('Group Model', () => {
|
||||
|
||||
context('hasNotCancelled', () => {
|
||||
it('returns false if group does not have customer id', () => {
|
||||
expect(party.hasNotCancelled()).to.be.undefined;
|
||||
expect(party.hasNotCancelled()).to.be.false;
|
||||
});
|
||||
|
||||
it('returns true if party does not have plan.dateTerminated', () => {
|
||||
it('returns true if group does not have plan.dateTerminated', () => {
|
||||
party.purchased.plan.customerId = 'test-id';
|
||||
|
||||
expect(party.hasNotCancelled()).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false if party if plan.dateTerminated is after today', () => {
|
||||
it('returns false if group if plan.dateTerminated is after today', () => {
|
||||
party.purchased.plan.customerId = 'test-id';
|
||||
party.purchased.plan.dateTerminated = moment().add(1, 'days').toDate();
|
||||
|
||||
expect(party.hasNotCancelled()).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false if party if plan.dateTerminated is before today', () => {
|
||||
it('returns false if group if plan.dateTerminated is before today', () => {
|
||||
party.purchased.plan.customerId = 'test-id';
|
||||
party.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
|
||||
|
||||
expect(party.hasNotCancelled()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
context('hasCancelled', () => {
|
||||
it('returns false if group does not have customer id', () => {
|
||||
expect(party.hasCancelled()).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false if group does not have plan.dateTerminated', () => {
|
||||
party.purchased.plan.customerId = 'test-id';
|
||||
|
||||
expect(party.hasCancelled()).to.be.false;
|
||||
});
|
||||
|
||||
it('returns true if group if plan.dateTerminated is after today', () => {
|
||||
party.purchased.plan.customerId = 'test-id';
|
||||
party.purchased.plan.dateTerminated = moment().add(1, 'days').toDate();
|
||||
|
||||
expect(party.hasCancelled()).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false if group if plan.dateTerminated is before today', () => {
|
||||
party.purchased.plan.customerId = 'test-id';
|
||||
party.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
|
||||
|
||||
expect(party.hasCancelled()).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -315,9 +315,8 @@ describe('User Model', () => {
|
||||
user = new User();
|
||||
});
|
||||
|
||||
|
||||
it('returns false if user does not have customer id', () => {
|
||||
expect(user.hasNotCancelled()).to.be.undefined;
|
||||
expect(user.hasNotCancelled()).to.be.false;
|
||||
});
|
||||
|
||||
it('returns true if user does not have plan.dateTerminated', () => {
|
||||
@@ -341,6 +340,38 @@ describe('User Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
context('hasCancelled', () => {
|
||||
let user;
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
});
|
||||
|
||||
it('returns false if user does not have customer id', () => {
|
||||
expect(user.hasCancelled()).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false if user does not have plan.dateTerminated', () => {
|
||||
user.purchased.plan.customerId = 'test-id';
|
||||
|
||||
expect(user.hasCancelled()).to.be.false;
|
||||
});
|
||||
|
||||
it('returns true if user if plan.dateTerminated is after today', () => {
|
||||
user.purchased.plan.customerId = 'test-id';
|
||||
user.purchased.plan.dateTerminated = moment().add(1, 'days').toDate();
|
||||
|
||||
expect(user.hasCancelled()).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false if user if plan.dateTerminated is before today', () => {
|
||||
user.purchased.plan.customerId = 'test-id';
|
||||
user.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
|
||||
|
||||
expect(user.hasCancelled()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
context('pre-save hook', () => {
|
||||
it('does not try to award achievements when achievements or items not selected in query', async () => {
|
||||
let user = new User();
|
||||
|
||||
@@ -47,6 +47,14 @@ describe('GET /challenges/:challengeId', () => {
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader._id,
|
||||
profile: {name: groupLeader.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: groupLeader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(chal.group).to.eql({
|
||||
_id: group._id,
|
||||
@@ -105,6 +113,14 @@ describe('GET /challenges/:challengeId', () => {
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: challengeLeader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(chal.group).to.eql({
|
||||
_id: group._id,
|
||||
@@ -131,6 +147,14 @@ describe('GET /challenges/:challengeId', () => {
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: challengeLeader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -179,6 +203,14 @@ describe('GET /challenges/:challengeId', () => {
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: challengeLeader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(chal.group).to.eql({
|
||||
_id: group._id,
|
||||
@@ -205,6 +237,14 @@ describe('GET /challenges/:challengeId', () => {
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: challengeLeader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,6 +60,14 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader._id,
|
||||
profile: {name: groupLeader.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: groupLeader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,8 +81,16 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
_id: leader._id,
|
||||
id: leader._id,
|
||||
profile: {name: leader.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: leader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
@@ -88,8 +104,16 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
_id: anotherUser._id,
|
||||
id: anotherUser._id,
|
||||
profile: {name: anotherUser.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: anotherUser.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
@@ -107,7 +131,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
let res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=not-true`);
|
||||
expect(res.length).to.equal(30);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
});
|
||||
@@ -126,7 +150,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
let res = await user.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res.length).to.equal(30);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
});
|
||||
@@ -145,7 +169,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
let res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=true`);
|
||||
expect(res.length).to.equal(32);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]);
|
||||
|
||||
let memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`);
|
||||
expect(memberProgress).to.have.all.keys(['_id', 'id', 'profile', 'tasks']);
|
||||
expect(memberProgress).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile', 'tasks']);
|
||||
expect(memberProgress.profile).to.have.all.keys(['name']);
|
||||
expect(memberProgress.tasks.length).to.equal(1);
|
||||
});
|
||||
|
||||
@@ -39,6 +39,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
@@ -46,6 +54,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,6 +74,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
@@ -65,6 +89,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,6 +157,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: privateGuild.leader._id,
|
||||
id: privateGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
@@ -132,6 +172,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: privateGuild.leader._id,
|
||||
id: privateGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -235,6 +283,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: party.leader._id,
|
||||
id: party.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
@@ -242,6 +298,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: party.leader._id,
|
||||
id: party.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -254,6 +318,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: party.leader._id,
|
||||
id: party.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
@@ -261,6 +333,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: party.leader._id,
|
||||
id: party.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -288,6 +368,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
@@ -295,6 +383,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -307,6 +403,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
@@ -314,6 +418,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,6 +40,14 @@ describe('GET challenges/user', () => {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(foundChallenge.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
@@ -62,6 +70,14 @@ describe('GET challenges/user', () => {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(foundChallenge1.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
@@ -79,6 +95,14 @@ describe('GET challenges/user', () => {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(foundChallenge2.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
@@ -101,6 +125,14 @@ describe('GET challenges/user', () => {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(foundChallenge1.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
@@ -118,6 +150,14 @@ describe('GET challenges/user', () => {
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(foundChallenge2.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
|
||||
@@ -79,6 +79,14 @@ describe('POST /challenges/:challengeId/join', () => {
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader._id,
|
||||
profile: {name: groupLeader.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: groupLeader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(res.name).to.equal(challenge.name);
|
||||
});
|
||||
|
||||
@@ -79,6 +79,14 @@ describe('PUT /challenges/:challengeId', () => {
|
||||
_id: member._id,
|
||||
id: member._id,
|
||||
profile: {name: member.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: member.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(res.name).to.equal('New Challenge Name');
|
||||
expect(res.description).to.equal('New challenge description.');
|
||||
|
||||
@@ -203,7 +203,7 @@ describe('GET /groups', () => {
|
||||
let page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
|
||||
.to.eventually.have.a.lengthOf(1 + 4); // 1 created now, 4 by other tests
|
||||
expect(page2[4].name).to.equal('guild with less members');
|
||||
});
|
||||
}).timeout(10000);
|
||||
});
|
||||
|
||||
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
||||
|
||||
@@ -50,6 +50,14 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
_id: invited._id,
|
||||
id: invited._id,
|
||||
profile: {name: invited.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: invited.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,7 +66,7 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
let invited = await generateUser();
|
||||
await user.post(`/groups/${group._id}/invite`, {uuids: [invited._id]});
|
||||
let res = await user.get('/groups/party/invites');
|
||||
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
@@ -76,10 +84,10 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
let res = await leader.get(`/groups/${group._id}/invites`);
|
||||
expect(res.length).to.equal(30);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
});
|
||||
}).timeout(10000);
|
||||
|
||||
it('supports using req.query.lastId to get more invites', async function () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
|
||||
@@ -56,13 +56,21 @@ describe('GET /groups/:groupId/members', () => {
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('populates only some fields', async () => {
|
||||
await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let res = await user.get('/groups/party/members');
|
||||
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
@@ -74,7 +82,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
'size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
|
||||
@@ -95,7 +103,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
'size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
|
||||
@@ -120,7 +128,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
let res = await user.get('/groups/party/members');
|
||||
expect(res.length).to.equal(30);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
});
|
||||
@@ -137,7 +145,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
let res = await user.get('/groups/party/members?includeAllMembers=true');
|
||||
expect(res.length).to.equal(30);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,73 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('username invites', () => {
|
||||
it('returns an error when invited user is not found', async () => {
|
||||
const fakeID = 'fakeuserid';
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [fakeID],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithUsernameNotFound', {username: fakeID}),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when inviting yourself to a group', async () => {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [inviter.auth.local.lowerCaseUsername],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('cannotInviteSelfToGroup'),
|
||||
});
|
||||
});
|
||||
|
||||
it('invites a user to a group by username', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userToInvite.auth.local.lowerCaseUsername],
|
||||
})).to.eventually.deep.equal([{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
}]);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
|
||||
it('invites multiple users to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userToInvite.auth.local.lowerCaseUsername, userToInvite2.auth.local.lowerCaseUsername],
|
||||
})).to.eventually.deep.equal([
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('user id invites', () => {
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
|
||||
@@ -93,7 +160,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('inviteMissingUuid'),
|
||||
message: t('inviteMustNotBeEmpty'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -228,7 +295,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('inviteMissingEmail'),
|
||||
message: t('inviteMustNotBeEmpty'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -417,7 +484,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
expect(await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
})).to.be.an('array');
|
||||
});
|
||||
}).timeout(10000);
|
||||
|
||||
// @TODO: Add this after we are able to mock the group plan route
|
||||
xit('returns an error when a non-leader invites to a group plan', async () => {
|
||||
@@ -564,7 +631,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
expect(await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
})).to.be.an('array');
|
||||
});
|
||||
}).timeout(10000);
|
||||
|
||||
it('does not allow 30+ members in a party', async () => {
|
||||
let invitesToGenerate = [];
|
||||
@@ -582,6 +649,6 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
error: 'BadRequest',
|
||||
message: t('partyExceedsMembersLimit', {maxMembersParty: PARTY_LIMIT_MEMBERS}),
|
||||
});
|
||||
});
|
||||
}).timeout(10000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,5 +56,5 @@ describe('GET /hall/patrons', () => {
|
||||
expect(morePatrons.length).to.equal(2);
|
||||
expect(morePatrons[0].backer.tier).to.equal(2);
|
||||
expect(morePatrons[1].backer.tier).to.equal(1);
|
||||
});
|
||||
}).timeout(10000);
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('GET /members/:memberId', () => {
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
'size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('payments : amazon #subscribeCancel', () => {
|
||||
|
||||
describe('success', () => {
|
||||
beforeEach(() => {
|
||||
amazonSubscribeCancelStub = sinon.stub(amzLib, 'cancelSubscription').returnsPromise().resolves({});
|
||||
amazonSubscribeCancelStub = sinon.stub(amzLib, 'cancelSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('payments - amazon - #checkout', () => {
|
||||
|
||||
describe('success', () => {
|
||||
beforeEach(async () => {
|
||||
amazonCheckoutStub = sinon.stub(amzLib, 'checkout').returnsPromise().resolves({});
|
||||
amazonCheckoutStub = sinon.stub(amzLib, 'checkout').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('payments - amazon - #subscribe', () => {
|
||||
let coupon;
|
||||
|
||||
beforeEach(() => {
|
||||
subscribeWithAmazonStub = sinon.stub(amzLib, 'subscribe').returnsPromise().resolves({});
|
||||
subscribeWithAmazonStub = sinon.stub(amzLib, 'subscribe').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('payments : apple #cancelSubscribe', () => {
|
||||
let cancelStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
cancelStub = sinon.stub(applePayments, 'cancelSubscribe').returnsPromise().resolves({});
|
||||
cancelStub = sinon.stub(applePayments, 'cancelSubscribe').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('payments : apple #verify', () => {
|
||||
let verifyStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').returnsPromise().resolves({});
|
||||
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('payments : apple #subscribe', () => {
|
||||
let subscribeStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subscribeStub = sinon.stub(applePayments, 'subscribe').returnsPromise().resolves({});
|
||||
subscribeStub = sinon.stub(applePayments, 'subscribe').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('payments : google #cancelSubscribe', () => {
|
||||
let cancelStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
cancelStub = sinon.stub(googlePayments, 'cancelSubscribe').returnsPromise().resolves({});
|
||||
cancelStub = sinon.stub(googlePayments, 'cancelSubscribe').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('payments : google #subscribe', () => {
|
||||
let subscribeStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subscribeStub = sinon.stub(googlePayments, 'subscribe').returnsPromise().resolves({});
|
||||
subscribeStub = sinon.stub(googlePayments, 'subscribe').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('payments : google #verify', () => {
|
||||
let verifyStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').returnsPromise().resolves({});
|
||||
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('payments : paypal #checkout', () => {
|
||||
let checkoutStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
checkoutStub = sinon.stub(paypalPayments, 'checkout').returnsPromise().resolves('/');
|
||||
checkoutStub = sinon.stub(paypalPayments, 'checkout').resolves('/');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('payments : paypal #checkoutSuccess', () => {
|
||||
let checkoutSuccessStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
checkoutSuccessStub = sinon.stub(paypalPayments, 'checkoutSuccess').returnsPromise().resolves({});
|
||||
checkoutSuccessStub = sinon.stub(paypalPayments, 'checkoutSuccess').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('payments : paypal #subscribe', () => {
|
||||
let subscribeStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subscribeStub = sinon.stub(paypalPayments, 'subscribe').returnsPromise().resolves('/');
|
||||
subscribeStub = sinon.stub(paypalPayments, 'subscribe').resolves('/');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('payments : paypal #subscribeCancel', () => {
|
||||
let subscribeCancelStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subscribeCancelStub = sinon.stub(paypalPayments, 'subscribeCancel').returnsPromise().resolves('/');
|
||||
subscribeCancelStub = sinon.stub(paypalPayments, 'subscribeCancel').resolves('/');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('payments : paypal #subscribeSuccess', () => {
|
||||
let subscribeSuccessStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subscribeSuccessStub = sinon.stub(paypalPayments, 'subscribeSuccess').returnsPromise().resolves({});
|
||||
subscribeSuccessStub = sinon.stub(paypalPayments, 'subscribeSuccess').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('payments - paypal - #ipn', () => {
|
||||
let ipnStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
ipnStub = sinon.stub(paypalPayments, 'ipn').returnsPromise().resolves({});
|
||||
ipnStub = sinon.stub(paypalPayments, 'ipn').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('payments - stripe - #subscribeCancel', () => {
|
||||
|
||||
describe('success', () => {
|
||||
beforeEach(async () => {
|
||||
stripeCancelSubscriptionStub = sinon.stub(stripePayments, 'cancelSubscription').returnsPromise().resolves({});
|
||||
stripeCancelSubscriptionStub = sinon.stub(stripePayments, 'cancelSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('payments - stripe - #checkout', () => {
|
||||
let stripeCheckoutSubscriptionStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
stripeCheckoutSubscriptionStub = sinon.stub(stripePayments, 'checkout').returnsPromise().resolves({});
|
||||
stripeCheckoutSubscriptionStub = sinon.stub(stripePayments, 'checkout').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('payments - stripe - #subscribeEdit', () => {
|
||||
let stripeEditSubscriptionStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
stripeEditSubscriptionStub = sinon.stub(stripePayments, 'editSubscription').returnsPromise().resolves({});
|
||||
stripeEditSubscriptionStub = sinon.stub(stripePayments, 'editSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -20,8 +20,8 @@ describe('DELETE user message', () => {
|
||||
|
||||
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');
|
||||
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('second');
|
||||
expect(userRes.inbox.messages[messagesId[1]].text).to.eql('first');
|
||||
});
|
||||
|
||||
it('one message', async () => {
|
||||
@@ -31,7 +31,7 @@ describe('DELETE user message', () => {
|
||||
|
||||
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');
|
||||
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('first');
|
||||
});
|
||||
|
||||
it('clear all', async () => {
|
||||
|
||||
@@ -39,14 +39,16 @@ describe('POST /user/push-devices', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user already has the push device', async () => {
|
||||
it('fails silently if user already has the push device', async () => {
|
||||
await user.post('/user/push-devices', {type, regId});
|
||||
await expect(user.post('/user/push-devices', {type, regId}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('pushDeviceAlreadyAdded'),
|
||||
});
|
||||
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('adds a push device to the user', async () => {
|
||||
|
||||
@@ -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 ';
|
||||
@@ -259,7 +276,7 @@ describe('POST /user/auth/local/register', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('enrolls new users in an A/B test', async () => {
|
||||
xit('enrolls new users in an A/B test', async () => {
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import {
|
||||
generateUser,
|
||||
requester,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /user/auth/pusher', () => {
|
||||
let user;
|
||||
let endpoint = '/user/auth/pusher';
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('requires authentication', async () => {
|
||||
let api = requester();
|
||||
|
||||
await expect(api.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('missingAuthHeaders'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.body.socket_id is missing', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
channel_name: '123',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.body.channel_name is missing', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
socket_id: '123',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.body.channel_name is badly formatted', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
channel_name: '123',
|
||||
socket_id: '123',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid Pusher channel type.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if an invalid channel type is passed', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
channel_name: 'invalid-group-123',
|
||||
socket_id: '123',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid Pusher channel type.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if an invalid resource type is passed', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
channel_name: 'presence-user-123',
|
||||
socket_id: '123',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid Pusher resource type.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if an invalid resource id is passed', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
channel_name: 'presence-group-123',
|
||||
socket_id: '123',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid Pusher resource id, must be a UUID.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the passed resource id doesn\'t match the user\'s party', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
channel_name: `presence-group-${generateUUID()}`,
|
||||
socket_id: '123',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: 'Resource id must be the user\'s party.',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 () => {
|
||||
@@ -77,7 +80,7 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.newUser).to.be.false;
|
||||
});
|
||||
|
||||
it('enrolls a new user in an A/B test', async () => {
|
||||
xit('enrolls a new user in an A/B test', async () => {
|
||||
await api.post(endpoint, {
|
||||
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||
network,
|
||||
@@ -133,7 +136,7 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.newUser).to.be.false;
|
||||
});
|
||||
|
||||
it('enrolls a new user in an A/B test', async () => {
|
||||
xit('enrolls a new user in an A/B test', async () => {
|
||||
await api.post(endpoint, {
|
||||
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||
network,
|
||||
|
||||
@@ -12,14 +12,14 @@ const ENDPOINT = '/user/auth/update-username';
|
||||
|
||||
describe('PUT /user/auth/update-username', async () => {
|
||||
let user;
|
||||
let newUsername = 'new-username';
|
||||
let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
|
||||
let password = 'password'; // from habitrpg/test/helpers/api-integration/v4/object-generators.js
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('successfully changes username', async () => {
|
||||
it('successfully changes username with password', async () => {
|
||||
let newUsername = 'new-username';
|
||||
let response = await user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
password,
|
||||
@@ -29,6 +29,38 @@ describe('PUT /user/auth/update-username', async () => {
|
||||
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';
|
||||
@@ -80,6 +112,7 @@ describe('PUT /user/auth/update-username', async () => {
|
||||
});
|
||||
|
||||
it('errors if password is wrong', async () => {
|
||||
let newUsername = 'new-username';
|
||||
await expect(user.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
password: 'wrong-password',
|
||||
@@ -90,19 +123,6 @@ describe('PUT /user/auth/update-username', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents social-only user from changing username', async () => {
|
||||
let socialUser = await generateUser({ 'auth.local': { ok: true } });
|
||||
|
||||
await expect(socialUser.put(ENDPOINT, {
|
||||
username: newUsername,
|
||||
password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('userHasNoLocalRegistration'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if new username is not provided', async () => {
|
||||
await expect(user.put(ENDPOINT, {
|
||||
password,
|
||||
@@ -112,5 +132,93 @@ describe('PUT /user/auth/update-username', async () => {
|
||||
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'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,4 +53,15 @@ describe('POST /user/buy-gear/:key', () => {
|
||||
message: 'You need to purchase a lower level gear before this one.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if tries to buy gear from a different class', async () => {
|
||||
let key = 'armor_rogue_1';
|
||||
|
||||
return expect(user.post(`/user/buy-gear/${key}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: 'You can\'t buy this item.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,6 +53,6 @@ describe('GET /user', () => {
|
||||
let returnedUser = await user.get('/user');
|
||||
|
||||
expect(returnedUser._id).to.equal(user._id);
|
||||
expect(returnedUser.inbox.messages).to.be.empty;
|
||||
expect(returnedUser.inbox.messages).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('PUT /user', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -473,7 +473,7 @@ describe('POST /user/auth/local/register', () => {
|
||||
});
|
||||
|
||||
it('rejects if username is already taken', async () => {
|
||||
let uniqueEmail = `${generateRandomUserName()}@exampe.com`;
|
||||
let uniqueEmail = `${generateRandomUserName()}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
|
||||
57
test/api/v4/user/auth/POST-user_verify_display_name.test.js
Normal file
57
test/api/v4/user/auth/POST-user_verify_display_name.test.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v4';
|
||||
|
||||
const ENDPOINT = '/user/auth/verify-display-name';
|
||||
|
||||
describe('POST /user/auth/verify-display-name', async () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('successfully verifies display name including funky characters', async () => {
|
||||
let newDisplayName = 'Sabé 🤬';
|
||||
let response = await user.post(ENDPOINT, {
|
||||
displayName: newDisplayName,
|
||||
});
|
||||
expect(response).to.eql({ isUsable: true });
|
||||
});
|
||||
|
||||
context('errors', async () => {
|
||||
it('errors if display name 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 display name is a slur', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
displayName: 'TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueSlur')] });
|
||||
});
|
||||
|
||||
it('errors if display name contains a slur', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
displayName: 'TESTPLACEHOLDERSLURWORDHERE_otherword',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength'), t('displaynameIssueSlur')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
displayName: 'something_TESTPLACEHOLDERSLURWORDHERE',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength'), t('displaynameIssueSlur')] });
|
||||
await expect(user.post(ENDPOINT, {
|
||||
displayName: 'somethingTESTPLACEHOLDERSLURWORDHEREotherword',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength'), t('displaynameIssueSlur')] });
|
||||
});
|
||||
|
||||
it('errors if display name has incorrect length', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
displayName: 'this is a very long display name over 30 characters',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength')] });
|
||||
});
|
||||
});
|
||||
});
|
||||
89
test/api/v4/user/auth/POST-user_verify_username.test.js
Normal file
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')] });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ require('babel-polyfill');
|
||||
|
||||
// Automatically setup SinonJS' sandbox for each test
|
||||
beforeEach(() => {
|
||||
global.sandbox = sinon.sandbox.create();
|
||||
global.sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -2,12 +2,14 @@ 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';
|
||||
import { toNextLevel } from 'common/script/statHelpers';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Store);
|
||||
|
||||
describe('Notifications', () => {
|
||||
let store;
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
store = new Store({
|
||||
@@ -29,16 +31,22 @@ describe('Notifications', () => {
|
||||
actions: {
|
||||
'user:fetch': () => {},
|
||||
'tasks:fetchUserTasks': () => {},
|
||||
'snackbars:add': () => {},
|
||||
},
|
||||
getters: {
|
||||
'members:hasClass': hasClass,
|
||||
},
|
||||
});
|
||||
wrapper = shallowMount(NotificationsComponent, {
|
||||
store,
|
||||
localVue,
|
||||
mocks: {
|
||||
$t: (string) => string,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -47,4 +55,130 @@ describe('Notifications', () => {
|
||||
|
||||
expect(wrapper.vm.userHasClass).to.be.true;
|
||||
});
|
||||
describe('user exp notifcation', () => {
|
||||
it('notifies when user gets more exp', () => {
|
||||
const expSpy = sinon.spy(wrapper.vm, 'exp');
|
||||
|
||||
const userLevel = 10;
|
||||
store.state.user.data.stats.lvl = userLevel;
|
||||
|
||||
const userExpBefore = 10;
|
||||
const userExpAfter = 12;
|
||||
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevel, userLevel);
|
||||
|
||||
expect(expSpy).to.be.calledWith(userExpAfter - userExpBefore);
|
||||
expSpy.restore();
|
||||
});
|
||||
|
||||
it('when user levels with exact xp', () => {
|
||||
const expSpy = sinon.spy(wrapper.vm, 'exp');
|
||||
|
||||
const userLevelBefore = 9;
|
||||
const userLevelAfter = 10;
|
||||
store.state.user.data.stats.lvl = userLevelAfter;
|
||||
|
||||
const expEarned = 5;
|
||||
const userExpBefore = toNextLevel(userLevelBefore) - expEarned;
|
||||
const userExpAfter = 0;
|
||||
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
|
||||
|
||||
expect(expSpy).to.be.calledWith(expEarned);
|
||||
expSpy.restore();
|
||||
});
|
||||
|
||||
it('when user levels with exact more exp than needed', () => {
|
||||
const expSpy = sinon.spy(wrapper.vm, 'exp');
|
||||
|
||||
const userLevelBefore = 9;
|
||||
const userLevelAfter = 10;
|
||||
store.state.user.data.stats.lvl = userLevelAfter;
|
||||
|
||||
const expEarned = 10;
|
||||
const expNeeded = 5;
|
||||
const userExpBefore = toNextLevel(userLevelBefore) - expNeeded;
|
||||
const userExpAfter = 5;
|
||||
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
|
||||
|
||||
expect(expSpy).to.be.calledWith(expEarned);
|
||||
expSpy.restore();
|
||||
});
|
||||
|
||||
it('when user has more exp than needed then levels', () => {
|
||||
const expSpy = sinon.spy(wrapper.vm, 'exp');
|
||||
|
||||
const userLevelBefore = 9;
|
||||
const userLevelAfter = 10;
|
||||
store.state.user.data.stats.lvl = userLevelAfter;
|
||||
|
||||
const expEarned = 10;
|
||||
const expNeeded = -5;
|
||||
const userExpBefore = toNextLevel(userLevelBefore) - expNeeded;
|
||||
const userExpAfter = 15;
|
||||
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
|
||||
|
||||
expect(expSpy).to.be.calledWith(expEarned);
|
||||
expSpy.restore();
|
||||
});
|
||||
|
||||
it('when user multilevels', () => {
|
||||
const expSpy = sinon.spy(wrapper.vm, 'exp');
|
||||
|
||||
const userLevelBefore = 8;
|
||||
const userLevelAfter = 10;
|
||||
store.state.user.data.stats.lvl = userLevelAfter;
|
||||
|
||||
const expEarned = 10 + toNextLevel(userLevelBefore + 1);
|
||||
const expNeeded = 5;
|
||||
const userExpBefore = toNextLevel(userLevelBefore) - expNeeded;
|
||||
const userExpAfter = 5;
|
||||
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
|
||||
|
||||
expect(expSpy).to.be.calledWith(expEarned);
|
||||
expSpy.restore();
|
||||
});
|
||||
|
||||
it('when user looses xp', () => {
|
||||
const expSpy = sinon.spy(wrapper.vm, 'exp');
|
||||
|
||||
const userLevel = 10;
|
||||
store.state.user.data.stats.lvl = userLevel;
|
||||
|
||||
const userExpBefore = 10;
|
||||
const userExpAfter = 5;
|
||||
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevel, userLevel);
|
||||
|
||||
expect(expSpy).to.be.calledWith(userExpAfter - userExpBefore);
|
||||
expSpy.restore();
|
||||
});
|
||||
|
||||
it('when user looses xp under 0', () => {
|
||||
const expSpy = sinon.spy(wrapper.vm, 'exp');
|
||||
|
||||
const userLevel = 10;
|
||||
store.state.user.data.stats.lvl = userLevel;
|
||||
|
||||
const userExpBefore = 5;
|
||||
const userExpAfter = -3;
|
||||
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevel, userLevel);
|
||||
|
||||
expect(expSpy).to.be.calledWith(userExpAfter - userExpBefore);
|
||||
expSpy.restore();
|
||||
});
|
||||
|
||||
it('when user dies', () => {
|
||||
const expSpy = sinon.spy(wrapper.vm, 'exp');
|
||||
|
||||
const userLevelBefore = 10;
|
||||
const userLevelAfter = 9;
|
||||
store.state.user.data.stats.lvl = userLevelAfter;
|
||||
|
||||
const expEarned = -20;
|
||||
const userExpBefore = 20;
|
||||
const userExpAfter = 0;
|
||||
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
|
||||
|
||||
expect(expSpy).to.be.calledWith(expEarned);
|
||||
expSpy.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
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
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',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,11 @@
|
||||
import moment from 'moment';
|
||||
|
||||
import taskDefaults from '../../../website/common/script/libs/taskDefaults';
|
||||
import { generateUser } from '../../helpers/common.helper';
|
||||
|
||||
describe('taskDefaults', () => {
|
||||
it('applies defaults to undefined type or habit', () => {
|
||||
let task = taskDefaults();
|
||||
let task = taskDefaults({}, generateUser());
|
||||
expect(task.type).to.eql('habit');
|
||||
expect(task._id).to.exist;
|
||||
expect(task.text).to.eql(task._id);
|
||||
@@ -18,7 +21,7 @@ describe('taskDefaults', () => {
|
||||
});
|
||||
|
||||
it('applies defaults to a daily', () => {
|
||||
let task = taskDefaults({ type: 'daily' });
|
||||
let task = taskDefaults({ type: 'daily' }, generateUser());
|
||||
expect(task.type).to.eql('daily');
|
||||
expect(task._id).to.exist;
|
||||
expect(task.text).to.eql(task._id);
|
||||
@@ -42,7 +45,7 @@ describe('taskDefaults', () => {
|
||||
});
|
||||
|
||||
it('applies defaults a reward', () => {
|
||||
let task = taskDefaults({ type: 'reward' });
|
||||
let task = taskDefaults({ type: 'reward' }, generateUser());
|
||||
expect(task.type).to.eql('reward');
|
||||
expect(task._id).to.exist;
|
||||
expect(task.text).to.eql(task._id);
|
||||
@@ -52,7 +55,7 @@ describe('taskDefaults', () => {
|
||||
});
|
||||
|
||||
it('applies defaults a todo', () => {
|
||||
let task = taskDefaults({ type: 'todo' });
|
||||
let task = taskDefaults({ type: 'todo' }, generateUser());
|
||||
expect(task.type).to.eql('todo');
|
||||
expect(task._id).to.exist;
|
||||
expect(task.text).to.eql(task._id);
|
||||
@@ -61,4 +64,18 @@ describe('taskDefaults', () => {
|
||||
expect(task.priority).to.eql(1);
|
||||
expect(task.completed).to.eql(false);
|
||||
});
|
||||
|
||||
it('starts a task yesterday if user cron is later today', () => {
|
||||
// Configure to have a day start that's *always* tomorrow.
|
||||
let user = generateUser({'preferences.dayStart': 25});
|
||||
let task = taskDefaults({ type: 'daily' }, user);
|
||||
|
||||
expect(task.startDate).to.eql(
|
||||
moment()
|
||||
.zone(user.preferences.timezoneOffset, 'hour')
|
||||
.startOf('day')
|
||||
.subtract(1, 'day')
|
||||
.toDate()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
21
test/common/ops/armoireCanOwn.js
Normal file
21
test/common/ops/armoireCanOwn.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import armoireSet from '../../../website/common/script/content/gear/sets/armoire';
|
||||
|
||||
describe('armoireSet items', () => {
|
||||
it('checks if canOwn has the same id', () => {
|
||||
for (const type of Object.keys(armoireSet)) {
|
||||
for (const itemKey of Object.keys(armoireSet[type])) {
|
||||
const ownedKey = `${type}_armoire_${itemKey}`;
|
||||
|
||||
expect(armoireSet[type][itemKey].canOwn({
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
[ownedKey]: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}), `${ownedKey} canOwn is broken`).to.eq(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -228,7 +228,7 @@ describe('shouldDo', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
day = moment.utc('2017-05-01').toDate();
|
||||
dailyTask.frequency = 'daily';
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.startDate = day;
|
||||
@@ -391,7 +391,7 @@ describe('shouldDo', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
day = moment.utc('2017-05-01').toDate();
|
||||
dailyTask.frequency = 'weekly';
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.repeat = {
|
||||
@@ -780,7 +780,7 @@ describe('shouldDo', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
day = moment.utc('2017-05-01').toDate();
|
||||
|
||||
dailyTask.frequency = 'monthly';
|
||||
dailyTask.everyX = 3;
|
||||
@@ -817,7 +817,7 @@ describe('shouldDo', () => {
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-10-02').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-11-06').toDate());
|
||||
|
||||
day = moment('2017-05-08').toDate();
|
||||
day = moment.utc('2017-05-08').toDate();
|
||||
|
||||
dailyTask.daysOfMonth = [];
|
||||
dailyTask.weeksOfMonth = [1];
|
||||
@@ -841,7 +841,7 @@ describe('shouldDo', () => {
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-10-09').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-11-13').toDate());
|
||||
|
||||
day = moment('2017-05-29').toDate();
|
||||
day = moment.utc('2017-05-29').toDate();
|
||||
|
||||
dailyTask.daysOfMonth = [];
|
||||
dailyTask.weeksOfMonth = [4];
|
||||
@@ -1143,7 +1143,7 @@ describe('shouldDo', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
day = moment.utc('2017-05-01').toDate();
|
||||
|
||||
dailyTask.frequency = 'yearly';
|
||||
dailyTask.everyX = 5;
|
||||
|
||||
@@ -13,7 +13,7 @@ global.expect = chai.expect;
|
||||
global.sinon = require('sinon');
|
||||
let sinonStubPromise = require('sinon-stub-promise');
|
||||
sinonStubPromise(global.sinon);
|
||||
global.sandbox = sinon.sandbox.create();
|
||||
global.sandbox = sinon.createSandbox();
|
||||
|
||||
import setupNconf from '../../website/server/libs/setupNconf';
|
||||
setupNconf('./config.json.example');
|
||||
|
||||
@@ -15,15 +15,16 @@ 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.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(
|
||||
@@ -118,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;
|
||||
@@ -139,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;
|
||||
}
|
||||
|
||||
@@ -160,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;
|
||||
@@ -168,6 +164,7 @@ div
|
||||
.resume {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
white-space:nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -223,6 +220,7 @@ export default {
|
||||
loading: true,
|
||||
currentTipNumber: 0,
|
||||
bannerHidden: false,
|
||||
bannerHeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -331,23 +329,13 @@ export default {
|
||||
if (notificationNotFoundMessage.indexOf(errorMessage) !== -1) snackbarTimeout = true;
|
||||
|
||||
let errorsToShow = [];
|
||||
let usernameCheck = false;
|
||||
let emailCheck = false;
|
||||
let passwordCheck = false;
|
||||
// show only the first error for each param
|
||||
let paramErrorsFound = {};
|
||||
if (errorData.errors) {
|
||||
for (let e of errorData.errors) {
|
||||
if (!usernameCheck && e.param === 'username') {
|
||||
if (!paramErrorsFound[e.param]) {
|
||||
errorsToShow.push(e.message);
|
||||
usernameCheck = true;
|
||||
}
|
||||
if (!emailCheck && e.param === 'email') {
|
||||
errorsToShow.push(e.message);
|
||||
emailCheck = true;
|
||||
}
|
||||
if (!passwordCheck && e.param === 'password') {
|
||||
errorsToShow.push(e.message);
|
||||
passwordCheck = true;
|
||||
paramErrorsFound[e.param] = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -413,7 +401,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();
|
||||
@@ -431,6 +418,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', {
|
||||
@@ -457,6 +452,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
|
||||
@@ -615,10 +611,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>
|
||||
@@ -650,5 +658,6 @@ export default {
|
||||
<style src="assets/css/sprites/spritesmith-main-20.css"></style>
|
||||
<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/spritesmith-main-23.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
<style src="smartbanner.js/dist/smartbanner.min.css"></style>
|
||||
|
||||
@@ -1,78 +1,72 @@
|
||||
.achievement-costumeContest6x {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -295px -865px;
|
||||
width: 144px;
|
||||
height: 156px;
|
||||
}
|
||||
.promo_alligator {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
}
|
||||
.promo_animal_tails {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -284px -699px;
|
||||
background-position: -994px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_armoire_backgrounds_201809 {
|
||||
.promo_armoire_backgrounds_201811 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -699px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_ember_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -699px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_fall_festival_2017 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -788px 0px;
|
||||
width: 414px;
|
||||
height: 210px;
|
||||
}
|
||||
.promo_fall_festival_2018 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -788px -211px;
|
||||
width: 393px;
|
||||
height: 213px;
|
||||
}
|
||||
.promo_forest_friends_bundle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -426px -699px;
|
||||
background-position: -481px -568px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_ios {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -337px;
|
||||
background-position: 0px -361px;
|
||||
width: 375px;
|
||||
height: 361px;
|
||||
}
|
||||
.promo_kangaroo {
|
||||
.promo_mystery_201810 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 420px;
|
||||
height: 336px;
|
||||
background-position: 0px -865px;
|
||||
width: 294px;
|
||||
height: 168px;
|
||||
}
|
||||
.promo_mystery_201808 {
|
||||
.promo_oddballs_bundle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -421px -286px;
|
||||
width: 78px;
|
||||
height: 81px;
|
||||
}
|
||||
.promo_seasonal_shop {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -969px -425px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
background-position: -481px -420px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -788px -606px;
|
||||
background-position: -994px -442px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_unconventional_armor {
|
||||
.promo_veteran_pets {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -788px -425px;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background-position: 0px -723px;
|
||||
width: 363px;
|
||||
height: 141px;
|
||||
}
|
||||
.scene_tools {
|
||||
.scene_nametag {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -421px 0px;
|
||||
width: 366px;
|
||||
height: 285px;
|
||||
background-position: -481px 0px;
|
||||
width: 512px;
|
||||
height: 208px;
|
||||
}
|
||||
.scene_sleep {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px -209px;
|
||||
width: 390px;
|
||||
height: 210px;
|
||||
}
|
||||
.scene_veteran_pets {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -364px -723px;
|
||||
width: 242px;
|
||||
height: 62px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,84 @@
|
||||
.quest_TEMPLATE_FOR_MISSING_IMAGE {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -502px -1510px;
|
||||
background-position: -502px -1519px;
|
||||
width: 221px;
|
||||
height: 39px;
|
||||
}
|
||||
.quest_ferret {
|
||||
.quest_dilatoryDistress1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -232px;
|
||||
background-position: -1540px -1085px;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
}
|
||||
.quest_dilatoryDistress2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -721px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_dilatoryDistress3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_frog {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -1112px;
|
||||
width: 221px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_ghost_stag {
|
||||
.quest_dustbunnies {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_egg {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -362px;
|
||||
width: 165px;
|
||||
height: 207px;
|
||||
}
|
||||
.quest_evilsanta {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1174px;
|
||||
width: 118px;
|
||||
height: 131px;
|
||||
}
|
||||
.quest_evilsanta2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_falcon {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_ferret {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_frog {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -1112px;
|
||||
width: 221px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_ghost_stag {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -232px;
|
||||
background-position: -440px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -251px -1510px;
|
||||
background-position: -251px -1519px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
@@ -42,625 +90,313 @@
|
||||
}
|
||||
.quest_gryphon {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -217px -1332px;
|
||||
background-position: -1094px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_guineapig {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -220px;
|
||||
background-position: -880px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_harpy {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -452px;
|
||||
background-position: 0px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_hedgehog {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -882px -1112px;
|
||||
background-position: 0px -1332px;
|
||||
width: 219px;
|
||||
height: 186px;
|
||||
}
|
||||
.quest_hippo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -452px;
|
||||
background-position: -440px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_horse {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -452px;
|
||||
background-position: -660px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kangaroo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px 0px;
|
||||
background-position: -880px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kraken {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1302px -1332px;
|
||||
background-position: -1311px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_lostMasterclasser1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -440px;
|
||||
background-position: -1100px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -672px;
|
||||
background-position: -1100px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -672px;
|
||||
background-position: -220px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -184px;
|
||||
background-position: -1757px -1023px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_mayhemMistiflying2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -672px;
|
||||
background-position: -220px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -672px;
|
||||
background-position: -440px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_monkey {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px 0px;
|
||||
background-position: -660px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -1085px;
|
||||
background-position: -1540px -651px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_moon2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -440px;
|
||||
background-position: -1100px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -660px;
|
||||
background-position: -1320px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -892px;
|
||||
background-position: -1320px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -892px;
|
||||
background-position: -880px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -892px;
|
||||
background-position: -1320px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_nudibranch {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -868px;
|
||||
background-position: -1540px -217px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_octopus {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1102px -1112px;
|
||||
background-position: -220px -1332px;
|
||||
width: 222px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_owl {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -892px;
|
||||
background-position: -220px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_peacock {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -434px;
|
||||
background-position: -1540px 0px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_penguin {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px 0px;
|
||||
background-position: -1757px -178px;
|
||||
width: 190px;
|
||||
height: 183px;
|
||||
}
|
||||
.quest_pterodactyl {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px -440px;
|
||||
background-position: 0px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rat {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px -660px;
|
||||
background-position: -1320px -880px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rock {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px 0px;
|
||||
background-position: -1540px -434px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_rooster {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1325px -1112px;
|
||||
background-position: -1528px -1332px;
|
||||
width: 213px;
|
||||
height: 174px;
|
||||
}
|
||||
.quest_sabretooth {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -1112px;
|
||||
background-position: -220px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_seaserpent {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -1112px;
|
||||
background-position: 0px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sheep {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px -880px;
|
||||
background-position: -660px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_slime {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px -220px;
|
||||
background-position: -880px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sloth {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px 0px;
|
||||
background-position: -440px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_snail {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -662px -1112px;
|
||||
background-position: -1102px -1112px;
|
||||
width: 219px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_snake {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1085px -1332px;
|
||||
background-position: -443px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_spider {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -1510px;
|
||||
background-position: 0px -1519px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_squirrel {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -892px;
|
||||
background-position: -1320px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -486px;
|
||||
background-position: -1757px -570px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_stoikalmCalamity2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -892px;
|
||||
background-position: 0px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -220px;
|
||||
background-position: -1100px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_taskwoodsTerror1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -335px;
|
||||
background-position: -1757px -872px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_taskwoodsTerror2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -217px;
|
||||
background-position: -1540px -868px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_taskwoodsTerror3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -672px;
|
||||
background-position: -880px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_treeling {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -434px -1332px;
|
||||
background-position: -877px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_trex {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1519px -1332px;
|
||||
background-position: -1757px 0px;
|
||||
width: 204px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_trex_undead {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -651px -1332px;
|
||||
background-position: -660px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_triceratops {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px 0px;
|
||||
background-position: -660px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_turtle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -220px;
|
||||
background-position: -220px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_unicorn {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -452px;
|
||||
background-position: 0px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_vice1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -868px -1332px;
|
||||
background-position: -1322px -1112px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_vice2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_vice3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_whale {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_yarn {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -651px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_atom1_soapBars {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -844px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_dilatoryDistress1_blueFins {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -1120px;
|
||||
width: 51px;
|
||||
height: 48px;
|
||||
}
|
||||
.quest_dilatoryDistress1_fireCoral {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -1258px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_egg_plainEgg {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -1465px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_evilsanta2_branches {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -913px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_evilsanta2_tracks {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -502px -1550px;
|
||||
width: 54px;
|
||||
height: 60px;
|
||||
}
|
||||
.quest_goldenknight1_testimony {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -1327px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_lostMasterclasser1_ancientTome {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1908px -184px;
|
||||
width: 33px;
|
||||
height: 42px;
|
||||
}
|
||||
.quest_lostMasterclasser1_forbiddenTome {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1908px -270px;
|
||||
width: 33px;
|
||||
height: 42px;
|
||||
}
|
||||
.quest_lostMasterclasser1_hiddenTome {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1908px -227px;
|
||||
width: 33px;
|
||||
height: 42px;
|
||||
}
|
||||
.quest_mayhemMistiflying2_mistifly1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -637px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_mayhemMistiflying2_mistifly2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -1396px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_mayhemMistiflying2_mistifly3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -982px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_moon1_shard {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -1534px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
.quest_moonstone1_moonstone {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1908px -335px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.quest_stoikalmCalamity2_icicleCoin {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -775px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_taskwoodsTerror2_brownie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -706px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_taskwoodsTerror2_dryad {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -1051px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_taskwoodsTerror2_pixie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1895px -1189px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.quest_vice2_lightCrystal {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1603px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.inventory_quest_scroll_armadillo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1327px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_atom1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1396px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_atom1_locked {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -1327px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_atom2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1465px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_atom2_locked {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -1396px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_atom3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1534px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_atom3_locked {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -1465px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_axolotl {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -1534px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_badger {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -1258px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_basilist {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1258px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_beetle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -1189px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_bunny {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1189px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_butterfly {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -1120px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_cheetah {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1120px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_cow {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -1051px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_dilatoryDistress1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -982px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_dilatoryDistress2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -913px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_dilatoryDistress2_locked {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -913px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_dilatoryDistress3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -844px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_dilatoryDistress3_locked {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -844px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_dilatory_derby {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1051px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_dustbunnies {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -775px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_egg {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -775px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_evilsanta {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -706px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_evilsanta2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -706px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_falcon {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -637px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_ferret {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -637px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_quest_scroll_frog {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1826px -982px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user