mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-10-27 19:22:55 +01:00
Compare commits
1 Commits
v5.12.1
...
phillip/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a018588021 |
@@ -86,6 +86,5 @@
|
||||
"RATE_LIMITER_ENABLED": "false",
|
||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||
"REDIS_PORT": "1234",
|
||||
"REDIS_PASSWORD": "12345678",
|
||||
"TRUSTED_DOMAINS": "localhost,habitica.com"
|
||||
"REDIS_PASSWORD": "12345678"
|
||||
}
|
||||
|
||||
Submodule habitica-images updated: f74a3b4e97...d66a5ea922
@@ -1,108 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20221213_pet_group_achievements';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets) {
|
||||
const pets = user.items.pets;
|
||||
if (pets['BearCub-Base']
|
||||
&& pets['BearCub-CottonCandyBlue']
|
||||
&& pets['BearCub-CottonCandyPink']
|
||||
&& pets['BearCub-Desert']
|
||||
&& pets['BearCub-Golden']
|
||||
&& pets['BearCub-Red']
|
||||
&& pets['BearCub-Shade']
|
||||
&& pets['BearCub-Skeleton']
|
||||
&& pets['BearCub-White']
|
||||
&& pets['BearCub-Zombie']
|
||||
&& pets['Fox-Base']
|
||||
&& pets['Fox-CottonCandyBlue']
|
||||
&& pets['Fox-CottonCandyPink']
|
||||
&& pets['Fox-Desert']
|
||||
&& pets['Fox-Golden']
|
||||
&& pets['Fox-Red']
|
||||
&& pets['Fox-Shade']
|
||||
&& pets['Fox-Skeleton']
|
||||
&& pets['Fox-White']
|
||||
&& pets['Fox-Zombie']
|
||||
&& pets['Penguin-Base']
|
||||
&& pets['Penguin-CottonCandyBlue']
|
||||
&& pets['Penguin-CottonCandyPink']
|
||||
&& pets['Penguin-Desert']
|
||||
&& pets['Penguin-Golden']
|
||||
&& pets['Penguin-Red']
|
||||
&& pets['Penguin-Shade']
|
||||
&& pets['Penguin-Skeleton']
|
||||
&& pets['Penguin-White']
|
||||
&& pets['Penguin-Zombie']
|
||||
&& pets['Whale-Base']
|
||||
&& pets['Whale-CottonCandyBlue']
|
||||
&& pets['Whale-CottonCandyPink']
|
||||
&& pets['Whale-Desert']
|
||||
&& pets['Whale-Golden']
|
||||
&& pets['Whale-Red']
|
||||
&& pets['Whale-Shade']
|
||||
&& pets['Whale-Skeleton']
|
||||
&& pets['Whale-White']
|
||||
&& pets['Whale-Zombie']
|
||||
&& pets['Wolf-Base']
|
||||
&& pets['Wolf-CottonCandyBlue']
|
||||
&& pets['Wolf-CottonCandyPink']
|
||||
&& pets['Wolf-Desert']
|
||||
&& pets['Wolf-Golden']
|
||||
&& pets['Wolf-Red']
|
||||
&& pets['Wolf-Shade']
|
||||
&& pets['Wolf-Skeleton']
|
||||
&& pets['Wolf-White']
|
||||
&& pets['Wolf-Zombie'] {
|
||||
set['achievements.polarPro'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
// migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2022-11-01') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,144 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20221227_nye';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = { migration: MIGRATION_NAME };
|
||||
let push;
|
||||
|
||||
if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2022'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2022',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2021'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2021',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2020'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2020',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2019'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2019',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2018'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2018',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2017'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2017',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2016'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2016',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2015'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2015',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2014'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2014',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
set['items.gear.owned.head_special_nye'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-01')},
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230123_habit_birthday';
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const inc = { 'balance': 5 };
|
||||
const set = {};
|
||||
const push = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (typeof user.items.gear.owned.armor_special_birthday2022 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2023'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2021 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2022'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2020 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2021'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2019 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2020'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2019'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2018'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2017'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2016'] = true;
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2015'] = true;
|
||||
} else {
|
||||
set['items.gear.owned.armor_special_birthday'] = true;
|
||||
}
|
||||
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_head_special_nye',
|
||||
title: 'Birthday Bash Day 1!',
|
||||
text: 'Enjoy your new Birthday Robe and 20 Gems on us!',
|
||||
destination: 'equipment',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: push}).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230127_habit_birthday_day5';
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const set = {};
|
||||
const push = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
set['items.gear.owned.back_special_anniversary'] = true;
|
||||
set['items.gear.owned.body_special_anniversary'] = true;
|
||||
set['items.gear.owned.eyewear_special_anniversary'] = true;
|
||||
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_head_special_nye',
|
||||
title: 'Birthday Bash Day 5!',
|
||||
text: 'Come celebrate by wearing your new Habitica Hero Cape, Collar, and Mask!',
|
||||
destination: 'equipment',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: push}).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230201_habit_birthday_day10';
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
'purchased.background.birthday_bash': true,
|
||||
};
|
||||
const push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_head_special_nye',
|
||||
title: 'Birthday Bash Day 10!',
|
||||
text: 'Join in for the end of our birthday celebrations with 10th Birthday background, Cake, and achievement!',
|
||||
destination: 'backgrounds',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
const inc = {
|
||||
'items.food.Cake_Skeleton': 1,
|
||||
'items.food.Cake_Base': 1,
|
||||
'items.food.Cake_CottonCandyBlue': 1,
|
||||
'items.food.Cake_CottonCandyPink': 1,
|
||||
'items.food.Cake_Shade': 1,
|
||||
'items.food.Cake_White': 1,
|
||||
'items.food.Cake_Golden': 1,
|
||||
'items.food.Cake_Zombie': 1,
|
||||
'items.food.Cake_Desert': 1,
|
||||
'items.food.Cake_Red': 1,
|
||||
'achievements.habitBirthdays': 1,
|
||||
};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: push, $inc: inc }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,158 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230522_pet_group_achievements';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets) {
|
||||
const pets = user.items.pets;
|
||||
if (pets['Parrot-Base']
|
||||
&& pets['Parrot-CottonCandyBlue']
|
||||
&& pets['Parrot-CottonCandyPink']
|
||||
&& pets['Parrot-Desert']
|
||||
&& pets['Parrot-Golden']
|
||||
&& pets['Parrot-Red']
|
||||
&& pets['Parrot-Shade']
|
||||
&& pets['Parrot-Skeleton']
|
||||
&& pets['Parrot-White']
|
||||
&& pets['Parrot-Zombie']
|
||||
&& pets['Rooster-Base']
|
||||
&& pets['Rooster-CottonCandyBlue']
|
||||
&& pets['Rooster-CottonCandyPink']
|
||||
&& pets['Rooster-Desert']
|
||||
&& pets['Rooster-Golden']
|
||||
&& pets['Rooster-Red']
|
||||
&& pets['Rooster-Shade']
|
||||
&& pets['Rooster-Skeleton']
|
||||
&& pets['Rooster-White']
|
||||
&& pets['Rooster-Zombie']
|
||||
&& pets['Triceratops-Base']
|
||||
&& pets['Triceratops-CottonCandyBlue']
|
||||
&& pets['Triceratops-CottonCandyPink']
|
||||
&& pets['Triceratops-Desert']
|
||||
&& pets['Triceratops-Golden']
|
||||
&& pets['Triceratops-Red']
|
||||
&& pets['Triceratops-Shade']
|
||||
&& pets['Triceratops-Skeleton']
|
||||
&& pets['Triceratops-White']
|
||||
&& pets['Triceratops-Zombie']
|
||||
&& pets['TRex-Base']
|
||||
&& pets['TRex-CottonCandyBlue']
|
||||
&& pets['TRex-CottonCandyPink']
|
||||
&& pets['TRex-Desert']
|
||||
&& pets['TRex-Golden']
|
||||
&& pets['TRex-Red']
|
||||
&& pets['TRex-Shade']
|
||||
&& pets['TRex-Skeleton']
|
||||
&& pets['TRex-White']
|
||||
&& pets['TRex-Zombie']
|
||||
&& pets['Pterodactyl-Base']
|
||||
&& pets['Pterodactyl-CottonCandyBlue']
|
||||
&& pets['Pterodactyl-CottonCandyPink']
|
||||
&& pets['Pterodactyl-Desert']
|
||||
&& pets['Pterodactyl-Golden']
|
||||
&& pets['Pterodactyl-Red']
|
||||
&& pets['Pterodactyl-Shade']
|
||||
&& pets['Pterodactyl-Skeleton']
|
||||
&& pets['Pterodactyl-White']
|
||||
&& pets['Pterodactyl-Zombie']
|
||||
&& pets['Owl-Base']
|
||||
&& pets['Owl-CottonCandyBlue']
|
||||
&& pets['Owl-CottonCandyPink']
|
||||
&& pets['Owl-Desert']
|
||||
&& pets['Owl-Golden']
|
||||
&& pets['Owl-Red']
|
||||
&& pets['Owl-Shade']
|
||||
&& pets['Owl-Skeleton']
|
||||
&& pets['Owl-White']
|
||||
&& pets['Owl-Zombie']
|
||||
&& pets['Velociraptor-Base']
|
||||
&& pets['Velociraptor-CottonCandyBlue']
|
||||
&& pets['Velociraptor-CottonCandyPink']
|
||||
&& pets['Velociraptor-Desert']
|
||||
&& pets['Velociraptor-Golden']
|
||||
&& pets['Velociraptor-Red']
|
||||
&& pets['Velociraptor-Shade']
|
||||
&& pets['Velociraptor-Skeleton']
|
||||
&& pets['Velociraptor-White']
|
||||
&& pets['Velociraptor-Zombie']
|
||||
&& pets['Penguin-Base']
|
||||
&& pets['Penguin-CottonCandyBlue']
|
||||
&& pets['Penguin-CottonCandyPink']
|
||||
&& pets['Penguin-Desert']
|
||||
&& pets['Penguin-Golden']
|
||||
&& pets['Penguin-Red']
|
||||
&& pets['Penguin-Shade']
|
||||
&& pets['Penguin-Skeleton']
|
||||
&& pets['Penguin-White']
|
||||
&& pets['Penguin-Zombie']
|
||||
&& pets['Falcon-Base']
|
||||
&& pets['Falcon-CottonCandyBlue']
|
||||
&& pets['Falcon-CottonCandyPink']
|
||||
&& pets['Falcon-Desert']
|
||||
&& pets['Falcon-Golden']
|
||||
&& pets['Falcon-Red']
|
||||
&& pets['Falcon-Shade']
|
||||
&& pets['Falcon-Skeleton']
|
||||
&& pets['Falcon-White']
|
||||
&& pets['Falcon-Zombie']
|
||||
&& pets['Peacock-Base']
|
||||
&& pets['Peacock-CottonCandyBlue']
|
||||
&& pets['Peacock-CottonCandyPink']
|
||||
&& pets['Peacock-Desert']
|
||||
&& pets['Peacock-Golden']
|
||||
&& pets['Peacock-Red']
|
||||
&& pets['Peacock-Shade']
|
||||
&& pets['Peacock-Skeleton']
|
||||
&& pets['Peacock-White']
|
||||
&& pets['Peacock-Zombie']) {
|
||||
set['achievements.dinosaurDynasty'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
// migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-04-15') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230718_summer_splash_orcas';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = { migration: MIGRATION_NAME };
|
||||
const push = {};
|
||||
|
||||
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
|
||||
return;
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
|
||||
set['items.pets.Orca-Base'] = 5;
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_orca_pet',
|
||||
title: 'Orcas for Summer Splash!',
|
||||
text: 'To celebrate Summer Splash, we\'ve given you an Orca Pet!',
|
||||
destination: 'stable',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
} else {
|
||||
set['items.mounts.Orca-Base'] = true;
|
||||
push.notifications = {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_orca_mount',
|
||||
title: 'Orcas for Summer Splash!',
|
||||
text: 'To celebrate Summer Splash, we\'ve given you an Orca Mount!',
|
||||
destination: 'stable',
|
||||
},
|
||||
seen: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await user.updateOne({ $set: set, $push: push }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2023-06-18')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,155 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230731_naming_day';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
let set;
|
||||
let push;
|
||||
const inc = {
|
||||
'items.food.Cake_Base': 1,
|
||||
'items.food.Cake_CottonCandyBlue': 1,
|
||||
'items.food.Cake_CottonCandyPink': 1,
|
||||
'items.food.Cake_Desert': 1,
|
||||
'items.food.Cake_Golden': 1,
|
||||
'items.food.Cake_Red': 1,
|
||||
'items.food.Cake_Shade': 1,
|
||||
'items.food.Cake_Skeleton': 1,
|
||||
'items.food.Cake_White': 1,
|
||||
'items.food.Cake_Zombie': 1,
|
||||
'achievements.habiticaDays': 1,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.back_special_namingDay2020 !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_cake',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you some cake!',
|
||||
destination: '/inventory/items',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.body_special_namingDay2018 !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.gear.owned.back_special_namingDay2020': true };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_back',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Tail and cake!',
|
||||
destination: '/inventory/equipment',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.head_special_namingDay2017 !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.gear.owned.body_special_namingDay2018': true };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_body',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Cloak and cake!',
|
||||
destination: '/inventory/equipment',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else if (user && user.items && user.items.pets && typeof user.items.pets['Gryphon-RoyalPurple'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.gear.owned.head_special_namingDay2017': true };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_head',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Helm and cake!',
|
||||
destination: '/inventory/equipment',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Gryphon-RoyalPurple'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.pets.Gryphon-RoyalPurple': 5 };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_pet',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Pet and cake!',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
set = { migration: MIGRATION_NAME, 'items.mounts.Gryphon-RoyalPurple': true };
|
||||
push = {
|
||||
notifications: {
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'notif_namingDay_mount',
|
||||
title: 'Happy Naming Day!',
|
||||
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Mount and cake!',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
if (push) {
|
||||
return await user.updateOne({ $set: set, $inc: inc, $push: push }).exec();
|
||||
} else {
|
||||
return await user.updateOne({ $set: set, $inc: inc }).exec();
|
||||
}
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-07-01') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
import { model as Group } from '../../../website/server/models/group';
|
||||
|
||||
const guildsPerRun = 500;
|
||||
const progressCount = 1000;
|
||||
const guildsQuery = {
|
||||
type: 'guild',
|
||||
};
|
||||
|
||||
let count = 0;
|
||||
async function updateGroup (guild) {
|
||||
count++;
|
||||
if (count % progressCount === 0) {
|
||||
console.warn(`${count} ${guild._id}`);
|
||||
}
|
||||
|
||||
if (guild.hasActiveGroupPlan()) {
|
||||
return console.warn(`Guild ${guild._id} is active Group Plan`);
|
||||
}
|
||||
|
||||
const leader = await User
|
||||
.findOne({ _id: guild.leader })
|
||||
.select({ _id: true })
|
||||
.exec();
|
||||
|
||||
if (!leader) {
|
||||
return console.warn(`Leader not found for Guild ${guild._id}`);
|
||||
}
|
||||
|
||||
if (guild.balance > 0) {
|
||||
await leader.updateBalance(
|
||||
guild.balance,
|
||||
'create_guild',
|
||||
'',
|
||||
`Guild Bank refund for ${guild.name} (${guild._id})`,
|
||||
);
|
||||
}
|
||||
|
||||
return guild.updateOne({ $set: { balance: 0 } }).exec();
|
||||
}
|
||||
|
||||
export default async function processGroups () {
|
||||
const guildFields = {
|
||||
_id: 1,
|
||||
balance: 1,
|
||||
leader: 1,
|
||||
name: 1,
|
||||
purchased: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const foundGroups = await Group // eslint-disable-line no-await-in-loop
|
||||
.find(guildsQuery)
|
||||
.limit(guildsPerRun)
|
||||
.sort({ _id: 1 })
|
||||
.select(guildFields)
|
||||
.exec();
|
||||
|
||||
if (foundGroups.length === 0) {
|
||||
console.warn('All appropriate Guilds found and modified.');
|
||||
console.warn(`\n${count} Guilds processed\n`);
|
||||
break;
|
||||
} else {
|
||||
guildsQuery._id = {
|
||||
$gt: foundGroups[foundGroups.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(foundGroups.map(guild => updateGroup(guild))); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
import { TransactionModel as Transaction } from '../../../website/server/models/transaction';
|
||||
|
||||
const transactionsPerRun = 500;
|
||||
const progressCount = 1000;
|
||||
const transactionsQuery = {
|
||||
transactionType: 'create_guild',
|
||||
amount: { $gt: 0 },
|
||||
};
|
||||
|
||||
let count = 0;
|
||||
async function updateTransaction (transaction) {
|
||||
count++;
|
||||
if (count % progressCount === 0) {
|
||||
console.warn(`${count} ${transaction._id}`);
|
||||
}
|
||||
|
||||
const leader = await User
|
||||
.findOne({ _id: transaction.userId })
|
||||
.select({ _id: true })
|
||||
.exec();
|
||||
|
||||
if (!leader) {
|
||||
return console.warn(`User not found for transaction ${transaction._id}`);
|
||||
}
|
||||
|
||||
return leader.updateOne(
|
||||
{ $inc: { balance: transaction.amount }},
|
||||
).exec();
|
||||
}
|
||||
|
||||
export default async function processTransactions () {
|
||||
const transactionFields = {
|
||||
_id: 1,
|
||||
userId: 1,
|
||||
currency: 1,
|
||||
amount: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const foundTransactions = await Transaction // eslint-disable-line no-await-in-loop
|
||||
.find(transactionsQuery)
|
||||
.limit(transactionsPerRun)
|
||||
.sort({ _id: 1 })
|
||||
.select(transactionFields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (foundTransactions.length === 0) {
|
||||
console.warn('All appropriate transactions found and modified.');
|
||||
console.warn(`\n${count} transactions processed\n`);
|
||||
break;
|
||||
} else {
|
||||
transactionsQuery._id = {
|
||||
$gt: foundTransactions[foundTransactions.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(foundTransactions.map(txn => updateTransaction(txn))); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,144 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230808_veteran_pet_ladder';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {};
|
||||
let push = { notifications: { $each: [] }};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
if (user.items.pets['Fox-Veteran']) {
|
||||
set['items.pets.Dragon-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_dragon',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Dragon.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else if (user.items.pets['Bear-Veteran']) {
|
||||
set['items.pets.Fox-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_fox',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Fox.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else if (user.items.pets['Lion-Veteran']) {
|
||||
set['items.pets.Bear-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_bear',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Bear.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else if (user.items.pets['Tiger-Veteran']) {
|
||||
set['items.pets.Lion-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_lion',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Lion.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else if (user.items.pets['Wolf-Veteran']) {
|
||||
set['items.pets.Tiger-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_tiger',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Tiger.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
} else {
|
||||
set['items.pets.Wolf-Veteran'] = 5;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'icon_pet_veteran_wolf',
|
||||
title: 'You’ve received a Veteran Pet!',
|
||||
text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Wolf.',
|
||||
destination: '/inventory/stable',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (user.contributor.level > 0) {
|
||||
set['items.gear.owned.armor_special_heroicTunic'] = true;
|
||||
set['items.gear.owned.back_special_heroicAureole'] = true;
|
||||
set['items.gear.owned.headAccessory_special_heroicCirclet'] = true;
|
||||
push.notifications.$each.push({
|
||||
type: 'ITEM_RECEIVED',
|
||||
data: {
|
||||
icon: 'heroic_set_icon',
|
||||
title: 'You’ve received the Heroic Set!',
|
||||
text: 'To commemorate your hard work as a contributor, we’ve awarded you the Heroic Circlet, Heroic Aureole, and Heroic Tunic.',
|
||||
destination: '/inventory/equipment',
|
||||
},
|
||||
seen: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: push}).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
// 'auth.timestamps.loggedin': { $gt: new Date('2023-07-08') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
migration: 1,
|
||||
contributor: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,118 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20231017_pet_group_achievements';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets) {
|
||||
const pets = user.items.pets;
|
||||
if (pets['Armadillo-Base']
|
||||
&& pets['Armadillo-CottonCandyBlue']
|
||||
&& pets['Armadillo-CottonCandyPink']
|
||||
&& pets['Armadillo-Desert']
|
||||
&& pets['Armadillo-Golden']
|
||||
&& pets['Armadillo-Red']
|
||||
&& pets['Armadillo-Shade']
|
||||
&& pets['Armadillo-Skeleton']
|
||||
&& pets['Armadillo-White']
|
||||
&& pets['Armadillo-Zombie']
|
||||
&& pets['Cactus-Base']
|
||||
&& pets['Cactus-CottonCandyBlue']
|
||||
&& pets['Cactus-CottonCandyPink']
|
||||
&& pets['Cactus-Desert']
|
||||
&& pets['Cactus-Golden']
|
||||
&& pets['Cactus-Red']
|
||||
&& pets['Cactus-Shade']
|
||||
&& pets['Cactus-Skeleton']
|
||||
&& pets['Cactus-White']
|
||||
&& pets['Cactus-Zombie']
|
||||
&& pets['Fox-Base']
|
||||
&& pets['Fox-CottonCandyBlue']
|
||||
&& pets['Fox-CottonCandyPink']
|
||||
&& pets['Fox-Desert']
|
||||
&& pets['Fox-Golden']
|
||||
&& pets['Fox-Red']
|
||||
&& pets['Fox-Shade']
|
||||
&& pets['Fox-Skeleton']
|
||||
&& pets['Fox-White']
|
||||
&& pets['Fox-Zombie']
|
||||
&& pets['Frog-Base']
|
||||
&& pets['Frog-CottonCandyBlue']
|
||||
&& pets['Frog-CottonCandyPink']
|
||||
&& pets['Frog-Desert']
|
||||
&& pets['Frog-Golden']
|
||||
&& pets['Frog-Red']
|
||||
&& pets['Frog-Shade']
|
||||
&& pets['Frog-Skeleton']
|
||||
&& pets['Frog-White']
|
||||
&& pets['Frog-Zombie']
|
||||
&& pets['Snake-Base']
|
||||
&& pets['Snake-CottonCandyBlue']
|
||||
&& pets['Snake-CottonCandyPink']
|
||||
&& pets['Snake-Desert']
|
||||
&& pets['Snake-Golden']
|
||||
&& pets['Snake-Red']
|
||||
&& pets['Snake-Shade']
|
||||
&& pets['Snake-Skeleton']
|
||||
&& pets['Snake-White']
|
||||
&& pets['Snake-Zombie']
|
||||
&& pets['Spider-Base']
|
||||
&& pets['Spider-CottonCandyBlue']
|
||||
&& pets['Spider-CottonCandyPink']
|
||||
&& pets['Spider-Desert']
|
||||
&& pets['Spider-Golden']
|
||||
&& pets['Spider-Red']
|
||||
&& pets['Spider-Shade']
|
||||
&& pets['Spider-Skeleton']
|
||||
&& pets['Spider-White']
|
||||
&& pets['Spider-Zombie']) {
|
||||
set['achievements.duneBuddy'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.updateOne({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-09-16') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,124 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20231114_pet_group_achievements';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
let set = {
|
||||
migration: MIGRATION_NAME,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets) {
|
||||
const pets = user.items.pets;
|
||||
if (pets['Cactus-Zombie'] > 0
|
||||
&& pets['Cactus-Skeleton'] > 0
|
||||
&& pets['Cactus-Base'] > 0
|
||||
&& pets['Cactus-Desert'] > 0
|
||||
&& pets['Cactus-Red'] > 0
|
||||
&& pets['Cactus-Shade'] > 0
|
||||
&& pets['Cactus-White']> 0
|
||||
&& pets['Cactus-Golden'] > 0
|
||||
&& pets['Cactus-CottonCandyBlue'] > 0
|
||||
&& pets['Cactus-CottonCandyPink'] > 0
|
||||
&& pets['Hedgehog-Zombie'] > 0
|
||||
&& pets['Hedgehog-Skeleton'] > 0
|
||||
&& pets['Hedgehog-Base'] > 0
|
||||
&& pets['Hedgehog-Desert'] > 0
|
||||
&& pets['Hedgehog-Red'] > 0
|
||||
&& pets['Hedgehog-Shade'] > 0
|
||||
&& pets['Hedgehog-White'] > 0
|
||||
&& pets['Hedgehog-Golder'] > 0
|
||||
&& pets['Hedgehog-CottonCandyBlue'] > 0
|
||||
&& pets['Hedgehog-CottonCandyPink'] > 0
|
||||
&& pets['Rock-Zombie'] > 0
|
||||
&& pets['Rock-Skeleton'] > 0
|
||||
&& pets['Rock-Base'] > 0
|
||||
&& pets['Rock-Desert'] > 0
|
||||
&& pets['Rock-Red'] > 0
|
||||
&& pets['Rock-Shade'] > 0
|
||||
&& pets['Rock-White'] > 0
|
||||
&& pets['Rock-Golden'] > 0
|
||||
&& pets['Rock-CottonCandyBlue'] > 0
|
||||
&& pets['Rock-CottonCandyPink'] > 0 ) {
|
||||
set['achievements.roughRider'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (user && user.items && user.items.mounts) {
|
||||
const mounts = user.items.mounts;
|
||||
if (mounts['Cactus-Zombie']
|
||||
&& mounts['Cactus-Skeleton']
|
||||
&& mounts['Cactus-Base']
|
||||
&& mounts['Cactus-Desert']
|
||||
&& mounts['Cactus-Red']
|
||||
&& mounts['Cactus-Shade']
|
||||
&& mounts['Cactus-White']
|
||||
&& mounts['Cactus-Golden']
|
||||
&& mounts['Cactus-CottonCandyPink']
|
||||
&& mounts['Cactus-CottonCandyBlue']
|
||||
&& mounts['Hedgehog-Zombie']
|
||||
&& mounts['Hedgehog-Skeleton']
|
||||
&& mounts['Hedgehog-Base']
|
||||
&& mounts['Hedgehog-Desert']
|
||||
&& mounts['Hedgehog-Red']
|
||||
&& mounts['Hedgehog-Shade']
|
||||
&& mounts['Hedgehog-White']
|
||||
&& mounts['Hedgehog-Golden']
|
||||
&& mounts['Hedgehog-CottonCandyPink']
|
||||
&& mounts['Hedgehog-CottonCandyBlue']
|
||||
&& mounts['Rock-Zombie']
|
||||
&& mounts['Rock-Skeleton']
|
||||
&& mounts['Rock-Base']
|
||||
&& mounts['Rock-Desert']
|
||||
&& mounts['Rock-Red']
|
||||
&& mounts['Rock-Shade']
|
||||
&& mounts['Rock-White']
|
||||
&& mounts['Rock-Golden']
|
||||
&& mounts['Rock-CottonCandyPink']
|
||||
&& mounts['Rock-CottonCandyBlue'] ) {
|
||||
set['achievements.roughRider'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-02-01') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20230314_pi_day';
|
||||
const MIGRATION_NAME = '20220314_pi_day';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
@@ -54,7 +54,7 @@ async function updateUser (user) {
|
||||
export default async function processUsers () {
|
||||
const query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-02-15') },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2022-02-15') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
|
||||
4184
package-lock.json
generated
4184
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
55
package.json
55
package.json
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "5.12.1",
|
||||
"version": "4.251.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/register": "^7.22.15",
|
||||
"@babel/core": "^7.19.6",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/register": "^7.18.9",
|
||||
"@google-cloud/trace-agent": "^7.1.2",
|
||||
"@parse/node-apn": "^5.2.3",
|
||||
"@parse/node-apn": "^5.1.3",
|
||||
"@slack/webhook": "^6.1.0",
|
||||
"accepts": "^1.3.8",
|
||||
"amazon-payments": "^0.2.9",
|
||||
"amplitude": "^6.0.0",
|
||||
"apidoc": "^0.54.0",
|
||||
"apple-auth": "^1.0.9",
|
||||
"bcrypt": "^5.1.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"bootstrap": "^4.6.2",
|
||||
"apidoc": "^0.53.1",
|
||||
"apple-auth": "^1.0.7",
|
||||
"bcrypt": "^5.1.0",
|
||||
"body-parser": "^1.20.1",
|
||||
"bootstrap": "^4.6.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-session": "^2.0.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
@@ -30,54 +30,53 @@
|
||||
"express": "^4.18.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"express-validator": "^5.2.0",
|
||||
"glob": "^8.1.0",
|
||||
"got": "^11.8.6",
|
||||
"glob": "^8.0.3",
|
||||
"got": "^11.8.3",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-nodemon": "^2.5.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"gulp.spritesmith": "^6.13.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"helmet": "^4.6.0",
|
||||
"image-size": "^1.0.2",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"js2xmlparser": "^5.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "^2.1.5",
|
||||
"lodash": "^4.17.21",
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.13.20",
|
||||
"mongoose": "^5.13.7",
|
||||
"morgan": "^1.10.0",
|
||||
"nconf": "^0.12.1",
|
||||
"nconf": "^0.12.0",
|
||||
"node-gcm": "^1.0.5",
|
||||
"on-headers": "^1.0.2",
|
||||
"passport": "^0.5.3",
|
||||
"passport": "^0.6.0",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-google-oauth2": "^0.2.0",
|
||||
"passport-google-oauth20": "2.0.0",
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"pp-ipn": "^1.1.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"rate-limiter-flexible": "^2.4.2",
|
||||
"rate-limiter-flexible": "^2.4.0",
|
||||
"redis": "^3.1.2",
|
||||
"regenerator-runtime": "^0.13.11",
|
||||
"remove-markdown": "^0.5.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.2.2",
|
||||
"stripe": "^12.18.0",
|
||||
"superagent": "^8.1.2",
|
||||
"stripe": "^10.13.0",
|
||||
"superagent": "^8.0.5",
|
||||
"universal-analytics": "^0.5.3",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.11.0",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.7.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^3.10.0",
|
||||
"winston-loggly-bulk": "^3.3.0",
|
||||
"xml2js": "^0.6.2"
|
||||
"winston": "^3.8.2",
|
||||
"winston-loggly-bulk": "^3.2.1",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
@@ -111,11 +110,11 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.4.0",
|
||||
"axios": "^0.27.2",
|
||||
"chai": "^4.3.7",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
"chalk": "^5.3.0",
|
||||
"chalk": "^5.1.2",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
@@ -123,7 +122,7 @@
|
||||
"monk": "^7.3.4",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.7.7",
|
||||
"sinon": "^15.2.0",
|
||||
"sinon": "^14.0.2",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -231,16 +231,13 @@ describe('cron', async () => {
|
||||
},
|
||||
});
|
||||
// user1 has a 1-month recurring subscription starting today
|
||||
beforeEach(async () => {
|
||||
user1.purchased.plan.customerId = 'subscribedId';
|
||||
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||
user1.purchased.plan.planId = 'basic';
|
||||
user1.purchased.plan.consecutive.count = 0;
|
||||
user1.purchased.plan.perkMonthCount = 0;
|
||||
user1.purchased.plan.consecutive.offset = 0;
|
||||
user1.purchased.plan.consecutive.trinkets = 0;
|
||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||
});
|
||||
user1.purchased.plan.customerId = 'subscribedId';
|
||||
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||
user1.purchased.plan.planId = 'basic';
|
||||
user1.purchased.plan.consecutive.count = 0;
|
||||
user1.purchased.plan.consecutive.offset = 0;
|
||||
user1.purchased.plan.consecutive.trinkets = 0;
|
||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||
|
||||
it('does not increment consecutive benefits after the first month', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
@@ -274,24 +271,6 @@ describe('cron', async () => {
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||
});
|
||||
|
||||
it('increments consecutive benefits after the second month if they also received a 1 month gift subscription', async () => {
|
||||
user1.purchased.plan.perkMonthCount = 1;
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects
|
||||
// e.g., from time zone oddness.
|
||||
await cron({
|
||||
user: user1, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user1.purchased.plan.perkMonthCount).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(2);
|
||||
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||
});
|
||||
|
||||
it('increments consecutive benefits after the third month', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
||||
.add(2, 'days')
|
||||
@@ -336,30 +315,6 @@ describe('cron', async () => {
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
||||
});
|
||||
|
||||
it('initializes plan.perkMonthCount if necessary', async () => {
|
||||
user.purchased.plan.perkMonthCount = undefined;
|
||||
clock = sinon.useFakeTimers(moment(user.purchased.plan.dateUpdated)
|
||||
.utcOffset(0)
|
||||
.startOf('month')
|
||||
.add(1, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user.purchased.plan.perkMonthCount).to.equal(1);
|
||||
user.purchased.plan.perkMonthCount = undefined;
|
||||
user.purchased.plan.consecutive.count = 8;
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user.purchased.plan.perkMonthCount).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a 3-month recurring subscription', async () => {
|
||||
@@ -375,16 +330,13 @@ describe('cron', async () => {
|
||||
},
|
||||
});
|
||||
// user3 has a 3-month recurring subscription starting today
|
||||
beforeEach(async () => {
|
||||
user3.purchased.plan.customerId = 'subscribedId';
|
||||
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||
user3.purchased.plan.planId = 'basic_3mo';
|
||||
user3.purchased.plan.perkMonthCount = 0;
|
||||
user3.purchased.plan.consecutive.count = 0;
|
||||
user3.purchased.plan.consecutive.offset = 3;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
});
|
||||
user3.purchased.plan.customerId = 'subscribedId';
|
||||
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||
user3.purchased.plan.planId = 'basic_3mo';
|
||||
user3.purchased.plan.consecutive.count = 0;
|
||||
user3.purchased.plan.consecutive.offset = 3;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
@@ -438,21 +390,6 @@ describe('cron', async () => {
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
});
|
||||
|
||||
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
|
||||
user3.purchased.plan.perkMonthCount = 2;
|
||||
user3.purchased.plan.consecutive.trinkets = 1;
|
||||
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user3, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user3.purchased.plan.perkMonthCount).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months')
|
||||
.add(2, 'days')
|
||||
@@ -519,16 +456,13 @@ describe('cron', async () => {
|
||||
},
|
||||
});
|
||||
// user6 has a 6-month recurring subscription starting today
|
||||
beforeEach(async () => {
|
||||
user6.purchased.plan.customerId = 'subscribedId';
|
||||
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||
user6.purchased.plan.planId = 'google_6mo';
|
||||
user6.purchased.plan.perkMonthCount = 0;
|
||||
user6.purchased.plan.consecutive.count = 0;
|
||||
user6.purchased.plan.consecutive.offset = 6;
|
||||
user6.purchased.plan.consecutive.trinkets = 2;
|
||||
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
||||
});
|
||||
user6.purchased.plan.customerId = 'subscribedId';
|
||||
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||
user6.purchased.plan.planId = 'google_6mo';
|
||||
user6.purchased.plan.consecutive.count = 0;
|
||||
user6.purchased.plan.consecutive.offset = 6;
|
||||
user6.purchased.plan.consecutive.trinkets = 2;
|
||||
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
@@ -569,19 +503,6 @@ describe('cron', async () => {
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
});
|
||||
|
||||
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
|
||||
user6.purchased.plan.perkMonthCount = 2;
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user6, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user6.purchased.plan.perkMonthCount).to.equal(2);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', async () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
|
||||
.add(2, 'days')
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('Amazon Payments - Checkout', () => {
|
||||
let closeOrderReferenceSpy;
|
||||
|
||||
let paymentBuyGemsStub;
|
||||
let paymentCreateSubscriptionStub;
|
||||
let paymentCreateSubscritionStub;
|
||||
let amount = gemsBlock.price / 100;
|
||||
|
||||
function expectOrderReferenceSpy () {
|
||||
@@ -85,8 +85,8 @@ describe('Amazon Payments - Checkout', () => {
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems');
|
||||
paymentBuyGemsStub.resolves({});
|
||||
|
||||
paymentCreateSubscriptionStub = sinon.stub(payments, 'createSubscription');
|
||||
paymentCreateSubscriptionStub.resolves({});
|
||||
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription');
|
||||
paymentCreateSubscritionStub.resolves({});
|
||||
|
||||
sinon.stub(common, 'uuid').returns('uuid-generated');
|
||||
sandbox.stub(gems, 'validateGiftMessage');
|
||||
@@ -109,7 +109,6 @@ describe('Amazon Payments - Checkout', () => {
|
||||
user,
|
||||
paymentMethod,
|
||||
headers,
|
||||
sku: undefined,
|
||||
};
|
||||
if (gift) {
|
||||
expectedArgs.gift = gift;
|
||||
@@ -216,14 +215,13 @@ describe('Amazon Payments - Checkout', () => {
|
||||
});
|
||||
|
||||
gift.member = receivingUser;
|
||||
expect(paymentCreateSubscriptionStub).to.be.calledOnce;
|
||||
expect(paymentCreateSubscriptionStub).to.be.calledWith({
|
||||
expect(paymentCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT,
|
||||
headers,
|
||||
gift,
|
||||
gemsBlock: undefined,
|
||||
sku: undefined,
|
||||
});
|
||||
expectAmazonStubs();
|
||||
});
|
||||
|
||||
@@ -12,10 +12,10 @@ const { i18n } = common;
|
||||
describe('Apple Payments', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
|
||||
describe('verifyPurchase', () => {
|
||||
describe('verifyGemPurchase', () => {
|
||||
let sku; let user; let token; let receipt; let
|
||||
headers;
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuySkuStub; let
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuyGemsStub; let
|
||||
iapGetPurchaseDataStub; let validateGiftMessageStub;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -29,15 +29,14 @@ describe('Apple Payments', () => {
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iap, 'validate')
|
||||
.resolves({});
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
||||
sinon.stub(iap, 'isExpired').returns(false);
|
||||
sinon.stub(iap, 'isCanceled').returns(false);
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||
transactionId: token,
|
||||
}]);
|
||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
@@ -45,10 +44,8 @@ describe('Apple Payments', () => {
|
||||
iap.setup.restore();
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
iap.isExpired.restore();
|
||||
iap.isCanceled.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
payments.buySkuItem.restore();
|
||||
payments.buyGems.restore();
|
||||
gems.validateGiftMessage.restore();
|
||||
});
|
||||
|
||||
@@ -57,7 +54,7 @@ describe('Apple Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -69,7 +66,7 @@ describe('Apple Payments', () => {
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData').returns([]);
|
||||
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -79,7 +76,7 @@ describe('Apple Payments', () => {
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -97,16 +94,14 @@ describe('Apple Payments', () => {
|
||||
productId: 'badProduct',
|
||||
transactionId: token,
|
||||
}]);
|
||||
paymentBuySkuStub.restore();
|
||||
|
||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
||||
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_INVALID_ITEM,
|
||||
});
|
||||
|
||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
@@ -143,7 +138,7 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await applePayments.verifyPurchase({ user, receipt, headers });
|
||||
await applePayments.verifyGemPurchase({ user, receipt, headers });
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -153,13 +148,13 @@ describe('Apple Payments', () => {
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.not.be.called;
|
||||
|
||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
||||
expect(paymentBuySkuStub).to.be.calledWith({
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
gift: undefined,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sku: gemTest.productId,
|
||||
gemsBlock: common.content.gems[gemTest.gemsBlock],
|
||||
headers,
|
||||
gift: undefined,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
@@ -178,7 +173,7 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
const gift = { uuid: receivingUser._id };
|
||||
await applePayments.verifyPurchase({
|
||||
await applePayments.verifyGemPurchase({
|
||||
user, gift, receipt, headers,
|
||||
});
|
||||
|
||||
@@ -192,16 +187,18 @@ describe('Apple Payments', () => {
|
||||
expect(validateGiftMessageStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.be.calledWith(gift, user);
|
||||
|
||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
||||
expect(paymentBuySkuStub).to.be.calledWith({
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
gift: {
|
||||
uuid: receivingUser._id,
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
},
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sku: 'com.habitrpg.ios.Habitica.4gems',
|
||||
headers,
|
||||
gift: {
|
||||
type: 'gems',
|
||||
gems: { amount: 4 },
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
gemsBlock: common.content.gems['4gems'],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -221,7 +218,6 @@ describe('Apple Payments', () => {
|
||||
headers = {};
|
||||
receipt = `{"token": "${token}"}`;
|
||||
nextPaymentProcessing = moment.utc().add({ days: 2 });
|
||||
user = new User();
|
||||
|
||||
iapSetupStub = sinon.stub(iap, 'setup')
|
||||
.resolves();
|
||||
@@ -232,17 +228,14 @@ describe('Apple Payments', () => {
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: 'wrongsku',
|
||||
transactionId: token,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
}]);
|
||||
@@ -257,12 +250,21 @@ describe('Apple Payments', () => {
|
||||
if (payments.createSubscription.restore) payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if sku is empty', async () => {
|
||||
await expect(applePayments.subscribe('', user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if receipt is invalid', async () => {
|
||||
iap.isValidated.restore();
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -293,15 +295,13 @@ describe('Apple Payments', () => {
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: new Date(),
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
productId: option.sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[option.subKey];
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
await applePayments.subscribe(option.sku, user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -321,253 +321,21 @@ describe('Apple Payments', () => {
|
||||
nextPaymentProcessing,
|
||||
});
|
||||
});
|
||||
if (option !== subOptions[3]) {
|
||||
const newOption = subOptions[3];
|
||||
it(`upgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => {
|
||||
const oldSub = common.content.subscriptionBlocks[option.subKey];
|
||||
oldSub.logic = 'refundAndRepay';
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||
user.purchased.plan.customerId = token;
|
||||
user.purchased.plan.planId = option.subKey;
|
||||
user.purchased.plan.additionalData = receipt;
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: newOption.sku,
|
||||
transactionId: `${token}new`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||
|
||||
await applePayments.subscribe(user,
|
||||
receipt,
|
||||
headers,
|
||||
nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: token,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
updatedFrom: oldSub,
|
||||
});
|
||||
});
|
||||
}
|
||||
if (option !== subOptions[0]) {
|
||||
const newOption = subOptions[0];
|
||||
it(`downgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => {
|
||||
const oldSub = common.content.subscriptionBlocks[option.subKey];
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||
user.purchased.plan.customerId = token;
|
||||
user.purchased.plan.planId = option.subKey;
|
||||
user.purchased.plan.additionalData = receipt;
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: moment.utc().valueOf(),
|
||||
productId: newOption.sku,
|
||||
transactionId: `${token}new`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||
|
||||
await applePayments.subscribe(user,
|
||||
receipt,
|
||||
headers,
|
||||
nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: token,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
updatedFrom: oldSub,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('uses the most recent subscription data', async () => {
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 4 }).toDate(),
|
||||
purchaseDate: moment.utc().subtract({ day: 5 }).toDate(),
|
||||
productId: 'com.habitrpg.ios.habitica.subscription.3month',
|
||||
transactionId: `${token}oldest`,
|
||||
originalTransactionId: `${token}evenOlder`,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
||||
purchaseDate: moment.utc().subtract({ day: 1 }).toDate(),
|
||||
productId: 'com.habitrpg.ios.habitica.subscription.12month',
|
||||
transactionId: `${token}newest`,
|
||||
originalTransactionId: `${token}newest`,
|
||||
}, {
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().subtract({ day: 2 }).toDate(),
|
||||
productId: 'com.habitrpg.ios.habitica.subscription.6month',
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks.basic_12mo;
|
||||
it('errors when a user is already subscribed', async () => {
|
||||
payments.createSubscription.restore();
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: `${token}newest`,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
sub,
|
||||
headers,
|
||||
additionalData: receipt,
|
||||
nextPaymentProcessing,
|
||||
});
|
||||
});
|
||||
|
||||
describe('does not apply multiple times', async () => {
|
||||
it('errors when a user is using the same subscription', async () => {
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a user is using a rebill of the same subscription', async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: `${token}renew`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a different user is using the subscription', async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
|
||||
const secondUser = new User();
|
||||
await secondUser.save();
|
||||
await expect(applePayments.subscribe(
|
||||
secondUser, receipt, headers, nextPaymentProcessing,
|
||||
))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when a multiple users exist using the subscription', async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: token,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
||||
const secondUser = new User();
|
||||
secondUser.purchased.plan = user.purchased.plan;
|
||||
secondUser.purchased.plan.dateTerminate = new Date();
|
||||
secondUser.save();
|
||||
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||
purchaseDate: moment.utc().toDate(),
|
||||
productId: sku,
|
||||
transactionId: `${token}new`,
|
||||
originalTransactionId: token,
|
||||
}]);
|
||||
|
||||
const thirdUser = new User();
|
||||
await thirdUser.save();
|
||||
await expect(applePayments.subscribe(
|
||||
thirdUser, receipt, headers, nextPaymentProcessing,
|
||||
))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -592,9 +360,9 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{ expirationDate: expirationDate.toDate() }]);
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
||||
sinon.stub(iap, 'isCanceled').returns(false);
|
||||
sinon.stub(iap, 'isExpired').returns(true);
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||
@@ -609,8 +377,6 @@ describe('Apple Payments', () => {
|
||||
iap.setup.restore();
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
iap.isExpired.restore();
|
||||
iap.isCanceled.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
payments.cancelSubscription.restore();
|
||||
});
|
||||
@@ -630,8 +396,6 @@ describe('Apple Payments', () => {
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]);
|
||||
iap.isExpired.restore();
|
||||
sinon.stub(iap, 'isExpired').returns(false);
|
||||
|
||||
await expect(applePayments.cancelSubscribe(user, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -654,38 +418,7 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should cancel a cancelled subscription with termination date in the future', async () => {
|
||||
const futureDate = expirationDate.add({ day: 1 });
|
||||
iap.getPurchaseData.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{ expirationDate: futureDate }]);
|
||||
iap.isExpired.restore();
|
||||
sinon.stub(iap, 'isExpired').returns(false);
|
||||
|
||||
iap.isCanceled.restore();
|
||||
sinon.stub(iap, 'isCanceled').returns(true);
|
||||
|
||||
await applePayments.cancelSubscribe(user, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({
|
||||
expirationDate: futureDate,
|
||||
});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentCancelSubscriptionSpy).to.be.calledOnce;
|
||||
expect(paymentCancelSubscriptionSpy).to.be.calledWith({
|
||||
user,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
nextBill: futureDate.toDate(),
|
||||
headers,
|
||||
});
|
||||
});
|
||||
|
||||
it('should cancel an expired subscription', async () => {
|
||||
it('should cancel a user subscription', async () => {
|
||||
await applePayments.cancelSubscribe(user, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
|
||||
@@ -12,11 +12,11 @@ const { i18n } = common;
|
||||
describe('Google Payments', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
|
||||
describe('verifyPurchase', () => {
|
||||
describe('verifyGemPurchase', () => {
|
||||
let sku; let user; let token; let receipt; let signature; let
|
||||
headers;
|
||||
headers; const gemsBlock = common.content.gems['21gems'];
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
|
||||
paymentBuySkuStub; let validateGiftMessageStub;
|
||||
paymentBuyGemsStub; let validateGiftMessageStub;
|
||||
|
||||
beforeEach(() => {
|
||||
sku = 'com.habitrpg.android.habitica.iap.21gems';
|
||||
@@ -27,10 +27,11 @@ describe('Google Payments', () => {
|
||||
|
||||
iapSetupStub = sinon.stub(iap, 'setup')
|
||||
.resolves();
|
||||
iapValidateStub = sinon.stub(iap, 'validate').resolves({ productId: sku });
|
||||
iapValidateStub = sinon.stub(iap, 'validate')
|
||||
.resolves({});
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
@@ -38,7 +39,7 @@ describe('Google Payments', () => {
|
||||
iap.setup.restore();
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
payments.buySkuItem.restore();
|
||||
payments.buyGems.restore();
|
||||
gems.validateGiftMessage.restore();
|
||||
});
|
||||
|
||||
@@ -47,7 +48,7 @@ describe('Google Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(googlePayments.verifyPurchase({
|
||||
await expect(googlePayments.verifyGemPurchase({
|
||||
user, receipt, signature, headers,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -59,25 +60,21 @@ describe('Google Payments', () => {
|
||||
|
||||
it('should throw an error if productId is invalid', async () => {
|
||||
receipt = `{"token": "${token}", "productId": "invalid"}`;
|
||||
iapValidateStub.restore();
|
||||
iapValidateStub = sinon.stub(iap, 'validate').resolves({});
|
||||
|
||||
paymentBuySkuStub.restore();
|
||||
await expect(googlePayments.verifyPurchase({
|
||||
await expect(googlePayments.verifyGemPurchase({
|
||||
user, receipt, signature, headers,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: googlePayments.constants.RESPONSE_INVALID_ITEM,
|
||||
});
|
||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
||||
});
|
||||
|
||||
it('should throw an error if user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(googlePayments.verifyPurchase({
|
||||
await expect(googlePayments.verifyGemPurchase({
|
||||
user, receipt, signature, headers,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -91,7 +88,7 @@ describe('Google Payments', () => {
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await googlePayments.verifyPurchase({
|
||||
await googlePayments.verifyGemPurchase({
|
||||
user, receipt, signature, headers,
|
||||
});
|
||||
|
||||
@@ -104,17 +101,15 @@ describe('Google Payments', () => {
|
||||
signature,
|
||||
});
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith(
|
||||
{ productId: sku },
|
||||
);
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
|
||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
||||
expect(paymentBuySkuStub).to.be.calledWith({
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
gift: undefined,
|
||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||
sku,
|
||||
gemsBlock,
|
||||
headers,
|
||||
gift: undefined,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
@@ -125,7 +120,7 @@ describe('Google Payments', () => {
|
||||
await receivingUser.save();
|
||||
|
||||
const gift = { uuid: receivingUser._id };
|
||||
await googlePayments.verifyPurchase({
|
||||
await googlePayments.verifyGemPurchase({
|
||||
user, gift, receipt, signature, headers,
|
||||
});
|
||||
|
||||
@@ -139,20 +134,20 @@ describe('Google Payments', () => {
|
||||
signature,
|
||||
});
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith(
|
||||
{ productId: sku },
|
||||
);
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
|
||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
||||
expect(paymentBuySkuStub).to.be.calledWith({
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
gift: {
|
||||
uuid: receivingUser._id,
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
},
|
||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||
sku,
|
||||
gemsBlock,
|
||||
headers,
|
||||
gift: {
|
||||
type: 'gems',
|
||||
gems: { amount: 21 },
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -203,28 +203,6 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => {
|
||||
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.exist;
|
||||
});
|
||||
|
||||
it('keeps plan.dateCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = recipient.purchased.plan.dateCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(recipient.purchased.plan.dateCreated).to.eql(initialDate);
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = recipient.purchased.plan.dateCurrentTypeCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
|
||||
});
|
||||
|
||||
it('does not change plan.customerId if it already exists', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
data.customerId = 'purchaserCustomerId';
|
||||
@@ -235,116 +213,6 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to 1 if user is not subscribed', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 1;
|
||||
recipient.purchased.plan.customerId = undefined;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to 1 if field is not initialized', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = -1;
|
||||
recipient.purchased.plan.customerId = undefined;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to 1 if user had previous count but lapsed subscription', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 2;
|
||||
recipient.purchased.plan.customerId = undefined;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('adds to plan.perkMonthCount if user is already subscribed', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 1;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount reaches 3 with existing subscription', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 2;
|
||||
data.sub.key = 'basic_earned';
|
||||
data.gift.subscription.key = 'basic_earned';
|
||||
data.gift.subscription.months = 1;
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount reaches 3 without existing subscription', async () => {
|
||||
recipient.purchased.plan.perkMonthCount = 0;
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount reaches 3 without initialized field', async () => {
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('awards perks if plan.perkMonthCount goes over 3', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.purchased.plan.perkMonthCount = 2;
|
||||
data.sub.key = 'basic_earned';
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
|
||||
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
});
|
||||
|
||||
it('sets plan.customerId to "Gift" if it does not already exist', async () => {
|
||||
expect(recipient.purchased.plan.customerId).to.not.exist;
|
||||
|
||||
@@ -511,7 +379,6 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||
expect(user.purchased.plan.gemsBought).to.eql(0);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
||||
expect(user.purchased.plan.paymentMethod).to.eql('Payment Method');
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
expect(user.purchased.plan.dateTerminated).to.eql(null);
|
||||
@@ -519,63 +386,6 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCreated if it did not previously exist', async () => {
|
||||
expect(user.purchased.plan.dateCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => {
|
||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.exist;
|
||||
});
|
||||
|
||||
it('keeps plan.dateCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = user.purchased.plan.dateCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.dateCreated).to.eql(initialDate);
|
||||
});
|
||||
|
||||
it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
const initialDate = user.purchased.plan.dateCurrentTypeCreated;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
|
||||
});
|
||||
|
||||
it('keeps plan.perkMonthCount when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(2);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to zero when creating new monthly subscription', async () => {
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('sets plan.perkMonthCount to zero when creating new 3 month subscription', async () => {
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.perkMonthCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('updates plan.consecutive.offset when changing subscription type', async () => {
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(3);
|
||||
data.sub.key = 'basic_6mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(6);
|
||||
});
|
||||
|
||||
it('awards the Royal Purple Jackalope pet', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -655,89 +465,6 @@ describe('payments/index', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('Upgrades subscription', () => {
|
||||
it('from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom = { key: 'basic_earned' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
|
||||
it('from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom = { key: 'basic_3mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
});
|
||||
|
||||
context('Downgrades subscription', () => {
|
||||
it('from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
|
||||
it('from basic_12mo to basic_3mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
const created = user.purchased.plan.dateCreated;
|
||||
const updated = user.purchased.plan.dateUpdated;
|
||||
|
||||
data.sub.key = 'basic_3mo';
|
||||
data.updatedFrom = { key: 'basic_12mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.dateCreated).to.eql(created);
|
||||
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
|
||||
expect(user.purchased.plan.customerId).to.eql('customer-id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Block subscription perks', () => {
|
||||
@@ -748,19 +475,9 @@ describe('payments/index', () => {
|
||||
});
|
||||
|
||||
it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
||||
});
|
||||
|
||||
it('resets plans.consecutive.offset if 1 month subscription', async () => {
|
||||
user.purchased.plan.consecutive.offset = 1;
|
||||
await user.save();
|
||||
data.sub.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
});
|
||||
|
||||
it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => {
|
||||
@@ -771,6 +488,7 @@ describe('payments/index', () => {
|
||||
|
||||
it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
@@ -778,6 +496,7 @@ describe('payments/index', () => {
|
||||
|
||||
it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => {
|
||||
data.sub.key = 'basic_12mo';
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
@@ -813,532 +532,6 @@ describe('payments/index', () => {
|
||||
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
context('Upgrades subscription', () => {
|
||||
context('Using payDifference logic', () => {
|
||||
beforeEach(async () => {
|
||||
data.updatedFrom = { logic: 'payDifference' };
|
||||
});
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
});
|
||||
|
||||
context('Using payFull logic', () => {
|
||||
beforeEach(async () => {
|
||||
data.updatedFrom = { logic: 'payFull' };
|
||||
});
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
||||
});
|
||||
});
|
||||
|
||||
context('Using refundAndRepay logic', () => {
|
||||
let clock;
|
||||
beforeEach(async () => {
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-01'));
|
||||
data.updatedFrom = { logic: 'refundAndRepay' };
|
||||
});
|
||||
context('Upgrades within first half of subscription', () => {
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-10'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-02-05'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-08'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-31'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2024-01-08'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-08-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
|
||||
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-07-31'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
});
|
||||
context('Upgrades within second half of subscription', () => {
|
||||
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-20'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-02-24'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-01-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-03-03'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
||||
});
|
||||
|
||||
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom.key = 'basic_earned';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2022-05-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_6mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2023-05-28'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
|
||||
});
|
||||
|
||||
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_3mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
data.updatedFrom.key = 'basic_3mo';
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2023-09-03'));
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
|
||||
});
|
||||
});
|
||||
afterEach(async () => {
|
||||
if (clock !== null) clock.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Downgrades subscription', () => {
|
||||
it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
||||
});
|
||||
|
||||
it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
|
||||
data.sub.key = 'basic_3mo';
|
||||
data.updatedFrom = { key: 'basic_12mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
||||
});
|
||||
|
||||
it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_6mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.planId).to.eql('basic_earned');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
|
||||
});
|
||||
|
||||
it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
data.sub.key = 'basic_12mo';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.planId).to.eql('basic_12mo');
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
|
||||
data.sub.key = 'basic_3mo';
|
||||
data.updatedFrom = { key: 'basic_12mo' };
|
||||
await api.createSubscription(data);
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Mystery Items', () => {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import {
|
||||
canBuySkuItem,
|
||||
} from '../../../../../website/server/libs/payments/skuItem';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
|
||||
describe('payments/skuItems', () => {
|
||||
let user;
|
||||
let clock;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
clock = null;
|
||||
});
|
||||
afterEach(() => {
|
||||
if (clock !== null) clock.restore();
|
||||
});
|
||||
|
||||
describe('#canBuySkuItem', () => {
|
||||
it('returns true for random sku', () => {
|
||||
expect(canBuySkuItem('something', user)).to.be.true;
|
||||
});
|
||||
|
||||
describe('#gryphatrice', () => {
|
||||
const sku = 'Pet-Gryphatrice-Jubilant';
|
||||
it('returns true during birthday week', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-01-31'));
|
||||
expect(canBuySkuItem(sku, user)).to.be.true;
|
||||
});
|
||||
it('returns false outside of birthday week', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-01-20'));
|
||||
expect(canBuySkuItem(sku, user)).to.be.false;
|
||||
});
|
||||
it('returns false if user already owns it', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2023-02-01'));
|
||||
user.items.pets['Gryphatrice-Jubilant'] = 5;
|
||||
expect(canBuySkuItem(sku, user)).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -242,7 +242,7 @@ describe('cron middleware', () => {
|
||||
|
||||
sandbox.spy(cronLib, 'recoverCron');
|
||||
|
||||
sandbox.stub(User, 'updateOne')
|
||||
sandbox.stub(User, 'update')
|
||||
.withArgs({
|
||||
_id: user._id,
|
||||
$or: [
|
||||
|
||||
@@ -1359,7 +1359,6 @@ describe('Group Model', () => {
|
||||
describe('#sendChat', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(User, 'update');
|
||||
sandbox.spy(User, 'updateMany');
|
||||
});
|
||||
|
||||
it('formats message', () => {
|
||||
@@ -1414,8 +1413,8 @@ describe('Group Model', () => {
|
||||
it('updates users about new messages in party', () => {
|
||||
party.sendChat({ message: 'message' });
|
||||
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: '' },
|
||||
});
|
||||
@@ -1428,8 +1427,8 @@ describe('Group Model', () => {
|
||||
|
||||
group.sendChat({ message: 'message' });
|
||||
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
guilds: group._id,
|
||||
_id: { $ne: '' },
|
||||
});
|
||||
@@ -1438,8 +1437,8 @@ describe('Group Model', () => {
|
||||
it('does not send update to user that sent the message', () => {
|
||||
party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
|
||||
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: 'user-id' },
|
||||
});
|
||||
@@ -1732,7 +1731,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates participting members (not including user)', async () => {
|
||||
sandbox.spy(User, 'updateMany');
|
||||
sandbox.spy(User, 'update');
|
||||
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
@@ -1740,7 +1739,7 @@ describe('Group Model', () => {
|
||||
questLeader._id, participatingMember._id, sleepingParticipatingMember._id,
|
||||
];
|
||||
|
||||
expect(User.updateMany).to.be.calledWith(
|
||||
expect(User.update).to.be.calledWith(
|
||||
{ _id: { $in: members } },
|
||||
{
|
||||
$set: {
|
||||
@@ -1753,11 +1752,11 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates non-user quest leader and decrements quest scroll', async () => {
|
||||
sandbox.spy(User, 'updateOne');
|
||||
sandbox.spy(User, 'update');
|
||||
|
||||
await party.startQuest(participatingMember);
|
||||
|
||||
expect(User.updateOne).to.be.calledWith(
|
||||
expect(User.update).to.be.calledWith(
|
||||
{ _id: questLeader._id },
|
||||
{
|
||||
$inc: {
|
||||
@@ -1819,29 +1818,29 @@ describe('Group Model', () => {
|
||||
};
|
||||
|
||||
it('doesn\'t retry successful operations', async () => {
|
||||
sandbox.stub(User, 'updateOne').returns(successfulMock);
|
||||
sandbox.stub(User, 'update').returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.updateOne).to.be.calledThrice;
|
||||
expect(User.update).to.be.calledThrice;
|
||||
});
|
||||
|
||||
it('stops retrying when a successful update has occurred', async () => {
|
||||
const updateStub = sandbox.stub(User, 'updateOne');
|
||||
const updateStub = sandbox.stub(User, 'update');
|
||||
updateStub.onCall(0).returns(failedMock);
|
||||
updateStub.returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.updateOne.callCount).to.equal(4);
|
||||
expect(User.update.callCount).to.equal(4);
|
||||
});
|
||||
|
||||
it('retries failed updates at most five times per user', async () => {
|
||||
sandbox.stub(User, 'updateOne').returns(failedMock);
|
||||
sandbox.stub(User, 'update').returns(failedMock);
|
||||
|
||||
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
||||
|
||||
expect(User.updateOne.callCount).to.eql(15); // for 3 users
|
||||
expect(User.update.callCount).to.eql(15); // for 3 users
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2088,17 +2087,17 @@ describe('Group Model', () => {
|
||||
|
||||
context('Party quests', () => {
|
||||
it('updates participating members with rewards', async () => {
|
||||
sandbox.spy(User, 'updateOne');
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.updateOne).to.be.calledThrice;
|
||||
expect(User.updateOne).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledThrice;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: questLeader._id,
|
||||
});
|
||||
expect(User.updateOne).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: participatingMember._id,
|
||||
});
|
||||
expect(User.updateOne).to.be.calledWithMatch({
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: sleepingParticipatingMember._id,
|
||||
});
|
||||
});
|
||||
@@ -2173,11 +2172,11 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates all users with rewards', async () => {
|
||||
sandbox.spy(User, 'updateMany');
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(tavernQuest);
|
||||
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({});
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({});
|
||||
});
|
||||
|
||||
it('sets quest completed to the world quest key', async () => {
|
||||
|
||||
@@ -16,7 +16,60 @@ describe('GET /challenges/:challengeId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Group Plan', () => {
|
||||
context('public guild', () => {
|
||||
let groupLeader;
|
||||
let group;
|
||||
let challenge;
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
|
||||
const populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'public' },
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('should return challenge data', async () => {
|
||||
await challenge.sync();
|
||||
const chal = await user.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.memberCount).to.equal(challenge.memberCount);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader._id,
|
||||
profile: { name: groupLeader.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: groupLeader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(chal.group).to.eql({
|
||||
_id: group._id,
|
||||
categories: [],
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
summary: group.name,
|
||||
type: group.type,
|
||||
privacy: group.privacy,
|
||||
leader: groupLeader.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('private guild', () => {
|
||||
let groupLeader;
|
||||
let challengeLeader;
|
||||
let group;
|
||||
@@ -31,14 +84,14 @@ describe('GET /challenges/:challengeId', () => {
|
||||
const populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
members = populatedGroup.members;
|
||||
|
||||
[challengeLeader, otherMember] = members;
|
||||
challengeLeader = members[0]; // eslint-disable-line prefer-destructuring
|
||||
otherMember = members[1]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
challenge = await generateChallenge(challengeLeader, group);
|
||||
});
|
||||
|
||||
@@ -71,18 +71,42 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('populates only some fields', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', privacy: 'private', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
it('works with challenges belonging to public guild', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const challenge = await generateChallenge(leader, group);
|
||||
await leader.post(`/challenges/${challenge._id}/join`);
|
||||
const res = await user.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: { name: user.profile.name },
|
||||
_id: leader._id,
|
||||
id: leader._id,
|
||||
profile: { name: leader.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
username: leader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('populates only some fields', async () => {
|
||||
const anotherUser = await generateUser({ balance: 3 });
|
||||
const group = await generateGroup(anotherUser, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const challenge = await generateChallenge(anotherUser, group);
|
||||
await anotherUser.post(`/challenges/${challenge._id}/join`);
|
||||
const res = await user.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: anotherUser._id,
|
||||
id: anotherUser._id,
|
||||
profile: { name: anotherUser.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: anotherUser.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
|
||||
@@ -72,6 +72,20 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('works with challenges belonging to a public guild', async () => {
|
||||
const groupLeader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(groupLeader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
const taskText = 'Test Text';
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{ type: 'habit', text: taskText }]);
|
||||
|
||||
const memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`);
|
||||
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);
|
||||
});
|
||||
|
||||
it('returns the member tasks for the challenges', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
|
||||
@@ -7,7 +7,117 @@ import {
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('GET challenges/groups/:groupId', () => {
|
||||
context('Group Plan', () => {
|
||||
context('Public Guild', () => {
|
||||
let publicGuild; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
user = groupLeader;
|
||||
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should return group challenges for non member with populated leader', async () => {
|
||||
const challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return group challenges for member with populated leader', async () => {
|
||||
const challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return newest challenges first', async () => {
|
||||
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('Private Guild', () => {
|
||||
let privateGuild; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
|
||||
@@ -18,7 +128,6 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
privateGuild = group;
|
||||
@@ -77,6 +186,68 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let publicGuild; let user; let officialChallenge; let unofficialChallenges;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
publicGuild = group;
|
||||
|
||||
await user.update({
|
||||
'permissions.challengeAdmin': true,
|
||||
});
|
||||
|
||||
officialChallenge = await generateChallenge(user, group, {
|
||||
categories: [{
|
||||
name: 'habitica_official',
|
||||
slug: 'habitica_official',
|
||||
}],
|
||||
});
|
||||
await user.post(`/challenges/${officialChallenge._id}/join`);
|
||||
|
||||
// We add 10 extra challenges to test whether the official challenge
|
||||
// (the oldest) makes it to the front page.
|
||||
unofficialChallenges = [];
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const challenge = await generateChallenge(user, group); // eslint-disable-line
|
||||
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
|
||||
unofficialChallenges.push(challenge);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
const challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
|
||||
it('should return newest challenges first, after official ones', async () => {
|
||||
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
unofficialChallenges.forEach((chal, index) => {
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id });
|
||||
expect(foundChallengeIndex).to.eql(10 - index);
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
context('Party', () => {
|
||||
let party; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
@@ -230,7 +401,7 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return tavern challenges using ID "habitrpg"', async () => {
|
||||
it('should return tavern challenges using ID "habitrpg', async () => {
|
||||
const challenges = await user.get('/challenges/groups/habitrpg');
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
@@ -264,58 +435,5 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let officialChallenge; let unofficialChallenges;
|
||||
|
||||
before(async () => {
|
||||
await user.update({
|
||||
'permissions.challengeAdmin': true,
|
||||
balance: 3,
|
||||
});
|
||||
|
||||
officialChallenge = await generateChallenge(user, tavern, {
|
||||
categories: [{
|
||||
name: 'habitica_official',
|
||||
slug: 'habitica_official',
|
||||
}],
|
||||
prize: 1,
|
||||
});
|
||||
await user.post(`/challenges/${officialChallenge._id}/join`);
|
||||
|
||||
// We add 10 extra challenges to test whether the official challenge
|
||||
// (the oldest) makes it to the front page.
|
||||
unofficialChallenges = [];
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const challenge = await generateChallenge(user, tavern, { prize: 1 }); // eslint-disable-line
|
||||
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
|
||||
unofficialChallenges.push(challenge);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
const challenges = await user.get('/challenges/groups/habitrpg');
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
|
||||
it('should return newest challenges first, after official ones', async () => {
|
||||
let challenges = await user.get('/challenges/groups/habitrpg');
|
||||
|
||||
unofficialChallenges.forEach((chal, index) => {
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id });
|
||||
expect(foundChallengeIndex).to.eql(10 - index);
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, tavern, { prize: 1 });
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/groups/habitrpg');
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,44 +2,39 @@ import {
|
||||
generateUser,
|
||||
generateChallenge,
|
||||
createAndPopulateGroup,
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('GET challenges/user', () => {
|
||||
context('no official challenges', () => {
|
||||
let user; let member; let nonMember; let challenge; let challenge2; let publicChallenge;
|
||||
let groupPlan; let userData; let groupData; let tavern; let tavernData;
|
||||
let user; let member; let nonMember; let challenge; let challenge2;
|
||||
let publicGuild; let userData; let groupData;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupPlan = group;
|
||||
publicGuild = group;
|
||||
groupData = {
|
||||
_id: groupPlan._id,
|
||||
_id: publicGuild._id,
|
||||
categories: [],
|
||||
id: groupPlan._id,
|
||||
type: groupPlan.type,
|
||||
privacy: groupPlan.privacy,
|
||||
name: groupPlan.name,
|
||||
summary: groupPlan.name,
|
||||
leader: groupPlan.leader._id,
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
};
|
||||
|
||||
user = groupLeader;
|
||||
userData = {
|
||||
_id: groupPlan.leader._id,
|
||||
id: groupPlan.leader._id,
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
@@ -51,31 +46,17 @@ describe('GET challenges/user', () => {
|
||||
},
|
||||
};
|
||||
|
||||
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
||||
tavernData = {
|
||||
_id: TAVERN_ID,
|
||||
categories: [],
|
||||
id: TAVERN_ID,
|
||||
type: tavern.type,
|
||||
privacy: tavern.privacy,
|
||||
name: tavern.name,
|
||||
summary: tavern.name,
|
||||
leader: tavern.leader._id,
|
||||
};
|
||||
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.update({ balance: 0.25 });
|
||||
publicChallenge = await generateChallenge(user, tavern, { prize: 1 });
|
||||
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
await nonMember.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
context('all challenges', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await member.get('/challenges/user?page=0');
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
@@ -83,13 +64,11 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge.group).to.eql(groupData);
|
||||
});
|
||||
|
||||
it('should return public challenges', async () => {
|
||||
it('should not return challenges a non-member has not joined', async () => {
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.exist;
|
||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
|
||||
it('should return challenges user has created', async () => {
|
||||
@@ -121,10 +100,10 @@ describe('GET challenges/user', () => {
|
||||
it('should return newest challenges first', async () => {
|
||||
let challenges = await user.get('/challenges/user?page=0');
|
||||
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: publicChallenge._id });
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
|
||||
const newChallenge = await generateChallenge(user, groupPlan);
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user?page=0');
|
||||
@@ -134,23 +113,52 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
const privateChallenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
const privateChallenge = await generateChallenge(groupLeader, group, {
|
||||
categories: [{
|
||||
name: 'academics',
|
||||
slug: 'academics',
|
||||
}],
|
||||
});
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
const challenges = await nonMember.get('/challenges/user?page=0&categories=academics&owned=not_owned');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
context('my challenges', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
const challenges = await nonMember.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
@@ -169,10 +177,6 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.exist;
|
||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||
});
|
||||
|
||||
it('should return challenges user has created if filter by owned', async () => {
|
||||
@@ -186,10 +190,6 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.exist;
|
||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||
});
|
||||
|
||||
it('should not return challenges user has created if filter by not owned', async () => {
|
||||
@@ -199,40 +199,36 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge1).to.not.exist;
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges in user groups', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.not.exist;
|
||||
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return public challenges', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let user; let officialChallenge; let unofficialChallenges; let
|
||||
group;
|
||||
publicGuild;
|
||||
|
||||
before(async () => {
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
summary: 'summary for TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
publicGuild = group;
|
||||
|
||||
await user.update({
|
||||
'permissions.challengeAdmin': true,
|
||||
@@ -275,7 +271,7 @@ describe('GET challenges/user', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, group);
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user?page=0');
|
||||
@@ -298,10 +294,9 @@ describe('GET challenges/user', () => {
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
generateChallenge,
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /challenges/:challengeId/flag', () => {
|
||||
let user;
|
||||
let challenge;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestParty',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
});
|
||||
|
||||
it('returns an error when challenge is not found', async () => {
|
||||
await expect(user.post(`/challenges/${generateUUID()}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('challengeNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('flags a challenge', async () => {
|
||||
const flagResult = await user.post(`/challenges/${challenge._id}/flag`);
|
||||
|
||||
expect(flagResult.challenge.flags[user._id]).to.equal(true);
|
||||
expect(flagResult.challenge.flagCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('flags a challenge with a higher count when from an admin', async () => {
|
||||
await user.update({ 'contributor.admin': true });
|
||||
|
||||
const flagResult = await user.post(`/challenges/${challenge._id}/flag`);
|
||||
|
||||
expect(flagResult.challenge.flags[user._id]).to.equal(true);
|
||||
expect(flagResult.challenge.flagCount).to.equal(5);
|
||||
});
|
||||
|
||||
it('returns an error when user tries to flag a challenge that is already flagged', async () => {
|
||||
await user.post(`/challenges/${challenge._id}/flag`);
|
||||
|
||||
await expect(user.post(`/challenges/${challenge._id}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageChallengeFlagAlreadyReported'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
generateChallenge,
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /challenges/:challengeId/clearflags', () => {
|
||||
let admin;
|
||||
let nonAdmin;
|
||||
let challenge;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestParty',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
admin = groupLeader;
|
||||
[nonAdmin] = members;
|
||||
|
||||
await admin.update({ 'permissions.moderator': true });
|
||||
|
||||
challenge = await generateChallenge(admin, group);
|
||||
await admin.post(`/challenges/${challenge._id}/flag`);
|
||||
});
|
||||
|
||||
it('returns error when non-admin attempts to clear flags', async () => {
|
||||
await expect(nonAdmin.post(`/challenges/${generateUUID()}/clearflags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageGroupChatAdminClearFlagCount'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when challenge is not found', async () => {
|
||||
await expect(admin.post(`/challenges/${generateUUID()}/clearflags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('challengeNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('clears flags and sets mod flag to false', async () => {
|
||||
await nonAdmin.post(`/challenges/${challenge._id}/flag`);
|
||||
const flagResult = await admin.post(`/challenges/${challenge._id}/clearflags`);
|
||||
|
||||
expect(flagResult.challenge.flagCount).to.eql(0);
|
||||
expect(flagResult.challenge.flags).to.have.property(admin._id, false);
|
||||
expect(flagResult.challenge.flags).to.have.property(nonAdmin._id, true);
|
||||
});
|
||||
});
|
||||
@@ -42,7 +42,26 @@ describe('POST /challenges', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when creating a challenge with summary with greater than MAX_SUMMARY_SIZE_FOR_CHALLENGES characters', async () => {
|
||||
it('returns error when creating a challenge in a public guild and you are not a member of it', async () => {
|
||||
const user = await generateUser();
|
||||
const { group } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
await expect(user.post('/challenges', {
|
||||
group: group._id,
|
||||
prize: 4,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('mustBeGroupMember'),
|
||||
});
|
||||
});
|
||||
|
||||
it('return error when creating a challenge with summary with greater than MAX_SUMMARY_SIZE_FOR_CHALLENGES characters', async () => {
|
||||
const user = await generateUser();
|
||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1);
|
||||
const group = createAndPopulateGroup({
|
||||
@@ -58,7 +77,7 @@ describe('POST /challenges', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('creating a Challenge for a Group Plan', () => {
|
||||
context('Creating a challenge for a valid group', () => {
|
||||
let groupLeader;
|
||||
let group;
|
||||
let groupMember;
|
||||
@@ -75,11 +94,9 @@ describe('POST /challenges', () => {
|
||||
challenges: true,
|
||||
},
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupLeader = await populatedGroup.groupLeader.sync();
|
||||
await groupLeader.update({ permissions: {} });
|
||||
group = populatedGroup.group;
|
||||
groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
@@ -18,7 +18,6 @@ describe('PUT /challenges/:challengeId', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
privateGuild = group;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -9,30 +10,27 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
admin;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupWithChat = group;
|
||||
user = groupLeader;
|
||||
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||
message = message.message;
|
||||
userThatDidNotCreateChat = members[0]; // eslint-disable-line prefer-destructuring
|
||||
admin = members[1]; // eslint-disable-line prefer-destructuring
|
||||
await admin.update({ permissions: { moderator: true } });
|
||||
userThatDidNotCreateChat = await generateUser();
|
||||
admin = await generateUser({ 'permissions.moderator': true });
|
||||
});
|
||||
|
||||
context('Chat errors', () => {
|
||||
it('returns an error if message does not exist', async () => {
|
||||
it('returns an error is message does not exist', async () => {
|
||||
const fakeChatId = generateUUID();
|
||||
await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
@@ -58,7 +56,7 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
nextMessage = nextMessage.message;
|
||||
});
|
||||
|
||||
it('allows creator to delete their message', async () => {
|
||||
it('allows creator to delete a their message', async () => {
|
||||
await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
|
||||
|
||||
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -11,22 +11,48 @@ describe('GET /groups/:groupId/chat', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
context('public Guild', () => {
|
||||
let group;
|
||||
|
||||
before(async () => {
|
||||
const leader = await generateUser({ balance: 2 });
|
||||
|
||||
group = await generateGroup(leader, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}, {
|
||||
chat: [
|
||||
{ text: 'Hello', flags: {}, id: 1 },
|
||||
{ text: 'Welcome to the Guild', flags: {}, id: 2 },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns Guild chat', async () => {
|
||||
const chat = await user.get(`/groups/${group._id}/chat`);
|
||||
|
||||
expect(chat[0].id).to.eql(group.chat[0].id);
|
||||
expect(chat[1].id).to.eql(group.chat[1].id);
|
||||
});
|
||||
});
|
||||
|
||||
context('private Guild', () => {
|
||||
let group;
|
||||
|
||||
before(async () => {
|
||||
({ group } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
const leader = await generateUser({ balance: 2 });
|
||||
|
||||
group = await generateGroup(leader, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
}, {
|
||||
chat: [
|
||||
'Hello',
|
||||
'Welcome to the Guild',
|
||||
],
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error if user is not member of requested private group', async () => {
|
||||
|
||||
@@ -1,42 +1,32 @@
|
||||
import find from 'lodash/find';
|
||||
import { find } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
describe('POST /chat/:chatId/flag', () => {
|
||||
let user; let admin; let anotherUser; let newUser; let
|
||||
group; let members; let userToDelete;
|
||||
group;
|
||||
const TEST_MESSAGE = 'Test Message';
|
||||
const USER_AGE_FOR_FLAGGING = 3;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group, groupLeader: user, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate(),
|
||||
},
|
||||
members: 4,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
[admin, anotherUser, newUser, userToDelete] = members;
|
||||
await user.update({ permissions: {} });
|
||||
await admin.update({ permissions: { moderator: true } });
|
||||
await anotherUser.update({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
await newUser.update({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
|
||||
await userToDelete.update({
|
||||
'auth.timestamps.created': moment().subtract(1, 'days').toDate(),
|
||||
'purchased.plan.dateTerminated': moment().subtract(1, 'minutes').toDate(),
|
||||
});
|
||||
|
||||
user = await generateUser({ balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
admin = await generateUser({ balance: 1, 'permissions.moderator': true });
|
||||
anotherUser = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
newUser = await generateUser({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||
|
||||
group = await user.post('/groups', {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -79,8 +69,8 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild - (private guild)',
|
||||
title_link: undefined,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
||||
mrkdwn_in: [
|
||||
@@ -88,7 +78,7 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
/* eslint-ensable camelcase */
|
||||
});
|
||||
|
||||
it('Does not increment message flag count and sends different message to moderator Slack when user is new', async () => {
|
||||
@@ -114,8 +104,8 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild - (private guild)',
|
||||
title_link: undefined,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
|
||||
mrkdwn_in: [
|
||||
@@ -123,12 +113,15 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
/* eslint-ensable camelcase */
|
||||
});
|
||||
|
||||
it('Flags a chat when the author\'s account was deleted', async () => {
|
||||
const { message } = await userToDelete.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
||||
await userToDelete.del('/user', {
|
||||
const deletedUser = await generateUser({
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
const { message } = await deletedUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
||||
await deletedUser.del('/user', {
|
||||
password: 'password',
|
||||
});
|
||||
|
||||
|
||||
@@ -6,27 +6,27 @@ import {
|
||||
|
||||
describe('POST /chat/:chatId/like', () => {
|
||||
let user;
|
||||
let anotherUser;
|
||||
let groupWithChat;
|
||||
let members;
|
||||
const testMessage = 'Test Message';
|
||||
let anotherUser;
|
||||
|
||||
before(async () => {
|
||||
({ group: groupWithChat, groupLeader: user, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
|
||||
[anotherUser] = members;
|
||||
user = groupLeader;
|
||||
groupWithChat = group;
|
||||
anotherUser = members[0]; // eslint-disable-line prefer-destructuring
|
||||
await anotherUser.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import nconf from 'nconf';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
sleep,
|
||||
server,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import {
|
||||
SPAM_MESSAGE_LIMIT,
|
||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import { CHAT_FLAG_FROM_SHADOW_MUTE, MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
describe('POST /chat', () => {
|
||||
let user; let groupWithChat; let member; let
|
||||
additionalMember;
|
||||
const testMessage = 'Test Message';
|
||||
const testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE';
|
||||
const testBannedWordMessage1 = 'TESTPLACEHOLDERSWEARWORDHERE1';
|
||||
const testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE';
|
||||
const testSlurMessage1 = 'TESTPLACEHOLDERSLURWORDHERE1';
|
||||
const bannedWordErrorMessage = t('bannedWordUsed', { swearWordsUsed: testBannedWordMessage });
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
user = groupLeader;
|
||||
await user.update({
|
||||
@@ -35,7 +43,8 @@ describe('POST /chat', () => {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
}); // prevent tests accidentally throwing messageGroupChatSpam
|
||||
groupWithChat = group;
|
||||
[member, additionalMember] = members;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
additionalMember = members[1]; // eslint-disable-line prefer-destructuring
|
||||
await member.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
await additionalMember.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
@@ -80,12 +89,32 @@ describe('POST /chat', () => {
|
||||
member.update({ 'flags.chatRevoked': false });
|
||||
});
|
||||
|
||||
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
|
||||
const userWithChatRevoked = await member.update({ 'flags.chatRevoked': true });
|
||||
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not error when chat privileges are revoked when sending a message to a private guild', async () => {
|
||||
await member.update({
|
||||
'flags.chatRevoked': true,
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Private Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const privateGuildMemberWithChatsRevoked = members[0];
|
||||
await privateGuildMemberWithChatsRevoked.update({
|
||||
'flags.chatRevoked': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await privateGuildMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
@@ -123,12 +152,54 @@ describe('POST /chat', () => {
|
||||
member.update({ 'flags.chatShadowMuted': false });
|
||||
});
|
||||
|
||||
it('creates a chat with flagCount already set and notifies mods when sending a message to a public guild', async () => {
|
||||
const userWithChatShadowMuted = await member.update({ 'flags.chatShadowMuted': true });
|
||||
const message = await userWithChatShadowMuted.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE);
|
||||
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][1]).to.eql('shadow-muted-post-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `@${member.auth.local.username} / ${member.profile.name} posted while shadow-muted`,
|
||||
attachments: [{
|
||||
fallback: 'Shadow-Muted Message',
|
||||
color: 'danger',
|
||||
author_name: `@${member.auth.local.username} ${member.profile.name} (${member.auth.local.email}; ${member._id})`,
|
||||
title: 'Shadow-Muted Post in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
|
||||
text: testMessage,
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
|
||||
it('creates a chat with zero flagCount when sending a message to a private guild', async () => {
|
||||
await member.update({
|
||||
'flags.chatShadowMuted': true,
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Private Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const userWithChatShadowMuted = members[0];
|
||||
await userWithChatShadowMuted.update({
|
||||
'flags.chatShadowMuted': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
@@ -155,9 +226,100 @@ describe('POST /chat', () => {
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('creates a chat with zero flagCount when non-shadow-muted user sends a message to a public guild', async () => {
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('banned word', () => {
|
||||
it('returns an error when chat message contains a banned word in tavern', async () => {
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: testBannedWordMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when chat message contains a banned word in a public guild', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await expect(members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is part of a phrase', async () => {
|
||||
const wordInPhrase = `phrase ${testBannedWordMessage} end`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is surrounded by non alphabet characters', async () => {
|
||||
const wordInPhrase = `_!${testBannedWordMessage}@_`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is typed in mixed case', async () => {
|
||||
const substrLength = Math.floor(testBannedWordMessage.length / 2);
|
||||
const chatMessage = testBannedWordMessage.substring(0, substrLength).toLowerCase()
|
||||
+ testBannedWordMessage.substring(substrLength).toUpperCase();
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedWordUsed', { swearWordsUsed: chatMessage }),
|
||||
});
|
||||
});
|
||||
|
||||
it('checks error message has all the banned words used, regardless of case', async () => {
|
||||
const testBannedWords = [
|
||||
testBannedWordMessage.toUpperCase(),
|
||||
testBannedWordMessage1.toLowerCase(),
|
||||
];
|
||||
const chatMessage = `Mixing ${testBannedWords[0]} and ${testBannedWords[1]} is bad for you.`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected
|
||||
.and.have.property('message')
|
||||
.that.includes(testBannedWords.join(', '));
|
||||
});
|
||||
|
||||
it('does not error when bad word is suffix of a word', async () => {
|
||||
const wordAsSuffix = `prefix${testBannedWordMessage}`;
|
||||
const message = await user.post('/groups/habitrpg/chat', { message: wordAsSuffix });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when bad word is prefix of a word', async () => {
|
||||
const wordAsPrefix = `${testBannedWordMessage}suffix`;
|
||||
const message = await user.post('/groups/habitrpg/chat', { message: wordAsPrefix });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when sending a chat message containing a banned word to a party', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
@@ -174,8 +336,37 @@ describe('POST /chat', () => {
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when sending a chat message containing a banned word to a public guild in which banned words are allowed', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
// Update the bannedWordsAllowed property for the group
|
||||
group.update({ bannedWordsAllowed: true });
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
|
||||
const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when sending a chat message containing a banned word to a private guild', async () => {
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testBannedWordMessage });
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'private guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
|
||||
const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
@@ -192,6 +383,45 @@ describe('POST /chat', () => {
|
||||
user.update({ 'flags.chatRevoked': false });
|
||||
});
|
||||
|
||||
it('errors and revokes privileges when chat message contains a banned slur', async () => {
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testSlurMessage })).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][1]).to.eql('slur-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${user.profile.name} (${user.id}) tried to post a slur`,
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`,
|
||||
title: 'Slur in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
|
||||
text: testSlurMessage,
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
// Chat privileges are revoked
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows slurs in private groups', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
@@ -207,17 +437,28 @@ describe('POST /chat', () => {
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('errors when slur is typed in mixed case', async () => {
|
||||
const substrLength = Math.floor(testSlurMessage1.length / 2);
|
||||
const chatMessage = testSlurMessage1.substring(0, substrLength).toLowerCase()
|
||||
+ testSlurMessage1.substring(substrLength).toUpperCase();
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when user account is too young', async () => {
|
||||
await user.update({ 'auth.timestamps.created': new Date() });
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: 'hi im new' }))
|
||||
const brandNewUser = await generateUser();
|
||||
await expect(brandNewUser.post('/groups/habitrpg/chat', { message: 'hi im new' }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('chatTemporarilyUnavailable'),
|
||||
});
|
||||
await user.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
it('creates a chat', async () => {
|
||||
@@ -278,43 +519,26 @@ describe('POST /chat', () => {
|
||||
const mount = 'test-mount';
|
||||
const pet = 'test-pet';
|
||||
const style = 'test-style';
|
||||
await user.update({
|
||||
const userWithStyle = await generateUser({
|
||||
'items.currentMount': mount,
|
||||
'items.currentPet': pet,
|
||||
'preferences.style': style,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.currentMount).to.eql(user.items.currentMount);
|
||||
expect(message.message.userStyles.items.currentPet).to.eql(user.items.currentPet);
|
||||
expect(message.message.userStyles.preferences.style).to.eql(user.preferences.style);
|
||||
expect(message.message.userStyles.preferences.hair).to.eql(user.preferences.hair);
|
||||
expect(message.message.userStyles.preferences.skin).to.eql(user.preferences.skin);
|
||||
expect(message.message.userStyles.preferences.shirt).to.eql(user.preferences.shirt);
|
||||
expect(message.message.userStyles.preferences.chair).to.eql(user.preferences.chair);
|
||||
expect(message.message.userStyles.items.currentMount).to.eql(userWithStyle.items.currentMount);
|
||||
expect(message.message.userStyles.items.currentPet).to.eql(userWithStyle.items.currentPet);
|
||||
expect(message.message.userStyles.preferences.style).to.eql(userWithStyle.preferences.style);
|
||||
expect(message.message.userStyles.preferences.hair).to.eql(userWithStyle.preferences.hair);
|
||||
expect(message.message.userStyles.preferences.skin).to.eql(userWithStyle.preferences.skin);
|
||||
expect(message.message.userStyles.preferences.shirt).to.eql(userWithStyle.preferences.shirt);
|
||||
expect(message.message.userStyles.preferences.chair).to.eql(userWithStyle.preferences.chair);
|
||||
expect(message.message.userStyles.preferences.background)
|
||||
.to.eql(user.preferences.background);
|
||||
});
|
||||
|
||||
it('creates equipped to user styles', async () => {
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.gear.equipped)
|
||||
.to.eql(user.items.gear.equipped);
|
||||
expect(message.message.userStyles.items.gear.costume).to.not.exist;
|
||||
});
|
||||
|
||||
it('creates costume to user styles', async () => {
|
||||
await user.update({ 'preferences.costume': true });
|
||||
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.gear.costume).to.eql(user.items.gear.costume);
|
||||
expect(message.message.userStyles.items.gear.equipped).to.not.exist;
|
||||
.to.eql(userWithStyle.preferences.background);
|
||||
});
|
||||
|
||||
it('adds backer info to chat', async () => {
|
||||
@@ -323,11 +547,12 @@ describe('POST /chat', () => {
|
||||
tier: 800,
|
||||
tokensApplied: true,
|
||||
};
|
||||
await user.update({
|
||||
const backer = await generateUser({
|
||||
backer: backerInfo,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await backer.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const messageBackerInfo = message.message.backer;
|
||||
|
||||
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
|
||||
@@ -407,5 +632,43 @@ describe('POST /chat', () => {
|
||||
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
||||
expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id)).to.exist;
|
||||
});
|
||||
|
||||
it('does not notify other users of a new message that is already hidden from shadow-muting', async () => {
|
||||
await user.update({ 'flags.chatShadowMuted': true });
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const memberWithNotification = await member.get('/user');
|
||||
|
||||
await user.update({ 'flags.chatShadowMuted': false });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.not.exist;
|
||||
expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id)).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
context('Spam prevention', () => {
|
||||
it('Returns an error when the user has been posting too many messages', async () => {
|
||||
// Post as many messages are needed to reach the spam limit
|
||||
for (let i = 0; i < SPAM_MESSAGE_LIMIT; i += 1) {
|
||||
const result = await additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
|
||||
expect(result.message.id).to.exist;
|
||||
}
|
||||
|
||||
await expect(additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageGroupChatSpam'),
|
||||
});
|
||||
});
|
||||
|
||||
it('contributor should not receive spam alert', async () => {
|
||||
const userSocialite = await member.update({ 'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL });
|
||||
|
||||
// Post 1 more message than the spam limit to ensure they do not reach the limit
|
||||
for (let i = 0; i < SPAM_MESSAGE_LIMIT + 1; i += 1) {
|
||||
const result = await userSocialite.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
|
||||
expect(result.message.id).to.exist;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,19 +12,18 @@ describe('POST /groups/:id/chat/seen', () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
guildLeader = groupLeader;
|
||||
[guildMember] = members;
|
||||
guildMember = members[0]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' });
|
||||
guildMessage = guildMessage.message;
|
||||
|
||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import config from '../../../../../config.json';
|
||||
@@ -12,24 +13,21 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
admin;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
members: 2,
|
||||
});
|
||||
|
||||
groupWithChat = group;
|
||||
author = groupLeader;
|
||||
[nonAdmin, admin] = members;
|
||||
await nonAdmin.update({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
await admin.update({ 'permissions.moderator': true });
|
||||
nonAdmin = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
admin = await generateUser({ 'permissions.moderator': true });
|
||||
|
||||
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||
message = message.message;
|
||||
@@ -68,7 +66,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 2,
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
@@ -78,17 +76,12 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/flag`);
|
||||
|
||||
// first test that the flag was actually successful
|
||||
// author always sees own message; flag count is hidden from non-admins
|
||||
let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
messages = await members[1].get(`/groups/${group._id}/chat`);
|
||||
expect(messages.length).to.eql(0);
|
||||
expect(messages[0].flagCount).to.eql(5);
|
||||
|
||||
// admin cannot directly request private group chat, but after unflag,
|
||||
// message should be revealed again and still have flagCount of 0
|
||||
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/clearflags`);
|
||||
messages = await members[1].get(`/groups/${group._id}/chat`);
|
||||
expect(messages.length).to.eql(1);
|
||||
|
||||
messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /group-plans', () => {
|
||||
@@ -7,15 +8,20 @@ describe('GET /group-plans', () => {
|
||||
let groupPlan;
|
||||
|
||||
before(async () => {
|
||||
({ group: groupPlan, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'group plan - is member',
|
||||
user = await generateUser({ balance: 4 });
|
||||
groupPlan = await generateGroup(user,
|
||||
{
|
||||
name: 'public guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
leaderDetails: { balance: 4 },
|
||||
}));
|
||||
{
|
||||
purchased: {
|
||||
plan: {
|
||||
customerId: 'existings',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns group plans for the user', async () => {
|
||||
|
||||
@@ -1,63 +1,70 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
resetHabiticaDB,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import {
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('GET /groups', () => {
|
||||
let user; let leader; let members;
|
||||
let secondGroup; let secondLeader;
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 2;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 3;
|
||||
let user;
|
||||
let userInGuild;
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 2;
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2;
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
|
||||
const GUILD_PER_PAGE = 30;
|
||||
const categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
let publicGuildNotMember;
|
||||
let privateGuildUserIsMemberOf;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
|
||||
({
|
||||
group: privateGuildUserIsMemberOf,
|
||||
groupLeader: leader,
|
||||
members,
|
||||
} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
categories,
|
||||
},
|
||||
leaderDetails: {
|
||||
balance: 10,
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
[user] = members;
|
||||
await user.update({ balance: 4 });
|
||||
const leader = await generateUser({ balance: 10 });
|
||||
user = await generateUser({ balance: 4 });
|
||||
|
||||
({ group: secondGroup, groupLeader: secondLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'c++ coders',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
const publicGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
name: 'public guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
summary: 'ohayou kombonwa',
|
||||
description: 'oyasumi',
|
||||
});
|
||||
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
|
||||
|
||||
await secondLeader.post(`/groups/${secondGroup._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${secondGroup._id}/join`);
|
||||
userInGuild = await generateUser({ guilds: [publicGuildUserIsMemberOf._id] });
|
||||
|
||||
await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'private guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
publicGuildNotMember = await generateGroup(leader, {
|
||||
name: 'public guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
summary: 'Natsume Soseki',
|
||||
description: 'Kinnosuke no Hondana',
|
||||
categories,
|
||||
});
|
||||
|
||||
privateGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
categories,
|
||||
});
|
||||
await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`);
|
||||
|
||||
await generateGroup(leader, {
|
||||
name: 'private guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
await generateGroup(leader, {
|
||||
@@ -91,16 +98,172 @@ describe('GET /groups', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only the tavern when tavern passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=tavern'))
|
||||
.to.eventually.have.a.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]')
|
||||
.and.to.have.property('_id', TAVERN_ID);
|
||||
});
|
||||
|
||||
it('returns only the user\'s party when party passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=party'))
|
||||
.to.eventually.have.a.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]');
|
||||
});
|
||||
|
||||
it('returns all public guilds when publicGuilds passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=publicGuilds'))
|
||||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
|
||||
});
|
||||
|
||||
describe('filters', () => {
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
const guilds = await user.get(`/groups?type=publicGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(publicGuildNotMember._id);
|
||||
});
|
||||
|
||||
it('returns private guilds filtered by category', async () => {
|
||||
const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
||||
});
|
||||
|
||||
it('filters public guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
const guilds = await user.get('/groups?type=publicGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('filters private guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('filters public guilds by leader role', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&leader=true');
|
||||
expect(guilds.length).to.equal(NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER);
|
||||
});
|
||||
|
||||
it('filters public guilds by member role', async () => {
|
||||
const guilds = await userInGuild.get('/groups?type=publicGuilds&member=true');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].name).to.have.string('is member');
|
||||
});
|
||||
|
||||
it('filters public guilds by single-word search term', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=kom');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
|
||||
});
|
||||
|
||||
it('filters public guilds by single-word search term left and right-padded by spaces', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=++++ohayou+kombonwa+++++');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
|
||||
});
|
||||
|
||||
it('filters public guilds by two-words search term separated by multiple spaces', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=kinnosuke+++++hon');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].description).to.have.string('Kinnosuke');
|
||||
});
|
||||
});
|
||||
|
||||
describe('public guilds pagination', () => {
|
||||
it('req.query.paginate must be a boolean string', async () => {
|
||||
await expect(user.get('/groups?paginate=aString&type=publicGuilds'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('req.query.paginate can only be true when req.query.type includes publicGuilds', async () => {
|
||||
await expect(user.get('/groups?paginate=true&type=notPublicGuilds'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: apiError('guildsOnlyPaginate'),
|
||||
});
|
||||
});
|
||||
|
||||
it('req.query.page can\'t be negative', async () => {
|
||||
await expect(user.get('/groups?paginate=true&page=-1&type=publicGuilds'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 30 guilds per page ordered by number of members', async () => {
|
||||
await user.update({ balance: 9000 });
|
||||
const delay = () => new Promise(resolve => setTimeout(resolve, 40));
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < 60; i += 1) {
|
||||
promises.push(generateGroup(user, {
|
||||
name: `public guild ${i} - is member`,
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}));
|
||||
await delay(); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
|
||||
const groups = await Promise.all(promises);
|
||||
|
||||
// update group number 32 and not the first to make sure sorting works
|
||||
await groups[32].update({ name: 'guild with most members', memberCount: 199 });
|
||||
await groups[33].update({ name: 'guild with less members', memberCount: -100 });
|
||||
|
||||
const page0 = await expect(user.get('/groups?type=publicGuilds&paginate=true'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
expect(page0[0].name).to.equal('guild with most members');
|
||||
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
const page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
|
||||
// 1 created now, 4 by other tests, -1 for no more tavern.
|
||||
.to.eventually.have.a.lengthOf(1 + 4 - 1);
|
||||
expect(page2[3].name).to.equal('guild with less members');
|
||||
}).timeout(10000);
|
||||
});
|
||||
|
||||
it('makes sure that the tavern doesn\'t show up when guilds is passed as a query', async () => {
|
||||
const guilds = await user.get('/groups?type=guilds');
|
||||
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('makes sure that the tavern doesn\'t show up when publicGuilds is passed as a query', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds');
|
||||
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=guilds'))
|
||||
.to.eventually.have.a
|
||||
.lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
});
|
||||
|
||||
it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => {
|
||||
@@ -109,21 +272,21 @@ describe('GET /groups', () => {
|
||||
});
|
||||
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
await expect(user.get('/groups?type=privateGuilds,party'))
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
|
||||
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW - 1); // -1 for no Tavern.
|
||||
});
|
||||
|
||||
describe('filters', () => {
|
||||
it('returns private guilds filtered by category', async () => {
|
||||
const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
const group = await generateGroup(user, {
|
||||
name: 'c++ coders',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
it('filters private guilds by size', async () => {
|
||||
const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
// search for 'c++ coders'
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=0&search=c%2B%2B+coders'))
|
||||
.to.eventually.have.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]')
|
||||
.and.to.have.property('_id', group._id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /groups/:groupId/invites', () => {
|
||||
@@ -72,16 +71,15 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns only first 30 invites by default (req.query.limit not specified)', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 31,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||
|
||||
const res = await leader.get(`/groups/${group._id}/invites`);
|
||||
expect(res.length).to.equal(30);
|
||||
@@ -92,16 +90,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
}).timeout(10000);
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -111,16 +101,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -130,16 +112,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -149,16 +123,15 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 31,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||
|
||||
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
||||
expect(res.length).to.equal(14);
|
||||
@@ -176,20 +149,17 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
}).timeout(30000);
|
||||
|
||||
it('supports using req.query.lastId to get more invites', async function test () {
|
||||
let group; let invitees;
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
({ group, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 32,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const expectedIds = invitees.map(generatedInvite => generatedInvite._id);
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 32; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate); // Group has 32 invites
|
||||
const expectedIds = generatedInvites.map(generatedInvite => generatedInvite._id);
|
||||
await user.post(`/groups/${group._id}/invite`, { uuids: expectedIds });
|
||||
|
||||
const res = await user.get(`/groups/${group._id}/invites`);
|
||||
expect(res.length).to.equal(30);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
@@ -76,15 +75,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
});
|
||||
|
||||
it('req.query.includeAllPublicFields === true works with guilds', async () => {
|
||||
let group;
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
upgradeToGroupPlan: true,
|
||||
members: 1,
|
||||
}));
|
||||
|
||||
const group = await generateGroup(user, { type: 'guild', name: generateUUID() });
|
||||
const [memberRes] = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`);
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
@@ -215,20 +206,20 @@ describe('GET /groups/:groupId/members', () => {
|
||||
|
||||
it('supports using req.query.lastId to get more members', async function test () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
const { group, groupLeader: leader, members: generatedUsers } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
upgradeToGroupPlan: true,
|
||||
leaderDetails: { balance: 4 },
|
||||
members: 57,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const usersToGenerate = [];
|
||||
for (let i = 0; i < 57; i += 1) {
|
||||
usersToGenerate.push(generateUser({ guilds: [group._id] }));
|
||||
}
|
||||
// Group has 59 members (1 is the leader)
|
||||
const generatedUsers = await Promise.all(usersToGenerate);
|
||||
const expectedIds = [leader._id].concat(generatedUsers.map(generatedUser => generatedUser._id));
|
||||
|
||||
const res = await leader.get(`/groups/${group._id}/members`);
|
||||
const res = await user.get(`/groups/${group._id}/members`);
|
||||
expect(res.length).to.equal(30);
|
||||
const res2 = await leader.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`);
|
||||
const res2 = await user.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`);
|
||||
expect(res2.length).to.equal(28);
|
||||
|
||||
const resIds = res.concat(res2).map(member => member._id);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
|
||||
describe('GET /groups/:id', () => {
|
||||
const typesOfGroups = {};
|
||||
typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' };
|
||||
typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
|
||||
typesOfGroups.party = { type: 'party', privacy: 'private' };
|
||||
|
||||
@@ -23,11 +24,10 @@ describe('GET /groups/:id', () => {
|
||||
const groupData = await createAndPopulateGroup({
|
||||
members: 30,
|
||||
groupDetails,
|
||||
upgradeToGroupPlan: groupDetails.type === 'guild',
|
||||
});
|
||||
|
||||
leader = groupData.groupLeader;
|
||||
[member] = groupData.members;
|
||||
member = groupData.members[0]; // eslint-disable-line prefer-destructuring
|
||||
createdGroup = groupData.group;
|
||||
});
|
||||
|
||||
@@ -49,6 +49,34 @@ describe('GET /groups/:id', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-member of a public guild', () => {
|
||||
let nonMember; let
|
||||
createdGroup;
|
||||
|
||||
before(async () => {
|
||||
const groupData = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
nonMember = await generateUser();
|
||||
});
|
||||
|
||||
it('returns the group object for a non-member', async () => {
|
||||
const group = await nonMember.get(`/groups/${createdGroup._id}`);
|
||||
|
||||
expect(group._id).to.eql(createdGroup._id);
|
||||
expect(group.name).to.eql(createdGroup.name);
|
||||
expect(group.type).to.eql(createdGroup.type);
|
||||
expect(group.privacy).to.eql(createdGroup.privacy);
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-member of a private guild', () => {
|
||||
let nonMember; let
|
||||
createdGroup;
|
||||
@@ -61,7 +89,6 @@ describe('GET /groups/:id', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
@@ -191,7 +218,7 @@ describe('GET /groups/:id', () => {
|
||||
});
|
||||
|
||||
context('Flagged messages', () => {
|
||||
let group; let members;
|
||||
let group;
|
||||
|
||||
const chat1 = {
|
||||
id: 'chat1',
|
||||
@@ -241,7 +268,7 @@ describe('GET /groups/:id', () => {
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
chat: [
|
||||
chat1,
|
||||
chat2,
|
||||
@@ -250,11 +277,9 @@ describe('GET /groups/:id', () => {
|
||||
chat5,
|
||||
],
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
({ group, members } = groupData);
|
||||
group = groupData.group;
|
||||
|
||||
await group.addChat([chat1, chat2, chat3, chat4, chat5]);
|
||||
});
|
||||
@@ -262,8 +287,8 @@ describe('GET /groups/:id', () => {
|
||||
context('non-admin', () => {
|
||||
let nonAdmin;
|
||||
|
||||
beforeEach(() => {
|
||||
[nonAdmin] = members;
|
||||
beforeEach(async () => {
|
||||
nonAdmin = await generateUser();
|
||||
});
|
||||
|
||||
it('does not include messages with a flag count of 2 or greater', async () => {
|
||||
@@ -289,8 +314,9 @@ describe('GET /groups/:id', () => {
|
||||
let admin;
|
||||
|
||||
beforeEach(async () => {
|
||||
[admin] = members;
|
||||
await admin.update({ permissions: { moderator: true } });
|
||||
admin = await generateUser({
|
||||
'permissions.moderator': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('includes all messages', async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('POST /group', () => {
|
||||
@@ -34,8 +35,8 @@ describe('POST /group', () => {
|
||||
|
||||
it('sets the group leader to the user who created the group', async () => {
|
||||
const group = await user.post('/groups', {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
expect(group.leader).to.eql({
|
||||
@@ -50,7 +51,7 @@ describe('POST /group', () => {
|
||||
const name = 'Test Group';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
const updatedGroup = await user.get(`/groups/${group._id}`);
|
||||
@@ -63,7 +64,7 @@ describe('POST /group', () => {
|
||||
const summary = 'Test Summary';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
summary,
|
||||
});
|
||||
|
||||
@@ -77,7 +78,7 @@ describe('POST /group', () => {
|
||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_GUILDS + 1);
|
||||
await expect(user.post('/groups', {
|
||||
name,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
summary,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -87,6 +88,157 @@ describe('POST /group', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Guilds', () => {
|
||||
it('returns an error when a user with insufficient funds attempts to create a guild', async () => {
|
||||
await user.update({ balance: 0 });
|
||||
|
||||
await expect(
|
||||
user.post('/groups', {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
}),
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageInsufficientGems'),
|
||||
});
|
||||
});
|
||||
|
||||
it('adds guild to user\'s list of guilds', async () => {
|
||||
const guild = await user.post('/groups', {
|
||||
name: 'some guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.guilds).to.include(guild._id);
|
||||
});
|
||||
|
||||
it('awards the Joined Guild achievement', async () => {
|
||||
await user.post('/groups', {
|
||||
name: 'some guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.achievements.joinedGuild).to.eql(true);
|
||||
});
|
||||
|
||||
context('public guild', () => {
|
||||
it('creates a group', async () => {
|
||||
const groupName = 'Test Public Guild';
|
||||
const groupType = 'guild';
|
||||
const groupPrivacy = 'public';
|
||||
|
||||
const publicGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(publicGuild._id).to.exist;
|
||||
expect(publicGuild.name).to.equal(groupName);
|
||||
expect(publicGuild.type).to.equal(groupType);
|
||||
expect(publicGuild.memberCount).to.equal(1);
|
||||
expect(publicGuild.privacy).to.equal(groupPrivacy);
|
||||
expect(publicGuild.leader).to.eql({
|
||||
_id: user._id,
|
||||
profile: {
|
||||
name: user.profile.name,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when a user with no chat privileges attempts to create a public guild', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
|
||||
await expect(
|
||||
user.post('/groups', {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}),
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('private guild', () => {
|
||||
const groupName = 'Test Private Guild';
|
||||
const groupType = 'guild';
|
||||
const groupPrivacy = 'private';
|
||||
|
||||
it('creates a group', async () => {
|
||||
const privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild._id).to.exist;
|
||||
expect(privateGuild.name).to.equal(groupName);
|
||||
expect(privateGuild.type).to.equal(groupType);
|
||||
expect(privateGuild.memberCount).to.equal(1);
|
||||
expect(privateGuild.privacy).to.equal(groupPrivacy);
|
||||
expect(privateGuild.leader).to.eql({
|
||||
_id: user._id,
|
||||
profile: {
|
||||
name: user.profile.name,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a private guild when the user has no chat privileges', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
const privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild._id).to.exist;
|
||||
});
|
||||
|
||||
it('deducts gems from user and adds them to guild bank', async () => {
|
||||
const privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild.balance).to.eql(1);
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.balance).to.eql(user.balance - 1);
|
||||
});
|
||||
|
||||
it('does not deduct the gems from user when guild creation fails', async () => {
|
||||
const stub = sinon.stub(Group.prototype, 'save').rejects();
|
||||
const promise = user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
await expect(promise).to.eventually.be.rejected;
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.balance).to.eql(user.balance);
|
||||
|
||||
stub.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Parties', () => {
|
||||
const partyName = 'Test Party';
|
||||
const partyType = 'party';
|
||||
|
||||
@@ -2,6 +2,7 @@ import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
checkExistence,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -18,24 +19,81 @@ describe('POST /group/:groupId/join', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Joining a private guild', () => {
|
||||
let user;
|
||||
let invitedUser;
|
||||
let guild;
|
||||
let invitees;
|
||||
context('Joining a public guild', () => {
|
||||
let user; let joiningUser; let
|
||||
publicGuild;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group: guild, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
user = groupLeader;
|
||||
joiningUser = await generateUser();
|
||||
});
|
||||
|
||||
it('allows non-invited users to join public guilds', async () => {
|
||||
const res = await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get('/user')).to.eventually.have.property('guilds').to.include(publicGuild._id);
|
||||
expect(res.leader._id).to.eql(user._id);
|
||||
expect(res.leader.profile.name).to.eql(user.profile.name);
|
||||
});
|
||||
|
||||
it('returns an error if user was already a member', async () => {
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
await expect(joiningUser.post(`/groups/${publicGuild._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('youAreAlreadyInGroup'),
|
||||
});
|
||||
});
|
||||
|
||||
it('promotes joining member in a public empty guild to leader', async () => {
|
||||
await user.post(`/groups/${publicGuild._id}/leave`);
|
||||
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.nested.property('leader._id', joiningUser._id);
|
||||
});
|
||||
|
||||
it('increments memberCount when joining guilds', async () => {
|
||||
const oldMemberCount = publicGuild.memberCount;
|
||||
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
|
||||
});
|
||||
|
||||
it('awards Joined Guild achievement', async () => {
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get('/user')).to.eventually.have.nested.property('achievements.joinedGuild', true);
|
||||
});
|
||||
});
|
||||
|
||||
context('Joining a private guild', () => {
|
||||
let user; let invitedUser; let
|
||||
guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
|
||||
[invitedUser] = invitees;
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('returns error when user is not invited to private guild', async () => {
|
||||
@@ -125,7 +183,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
|
||||
party = group;
|
||||
user = groupLeader;
|
||||
[invitedUser] = invitees;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('returns error when user is not invited to party', async () => {
|
||||
@@ -200,6 +258,47 @@ describe('POST /group/:groupId/join', () => {
|
||||
await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 2);
|
||||
});
|
||||
|
||||
it('deletes previous party where the user was the only member', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const oldParty = await userToInvite.post('/groups', { // add user to a party
|
||||
name: 'Another Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
|
||||
await user.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
await userToInvite.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(user.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
it('does not allow user to leave a party if a quest was active and they were the only member', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const oldParty = await userToInvite.post('/groups', { // add user to a party
|
||||
name: 'Another Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
await userToInvite.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
});
|
||||
await userToInvite.post(`/groups/${oldParty._id}/quests/invite/${PET_QUEST}`);
|
||||
|
||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
|
||||
await user.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
await expect(userToInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageCannotLeaveWhileQuesting'),
|
||||
});
|
||||
});
|
||||
|
||||
it('invites joining member to active quest', async () => {
|
||||
await user.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
generateChallenge,
|
||||
checkExistence,
|
||||
createAndPopulateGroup,
|
||||
sleep,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
@@ -13,187 +14,253 @@ import payments from '../../../../../website/server/libs/payments/payments';
|
||||
import calculateSubscriptionTerminationDate from '../../../../../website/server/libs/payments/calculateSubscriptionTerminationDate';
|
||||
|
||||
describe('POST /groups/:groupId/leave', () => {
|
||||
let groupToLeave;
|
||||
let leader;
|
||||
let member;
|
||||
let members;
|
||||
let memberCount;
|
||||
const typesOfGroups = {
|
||||
'public guild': { type: 'guild', privacy: 'public' },
|
||||
'private guild': { type: 'guild', privacy: 'private' },
|
||||
party: { type: 'party', privacy: 'private' },
|
||||
};
|
||||
|
||||
context('Leaving a Group Plan', () => {
|
||||
beforeEach(async () => {
|
||||
({ group: groupToLeave, groupLeader: leader, members } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
[member] = members;
|
||||
memberCount = groupToLeave.memberCount;
|
||||
await leader.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
it('prevents non members from leaving', async () => {
|
||||
const user = await generateUser();
|
||||
await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('lets user leave', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userThatLeftGroup = await member.get('/user');
|
||||
|
||||
expect(userThatLeftGroup.guilds).to.be.empty;
|
||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
});
|
||||
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.be.undefined;
|
||||
});
|
||||
|
||||
context('with challenges', () => {
|
||||
let challenge;
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a ${groupType}`, () => {
|
||||
let groupToLeave;
|
||||
let leader;
|
||||
let member;
|
||||
let memberCount;
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
groupToLeave = group;
|
||||
leader = groupLeader;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
memberCount = group.memberCount;
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
it('prevents non members from leaving', async () => {
|
||||
const user = await generateUser();
|
||||
await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('removes all challenge tasks when keep parameter is set to remove', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
|
||||
|
||||
const userWithoutChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('keeps all challenge tasks when keep parameter is not set', async () => {
|
||||
it(`lets user leave a ${groupType}`, async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
const userThatLeftGroup = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
expect(userThatLeftGroup.guilds).to.be.empty;
|
||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
});
|
||||
|
||||
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
||||
it(`sets a new group leader when leader leaves a ${groupType}`, async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
expect(groupToLeave.leader).to.equal(member._id);
|
||||
});
|
||||
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
await sleep(0.5);
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.be.undefined;
|
||||
});
|
||||
|
||||
context('with challenges', () => {
|
||||
let challenge;
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await leader.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await sleep(0.5);
|
||||
});
|
||||
|
||||
it('removes all challenge tasks when keep parameter is set to remove', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
|
||||
|
||||
const userWithoutChallengeTasks = await leader.get('/user');
|
||||
|
||||
expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('keeps all challenge tasks when keep parameter is not set', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await leader.get('/user');
|
||||
|
||||
// @TODO find elegant way to assert against the task existing
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
||||
|
||||
const userWithChallengeTasks = await leader.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
});
|
||||
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await leader.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents quest leader from leaving a groupToLeave');
|
||||
it('prevents a user from leaving during an active quest');
|
||||
});
|
||||
});
|
||||
|
||||
context('Leaving a Party', () => {
|
||||
let invitees;
|
||||
let invitedUser;
|
||||
context('Leaving a group as the last member', () => {
|
||||
context('private guild', () => {
|
||||
let privateGuild;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
({
|
||||
group: groupToLeave,
|
||||
groupLeader: leader,
|
||||
members,
|
||||
invitees,
|
||||
} = await createAndPopulateGroup({
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
members: 1,
|
||||
invites: 1,
|
||||
}));
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Private Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
invites: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
});
|
||||
|
||||
[member] = members;
|
||||
[invitedUser] = invitees;
|
||||
memberCount = groupToLeave.memberCount;
|
||||
await leader.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
privateGuild = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
it('prevents non members from leaving', async () => {
|
||||
const user = await generateUser();
|
||||
await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
await leader.post(`/groups/${group._id}/chat`, { message: 'Some message' });
|
||||
});
|
||||
|
||||
it('removes a group when the last member leaves', async () => {
|
||||
await leader.post(`/groups/${privateGuild._id}/leave`);
|
||||
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
it('removes invitations when the last member leaves', async () => {
|
||||
await leader.post(`/groups/${privateGuild._id}/leave`);
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.guilds).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
it('lets user leave', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
context('public guild', () => {
|
||||
let publicGuild;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
|
||||
const userThatLeftGroup = await member.get('/user');
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
|
||||
expect(userThatLeftGroup.guilds).to.be.empty;
|
||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
publicGuild = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('keeps the group when the last member leaves', async () => {
|
||||
await leader.post(`/groups/${publicGuild._id}/leave`);
|
||||
|
||||
await expect(checkExistence('groups', publicGuild._id)).to.eventually.equal(true);
|
||||
});
|
||||
|
||||
it('keeps the invitations when the last member leaves a public guild', async () => {
|
||||
await leader.post(`/groups/${publicGuild._id}/leave`);
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.guilds).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('deletes non existent guild from user when user tries to leave', async () => {
|
||||
const nonExistentGuildId = generateUUID();
|
||||
const userWithNonExistentGuild = await generateUser({ guilds: [nonExistentGuildId] });
|
||||
expect(userWithNonExistentGuild.guilds).to.contain(nonExistentGuildId);
|
||||
|
||||
await expect(userWithNonExistentGuild.post(`/groups/${nonExistentGuildId}/leave`))
|
||||
.to.eventually.be.rejected;
|
||||
|
||||
await userWithNonExistentGuild.sync();
|
||||
|
||||
expect(userWithNonExistentGuild.guilds).to.not.contain(nonExistentGuildId);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets a new group leader when leader leaves', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
context('party', () => {
|
||||
let party;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
expect(groupToLeave.leader).to.equal(member._id);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
await member.sync();
|
||||
party = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
it('removes a group when the last member leaves a party', async () => {
|
||||
await leader.post(`/groups/${party._id}/leave`);
|
||||
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await member.sync();
|
||||
await expect(checkExistence('party', party._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.be.undefined;
|
||||
});
|
||||
it('removes invitations when the last member leaves a party', async () => {
|
||||
await leader.post(`/groups/${party._id}/leave`);
|
||||
|
||||
it('removes a party when the last member leaves', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
await expect(checkExistence('party', groupToLeave._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
it('removes invitations when the last member leaves a party', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes non existent party from user when user tries to leave', async () => {
|
||||
@@ -208,71 +275,23 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
|
||||
expect(userWithNonExistentParty.party).to.eql({});
|
||||
});
|
||||
|
||||
context('with challenges', () => {
|
||||
let challenge;
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
});
|
||||
|
||||
it('removes all challenge tasks when keep parameter is set to remove', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
|
||||
|
||||
const userWithoutChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('keeps all challenge tasks when keep parameter is not set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
});
|
||||
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const typesOfGroups = {
|
||||
'private guild': { type: 'guild', privacy: 'private' },
|
||||
party: { type: 'party', privacy: 'private' },
|
||||
};
|
||||
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a group plan when the group is a ${groupType}`, () => {
|
||||
if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans
|
||||
let groupWithPlan;
|
||||
let leader;
|
||||
let member;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group: groupWithPlan, groupLeader: leader, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
[member] = members;
|
||||
});
|
||||
leader = groupLeader;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
groupWithPlan = group;
|
||||
const userWithFreePlan = await User.findById(leader._id).exec();
|
||||
|
||||
// Create subscription
|
||||
@@ -302,21 +321,45 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.dateTerminated).to.exist;
|
||||
});
|
||||
|
||||
it('preserves the free subscription when leaving a any other group without a plan', async () => {
|
||||
// Joining a guild without a group plan
|
||||
const { group: groupWithNoPlan } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Group Without Plan',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
await member.post(`/groups/${groupWithNoPlan._id}/join`);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.planId).to.equal('group_plan_auto');
|
||||
expect(member.purchased.plan.dateTerminated).to.not.exist;
|
||||
|
||||
// Leaving the guild without a group plan
|
||||
await member.post(`/groups/${groupWithNoPlan._id}/leave`);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.dateTerminated).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a group with extraMonths left plan when the group is a ${groupType}`, () => {
|
||||
if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans
|
||||
const extraMonths = 12;
|
||||
let groupWithPlan;
|
||||
let member;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group: groupWithPlan, members } = await createAndPopulateGroup({
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
[member] = members;
|
||||
groupWithPlan = group;
|
||||
await member.update({
|
||||
'purchased.plan.extraMonths': extraMonths,
|
||||
});
|
||||
|
||||
@@ -5,6 +5,43 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /group/:groupId/reject-invite', () => {
|
||||
context('Rejecting a public guild invite', () => {
|
||||
let publicGuild; let
|
||||
invitedUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('returns error when user is not invited', async () => {
|
||||
const userWithoutInvite = await generateUser();
|
||||
|
||||
await expect(userWithoutInvite.post(`/groups/${publicGuild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageGroupRequiresInvite'),
|
||||
});
|
||||
});
|
||||
|
||||
it('clears invitation from user', async () => {
|
||||
await invitedUser.post(`/groups/${publicGuild._id}/reject-invite`);
|
||||
|
||||
await expect(invitedUser.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.guilds')
|
||||
.to.not.include({ id: publicGuild._id });
|
||||
});
|
||||
});
|
||||
|
||||
context('Rejecting a private guild invite', () => {
|
||||
let invitedUser; let
|
||||
guild;
|
||||
@@ -17,7 +54,6 @@ describe('POST /group/:groupId/reject-invite', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
|
||||
@@ -25,7 +25,6 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
},
|
||||
invites: 1,
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
@@ -130,11 +129,9 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
it('sends email to removed user', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledTwice;
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.eql(member._id);
|
||||
expect(email.sendTxn.args[0][1]).to.eql('kicked-from-guild');
|
||||
expect(email.sendTxn.args[1][0]._id).to.eql(member._id);
|
||||
expect(email.sendTxn.args[1][1]).to.eql('group-member-removed');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import nconf from 'nconf';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
const INVITES_LIMIT = 100;
|
||||
const PARTY_LIMIT_MEMBERS = 30;
|
||||
const PARTY_LIMIT_MEMBERS = 29;
|
||||
const MAX_EMAIL_INVITES_BY_USER = 200;
|
||||
|
||||
describe('Post /groups/:groupId/invite', () => {
|
||||
let inviter;
|
||||
let group;
|
||||
const groupName = 'Test Party';
|
||||
const groupName = 'Test Public Guild';
|
||||
|
||||
beforeEach(async () => {
|
||||
inviter = await generateUser({ balance: 4 });
|
||||
group = await inviter.post('/groups', {
|
||||
name: groupName,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,60 +48,48 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when recipient has blocked the senders', async () => {
|
||||
const inviterNoBlocks = await inviter.update({ 'inbox.blocks': [] });
|
||||
const userWithBlockedInviter = await generateUser({ 'inbox.blocks': [inviter._id] });
|
||||
await expect(inviterNoBlocks.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userWithBlockedInviter.auth.local.lowerCaseUsername],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notAuthorizedToSendMessageToThisUser'),
|
||||
});
|
||||
});
|
||||
|
||||
it('invites a user to a group by username', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userToInvite.auth.local.lowerCaseUsername],
|
||||
});
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
})).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.parties[0].id', group._id);
|
||||
.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();
|
||||
|
||||
const response = await (inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [
|
||||
userToInvite.auth.local.lowerCaseUsername,
|
||||
userToInvite2.auth.local.lowerCaseUsername,
|
||||
],
|
||||
}));
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
expect(response[1]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[1]._id).to.be.a('String');
|
||||
expect(response[1].id).to.eql(group._id);
|
||||
expect(response[1].name).to.eql(groupName);
|
||||
expect(response[1].inviter).to.eql(inviter._id);
|
||||
})).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.parties[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -212,42 +200,42 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('invites a user to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
})).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.parties[0].id', group._id);
|
||||
.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();
|
||||
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id, userToInvite2._id],
|
||||
});
|
||||
})).to.eventually.deep.equal([
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
expect(response[1]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[1]._id).to.be.a('String');
|
||||
expect(response[1].id).to.eql(group._id);
|
||||
expect(response[1].name).to.eql(groupName);
|
||||
expect(response[1].inviter).to.eql(inviter._id);
|
||||
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
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);
|
||||
});
|
||||
|
||||
it('returns an error when inviting multiple users and a user is not found', async () => {
|
||||
@@ -336,8 +324,12 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
invitesSent: MAX_EMAIL_INVITES_BY_USER,
|
||||
balance: 4,
|
||||
});
|
||||
const tmpGroup = await inviterWithMax.post('/groups', {
|
||||
name: groupName,
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
await expect(inviterWithMax.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviterWithMax.post(`/groups/${tmpGroup._id}/invite`, {
|
||||
emails: [testInvite],
|
||||
inviter: 'inviter name',
|
||||
}))
|
||||
@@ -413,15 +405,15 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
const invitedUser = await newUser.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(group._id);
|
||||
expect(invitedUser.invitations.guilds[0].id).to.equal(group._id);
|
||||
expect(invite).to.exist;
|
||||
});
|
||||
|
||||
it('invites user to group with cancelled plan', async () => {
|
||||
let cancelledPlanGroup;
|
||||
({ group: cancelledPlanGroup, groupLeader: inviter } = await createAndPopulateGroup({
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
it('invites marks invite with cancelled plan', async () => {
|
||||
const cancelledPlanGroup = await generateGroup(inviter, {
|
||||
type: 'guild',
|
||||
name: generateUUID(),
|
||||
});
|
||||
await cancelledPlanGroup.createCancelledSubscription();
|
||||
|
||||
const newUser = await generateUser();
|
||||
@@ -431,13 +423,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
const invitedUser = await newUser.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(cancelledPlanGroup._id);
|
||||
expect(invitedUser.invitations.parties[0].cancelledPlan).to.be.true;
|
||||
expect(invitedUser.invitations.guilds[0].id).to.equal(cancelledPlanGroup._id);
|
||||
expect(invitedUser.invitations.guilds[0].cancelledPlan).to.be.true;
|
||||
expect(invite).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('party invites', () => {
|
||||
describe('guild invites', () => {
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||
const userToInvite = await generateUser();
|
||||
@@ -451,7 +443,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user has a pending invitation to the party', async () => {
|
||||
it('returns an error when invited user is already invited to the group', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
@@ -459,6 +451,96 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('userAlreadyInvitedToGroup', { userId: userToInvite._id, username: userToInvite.profile.name }),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user is already in the group', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
await userToInvite.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('userAlreadyInGroup', { userId: userToInvite._id, username: userToInvite.profile.name }),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows 30+ members in a guild', async () => {
|
||||
const invitesToGenerate = [];
|
||||
// Generate 30 users to invite (30 + leader = 31 members)
|
||||
for (let i = 0; i < PARTY_LIMIT_MEMBERS; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
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 () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const nonGroupLeader = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [nonGroupLeader._id],
|
||||
});
|
||||
await nonGroupLeader.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(nonGroupLeader.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanInviteToGroupPlan'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('party invites', () => {
|
||||
let party;
|
||||
|
||||
beforeEach(async () => {
|
||||
party = await inviter.post('/groups', {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||
const userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user has a pending invitation to the party', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
@@ -470,13 +552,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('returns an error when invited user is already in a party of more than 1 member', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id, userToInvite2._id],
|
||||
});
|
||||
await userToInvite.post(`/groups/${group._id}/join`);
|
||||
await userToInvite2.post(`/groups/${group._id}/join`);
|
||||
await userToInvite.post(`/groups/${party._id}/join`);
|
||||
await userToInvite2.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
@@ -486,7 +568,20 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('allows inviting a user to 2 different parties', async () => {
|
||||
it('allow inviting a user to a party if they are partying solo', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await userToInvite.post('/groups', { // add user to a party
|
||||
name: 'Another Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
|
||||
it('allow inviting a user to 2 different parties', async () => {
|
||||
// Create another inviter
|
||||
const inviter2 = await generateUser();
|
||||
|
||||
@@ -500,7 +595,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
// Invite to first party
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
@@ -513,65 +608,49 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
const invitedUser = await userToInvite.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties.length).to.equal(2);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(group._id);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(party._id);
|
||||
expect(invitedUser.invitations.parties[1].id).to.equal(party2._id);
|
||||
});
|
||||
|
||||
it('allows inviting a user if party id is not associated with a real party', async () => {
|
||||
it('allow inviting a user if party id is not associated with a real party', async () => {
|
||||
const userToInvite = await generateUser({
|
||||
party: { _id: generateUUID() },
|
||||
});
|
||||
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(group._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('party size limits', () => {
|
||||
let partyLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group, groupLeader: partyLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
// Generate party with 20 members
|
||||
members: PARTY_LIMIT_MEMBERS - 10,
|
||||
}));
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
|
||||
it('allows 30 members in a party', async () => {
|
||||
const invitesToGenerate = [];
|
||||
// Generate 10 new invites
|
||||
for (let i = 1; i < 10; i += 1) {
|
||||
// Generate 29 users to invite (29 + leader = 30 members)
|
||||
for (let i = 0; i < PARTY_LIMIT_MEMBERS - 1; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
expect(await partyLeader.post(`/groups/${group._id}/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 () => {
|
||||
it('does not allow 30+ members in a party', async () => {
|
||||
const invitesToGenerate = [];
|
||||
// Generate 11 invites
|
||||
for (let i = 1; i < 11; i += 1) {
|
||||
// Generate 30 users to invite (30 + leader = 31 members)
|
||||
for (let i = 0; i < PARTY_LIMIT_MEMBERS; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
await expect(partyLeader.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS }),
|
||||
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS + 1 }),
|
||||
});
|
||||
}).timeout(10000);
|
||||
});
|
||||
|
||||
@@ -17,10 +17,9 @@ describe('POST /group/:groupId/add-manager', () => {
|
||||
groupDetails: {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupToUpdate = group;
|
||||
|
||||
@@ -23,11 +23,10 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
categories: groupCategories,
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
adminUser = await generateUser({ 'permissions.moderator': true });
|
||||
groupToUpdate = group;
|
||||
@@ -107,28 +106,14 @@ describe('PUT /group', () => {
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
it('does not allow a leader to change leader of active group plan', async () => {
|
||||
await expect(leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
it('allows a leader to change leaders', async () => {
|
||||
const updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
name: groupUpdatedName,
|
||||
leader: nonLeader._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotChangeLeaderWithActiveGroupPlan'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a leader of a party to change leaders', async () => {
|
||||
const { group: party, groupLeader: partyLeader, members } = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
});
|
||||
const updatedGroup = await partyLeader.put(`/groups/${party._id}`, {
|
||||
name: groupUpdatedName,
|
||||
leader: members[0]._id,
|
||||
});
|
||||
|
||||
expect(updatedGroup.leader._id).to.eql(members[0]._id);
|
||||
expect(updatedGroup.leader.profile.name).to.eql(members[0].profile.name);
|
||||
expect(updatedGroup.leader._id).to.eql(nonLeader._id);
|
||||
expect(updatedGroup.leader.profile.name).to.eql(nonLeader.profile.name);
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
@@ -137,16 +122,15 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
const updateGroupDetails = {
|
||||
id: group._id,
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
bannedWordsAllowed: true,
|
||||
};
|
||||
|
||||
@@ -166,11 +150,9 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await groupLeader.update({ permissions: {} });
|
||||
|
||||
const updateGroupDetails = {
|
||||
id: group._id,
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /members/:memberId/clear-flags', () => {
|
||||
let reporter;
|
||||
let admin;
|
||||
let moderator;
|
||||
|
||||
beforeEach(async () => {
|
||||
reporter = await generateUser();
|
||||
admin = await generateUser({ permissions: { userSupport: true } });
|
||||
moderator = await generateUser({ permissions: { moderator: true } });
|
||||
await reporter.post(`/members/${admin._id}/flag`);
|
||||
});
|
||||
|
||||
context('error cases', () => {
|
||||
it('returns error when memberId is not a UUID', async () => {
|
||||
await expect(moderator.post('/members/gribbly/clear-flags'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when member with UUID is not found', async () => {
|
||||
const randomId = generateUUID();
|
||||
|
||||
await expect(moderator.post(`/members/${randomId}/clear-flags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', { userId: randomId }),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when requesting user is not a moderator', async () => {
|
||||
await expect(reporter.post(`/members/${admin._id}/clear-flags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Only a moderator may clear reports from a profile.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('valid request', () => {
|
||||
it('removes a single flag from user', async () => {
|
||||
await expect(moderator.post(`/members/${admin._id}/clear-flags`)).to.eventually.be.ok;
|
||||
const updatedTarget = await admin.get(`/hall/heroes/${admin._id}`);
|
||||
expect(updatedTarget.profile.flags).to.eql({});
|
||||
});
|
||||
|
||||
it('removes multiple flags from user', async () => {
|
||||
await moderator.post(`/members/${admin._id}/flag`);
|
||||
await expect(moderator.post(`/members/${admin._id}/clear-flags`)).to.eventually.be.ok;
|
||||
const updatedTarget = await admin.get(`/hall/heroes/${admin._id}`);
|
||||
expect(updatedTarget.profile.flags).to.eql({});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,151 +0,0 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /members/:memberId/flag', () => {
|
||||
let reporter;
|
||||
let target;
|
||||
|
||||
beforeEach(async () => {
|
||||
reporter = await generateUser();
|
||||
target = await generateUser({
|
||||
'profile.blurb': 'Naughty Text',
|
||||
'profile.imageUrl': 'https://evil.com/',
|
||||
});
|
||||
});
|
||||
|
||||
context('error cases', () => {
|
||||
it('returns error when memberId is not a UUID', async () => {
|
||||
await expect(reporter.post('/members/gribbly/flag'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when member with UUID is not found', async () => {
|
||||
const randomId = generateUUID();
|
||||
|
||||
await expect(reporter.post(`/members/${randomId}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', { userId: randomId }),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when non-admin flags same profile twice', async () => {
|
||||
await reporter.post(`/members/${target._id}/flag`);
|
||||
await expect(reporter.post(`/members/${target._id}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'A profile can not be flagged more than once by the same user.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('valid request', () => {
|
||||
let admin;
|
||||
const comment = 'this profile is bad';
|
||||
const source = 'Third Party Script';
|
||||
|
||||
beforeEach(async () => {
|
||||
admin = await generateUser({ 'permissions.userSupport': true });
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('adds flags object to target user', async () => {
|
||||
await reporter.post(`/members/${target._id}/flag`);
|
||||
const updatedTarget = await admin.get(`/hall/heroes/${target._id}`);
|
||||
expect(updatedTarget.profile.flags[reporter._id]).to.have.all.keys([
|
||||
'comment',
|
||||
'source',
|
||||
'timestamp',
|
||||
]);
|
||||
expect(moment(updatedTarget.profile.flags[reporter._id].timestamp).toDate()).to.be.a('date');
|
||||
});
|
||||
|
||||
it('allows addition of a comment and source', async () => {
|
||||
await reporter.post(`/members/${target._id}/flag`, {
|
||||
comment,
|
||||
source,
|
||||
});
|
||||
const updatedTarget = await admin.get(`/hall/heroes/${target._id}`);
|
||||
expect(updatedTarget.profile.flags[reporter._id].comment).to.eql(comment);
|
||||
expect(updatedTarget.profile.flags[reporter._id].source).to.eql(source);
|
||||
});
|
||||
|
||||
it('allows moderator to flag twice', async () => {
|
||||
const moderator = await generateUser({ 'permissions.moderator': true });
|
||||
await moderator.post(`/members/${target._id}/flag`);
|
||||
await expect(moderator.post(`/members/${target._id}/flag`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('allows multiple non-moderators to flag individually', async () => {
|
||||
await admin.post(`/members/${target._id}/flag`);
|
||||
await reporter.post(`/members/${target._id}/flag`);
|
||||
const updatedTarget = await admin.get(`/hall/heroes/${target._id}`);
|
||||
expect(updatedTarget.profile.flags[admin._id]).to.exist;
|
||||
expect(updatedTarget.profile.flags[reporter._id]).to.exist;
|
||||
});
|
||||
|
||||
it('sends a flag report to moderation Slack', async () => {
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
await reporter.post(`/members/${target._id}/flag`, {
|
||||
comment,
|
||||
source,
|
||||
});
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `@${reporter.auth.local.username} (${reporter._id}; language: ${reporter.preferences.language}) flagged @${target.auth.local.username}'s profile from ${source} and commented: ${comment}`,
|
||||
attachments: [{
|
||||
fallback: 'Flag Profile',
|
||||
color: 'danger',
|
||||
title: 'User Profile Report',
|
||||
title_link: `${BASE_URL}/profile/${target._id}`,
|
||||
text: `Display Name: ${target.profile.name}\n\nImage URL: ${target.profile.imageUrl}\n\nAbout: ${target.profile.blurb}`,
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
|
||||
it('excludes empty fields when sending Slack message', async () => {
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
await reporter.post(`/members/${admin._id}/flag`, {
|
||||
comment,
|
||||
source,
|
||||
});
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `@${reporter.auth.local.username} (${reporter._id}; language: ${reporter.preferences.language}) flagged @${admin.auth.local.username}'s profile from ${source} and commented: ${comment}`,
|
||||
attachments: [{
|
||||
fallback: 'Flag Profile',
|
||||
color: 'danger',
|
||||
title: 'User Profile Report',
|
||||
title_link: `${BASE_URL}/profile/${admin._id}`,
|
||||
text: `Display Name: ${admin.profile.name}`,
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||
@@ -50,21 +50,22 @@ describe('payments : amazon #subscribeCancel', () => {
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
});
|
||||
|
||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ describe('payments - amazon - #subscribe', () => {
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
|
||||
@@ -45,10 +45,11 @@ describe('payments : apple #subscribe', () => {
|
||||
});
|
||||
|
||||
expect(subscribeStub).to.be.calledOnce;
|
||||
expect(subscribeStub.args[0][0]._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][1]).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][2]['x-api-user']).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][0]).to.eql(sku);
|
||||
expect(subscribeStub.args[0][1]._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][2]).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][3]['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,11 +21,11 @@ describe('payments : apple #verify', () => {
|
||||
let verifyStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
verifyStub = sinon.stub(applePayments, 'verifyPurchase').resolves({});
|
||||
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
applePayments.verifyPurchase.restore();
|
||||
applePayments.verifyGemPurchase.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
|
||||
@@ -21,11 +21,11 @@ describe('payments : google #verify', () => {
|
||||
let verifyStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
verifyStub = sinon.stub(googlePayments, 'verifyPurchase').resolves({});
|
||||
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
googlePayments.verifyPurchase.restore();
|
||||
googlePayments.verifyGemPurchase.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
@@ -48,21 +48,22 @@ describe('payments - stripe - #subscribeCancel', () => {
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
});
|
||||
|
||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
||||
it('does not accept quest for a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`))
|
||||
|
||||
@@ -43,7 +43,6 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
it('does not force start quest for a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/force-start`))
|
||||
|
||||
@@ -51,13 +51,14 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
});
|
||||
|
||||
it('does not issue invites for Guilds', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
const { group } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'public' },
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||
const alternateGroup = group;
|
||||
|
||||
await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('guildQuestsNotSupported'),
|
||||
|
||||
@@ -52,7 +52,6 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`))
|
||||
|
||||
@@ -52,7 +52,6 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`))
|
||||
|
||||
@@ -51,7 +51,6 @@ describe('POST /groups/:groupId/quests/leave', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`))
|
||||
|
||||
@@ -53,7 +53,6 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||
@@ -7,12 +8,8 @@ describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||
guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
user = await generateUser({ balance: 1 });
|
||||
guild = await generateGroup(user, { type: 'guild' }, { 'purchased.plan.customerId': 'group-unlimited' });
|
||||
});
|
||||
|
||||
it('can move task to new position', async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
find,
|
||||
each,
|
||||
map,
|
||||
} from 'lodash';
|
||||
@@ -197,6 +198,95 @@ describe('DELETE /user', () => {
|
||||
await expect(checkExistence('party', party._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('last member of a private guild', () => {
|
||||
let privateGuild;
|
||||
|
||||
beforeEach(async () => {
|
||||
privateGuild = await generateGroup(user, {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes guild when user is the only member', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is leader of', () => {
|
||||
let guild; let oldLeader; let
|
||||
newLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
newLeader = members[0]; // eslint-disable-line prefer-destructuring
|
||||
oldLeader = groupLeader;
|
||||
});
|
||||
|
||||
it('chooses new group leader for any group user was the leader of', async () => {
|
||||
await oldLeader.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
const updatedGuild = await newLeader.get(`/groups/${guild._id}`);
|
||||
|
||||
expect(updatedGuild.leader).to.exist;
|
||||
expect(updatedGuild.leader._id).to.not.eql(oldLeader._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is a part of', () => {
|
||||
let group1; let group2; let userToDelete; let
|
||||
otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
userToDelete = await generateUser({ balance: 10 });
|
||||
|
||||
group1 = await generateGroup(userToDelete, {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
});
|
||||
|
||||
group2 = group;
|
||||
otherUser = members[0]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
await userToDelete.post(`/groups/${group2._id}/join`);
|
||||
});
|
||||
|
||||
it('removes user from all groups user was a part of', async () => {
|
||||
await userToDelete.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
const updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`);
|
||||
const updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`);
|
||||
const userInGroup = find(updatedGroup2Members, member => member._id === userToDelete._id);
|
||||
|
||||
expect(updatedGroup1Members).to.be.empty;
|
||||
expect(updatedGroup2Members).to.not.be.empty;
|
||||
expect(userInGroup).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('user with Google auth', async () => {
|
||||
|
||||
@@ -51,7 +51,6 @@ describe('POST /user/purchase/:type/:key', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
@@ -78,7 +77,6 @@ describe('POST /user/purchase/:type/:key', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
|
||||
@@ -714,6 +714,31 @@ describe('POST /user/auth/local/register', () => {
|
||||
|
||||
expect(user.invitations.party).to.eql({});
|
||||
});
|
||||
|
||||
it('adds a user to a guild on an invite of type other than party', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
});
|
||||
|
||||
const invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
const user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.guilds[0]).to.eql({
|
||||
id: group._id,
|
||||
name: group.name,
|
||||
inviter: groupLeader._id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('successful login via api', () => {
|
||||
|
||||
@@ -96,20 +96,6 @@ describe('PUT /user/auth/update-password', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when newPassword is too long', async () => {
|
||||
const body = {
|
||||
password,
|
||||
newPassword: '12345678910111213141516171819202122232425262728293031323334353637383940',
|
||||
confirmPassword: '12345678910111213141516171819202122232425262728293031323334353637383940',
|
||||
};
|
||||
|
||||
await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when confirmPassword is missing', async () => {
|
||||
const body = {
|
||||
password,
|
||||
|
||||
@@ -35,6 +35,13 @@ describe('GET /world-state', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a string representing the current season for NPC sprites', async () => {
|
||||
const res = await requester().get('/world-state');
|
||||
|
||||
expect(res).to.have.nested.property('npcImageSuffix');
|
||||
expect(res.npcImageSuffix).to.be.a('string');
|
||||
});
|
||||
|
||||
context('no current event', () => {
|
||||
beforeEach(async () => {
|
||||
sinon.stub(worldState, 'getCurrentEvent').returns(null);
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('GET /faq', () => {
|
||||
expect(res).to.have.property('stillNeedHelp');
|
||||
expect(res.stillNeedHelp.ios).to.equal(translate('iosFaqStillNeedHelp'));
|
||||
expect(res).to.have.property('questions');
|
||||
expect(res.questions[0].question).to.equal(translate('faqQuestion25'));
|
||||
expect(res.questions[0].question).to.equal(translate('faqQuestion0'));
|
||||
});
|
||||
|
||||
it('returns faq not in English', async () => {
|
||||
@@ -29,6 +29,18 @@ describe('GET /faq', () => {
|
||||
});
|
||||
|
||||
describe('platform parameter', () => {
|
||||
it('returns faq with answers for ios platform only', async () => {
|
||||
const res = await requester().get('/faq?platform=ios');
|
||||
|
||||
expect(res).to.have.property('stillNeedHelp');
|
||||
expect(res.stillNeedHelp).to.eql({ ios: translate('iosFaqStillNeedHelp') });
|
||||
|
||||
expect(res).to.have.property('questions');
|
||||
expect(res.questions[0]).to.eql({
|
||||
question: translate('faqQuestion0'),
|
||||
ios: translate('iosFaqAnswer0'),
|
||||
});
|
||||
});
|
||||
it('returns an error when invalid platform parameter is specified', async () => {
|
||||
const request = requester().get('/faq?platform=wrong');
|
||||
await expect(request)
|
||||
@@ -45,10 +57,8 @@ describe('GET /faq', () => {
|
||||
|
||||
expect(res).to.have.property('questions');
|
||||
expect(res.questions[0]).to.eql({
|
||||
exclusions: [],
|
||||
heading: 'task-types',
|
||||
question: translate('faqQuestion25'),
|
||||
web: translate('webFaqAnswer25'),
|
||||
question: translate('faqQuestion0'),
|
||||
android: translate('androidFaqAnswer0'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -202,86 +202,18 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
await group.groupLeader.post('/user/class/cast/mpheal');
|
||||
|
||||
promises = [];
|
||||
promises.push(group.groupLeader.sync());
|
||||
promises.push(group.members[0].sync());
|
||||
promises.push(group.members[1].sync());
|
||||
promises.push(group.members[2].sync());
|
||||
promises.push(group.members[3].sync());
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(group.groupLeader.stats.mp).to.be.equal(170); // spell caster
|
||||
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
|
||||
expect(group.members[1].stats.mp).to.equal(0); // wizard
|
||||
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
|
||||
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
|
||||
});
|
||||
|
||||
const spellList = [
|
||||
{
|
||||
className: 'warrior',
|
||||
spells: [['smash', 'task'], ['defensiveStance'], ['valorousPresence'], ['intimidate']],
|
||||
},
|
||||
{
|
||||
className: 'wizard',
|
||||
spells: [['fireball', 'task'], ['mpheal'], ['earth'], ['frost']],
|
||||
},
|
||||
{
|
||||
className: 'healer',
|
||||
spells: [['heal'], ['brightness'], ['protectAura'], ['healAll']],
|
||||
},
|
||||
{
|
||||
className: 'rogue',
|
||||
spells: [['pickPocket', 'task'], ['backStab', 'task'], ['toolsOfTrade'], ['stealth']],
|
||||
},
|
||||
];
|
||||
|
||||
spellList.forEach(async habitClass => {
|
||||
describe(`For a ${habitClass.className}`, async () => {
|
||||
habitClass.spells.forEach(async spell => {
|
||||
describe(`Using ${spell[0]}`, async () => {
|
||||
it('Deducts MP from spell caster', async () => {
|
||||
const { groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 3,
|
||||
});
|
||||
await groupLeader.update({
|
||||
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
|
||||
});
|
||||
// need this for task spells and for stealth
|
||||
const task = await groupLeader.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
});
|
||||
if (spell.length === 2 && spell[1] === 'task') {
|
||||
await groupLeader.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
|
||||
} else {
|
||||
await groupLeader.post(`/user/class/cast/${spell[0]}`);
|
||||
}
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.stats.mp).to.be.lessThan(200);
|
||||
});
|
||||
it('works without a party', async () => {
|
||||
await user.update({
|
||||
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
|
||||
});
|
||||
// need this for task spells and for stealth
|
||||
const task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
});
|
||||
if (spell.length === 2 && spell[1] === 'task') {
|
||||
await user.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
|
||||
} else {
|
||||
await user.post(`/user/class/cast/${spell[0]}`);
|
||||
}
|
||||
await user.sync();
|
||||
expect(user.stats.mp).to.be.lessThan(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('cast bulk', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({ // eslint-disable-line prefer-const
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
|
||||
@@ -21,9 +21,7 @@ describe('POST /user/reset', () => {
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
@@ -41,9 +39,7 @@ describe('POST /user/reset', () => {
|
||||
type: 'daily',
|
||||
});
|
||||
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
@@ -61,9 +57,7 @@ describe('POST /user/reset', () => {
|
||||
type: 'todo',
|
||||
});
|
||||
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
@@ -81,9 +75,7 @@ describe('POST /user/reset', () => {
|
||||
type: 'reward',
|
||||
});
|
||||
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
@@ -95,26 +87,6 @@ describe('POST /user/reset', () => {
|
||||
expect(user.tasksOrder.rewards).to.be.empty;
|
||||
});
|
||||
|
||||
it('does not allow to reset if the password is missing', async () => {
|
||||
await expect(user.post('/user/reset', {
|
||||
password: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not allow to reset if the password is wrong', async () => {
|
||||
await expect(user.post('/user/reset', {
|
||||
password: 'passdw',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('wrongPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not delete challenge or group tasks', async () => {
|
||||
const guild = await generateGroup(user, {}, { 'purchased.plan.customerId': 'group-unlimited' });
|
||||
const challenge = await generateChallenge(user, guild);
|
||||
@@ -130,9 +102,7 @@ describe('POST /user/reset', () => {
|
||||
});
|
||||
await user.post(`/tasks/${groupTask._id}/assign`, [user._id]);
|
||||
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
await user.put('/user', {
|
||||
@@ -163,9 +133,7 @@ describe('POST /user/reset', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await hero.post('/user/reset');
|
||||
|
||||
const heroRes = await admin.get(`/hall/heroes/${hero.auth.local.username}`);
|
||||
|
||||
|
||||
@@ -665,7 +665,6 @@ describe('POST /user/auth/local/register', () => {
|
||||
it('adds a user to a guild on an invite of type other than party', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
const invite = encrypt(JSON.stringify({
|
||||
|
||||
@@ -215,7 +215,6 @@ describe('cron utility functions', () => {
|
||||
|
||||
it('monthly plan, next date in 3 months', () => {
|
||||
const user = baseUserData(60, 0, 'group_plan_auto');
|
||||
user.purchased.plan.perkMonthCount = 0;
|
||||
|
||||
const planContext = getPlanContext(user, now);
|
||||
|
||||
@@ -225,7 +224,6 @@ describe('cron utility functions', () => {
|
||||
|
||||
it('monthly plan, next date in 1 month', () => {
|
||||
const user = baseUserData(62, 0, 'group_plan_auto');
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
|
||||
const planContext = getPlanContext(user, now);
|
||||
|
||||
@@ -250,15 +248,5 @@ describe('cron utility functions', () => {
|
||||
expect(planContext.nextHourglassDate)
|
||||
.to.be.sameMoment('2022-07-10T02:00:00.144Z');
|
||||
});
|
||||
|
||||
it('multi-month plan with perk count', () => {
|
||||
const user = baseUserData(60, 1, 'basic_3mo');
|
||||
user.purchased.plan.perkMonthCount = 2;
|
||||
|
||||
const planContext = getPlanContext(user, now);
|
||||
|
||||
expect(planContext.nextHourglassDate)
|
||||
.to.be.sameMoment('2022-07-10T02:00:00.144Z');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -126,5 +126,13 @@ describe('shared.ops.addTask', () => {
|
||||
expect(addTask(user)._editing).not.be.ok;
|
||||
expect(addTask(user)._edit).to.not.be.ok;
|
||||
});
|
||||
|
||||
it('respects advancedCollapsed preference', () => {
|
||||
user.preferences.advancedCollapsed = true;
|
||||
expect(addTask(user)._advanced).not.be.ok;
|
||||
|
||||
user.preferences.advancedCollapsed = false;
|
||||
expect(addTask(user)._advanced).to.be.ok;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -197,14 +197,14 @@ describe('shared.ops.purchase', () => {
|
||||
|
||||
it('purchases quest bundles', async () => {
|
||||
const startingBalance = user.balance;
|
||||
const clock = sandbox.useFakeTimers(moment('2022-03-16').valueOf());
|
||||
const clock = sandbox.useFakeTimers(moment('2019-05-20').valueOf());
|
||||
const type = 'bundles';
|
||||
const key = 'cuddleBuddies';
|
||||
const key = 'featheredFriends';
|
||||
const price = 1.75;
|
||||
const questList = [
|
||||
'bunny',
|
||||
'ferret',
|
||||
'guineapig',
|
||||
'falcon',
|
||||
'harpy',
|
||||
'owl',
|
||||
];
|
||||
|
||||
await purchase(user, { params: { type, key } });
|
||||
|
||||
@@ -15,6 +15,12 @@ describe('FAQ Locales', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('has a valid ios answers', () => {
|
||||
each(questions, question => {
|
||||
expectValidTranslationString(question.ios);
|
||||
});
|
||||
});
|
||||
|
||||
it('has a valid web answers', () => {
|
||||
each(questions, question => {
|
||||
expectValidTranslationString(question.web);
|
||||
@@ -23,6 +29,10 @@ describe('FAQ Locales', () => {
|
||||
});
|
||||
|
||||
describe('Still Need Help Message', () => {
|
||||
it('has a valid ios message', () => {
|
||||
expectValidTranslationString(stillNeedHelp.ios);
|
||||
});
|
||||
|
||||
it('has a valid web message', () => {
|
||||
expectValidTranslationString(stillNeedHelp.web);
|
||||
});
|
||||
|
||||
@@ -12,9 +12,8 @@ const webhookData = {};
|
||||
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true,
|
||||
limit: '10mb',
|
||||
}));
|
||||
app.use(bodyParser.json({ limit: '10mb' }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.post('/webhooks/:id', (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
@@ -53,8 +53,7 @@ function _requestMaker (user, method, additionalSets = {}) {
|
||||
if (user && user._id && user.apiToken) {
|
||||
request
|
||||
.set('x-api-user', user._id)
|
||||
.set('x-api-key', user.apiToken)
|
||||
.set('x-client', 'habitica-web');
|
||||
.set('x-api-key', user.apiToken);
|
||||
}
|
||||
|
||||
if (!isEmpty(additionalSets)) {
|
||||
|
||||
@@ -127,9 +127,6 @@ export async function createAndPopulateGroup (settings = {}) {
|
||||
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
||||
const { groupDetails } = settings;
|
||||
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
||||
if (upgradeToGroupPlan) {
|
||||
leaderDetails.permissions = { fullAccess: true };
|
||||
}
|
||||
|
||||
const groupLeader = await generateUser(leaderDetails);
|
||||
const group = await generateGroup(groupLeader, groupDetails);
|
||||
|
||||
@@ -120,9 +120,6 @@ export async function createAndPopulateGroup (settings = {}) {
|
||||
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
||||
const { groupDetails } = settings;
|
||||
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
||||
if (upgradeToGroupPlan) {
|
||||
leaderDetails.permissions = { fullAccess: true };
|
||||
}
|
||||
|
||||
const groupLeader = await generateUser(leaderDetails);
|
||||
const group = await generateGroup(groupLeader, groupDetails);
|
||||
|
||||
@@ -15,7 +15,6 @@ module.exports = {
|
||||
'import/no-unresolved': 'off',
|
||||
'import/extensions': 'off',
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/no-mutating-props': 'warn',
|
||||
'vue/html-self-closing': ['error', {
|
||||
html: {
|
||||
void: 'never',
|
||||
|
||||
@@ -55,13 +55,3 @@ in a separate `.add('function of component', ...`
|
||||
### Storybook Build
|
||||
|
||||
After each client build, storybook build is also triggered and will be available in `dist/storybook`
|
||||
|
||||
### Vue Structure
|
||||
|
||||
Currently pages and components are mixed in `/src/components` this is not a good way to find the files easy.
|
||||
|
||||
Thats why each changed / upcoming page / component should be put in either `/src/components` or in the `/src/pages` directory.
|
||||
|
||||
Inside Pages, each page can have a subfolder which contains sub-components only needed for that page - otherwise it has to be added to the normal components folder.
|
||||
|
||||
At the end of all the changes - the components should only contain components needed between all pages
|
||||
|
||||
13508
website/client/package-lock.json
generated
13508
website/client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,56 +18,54 @@
|
||||
"@storybook/addon-links": "6.5.8",
|
||||
"@storybook/addon-notes": "5.3.21",
|
||||
"@storybook/addons": "6.5.9",
|
||||
"@storybook/vue": "6.5.14",
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@storybook/vue": "6.3.13",
|
||||
"@vue/cli-plugin-babel": "^4.5.15",
|
||||
"@vue/cli-plugin-eslint": "^4.5.19",
|
||||
"@vue/cli-plugin-router": "^5.0.8",
|
||||
"@vue/cli-plugin-unit-mocha": "^5.0.8",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.5.15",
|
||||
"@vue/cli-service": "^4.5.15",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^8.21.3",
|
||||
"amplitude-js": "^8.21.1",
|
||||
"axios": "^0.27.2",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"chai": "^4.3.7",
|
||||
"core-js": "^3.33.1",
|
||||
"dompurify": "^3.0.3",
|
||||
"bootstrap-vue": "^2.22.0",
|
||||
"chai": "^4.3.6",
|
||||
"core-js": "^3.26.0",
|
||||
"dompurify": "^2.4.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"hellojs": "^1.20.0",
|
||||
"hellojs": "^1.19.5",
|
||||
"inspectpack": "^4.7.1",
|
||||
"intro.js": "^7.2.0",
|
||||
"jquery": "^3.7.1",
|
||||
"intro.js": "^6.0.0",
|
||||
"jquery": "^3.6.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"nconf": "^0.12.1",
|
||||
"sass": "^1.63.4",
|
||||
"nconf": "^0.12.0",
|
||||
"sass": "^1.34.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.19.3",
|
||||
"stopword": "^2.0.8",
|
||||
"smartbanner.js": "^1.19.1",
|
||||
"stopword": "^2.0.5",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
"svg-url-loader": "^7.1.1",
|
||||
"svgo": "^1.3.2",
|
||||
"svgo-loader": "^2.2.1",
|
||||
"uuid": "^9.0.1",
|
||||
"validator": "^13.9.0",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.7.0",
|
||||
"vue": "^2.7.10",
|
||||
"vue-cli-plugin-storybook": "2.1.0",
|
||||
"vue-fragment": "^1.6.0",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-template-compiler": "^2.7.10",
|
||||
"vue-template-babel-compiler": "^2.0.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0",
|
||||
"webpack": "^4.47.0"
|
||||
"webpack": "^4.46.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0"
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.18.9"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 148 B |
@@ -35,17 +35,13 @@
|
||||
<sub-canceled-modal v-if="isUserLoaded" />
|
||||
<bug-report-modal v-if="isUserLoaded" />
|
||||
<bug-report-success-modal v-if="isUserLoaded" />
|
||||
<external-link-modal />
|
||||
<birthday-modal />
|
||||
<snackbars />
|
||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||
<template v-else>
|
||||
<template v-if="isUserLoaded">
|
||||
<chat-banner />
|
||||
<damage-paused-banner />
|
||||
<gems-promo-banner />
|
||||
<gift-promo-banner />
|
||||
<birthday-banner />
|
||||
<notifications-display />
|
||||
<app-menu />
|
||||
<div
|
||||
@@ -157,14 +153,11 @@
|
||||
import axios from 'axios';
|
||||
import { loadProgressBar } from 'axios-progress-bar';
|
||||
|
||||
import birthdayModal from '@/components/news/birthdayModal';
|
||||
import AppMenu from './components/header/menu';
|
||||
import AppHeader from './components/header/index';
|
||||
import ChatBanner from './components/header/banners/chatBanner';
|
||||
import DamagePausedBanner from './components/header/banners/damagePaused';
|
||||
import GemsPromoBanner from './components/header/banners/gemsPromo';
|
||||
import GiftPromoBanner from './components/header/banners/giftPromo';
|
||||
import BirthdayBanner from './components/header/banners/birthdayBanner';
|
||||
import AppFooter from './components/appFooter';
|
||||
import notificationsDisplay from './components/notifications';
|
||||
import snackbars from './components/snackbars/notifications';
|
||||
@@ -178,7 +171,6 @@ import amazonPaymentsModal from '@/components/payments/amazonModal';
|
||||
import paymentsSuccessModal from '@/components/payments/successModal';
|
||||
import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
|
||||
import subCanceledModal from '@/components/payments/canceledModal';
|
||||
import externalLinkModal from '@/components/externalLinkModal.vue';
|
||||
|
||||
import spellsMixin from '@/mixins/spells';
|
||||
import {
|
||||
@@ -199,12 +191,9 @@ export default {
|
||||
AppMenu,
|
||||
AppHeader,
|
||||
AppFooter,
|
||||
birthdayModal,
|
||||
ChatBanner,
|
||||
DamagePausedBanner,
|
||||
GemsPromoBanner,
|
||||
GiftPromoBanner,
|
||||
BirthdayBanner,
|
||||
notificationsDisplay,
|
||||
snackbars,
|
||||
BuyModal,
|
||||
@@ -215,7 +204,6 @@ export default {
|
||||
subCanceledModal,
|
||||
bugReportModal,
|
||||
bugReportSuccessModal,
|
||||
externalLinkModal,
|
||||
},
|
||||
mixins: [notifications, spellsMixin],
|
||||
data () {
|
||||
|
||||
@@ -94,12 +94,6 @@
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.back_special_heroicAureole {
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_special_heroicAureole.gif") no-repeat;
|
||||
}
|
||||
|
||||
.head_special_0 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-ShadeHelmet.gif") no-repeat;
|
||||
}
|
||||
@@ -162,12 +156,6 @@
|
||||
height: 99px;
|
||||
}
|
||||
|
||||
.Pet-Gryphatrice-Jubilant {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Gryphatrice-Jubilant.gif") no-repeat;
|
||||
width: 81px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.Mount_Head_Gryphon-Gryphatrice, .Mount_Body_Gryphon-Gryphatrice {
|
||||
width: 135px;
|
||||
height: 135px;
|
||||
@@ -198,6 +186,14 @@
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_steamworks.gif") no-repeat;
|
||||
}
|
||||
|
||||
/* FIXME figure out how to handle customize menu!!
|
||||
.customize-menu .f_head_0 {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-position: -1917px -9px;
|
||||
}
|
||||
*/
|
||||
|
||||
[class*="Mount_Head_"],
|
||||
[class*="Mount_Body_"] {
|
||||
margin-top:18px; /* Sprite accommodates 105x123 box */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 332 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user