mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
Compare commits
1 Commits
phillip/am
...
phillip/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a018588021 |
@@ -1,10 +1,6 @@
|
|||||||
/* eslint-disable import/no-commonjs */
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
extends: [
|
extends: [
|
||||||
'habitrpg/lib/node',
|
'habitrpg/lib/node'
|
||||||
],
|
],
|
||||||
rules: {
|
}
|
||||||
'import/no-extraneous-dependencies': 'off',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
36
.github/dependabot.yml
vendored
36
.github/dependabot.yml
vendored
@@ -104,6 +104,42 @@ updates:
|
|||||||
- 7.7.0
|
- 7.7.0
|
||||||
- 7.8.0
|
- 7.8.0
|
||||||
- 7.9.0
|
- 7.9.0
|
||||||
|
- dependency-name: "@storybook/addon-knobs"
|
||||||
|
versions:
|
||||||
|
- 6.1.17
|
||||||
|
- 6.1.18
|
||||||
|
- 6.1.20
|
||||||
|
- 6.1.21
|
||||||
|
- 6.2.2
|
||||||
|
- 6.2.3
|
||||||
|
- 6.2.7
|
||||||
|
- dependency-name: "@storybook/addon-links"
|
||||||
|
versions:
|
||||||
|
- 6.1.17
|
||||||
|
- 6.1.18
|
||||||
|
- 6.1.20
|
||||||
|
- 6.1.21
|
||||||
|
- 6.2.2
|
||||||
|
- 6.2.3
|
||||||
|
- 6.2.7
|
||||||
|
- dependency-name: "@storybook/vue"
|
||||||
|
versions:
|
||||||
|
- 6.1.17
|
||||||
|
- 6.1.18
|
||||||
|
- 6.1.20
|
||||||
|
- 6.1.21
|
||||||
|
- 6.2.2
|
||||||
|
- 6.2.3
|
||||||
|
- 6.2.7
|
||||||
|
- dependency-name: "@storybook/addon-actions"
|
||||||
|
versions:
|
||||||
|
- 6.1.17
|
||||||
|
- 6.1.18
|
||||||
|
- 6.1.20
|
||||||
|
- 6.1.21
|
||||||
|
- 6.2.2
|
||||||
|
- 6.2.3
|
||||||
|
- 6.2.7
|
||||||
- dependency-name: core-js
|
- dependency-name: core-js
|
||||||
versions:
|
versions:
|
||||||
- 3.10.0
|
- 3.10.0
|
||||||
|
|||||||
@@ -86,6 +86,5 @@
|
|||||||
"RATE_LIMITER_ENABLED": "false",
|
"RATE_LIMITER_ENABLED": "false",
|
||||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||||
"REDIS_PORT": "1234",
|
"REDIS_PORT": "1234",
|
||||||
"REDIS_PASSWORD": "12345678",
|
"REDIS_PASSWORD": "12345678"
|
||||||
"TRUSTED_DOMAINS": "localhost,habitica.com"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ gulp.task('build:prepare-mongo', async () => {
|
|||||||
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
|
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
|
||||||
|
|
||||||
// use run-rs without --keep, kill it as soon as the replica set starts
|
// use run-rs without --keep, kill it as soon as the replica set starts
|
||||||
const runRsProcess = spawn('run-rs', ['-v', '4.1.1', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
|
const runRsProcess = spawn('run-rs', ['-v', '4.2.8', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
|
||||||
|
|
||||||
for await (const chunk of runRsProcess.stdout) {
|
for await (const chunk of runRsProcess.stdout) {
|
||||||
const stringChunk = chunk.toString();
|
const stringChunk = chunk.toString();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import mongoose from 'mongoose';
|
|||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import repl from 'repl';
|
import repl from 'repl';
|
||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
|
import logger from '../website/server/libs/logger';
|
||||||
import {
|
import {
|
||||||
getDevelopmentConnectionUrl,
|
getDevelopmentConnectionUrl,
|
||||||
getDefaultConnectionOptions,
|
getDefaultConnectionOptions,
|
||||||
@@ -38,6 +39,10 @@ const improveRepl = context => {
|
|||||||
mongoose.connect(
|
mongoose.connect(
|
||||||
connectionUrl,
|
connectionUrl,
|
||||||
mongooseOptions,
|
mongooseOptions,
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
logger.info('Connected with Mongoose');
|
||||||
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -59,15 +59,13 @@ gulp.task('test:prepare:mongo', cb => {
|
|||||||
const mongooseOptions = getDefaultConnectionOptions();
|
const mongooseOptions = getDefaultConnectionOptions();
|
||||||
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
||||||
|
|
||||||
mongoose.connect(connectionUrl, mongooseOptions)
|
mongoose.connect(connectionUrl, mongooseOptions, err => {
|
||||||
.then(() => mongoose.connection.dropDatabase())
|
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
|
||||||
.then(() => mongoose.connection.close()).then(() => {
|
return mongoose.connection.dropDatabase(err2 => {
|
||||||
cb();
|
if (err2) return cb(err2);
|
||||||
})
|
return mongoose.connection.close(cb);
|
||||||
.catch(err => {
|
|
||||||
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
|
|
||||||
throw err;
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test:prepare:server', gulp.series('test:prepare:mongo', done => {
|
gulp.task('test:prepare:server', gulp.series('test:prepare:mongo', done => {
|
||||||
@@ -118,10 +116,8 @@ gulp.task('test:common:safe', gulp.series('test:prepare:build', cb => {
|
|||||||
pipe(runner);
|
pipe(runner);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
gulp.task('test:content', gulp.series(
|
gulp.task('test:content', gulp.series('test:prepare:build',
|
||||||
'test:prepare:build',
|
runInChildProcess(CONTENT_TEST_COMMAND, LIMIT_MAX_BUFFER_OPTIONS)));
|
||||||
runInChildProcess(CONTENT_TEST_COMMAND, LIMIT_MAX_BUFFER_OPTIONS),
|
|
||||||
));
|
|
||||||
|
|
||||||
gulp.task('test:content:clean', cb => {
|
gulp.task('test:content:clean', cb => {
|
||||||
pipe(exec(testBin(CONTENT_TEST_COMMAND), LIMIT_MAX_BUFFER_OPTIONS, () => cb()));
|
pipe(exec(testBin(CONTENT_TEST_COMMAND), LIMIT_MAX_BUFFER_OPTIONS, () => cb()));
|
||||||
@@ -146,20 +142,16 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', cb => {
|
|||||||
pipe(runner);
|
pipe(runner);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
gulp.task(
|
gulp.task('test:api:unit:run',
|
||||||
'test:api:unit:run',
|
runInChildProcess(integrationTestCommand('test/api/unit', 'coverage/api-unit')));
|
||||||
runInChildProcess(integrationTestCommand('test/api/unit', 'coverage/api-unit')),
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done())));
|
gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done())));
|
||||||
|
|
||||||
gulp.task('test:api-v3:integration', gulp.series(
|
gulp.task('test:api-v3:integration', gulp.series('test:prepare:mongo',
|
||||||
'test:prepare:mongo',
|
|
||||||
runInChildProcess(
|
runInChildProcess(
|
||||||
integrationTestCommand('test/api/v3/integration', 'coverage/api-v3-integration'),
|
integrationTestCommand('test/api/v3/integration', 'coverage/api-v3-integration'),
|
||||||
LIMIT_MAX_BUFFER_OPTIONS,
|
LIMIT_MAX_BUFFER_OPTIONS,
|
||||||
),
|
)));
|
||||||
));
|
|
||||||
|
|
||||||
gulp.task('test:api-v3:integration:watch', () => gulp.watch([
|
gulp.task('test:api-v3:integration:watch', () => gulp.watch([
|
||||||
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
|
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
|
||||||
@@ -172,13 +164,11 @@ gulp.task('test:api-v3:integration:separate-server', runInChildProcess(
|
|||||||
'LOAD_SERVER=0',
|
'LOAD_SERVER=0',
|
||||||
));
|
));
|
||||||
|
|
||||||
gulp.task('test:api-v4:integration', gulp.series(
|
gulp.task('test:api-v4:integration', gulp.series('test:prepare:mongo',
|
||||||
'test:prepare:mongo',
|
|
||||||
runInChildProcess(
|
runInChildProcess(
|
||||||
integrationTestCommand('test/api/v4', 'api-v4-integration'),
|
integrationTestCommand('test/api/v4', 'api-v4-integration'),
|
||||||
LIMIT_MAX_BUFFER_OPTIONS,
|
LIMIT_MAX_BUFFER_OPTIONS,
|
||||||
),
|
)));
|
||||||
));
|
|
||||||
|
|
||||||
gulp.task('test:api-v4:integration:separate-server', runInChildProcess(
|
gulp.task('test:api-v4:integration:separate-server', runInChildProcess(
|
||||||
'mocha test/api/v4 --recursive --require ./test/helpers/start-server',
|
'mocha test/api/v4 --recursive --require ./test/helpers/start-server',
|
||||||
|
|||||||
Submodule habitica-images updated: e6d883bc2e...d66a5ea922
@@ -3,6 +3,6 @@ module.exports = {
|
|||||||
root: false,
|
root: false,
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': 0,
|
'no-console': 0,
|
||||||
'no-use-before-define': ['error', { functions: false }],
|
'no-use-before-define': ['error', { functions: false }]
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/* eslint-disable no-console */
|
|
||||||
const MIGRATION_NAME = '20231228_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_nye2022 !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2023'] = true;
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2022'] = true;
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2021'] = true;
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2020'] = true;
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2019'] = true;
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2018'] = true;
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2017'] = true;
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2016'] = true;
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2015'] = true;
|
|
||||||
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
|
||||||
set['items.gear.owned.head_special_nye2014'] = true;
|
|
||||||
} else {
|
|
||||||
set['items.gear.owned.head_special_nye'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
push.notifications = {
|
|
||||||
type: 'ITEM_RECEIVED',
|
|
||||||
data: {
|
|
||||||
icon: 'notif_head_special_nye',
|
|
||||||
title: 'Happy New Year!',
|
|
||||||
text: 'Check your Equipment for this year\'s party hat!',
|
|
||||||
destination: 'inventory/equipment',
|
|
||||||
},
|
|
||||||
seen: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
|
||||||
|
|
||||||
return await User.updateOne({_id: user._id}, {$set: set, $push: push}).exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function processUsers () {
|
|
||||||
let query = {
|
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2023-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,102 +0,0 @@
|
|||||||
/* eslint-disable no-console */
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { model as User } from '../../../website/server/models/user';
|
|
||||||
|
|
||||||
const MIGRATION_NAME = '20240131_habit_birthday';
|
|
||||||
const progressCount = 1000;
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
async function updateUser (user) {
|
|
||||||
count += 1;
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
const set = {};
|
|
||||||
const push = {
|
|
||||||
notifications: {
|
|
||||||
type: 'ITEM_RECEIVED',
|
|
||||||
data: {
|
|
||||||
icon: 'notif_namingDay_cake',
|
|
||||||
title: 'Happy Habit Birthday!',
|
|
||||||
text: 'Habitica turns 11 today! Enjoy free party robes and cake!',
|
|
||||||
destination: 'inventory/equipment',
|
|
||||||
},
|
|
||||||
seen: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
set.migration = MIGRATION_NAME;
|
|
||||||
|
|
||||||
if (typeof user.items.gear.owned.armor_special_birthday2023 !== 'undefined') {
|
|
||||||
set['items.gear.owned.armor_special_birthday2024'] = true;
|
|
||||||
} else 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
|
||||||
|
|
||||||
return await User.updateOne({_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('2023-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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -21,14 +21,12 @@ async function handOutJackalopes () {
|
|||||||
if (user.party._id) groupList.push(user.party._id);
|
if (user.party._id) groupList.push(user.party._id);
|
||||||
groupList = groupList.concat(user.guilds);
|
groupList = groupList.concat(user.guilds);
|
||||||
|
|
||||||
const subscribedGroup = await Group.findOne(
|
const subscribedGroup = await Group.findOne({
|
||||||
{
|
_id: { $in: groupList },
|
||||||
_id: { $in: groupList },
|
'purchased.plan.planId': 'group_monthly',
|
||||||
'purchased.plan.planId': 'group_monthly',
|
'purchased.plan.dateTerminated': null,
|
||||||
'purchased.plan.dateTerminated': null,
|
},
|
||||||
},
|
{ _id: 1 });
|
||||||
{ _id: 1 },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (subscribedGroup) {
|
if (subscribedGroup) {
|
||||||
User.update({ _id: user._id }, { $set: { 'items.mounts.Jackalope-RoyalPurple': true } }).exec();
|
User.update({ _id: user._id }, { $set: { 'items.mounts.Jackalope-RoyalPurple': true } }).exec();
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ function getAchievementUpdate (newUser, oldUser) {
|
|||||||
// Rebirth level
|
// Rebirth level
|
||||||
if (achievementsUpdate.rebirthLevel) {
|
if (achievementsUpdate.rebirthLevel) {
|
||||||
achievementsUpdate.rebirthLevel = Math.max(
|
achievementsUpdate.rebirthLevel = Math.max(
|
||||||
achievementsUpdate.rebirthLevel,
|
achievementsUpdate.rebirthLevel, oldAchievements.rebirthLevel,
|
||||||
oldAchievements.rebirthLevel,
|
|
||||||
);
|
);
|
||||||
} else if (oldAchievements.rebirthLevel) {
|
} else if (oldAchievements.rebirthLevel) {
|
||||||
achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel;
|
achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
if (count % progressCount === 0) {
|
if (count % progressCount === 0) {
|
||||||
console.warn(`${count} ${user._id}`);
|
console.warn(`${count} ${user._id}`);
|
||||||
// eslint-disable-next-line no-promise-executor-return
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
|
|||||||
|
|
||||||
import { model as User } from '../../website/server/models/user';
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
const MIGRATION_NAME = '20230314_pi_day';
|
const MIGRATION_NAME = '20220314_pi_day';
|
||||||
|
|
||||||
const progressCount = 1000;
|
const progressCount = 1000;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
@@ -54,7 +54,7 @@ async function updateUser (user) {
|
|||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
const query = {
|
const query = {
|
||||||
migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.timestamps.loggedin': { $gt: new Date('2023-02-15') },
|
'auth.timestamps.loggedin': { $gt: new Date('2022-02-15') },
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
|
|||||||
8332
package-lock.json
generated
8332
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
64
package.json
64
package.json
@@ -1,37 +1,37 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||||
"version": "5.16.1",
|
"version": "4.251.0",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.19.6",
|
||||||
"@babel/preset-env": "^7.22.10",
|
"@babel/preset-env": "^7.20.2",
|
||||||
"@babel/register": "^7.22.15",
|
"@babel/register": "^7.18.9",
|
||||||
"@google-cloud/trace-agent": "^7.1.2",
|
"@google-cloud/trace-agent": "^7.1.2",
|
||||||
"@parse/node-apn": "^5.2.3",
|
"@parse/node-apn": "^5.1.3",
|
||||||
"@slack/webhook": "^6.1.0",
|
"@slack/webhook": "^6.1.0",
|
||||||
"accepts": "^1.3.8",
|
"accepts": "^1.3.8",
|
||||||
"amazon-payments": "^0.2.9",
|
"amazon-payments": "^0.2.9",
|
||||||
"amplitude": "^6.0.0",
|
"amplitude": "^6.0.0",
|
||||||
"apidoc": "^0.54.0",
|
"apidoc": "^0.53.1",
|
||||||
"apple-auth": "^1.0.9",
|
"apple-auth": "^1.0.7",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.0",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.1",
|
||||||
"bootstrap": "^4.6.2",
|
"bootstrap": "^4.6.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-session": "^2.0.0",
|
"cookie-session": "^2.0.0",
|
||||||
"coupon-code": "^0.4.5",
|
"coupon-code": "^0.4.5",
|
||||||
"csv-stringify": "^5.6.5",
|
"csv-stringify": "^5.6.5",
|
||||||
"cwait": "^1.1.1",
|
"cwait": "^1.1.1",
|
||||||
"domain-middleware": "~0.1.0",
|
"domain-middleware": "~0.1.0",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-habitrpg": "^6.2.3",
|
"eslint-config-habitrpg": "^6.2.0",
|
||||||
"eslint-plugin-mocha": "^5.0.0",
|
"eslint-plugin-mocha": "^5.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"express-validator": "^5.2.0",
|
"express-validator": "^5.2.0",
|
||||||
"glob": "^8.1.0",
|
"glob": "^8.0.3",
|
||||||
"got": "^11.8.6",
|
"got": "^11.8.3",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"gulp-babel": "^8.0.0",
|
"gulp-babel": "^8.0.0",
|
||||||
"gulp-imagemin": "^7.1.0",
|
"gulp-imagemin": "^7.1.0",
|
||||||
@@ -39,42 +39,44 @@
|
|||||||
"gulp.spritesmith": "^6.13.0",
|
"gulp.spritesmith": "^6.13.0",
|
||||||
"habitica-markdown": "^3.0.0",
|
"habitica-markdown": "^3.0.0",
|
||||||
"helmet": "^4.6.0",
|
"helmet": "^4.6.0",
|
||||||
|
"image-size": "^1.0.2",
|
||||||
"in-app-purchase": "^1.11.3",
|
"in-app-purchase": "^1.11.3",
|
||||||
"js2xmlparser": "^5.0.0",
|
"js2xmlparser": "^5.0.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"jwks-rsa": "^2.1.5",
|
"jwks-rsa": "^2.1.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"merge-stream": "^2.0.0",
|
"merge-stream": "^2.0.0",
|
||||||
"method-override": "^3.0.0",
|
"method-override": "^3.0.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-recur": "^1.0.7",
|
"moment-recur": "^1.0.7",
|
||||||
"mongoose": "^7.6.3",
|
"mongoose": "^5.13.7",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.0",
|
||||||
"node-gcm": "^1.0.5",
|
"node-gcm": "^1.0.5",
|
||||||
"nodemon": "^2.0.20",
|
|
||||||
"on-headers": "^1.0.2",
|
"on-headers": "^1.0.2",
|
||||||
"passport": "^0.5.3",
|
"passport": "^0.6.0",
|
||||||
"passport-facebook": "^3.0.0",
|
"passport-facebook": "^3.0.0",
|
||||||
"passport-google-oauth2": "^0.2.0",
|
"passport-google-oauth2": "^0.2.0",
|
||||||
"passport-google-oauth20": "2.0.0",
|
"passport-google-oauth20": "2.0.0",
|
||||||
"paypal-rest-sdk": "^1.8.1",
|
"paypal-rest-sdk": "^1.8.1",
|
||||||
"pp-ipn": "^1.1.0",
|
"pp-ipn": "^1.1.0",
|
||||||
"ps-tree": "^1.0.0",
|
"ps-tree": "^1.0.0",
|
||||||
"rate-limiter-flexible": "^2.4.2",
|
"rate-limiter-flexible": "^2.4.0",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
|
"regenerator-runtime": "^0.13.11",
|
||||||
"remove-markdown": "^0.5.0",
|
"remove-markdown": "^0.5.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"short-uuid": "^4.2.2",
|
"short-uuid": "^4.2.2",
|
||||||
"stripe": "^12.18.0",
|
"stripe": "^10.13.0",
|
||||||
"superagent": "^8.1.2",
|
"superagent": "^8.0.5",
|
||||||
"universal-analytics": "^0.5.3",
|
"universal-analytics": "^0.5.3",
|
||||||
"useragent": "^2.1.9",
|
"useragent": "^2.1.9",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^8.3.2",
|
||||||
"validator": "^13.11.0",
|
"validator": "^13.7.0",
|
||||||
"winston": "^3.10.0",
|
"vinyl-buffer": "^1.0.1",
|
||||||
"winston-loggly-bulk": "^3.3.0",
|
"winston": "^3.8.2",
|
||||||
"xml2js": "^0.6.2"
|
"winston-loggly-bulk": "^3.2.1",
|
||||||
|
"xml2js": "^0.4.23"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -103,16 +105,16 @@
|
|||||||
"client:unit": "cd website/client && npm run test:unit",
|
"client:unit": "cd website/client && npm run test:unit",
|
||||||
"start": "gulp nodemon",
|
"start": "gulp nodemon",
|
||||||
"debug": "gulp nodemon --inspect",
|
"debug": "gulp nodemon --inspect",
|
||||||
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
"mongo:dev": "run-rs -v 4.2.8 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||||
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
||||||
"apidoc": "gulp apidoc"
|
"apidoc": "gulp apidoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios": "^1.4.0",
|
"axios": "^0.27.2",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-moment": "^0.1.0",
|
"chai-moment": "^0.1.0",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.1.2",
|
||||||
"cross-spawn": "^7.0.3",
|
"cross-spawn": "^7.0.3",
|
||||||
"expect.js": "^0.3.1",
|
"expect.js": "^0.3.1",
|
||||||
"istanbul": "^1.1.0-alpha.1",
|
"istanbul": "^1.1.0-alpha.1",
|
||||||
@@ -120,7 +122,7 @@
|
|||||||
"monk": "^7.3.4",
|
"monk": "^7.3.4",
|
||||||
"require-again": "^2.0.0",
|
"require-again": "^2.0.0",
|
||||||
"run-rs": "^0.7.7",
|
"run-rs": "^0.7.7",
|
||||||
"sinon": "^15.2.0",
|
"sinon": "^14.0.2",
|
||||||
"sinon-chai": "^3.7.0",
|
"sinon-chai": "^3.7.0",
|
||||||
"sinon-stub-promise": "^4.0.0"
|
"sinon-stub-promise": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ async function deleteHabiticaData (user, email) {
|
|||||||
{ _id: user._id },
|
{ _id: user._id },
|
||||||
{ $set: set },
|
{ $set: set },
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line no-promise-executor-return
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
const response = await axios.delete(
|
const response = await axios.delete(
|
||||||
`${BASE_URL}/api/v3/user`,
|
`${BASE_URL}/api/v3/user`,
|
||||||
@@ -97,7 +96,6 @@ async function processEmailAddress (email) {
|
|||||||
return console.log(`No users found with email address ${email}`);
|
return console.log(`No users found with email address ${email}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-promise-executor-return
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
return Promise.all(users.map(user => (async () => {
|
return Promise.all(users.map(user => (async () => {
|
||||||
await deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
|
await deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/* eslint-disable import/no-commonjs */
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: [
|
extends: [
|
||||||
'habitrpg/lib/mocha',
|
'habitrpg/lib/mocha',
|
||||||
@@ -9,9 +7,6 @@ module.exports = {
|
|||||||
chai: true,
|
chai: true,
|
||||||
expect: true,
|
expect: true,
|
||||||
sinon: true,
|
sinon: true,
|
||||||
sandbox: true,
|
sandbox: true
|
||||||
},
|
},
|
||||||
rules: {
|
}
|
||||||
'import/no-extraneous-dependencies': 'off',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ describe('bug-report', () => {
|
|||||||
_id: userId,
|
_id: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await bugReportLogic(user, userMail, userMessage, userAgent);
|
const result = await bugReportLogic(
|
||||||
|
user, userMail, userMessage, userAgent,
|
||||||
|
);
|
||||||
|
|
||||||
expect(emailLib.sendTxn).to.be.called;
|
expect(emailLib.sendTxn).to.be.called;
|
||||||
expect(result).to.deep.equal({
|
expect(result).to.deep.equal({
|
||||||
|
|||||||
@@ -231,16 +231,13 @@ describe('cron', async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
// user1 has a 1-month recurring subscription starting today
|
// user1 has a 1-month recurring subscription starting today
|
||||||
beforeEach(async () => {
|
user1.purchased.plan.customerId = 'subscribedId';
|
||||||
user1.purchased.plan.customerId = 'subscribedId';
|
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||||
user1.purchased.plan.dateUpdated = moment().toDate();
|
user1.purchased.plan.planId = 'basic';
|
||||||
user1.purchased.plan.planId = 'basic';
|
user1.purchased.plan.consecutive.count = 0;
|
||||||
user1.purchased.plan.consecutive.count = 0;
|
user1.purchased.plan.consecutive.offset = 0;
|
||||||
user1.purchased.plan.perkMonthCount = 0;
|
user1.purchased.plan.consecutive.trinkets = 0;
|
||||||
user1.purchased.plan.consecutive.offset = 0;
|
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||||
user1.purchased.plan.consecutive.trinkets = 0;
|
|
||||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment consecutive benefits after the first month', async () => {
|
it('does not increment consecutive benefits after the first month', async () => {
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
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);
|
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 () => {
|
it('increments consecutive benefits after the third month', async () => {
|
||||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
@@ -336,30 +315,6 @@ describe('cron', async () => {
|
|||||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||||
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
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 () => {
|
describe('for a 3-month recurring subscription', async () => {
|
||||||
@@ -375,16 +330,13 @@ describe('cron', async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
// user3 has a 3-month recurring subscription starting today
|
// user3 has a 3-month recurring subscription starting today
|
||||||
beforeEach(async () => {
|
user3.purchased.plan.customerId = 'subscribedId';
|
||||||
user3.purchased.plan.customerId = 'subscribedId';
|
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||||
user3.purchased.plan.dateUpdated = moment().toDate();
|
user3.purchased.plan.planId = 'basic_3mo';
|
||||||
user3.purchased.plan.planId = 'basic_3mo';
|
user3.purchased.plan.consecutive.count = 0;
|
||||||
user3.purchased.plan.perkMonthCount = 0;
|
user3.purchased.plan.consecutive.offset = 3;
|
||||||
user3.purchased.plan.consecutive.count = 0;
|
user3.purchased.plan.consecutive.trinkets = 1;
|
||||||
user3.purchased.plan.consecutive.offset = 3;
|
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||||
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 () => {
|
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')
|
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);
|
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 () => {
|
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')
|
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months')
|
||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
@@ -519,16 +456,13 @@ describe('cron', async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
// user6 has a 6-month recurring subscription starting today
|
// user6 has a 6-month recurring subscription starting today
|
||||||
beforeEach(async () => {
|
user6.purchased.plan.customerId = 'subscribedId';
|
||||||
user6.purchased.plan.customerId = 'subscribedId';
|
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||||
user6.purchased.plan.dateUpdated = moment().toDate();
|
user6.purchased.plan.planId = 'google_6mo';
|
||||||
user6.purchased.plan.planId = 'google_6mo';
|
user6.purchased.plan.consecutive.count = 0;
|
||||||
user6.purchased.plan.perkMonthCount = 0;
|
user6.purchased.plan.consecutive.offset = 6;
|
||||||
user6.purchased.plan.consecutive.count = 0;
|
user6.purchased.plan.consecutive.trinkets = 2;
|
||||||
user6.purchased.plan.consecutive.offset = 6;
|
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
||||||
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 () => {
|
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')
|
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);
|
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 () => {
|
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')
|
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
|
||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ describe('Password Utilities', () => {
|
|||||||
expiresAt: moment().subtract({ minutes: 1 }),
|
expiresAt: moment().subtract({ minutes: 1 }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'auth.local.passwordResetCode': code,
|
'auth.local.passwordResetCode': code,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ describe('Password Utilities', () => {
|
|||||||
expiresAt: moment().add({ days: 1 }),
|
expiresAt: moment().add({ days: 1 }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'auth.local.passwordResetCode': 'invalid',
|
'auth.local.passwordResetCode': 'invalid',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ describe('Password Utilities', () => {
|
|||||||
expiresAt: moment().add({ days: 1 }),
|
expiresAt: moment().add({ days: 1 }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'auth.local.passwordResetCode': code,
|
'auth.local.passwordResetCode': code,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ describe('Amazon Payments - Checkout', () => {
|
|||||||
let closeOrderReferenceSpy;
|
let closeOrderReferenceSpy;
|
||||||
|
|
||||||
let paymentBuyGemsStub;
|
let paymentBuyGemsStub;
|
||||||
let paymentCreateSubscriptionStub;
|
let paymentCreateSubscritionStub;
|
||||||
let amount = gemsBlock.price / 100;
|
let amount = gemsBlock.price / 100;
|
||||||
|
|
||||||
function expectOrderReferenceSpy () {
|
function expectOrderReferenceSpy () {
|
||||||
@@ -85,8 +85,8 @@ describe('Amazon Payments - Checkout', () => {
|
|||||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems');
|
paymentBuyGemsStub = sinon.stub(payments, 'buyGems');
|
||||||
paymentBuyGemsStub.resolves({});
|
paymentBuyGemsStub.resolves({});
|
||||||
|
|
||||||
paymentCreateSubscriptionStub = sinon.stub(payments, 'createSubscription');
|
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription');
|
||||||
paymentCreateSubscriptionStub.resolves({});
|
paymentCreateSubscritionStub.resolves({});
|
||||||
|
|
||||||
sinon.stub(common, 'uuid').returns('uuid-generated');
|
sinon.stub(common, 'uuid').returns('uuid-generated');
|
||||||
sandbox.stub(gems, 'validateGiftMessage');
|
sandbox.stub(gems, 'validateGiftMessage');
|
||||||
@@ -109,7 +109,6 @@ describe('Amazon Payments - Checkout', () => {
|
|||||||
user,
|
user,
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
headers,
|
headers,
|
||||||
sku: undefined,
|
|
||||||
};
|
};
|
||||||
if (gift) {
|
if (gift) {
|
||||||
expectedArgs.gift = gift;
|
expectedArgs.gift = gift;
|
||||||
@@ -216,14 +215,13 @@ describe('Amazon Payments - Checkout', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gift.member = receivingUser;
|
gift.member = receivingUser;
|
||||||
expect(paymentCreateSubscriptionStub).to.be.calledOnce;
|
expect(paymentCreateSubscritionStub).to.be.calledOnce;
|
||||||
expect(paymentCreateSubscriptionStub).to.be.calledWith({
|
expect(paymentCreateSubscritionStub).to.be.calledWith({
|
||||||
user,
|
user,
|
||||||
paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT,
|
paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT,
|
||||||
headers,
|
headers,
|
||||||
gift,
|
gift,
|
||||||
gemsBlock: undefined,
|
gemsBlock: undefined,
|
||||||
sku: undefined,
|
|
||||||
});
|
});
|
||||||
expectAmazonStubs();
|
expectAmazonStubs();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ const { i18n } = common;
|
|||||||
describe('Apple Payments', () => {
|
describe('Apple Payments', () => {
|
||||||
const subKey = 'basic_3mo';
|
const subKey = 'basic_3mo';
|
||||||
|
|
||||||
describe('verifyPurchase', () => {
|
describe('verifyGemPurchase', () => {
|
||||||
let sku; let user; let token; let receipt; let
|
let sku; let user; let token; let receipt; let
|
||||||
headers;
|
headers;
|
||||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuySkuStub; let
|
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuyGemsStub; let
|
||||||
iapGetPurchaseDataStub; let validateGiftMessageStub;
|
iapGetPurchaseDataStub; let validateGiftMessageStub;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -29,15 +29,14 @@ describe('Apple Payments', () => {
|
|||||||
.resolves();
|
.resolves();
|
||||||
iapValidateStub = sinon.stub(iap, 'validate')
|
iapValidateStub = sinon.stub(iap, 'validate')
|
||||||
.resolves({});
|
.resolves({});
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||||
sinon.stub(iap, 'isExpired').returns(false);
|
.returns(true);
|
||||||
sinon.stub(iap, 'isCanceled').returns(false);
|
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{
|
.returns([{
|
||||||
productId: 'com.habitrpg.ios.Habitica.21gems',
|
productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||||
transactionId: token,
|
transactionId: token,
|
||||||
}]);
|
}]);
|
||||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,10 +44,8 @@ describe('Apple Payments', () => {
|
|||||||
iap.setup.restore();
|
iap.setup.restore();
|
||||||
iap.validate.restore();
|
iap.validate.restore();
|
||||||
iap.isValidated.restore();
|
iap.isValidated.restore();
|
||||||
iap.isExpired.restore();
|
|
||||||
iap.isCanceled.restore();
|
|
||||||
iap.getPurchaseData.restore();
|
iap.getPurchaseData.restore();
|
||||||
payments.buySkuItem.restore();
|
payments.buyGems.restore();
|
||||||
gems.validateGiftMessage.restore();
|
gems.validateGiftMessage.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ describe('Apple Payments', () => {
|
|||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||||
.returns(false);
|
.returns(false);
|
||||||
|
|
||||||
await expect(applePayments.verifyPurchase({ user, receipt, headers }))
|
await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 401,
|
||||||
name: 'NotAuthorized',
|
name: 'NotAuthorized',
|
||||||
@@ -69,7 +66,7 @@ describe('Apple Payments', () => {
|
|||||||
iapGetPurchaseDataStub.restore();
|
iapGetPurchaseDataStub.restore();
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData').returns([]);
|
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({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 401,
|
||||||
name: 'NotAuthorized',
|
name: 'NotAuthorized',
|
||||||
@@ -79,7 +76,7 @@ describe('Apple Payments', () => {
|
|||||||
|
|
||||||
it('errors if the user cannot purchase gems', async () => {
|
it('errors if the user cannot purchase gems', async () => {
|
||||||
sinon.stub(user, 'canGetGems').resolves(false);
|
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({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 401,
|
||||||
name: 'NotAuthorized',
|
name: 'NotAuthorized',
|
||||||
@@ -97,16 +94,14 @@ describe('Apple Payments', () => {
|
|||||||
productId: 'badProduct',
|
productId: 'badProduct',
|
||||||
transactionId: token,
|
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({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 400,
|
httpCode: 401,
|
||||||
name: 'BadRequest',
|
name: 'NotAuthorized',
|
||||||
message: applePayments.constants.RESPONSE_INVALID_ITEM,
|
message: applePayments.constants.RESPONSE_INVALID_ITEM,
|
||||||
});
|
});
|
||||||
|
|
||||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
|
||||||
user.canGetGems.restore();
|
user.canGetGems.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,7 +138,7 @@ describe('Apple Payments', () => {
|
|||||||
}]);
|
}]);
|
||||||
|
|
||||||
sinon.stub(user, 'canGetGems').resolves(true);
|
sinon.stub(user, 'canGetGems').resolves(true);
|
||||||
await applePayments.verifyPurchase({ user, receipt, headers });
|
await applePayments.verifyGemPurchase({ user, receipt, headers });
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
expect(iapValidateStub).to.be.calledOnce;
|
expect(iapValidateStub).to.be.calledOnce;
|
||||||
@@ -153,13 +148,13 @@ describe('Apple Payments', () => {
|
|||||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||||
expect(validateGiftMessageStub).to.not.be.called;
|
expect(validateGiftMessageStub).to.not.be.called;
|
||||||
|
|
||||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||||
expect(paymentBuySkuStub).to.be.calledWith({
|
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||||
user,
|
user,
|
||||||
gift: undefined,
|
|
||||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||||
sku: gemTest.productId,
|
gemsBlock: common.content.gems[gemTest.gemsBlock],
|
||||||
headers,
|
headers,
|
||||||
|
gift: undefined,
|
||||||
});
|
});
|
||||||
expect(user.canGetGems).to.be.calledOnce;
|
expect(user.canGetGems).to.be.calledOnce;
|
||||||
user.canGetGems.restore();
|
user.canGetGems.restore();
|
||||||
@@ -178,7 +173,7 @@ describe('Apple Payments', () => {
|
|||||||
}]);
|
}]);
|
||||||
|
|
||||||
const gift = { uuid: receivingUser._id };
|
const gift = { uuid: receivingUser._id };
|
||||||
await applePayments.verifyPurchase({
|
await applePayments.verifyGemPurchase({
|
||||||
user, gift, receipt, headers,
|
user, gift, receipt, headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -192,16 +187,18 @@ describe('Apple Payments', () => {
|
|||||||
expect(validateGiftMessageStub).to.be.calledOnce;
|
expect(validateGiftMessageStub).to.be.calledOnce;
|
||||||
expect(validateGiftMessageStub).to.be.calledWith(gift, user);
|
expect(validateGiftMessageStub).to.be.calledWith(gift, user);
|
||||||
|
|
||||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||||
expect(paymentBuySkuStub).to.be.calledWith({
|
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||||
user,
|
user,
|
||||||
gift: {
|
|
||||||
uuid: receivingUser._id,
|
|
||||||
member: sinon.match({ _id: receivingUser._id }),
|
|
||||||
},
|
|
||||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||||
sku: 'com.habitrpg.ios.Habitica.4gems',
|
|
||||||
headers,
|
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 = {};
|
headers = {};
|
||||||
receipt = `{"token": "${token}"}`;
|
receipt = `{"token": "${token}"}`;
|
||||||
nextPaymentProcessing = moment.utc().add({ days: 2 });
|
nextPaymentProcessing = moment.utc().add({ days: 2 });
|
||||||
user = new User();
|
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iap, 'setup')
|
iapSetupStub = sinon.stub(iap, 'setup')
|
||||||
.resolves();
|
.resolves();
|
||||||
@@ -232,17 +228,14 @@ describe('Apple Payments', () => {
|
|||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{
|
.returns([{
|
||||||
expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
|
expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
|
||||||
purchaseDate: moment.utc().valueOf(),
|
|
||||||
productId: sku,
|
productId: sku,
|
||||||
transactionId: token,
|
transactionId: token,
|
||||||
}, {
|
}, {
|
||||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||||
purchaseDate: moment.utc().valueOf(),
|
|
||||||
productId: 'wrongsku',
|
productId: 'wrongsku',
|
||||||
transactionId: token,
|
transactionId: token,
|
||||||
}, {
|
}, {
|
||||||
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||||
purchaseDate: moment.utc().valueOf(),
|
|
||||||
productId: sku,
|
productId: sku,
|
||||||
transactionId: token,
|
transactionId: token,
|
||||||
}]);
|
}]);
|
||||||
@@ -257,12 +250,21 @@ describe('Apple Payments', () => {
|
|||||||
if (payments.createSubscription.restore) payments.createSubscription.restore();
|
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 () => {
|
it('should throw an error if receipt is invalid', async () => {
|
||||||
iap.isValidated.restore();
|
iap.isValidated.restore();
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||||
.returns(false);
|
.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({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 401,
|
||||||
name: 'NotAuthorized',
|
name: 'NotAuthorized',
|
||||||
@@ -293,15 +295,13 @@ describe('Apple Payments', () => {
|
|||||||
iap.getPurchaseData.restore();
|
iap.getPurchaseData.restore();
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{
|
.returns([{
|
||||||
expirationDate: moment.utc().add({ day: 2 }).toDate(),
|
expirationDate: moment.utc().add({ day: 1 }).toDate(),
|
||||||
purchaseDate: new Date(),
|
|
||||||
productId: option.sku,
|
productId: option.sku,
|
||||||
transactionId: token,
|
transactionId: token,
|
||||||
originalTransactionId: token,
|
|
||||||
}]);
|
}]);
|
||||||
sub = common.content.subscriptionBlocks[option.subKey];
|
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(iapSetupStub).to.be.calledOnce;
|
||||||
expect(iapValidateStub).to.be.calledOnce;
|
expect(iapValidateStub).to.be.calledOnce;
|
||||||
@@ -321,253 +321,21 @@ describe('Apple Payments', () => {
|
|||||||
nextPaymentProcessing,
|
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 () => {
|
it('errors when a user is already subscribed', async () => {
|
||||||
iap.getPurchaseData.restore();
|
payments.createSubscription.restore();
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
user = new User();
|
||||||
.returns([{
|
await user.save();
|
||||||
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;
|
|
||||||
|
|
||||||
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
|
await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
|
||||||
|
|
||||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
user,
|
httpCode: 401,
|
||||||
customerId: `${token}newest`,
|
name: 'NotAuthorized',
|
||||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||||
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,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -592,9 +360,9 @@ describe('Apple Payments', () => {
|
|||||||
});
|
});
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{ expirationDate: expirationDate.toDate() }]);
|
.returns([{ expirationDate: expirationDate.toDate() }]);
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||||
sinon.stub(iap, 'isCanceled').returns(false);
|
.returns(true);
|
||||||
sinon.stub(iap, 'isExpired').returns(true);
|
|
||||||
user = new User();
|
user = new User();
|
||||||
user.profile.name = 'sender';
|
user.profile.name = 'sender';
|
||||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||||
@@ -609,8 +377,6 @@ describe('Apple Payments', () => {
|
|||||||
iap.setup.restore();
|
iap.setup.restore();
|
||||||
iap.validate.restore();
|
iap.validate.restore();
|
||||||
iap.isValidated.restore();
|
iap.isValidated.restore();
|
||||||
iap.isExpired.restore();
|
|
||||||
iap.isCanceled.restore();
|
|
||||||
iap.getPurchaseData.restore();
|
iap.getPurchaseData.restore();
|
||||||
payments.cancelSubscription.restore();
|
payments.cancelSubscription.restore();
|
||||||
});
|
});
|
||||||
@@ -630,8 +396,6 @@ describe('Apple Payments', () => {
|
|||||||
iap.getPurchaseData.restore();
|
iap.getPurchaseData.restore();
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]);
|
.returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]);
|
||||||
iap.isExpired.restore();
|
|
||||||
sinon.stub(iap, 'isExpired').returns(false);
|
|
||||||
|
|
||||||
await expect(applePayments.cancelSubscribe(user, headers))
|
await expect(applePayments.cancelSubscribe(user, headers))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.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 () => {
|
it('should cancel a user subscription', 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 () => {
|
|
||||||
await applePayments.cancelSubscribe(user, headers);
|
await applePayments.cancelSubscribe(user, headers);
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ const { i18n } = common;
|
|||||||
describe('Google Payments', () => {
|
describe('Google Payments', () => {
|
||||||
const subKey = 'basic_3mo';
|
const subKey = 'basic_3mo';
|
||||||
|
|
||||||
describe('verifyPurchase', () => {
|
describe('verifyGemPurchase', () => {
|
||||||
let sku; let user; let token; let receipt; let signature; let
|
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
|
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
|
||||||
paymentBuySkuStub; let validateGiftMessageStub;
|
paymentBuyGemsStub; let validateGiftMessageStub;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sku = 'com.habitrpg.android.habitica.iap.21gems';
|
sku = 'com.habitrpg.android.habitica.iap.21gems';
|
||||||
@@ -27,10 +27,11 @@ describe('Google Payments', () => {
|
|||||||
|
|
||||||
iapSetupStub = sinon.stub(iap, 'setup')
|
iapSetupStub = sinon.stub(iap, 'setup')
|
||||||
.resolves();
|
.resolves();
|
||||||
iapValidateStub = sinon.stub(iap, 'validate').resolves({ productId: sku });
|
iapValidateStub = sinon.stub(iap, 'validate')
|
||||||
|
.resolves({});
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||||
.returns(true);
|
.returns(true);
|
||||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ describe('Google Payments', () => {
|
|||||||
iap.setup.restore();
|
iap.setup.restore();
|
||||||
iap.validate.restore();
|
iap.validate.restore();
|
||||||
iap.isValidated.restore();
|
iap.isValidated.restore();
|
||||||
payments.buySkuItem.restore();
|
payments.buyGems.restore();
|
||||||
gems.validateGiftMessage.restore();
|
gems.validateGiftMessage.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ describe('Google Payments', () => {
|
|||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||||
.returns(false);
|
.returns(false);
|
||||||
|
|
||||||
await expect(googlePayments.verifyPurchase({
|
await expect(googlePayments.verifyGemPurchase({
|
||||||
user, receipt, signature, headers,
|
user, receipt, signature, headers,
|
||||||
}))
|
}))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
@@ -59,25 +60,21 @@ describe('Google Payments', () => {
|
|||||||
|
|
||||||
it('should throw an error if productId is invalid', async () => {
|
it('should throw an error if productId is invalid', async () => {
|
||||||
receipt = `{"token": "${token}", "productId": "invalid"}`;
|
receipt = `{"token": "${token}", "productId": "invalid"}`;
|
||||||
iapValidateStub.restore();
|
|
||||||
iapValidateStub = sinon.stub(iap, 'validate').resolves({});
|
|
||||||
|
|
||||||
paymentBuySkuStub.restore();
|
await expect(googlePayments.verifyGemPurchase({
|
||||||
await expect(googlePayments.verifyPurchase({
|
|
||||||
user, receipt, signature, headers,
|
user, receipt, signature, headers,
|
||||||
}))
|
}))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 400,
|
httpCode: 401,
|
||||||
name: 'BadRequest',
|
name: 'NotAuthorized',
|
||||||
message: googlePayments.constants.RESPONSE_INVALID_ITEM,
|
message: googlePayments.constants.RESPONSE_INVALID_ITEM,
|
||||||
});
|
});
|
||||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if user cannot purchase gems', async () => {
|
it('should throw an error if user cannot purchase gems', async () => {
|
||||||
sinon.stub(user, 'canGetGems').resolves(false);
|
sinon.stub(user, 'canGetGems').resolves(false);
|
||||||
|
|
||||||
await expect(googlePayments.verifyPurchase({
|
await expect(googlePayments.verifyGemPurchase({
|
||||||
user, receipt, signature, headers,
|
user, receipt, signature, headers,
|
||||||
}))
|
}))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
@@ -91,7 +88,7 @@ describe('Google Payments', () => {
|
|||||||
|
|
||||||
it('purchases gems', async () => {
|
it('purchases gems', async () => {
|
||||||
sinon.stub(user, 'canGetGems').resolves(true);
|
sinon.stub(user, 'canGetGems').resolves(true);
|
||||||
await googlePayments.verifyPurchase({
|
await googlePayments.verifyGemPurchase({
|
||||||
user, receipt, signature, headers,
|
user, receipt, signature, headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,17 +101,15 @@ describe('Google Payments', () => {
|
|||||||
signature,
|
signature,
|
||||||
});
|
});
|
||||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||||
expect(iapIsValidatedStub).to.be.calledWith(
|
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||||
{ productId: sku },
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||||
expect(paymentBuySkuStub).to.be.calledWith({
|
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||||
user,
|
user,
|
||||||
gift: undefined,
|
|
||||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||||
sku,
|
gemsBlock,
|
||||||
headers,
|
headers,
|
||||||
|
gift: undefined,
|
||||||
});
|
});
|
||||||
expect(user.canGetGems).to.be.calledOnce;
|
expect(user.canGetGems).to.be.calledOnce;
|
||||||
user.canGetGems.restore();
|
user.canGetGems.restore();
|
||||||
@@ -125,7 +120,7 @@ describe('Google Payments', () => {
|
|||||||
await receivingUser.save();
|
await receivingUser.save();
|
||||||
|
|
||||||
const gift = { uuid: receivingUser._id };
|
const gift = { uuid: receivingUser._id };
|
||||||
await googlePayments.verifyPurchase({
|
await googlePayments.verifyGemPurchase({
|
||||||
user, gift, receipt, signature, headers,
|
user, gift, receipt, signature, headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -139,20 +134,20 @@ describe('Google Payments', () => {
|
|||||||
signature,
|
signature,
|
||||||
});
|
});
|
||||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||||
expect(iapIsValidatedStub).to.be.calledWith(
|
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||||
{ productId: sku },
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(paymentBuySkuStub).to.be.calledOnce;
|
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||||
expect(paymentBuySkuStub).to.be.calledWith({
|
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||||
user,
|
user,
|
||||||
gift: {
|
|
||||||
uuid: receivingUser._id,
|
|
||||||
member: sinon.match({ _id: receivingUser._id }),
|
|
||||||
},
|
|
||||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||||
sku,
|
gemsBlock,
|
||||||
headers,
|
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;
|
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 () => {
|
it('does not change plan.customerId if it already exists', async () => {
|
||||||
recipient.purchased.plan = plan;
|
recipient.purchased.plan = plan;
|
||||||
data.customerId = 'purchaserCustomerId';
|
data.customerId = 'purchaserCustomerId';
|
||||||
@@ -235,116 +213,6 @@ describe('payments/index', () => {
|
|||||||
expect(recipient.purchased.plan.customerId).to.eql('customer-id');
|
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 () => {
|
it('sets plan.customerId to "Gift" if it does not already exist', async () => {
|
||||||
expect(recipient.purchased.plan.customerId).to.not.exist;
|
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.customerId).to.eql('customer-id');
|
||||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||||
expect(user.purchased.plan.gemsBought).to.eql(0);
|
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.paymentMethod).to.eql('Payment Method');
|
||||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||||
expect(user.purchased.plan.dateTerminated).to.eql(null);
|
expect(user.purchased.plan.dateTerminated).to.eql(null);
|
||||||
@@ -519,63 +386,6 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.dateCreated).to.exist;
|
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 () => {
|
it('awards the Royal Purple Jackalope pet', async () => {
|
||||||
await api.createSubscription(data);
|
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', () => {
|
context('Block subscription perks', () => {
|
||||||
@@ -748,19 +475,9 @@ describe('payments/index', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
|
it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
expect(user.purchased.plan.extraMonths).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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => {
|
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 () => {
|
it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => {
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
|
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 () => {
|
it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => {
|
||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
|
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);
|
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', () => {
|
context('Mystery Items', () => {
|
||||||
@@ -1382,6 +575,18 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(1);
|
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(1);
|
||||||
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
|
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not award mystery item when user already has the item in the mystery box', async () => {
|
||||||
|
user.purchased.plan.mysteryItems = [mayMysteryItem];
|
||||||
|
|
||||||
|
sandbox.spy(user.purchased.plan.mysteryItems, 'push');
|
||||||
|
|
||||||
|
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(user.purchased.plan.mysteryItems.push).to.be.calledOnce;
|
||||||
|
expect(user.purchased.plan.mysteryItems.push).to.be.calledWith('head_mystery_201605');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1587,10 +792,10 @@ describe('payments/index', () => {
|
|||||||
it('sends gem donation message in each participant\'s language', async () => {
|
it('sends gem donation message in each participant\'s language', async () => {
|
||||||
// TODO using english for both users because other languages are not loaded
|
// TODO using english for both users because other languages are not loaded
|
||||||
// for api.buyGems
|
// for api.buyGems
|
||||||
await recipient.updateOne({
|
await recipient.update({
|
||||||
'preferences.language': 'en',
|
'preferences.language': 'en',
|
||||||
});
|
});
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'preferences.language': 'en',
|
'preferences.language': 'en',
|
||||||
});
|
});
|
||||||
await api.buyGems(data);
|
await api.buyGems(data);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -53,9 +53,11 @@ describe('cron middleware', () => {
|
|||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
|
|
||||||
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
|
||||||
expect(foundTask).to.not.exist;
|
if (secondErr) return reject(err);
|
||||||
resolve();
|
expect(secondErr).to.not.exist;
|
||||||
|
expect(taskFound).to.not.exist;
|
||||||
|
return resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -76,8 +78,10 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
|
||||||
expect(foundTask).to.exist;
|
if (secondErr) return reject(secondErr);
|
||||||
|
expect(secondErr).to.not.exist;
|
||||||
|
expect(taskFound).to.exist;
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
@@ -99,8 +103,10 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
|
||||||
expect(foundTask).to.not.exist;
|
if (secondErr) return reject(secondErr);
|
||||||
|
expect(secondErr).to.not.exist;
|
||||||
|
expect(taskFound).to.not.exist;
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
@@ -164,7 +170,8 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
return User.findOne({ _id: user._id }).then(updatedUser => {
|
return User.findOne({ _id: user._id }, (secondErr, updatedUser) => {
|
||||||
|
if (secondErr) return reject(secondErr);
|
||||||
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
@@ -181,7 +188,8 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
return Tasks.Task.findOne({ _id: todo._id }).then(todoFound => {
|
return Tasks.Task.findOne({ _id: todo._id }, (secondErr, todoFound) => {
|
||||||
|
if (secondErr) return reject(secondErr);
|
||||||
expect(todoFound.value).to.be.lessThan(todoValueBefore);
|
expect(todoFound.value).to.be.lessThan(todoValueBefore);
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
@@ -216,7 +224,8 @@ describe('cron middleware', () => {
|
|||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
cronMiddleware(req, res, err => {
|
cronMiddleware(req, res, err => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
return User.findOne({ _id: user._id }).then(updatedUser => {
|
return User.findOne({ _id: user._id }, (secondErr, updatedUser) => {
|
||||||
|
if (secondErr) return reject(secondErr);
|
||||||
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
@@ -229,11 +238,11 @@ describe('cron middleware', () => {
|
|||||||
await user.save();
|
await user.save();
|
||||||
|
|
||||||
const updatedUser = user.toObject();
|
const updatedUser = user.toObject();
|
||||||
updatedUser.matchedCount = 0;
|
updatedUser.nMatched = 0;
|
||||||
|
|
||||||
sandbox.spy(cronLib, 'recoverCron');
|
sandbox.spy(cronLib, 'recoverCron');
|
||||||
|
|
||||||
sandbox.stub(User, 'updateOne')
|
sandbox.stub(User, 'update')
|
||||||
.withArgs({
|
.withArgs({
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
$or: [
|
$or: [
|
||||||
@@ -260,7 +269,7 @@ describe('cron middleware', () => {
|
|||||||
it('cronSignature less than an hour ago should error', async () => {
|
it('cronSignature less than an hour ago should error', async () => {
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
await User.updateOne({
|
await User.update({
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
}, {
|
}, {
|
||||||
$set: {
|
$set: {
|
||||||
@@ -282,7 +291,7 @@ describe('cron middleware', () => {
|
|||||||
it('cronSignature longer than an hour ago should allow cron', async () => {
|
it('cronSignature longer than an hour ago should allow cron', async () => {
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
await User.updateOne({
|
await User.update({
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
}, {
|
}, {
|
||||||
$set: {
|
$set: {
|
||||||
|
|||||||
@@ -1358,8 +1358,7 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
describe('#sendChat', () => {
|
describe('#sendChat', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox.spy(User, 'updateOne');
|
sandbox.spy(User, 'update');
|
||||||
sandbox.spy(User, 'updateMany');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats message', () => {
|
it('formats message', () => {
|
||||||
@@ -1414,8 +1413,8 @@ describe('Group Model', () => {
|
|||||||
it('updates users about new messages in party', () => {
|
it('updates users about new messages in party', () => {
|
||||||
party.sendChat({ message: 'message' });
|
party.sendChat({ message: 'message' });
|
||||||
|
|
||||||
expect(User.updateMany).to.be.calledOnce;
|
expect(User.update).to.be.calledOnce;
|
||||||
expect(User.updateMany).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
'party._id': party._id,
|
'party._id': party._id,
|
||||||
_id: { $ne: '' },
|
_id: { $ne: '' },
|
||||||
});
|
});
|
||||||
@@ -1428,8 +1427,8 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
group.sendChat({ message: 'message' });
|
group.sendChat({ message: 'message' });
|
||||||
|
|
||||||
expect(User.updateMany).to.be.calledOnce;
|
expect(User.update).to.be.calledOnce;
|
||||||
expect(User.updateMany).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
guilds: group._id,
|
guilds: group._id,
|
||||||
_id: { $ne: '' },
|
_id: { $ne: '' },
|
||||||
});
|
});
|
||||||
@@ -1438,8 +1437,8 @@ describe('Group Model', () => {
|
|||||||
it('does not send update to user that sent the message', () => {
|
it('does not send update to user that sent the message', () => {
|
||||||
party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
|
party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
|
||||||
|
|
||||||
expect(User.updateMany).to.be.calledOnce;
|
expect(User.update).to.be.calledOnce;
|
||||||
expect(User.updateMany).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
'party._id': party._id,
|
'party._id': party._id,
|
||||||
_id: { $ne: 'user-id' },
|
_id: { $ne: 'user-id' },
|
||||||
});
|
});
|
||||||
@@ -1450,7 +1449,7 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
party.sendChat({ message: 'message' });
|
party.sendChat({ message: 'message' });
|
||||||
|
|
||||||
expect(User.updateMany).to.not.be.called;
|
expect(User.update).to.not.be.called;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips sending messages to the tavern', () => {
|
it('skips sending messages to the tavern', () => {
|
||||||
@@ -1458,7 +1457,7 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
party.sendChat({ message: 'message' });
|
party.sendChat({ message: 'message' });
|
||||||
|
|
||||||
expect(User.updateMany).to.not.be.called;
|
expect(User.update).to.not.be.called;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1732,7 +1731,7 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates participting members (not including user)', async () => {
|
it('updates participting members (not including user)', async () => {
|
||||||
sandbox.spy(User, 'updateMany');
|
sandbox.spy(User, 'update');
|
||||||
|
|
||||||
await party.startQuest(nonParticipatingMember);
|
await party.startQuest(nonParticipatingMember);
|
||||||
|
|
||||||
@@ -1740,7 +1739,7 @@ describe('Group Model', () => {
|
|||||||
questLeader._id, participatingMember._id, sleepingParticipatingMember._id,
|
questLeader._id, participatingMember._id, sleepingParticipatingMember._id,
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(User.updateMany).to.be.calledWith(
|
expect(User.update).to.be.calledWith(
|
||||||
{ _id: { $in: members } },
|
{ _id: { $in: members } },
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
@@ -1753,11 +1752,11 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates non-user quest leader and decrements quest scroll', async () => {
|
it('updates non-user quest leader and decrements quest scroll', async () => {
|
||||||
sandbox.spy(User, 'updateOne');
|
sandbox.spy(User, 'update');
|
||||||
|
|
||||||
await party.startQuest(participatingMember);
|
await party.startQuest(participatingMember);
|
||||||
|
|
||||||
expect(User.updateOne).to.be.calledWith(
|
expect(User.update).to.be.calledWith(
|
||||||
{ _id: questLeader._id },
|
{ _id: questLeader._id },
|
||||||
{
|
{
|
||||||
$inc: {
|
$inc: {
|
||||||
@@ -1819,29 +1818,29 @@ describe('Group Model', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it('doesn\'t retry successful operations', async () => {
|
it('doesn\'t retry successful operations', async () => {
|
||||||
sandbox.stub(User, 'updateOne').returns(successfulMock);
|
sandbox.stub(User, 'update').returns(successfulMock);
|
||||||
|
|
||||||
await party.finishQuest(quest);
|
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 () => {
|
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.onCall(0).returns(failedMock);
|
||||||
updateStub.returns(successfulMock);
|
updateStub.returns(successfulMock);
|
||||||
|
|
||||||
await party.finishQuest(quest);
|
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 () => {
|
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;
|
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', () => {
|
context('Party quests', () => {
|
||||||
it('updates participating members with rewards', async () => {
|
it('updates participating members with rewards', async () => {
|
||||||
sandbox.spy(User, 'updateOne');
|
sandbox.spy(User, 'update');
|
||||||
await party.finishQuest(quest);
|
await party.finishQuest(quest);
|
||||||
|
|
||||||
expect(User.updateOne).to.be.calledThrice;
|
expect(User.update).to.be.calledThrice;
|
||||||
expect(User.updateOne).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
_id: questLeader._id,
|
_id: questLeader._id,
|
||||||
});
|
});
|
||||||
expect(User.updateOne).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
_id: participatingMember._id,
|
_id: participatingMember._id,
|
||||||
});
|
});
|
||||||
expect(User.updateOne).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
_id: sleepingParticipatingMember._id,
|
_id: sleepingParticipatingMember._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2173,11 +2172,11 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates all users with rewards', async () => {
|
it('updates all users with rewards', async () => {
|
||||||
sandbox.spy(User, 'updateMany');
|
sandbox.spy(User, 'update');
|
||||||
await party.finishQuest(tavernQuest);
|
await party.finishQuest(tavernQuest);
|
||||||
|
|
||||||
expect(User.updateMany).to.be.calledOnce;
|
expect(User.update).to.be.calledOnce;
|
||||||
expect(User.updateMany).to.be.calledWithMatch({});
|
expect(User.update).to.be.calledWithMatch({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets quest completed to the world quest key', async () => {
|
it('sets quest completed to the world quest key', async () => {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ describe('NewsPost Model', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Delete all existing posts from the database
|
// Delete all existing posts from the database
|
||||||
await NewsPost.deleteMany();
|
await NewsPost.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -116,7 +116,7 @@ describe('NewsPost Model', () => {
|
|||||||
_id: v4(), publishDate: new Date(), published: true,
|
_id: v4(), publishDate: new Date(), published: true,
|
||||||
};
|
};
|
||||||
NewsPost.updateLastNewsPost(previousPost);
|
NewsPost.updateLastNewsPost(previousPost);
|
||||||
intervalId = refreshNewsPost(100); // refreshes every 100ms
|
intervalId = refreshNewsPost(50); // refreshes every 50ms
|
||||||
|
|
||||||
await sleep(0.1); // wait 100ms to make sure the new post has a more recent publishDate
|
await sleep(0.1); // wait 100ms to make sure the new post has a more recent publishDate
|
||||||
const newPost = await NewsPost.create({
|
const newPost = await NewsPost.create({
|
||||||
|
|||||||
@@ -221,8 +221,7 @@ describe('Task Model', () => {
|
|||||||
|
|
||||||
it('returns task by alias', async () => {
|
it('returns task by alias', async () => {
|
||||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||||
[taskWithAlias.alias],
|
[taskWithAlias.alias], user._id,
|
||||||
user._id,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
|
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
|
||||||
@@ -230,8 +229,7 @@ describe('Task Model', () => {
|
|||||||
|
|
||||||
it('returns multiple tasks', async () => {
|
it('returns multiple tasks', async () => {
|
||||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||||
[taskWithAlias.alias, secondTask._id],
|
[taskWithAlias.alias, secondTask._id], user._id,
|
||||||
user._id,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(foundTasks.length).to.eql(2);
|
expect(foundTasks.length).to.eql(2);
|
||||||
@@ -241,8 +239,7 @@ describe('Task Model', () => {
|
|||||||
|
|
||||||
it('returns a task only once if searched by both id and alias', async () => {
|
it('returns a task only once if searched by both id and alias', async () => {
|
||||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||||
[taskWithAlias.alias, taskWithAlias._id],
|
[taskWithAlias.alias, taskWithAlias._id], user._id,
|
||||||
user._id,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(foundTasks.length).to.eql(1);
|
expect(foundTasks.length).to.eql(1);
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ describe('User Model', () => {
|
|||||||
it('removes invalid tags when loading the user', async () => {
|
it('removes invalid tags when loading the user', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
$set: {
|
$set: {
|
||||||
tags: [
|
tags: [
|
||||||
null, // invalid, not an object
|
null, // invalid, not an object
|
||||||
@@ -212,7 +212,7 @@ describe('User Model', () => {
|
|||||||
it('removes invalid push devices when loading the user', async () => {
|
it('removes invalid push devices when loading the user', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
$set: {
|
$set: {
|
||||||
pushDevices: [
|
pushDevices: [
|
||||||
null, // invalid, not an object
|
null, // invalid, not an object
|
||||||
@@ -236,7 +236,7 @@ describe('User Model', () => {
|
|||||||
it('removes duplicate push devices when loading the user', async () => {
|
it('removes duplicate push devices when loading the user', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
$set: {
|
$set: {
|
||||||
pushDevices: [
|
pushDevices: [
|
||||||
{ type: 'android', regId: '1234' },
|
{ type: 'android', regId: '1234' },
|
||||||
@@ -258,7 +258,7 @@ describe('User Model', () => {
|
|||||||
it('removes invalid notifications when loading the user', async () => {
|
it('removes invalid notifications when loading the user', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
$set: {
|
$set: {
|
||||||
notifications: [
|
notifications: [
|
||||||
null, // invalid, not an object
|
null, // invalid, not an object
|
||||||
@@ -284,7 +284,7 @@ describe('User Model', () => {
|
|||||||
it('removes multiple NEW_CHAT_MESSAGE for the same group', async () => {
|
it('removes multiple NEW_CHAT_MESSAGE for the same group', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
await user.save();
|
await user.save();
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
$set: {
|
$set: {
|
||||||
notifications: [
|
notifications: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ describe('DELETE /challenges/:challengeId', () => {
|
|||||||
const testTask = _.find(tasks, task => task.text === taskText);
|
const testTask = _.find(tasks, task => task.text === taskText);
|
||||||
|
|
||||||
expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED');
|
expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED');
|
||||||
expect(testTask.challenge.winner).to.be.undefined;
|
expect(testTask.challenge.winner).to.be.null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 groupLeader;
|
||||||
let challengeLeader;
|
let challengeLeader;
|
||||||
let group;
|
let group;
|
||||||
@@ -31,14 +84,14 @@ describe('GET /challenges/:challengeId', () => {
|
|||||||
const populatedGroup = await createAndPopulateGroup({
|
const populatedGroup = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
members: 2,
|
members: 2,
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
groupLeader = populatedGroup.groupLeader;
|
groupLeader = populatedGroup.groupLeader;
|
||||||
group = populatedGroup.group;
|
group = populatedGroup.group;
|
||||||
members = populatedGroup.members;
|
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);
|
challenge = await generateChallenge(challengeLeader, group);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should successfully return when it contains erroneous residue user data', async () => {
|
it('should successfully return when it contains erroneous residue user data', async () => {
|
||||||
await members[0].updateOne({ challenges: [] });
|
await members[0].update({ challenges: [] });
|
||||||
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
|
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
|
||||||
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
|
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
|
||||||
const splitRes = res.split('\n');
|
const splitRes = res.split('\n');
|
||||||
|
|||||||
@@ -71,18 +71,42 @@ describe('GET /challenges/:challengeId/members', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('populates only some fields', async () => {
|
it('works with challenges belonging to public guild', async () => {
|
||||||
const group = await generateGroup(user, { type: 'party', privacy: 'private', name: generateUUID() });
|
const leader = await generateUser({ balance: 4 });
|
||||||
const challenge = await generateChallenge(user, group);
|
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||||
await user.post(`/challenges/${challenge._id}/join`);
|
const challenge = await generateChallenge(leader, group);
|
||||||
|
await leader.post(`/challenges/${challenge._id}/join`);
|
||||||
const res = await user.get(`/challenges/${challenge._id}/members`);
|
const res = await user.get(`/challenges/${challenge._id}/members`);
|
||||||
expect(res[0]).to.eql({
|
expect(res[0]).to.eql({
|
||||||
_id: user._id,
|
_id: leader._id,
|
||||||
id: user._id,
|
id: leader._id,
|
||||||
profile: { name: user.profile.name },
|
profile: { name: leader.profile.name },
|
||||||
auth: {
|
auth: {
|
||||||
local: {
|
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: {
|
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 () => {
|
it('returns the member tasks for the challenges', async () => {
|
||||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||||
const challenge = await generateChallenge(user, group);
|
const challenge = await generateChallenge(user, group);
|
||||||
|
|||||||
@@ -7,7 +7,117 @@ import {
|
|||||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||||
|
|
||||||
describe('GET challenges/groups/:groupId', () => {
|
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
|
let privateGuild; let user; let nonMember; let challenge; let
|
||||||
challenge2;
|
challenge2;
|
||||||
|
|
||||||
@@ -18,7 +128,6 @@ describe('GET challenges/groups/:groupId', () => {
|
|||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
privateGuild = group;
|
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', () => {
|
context('Party', () => {
|
||||||
let party; let user; let nonMember; let challenge; let
|
let party; let user; let nonMember; let challenge; let
|
||||||
challenge2;
|
challenge2;
|
||||||
@@ -186,7 +357,7 @@ describe('GET challenges/groups/:groupId', () => {
|
|||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
await user.updateOne({ balance: 0.5 });
|
await user.update({ balance: 0.5 });
|
||||||
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
||||||
|
|
||||||
challenge = await generateChallenge(user, tavern, { prize: 1 });
|
challenge = await generateChallenge(user, tavern, { prize: 1 });
|
||||||
@@ -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 challenges = await user.get('/challenges/groups/habitrpg');
|
||||||
|
|
||||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
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.updateOne({
|
|
||||||
'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,
|
generateUser,
|
||||||
generateChallenge,
|
generateChallenge,
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
resetHabiticaDB,
|
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
|
||||||
|
|
||||||
describe('GET challenges/user', () => {
|
describe('GET challenges/user', () => {
|
||||||
context('no official challenges', () => {
|
context('no official challenges', () => {
|
||||||
let user; let member; let nonMember; let challenge; let challenge2; let publicChallenge;
|
let user; let member; let nonMember; let challenge; let challenge2;
|
||||||
let groupPlan; let userData; let groupData; let tavern; let tavernData;
|
let publicGuild; let userData; let groupData;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await resetHabiticaDB();
|
|
||||||
|
|
||||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'TestGuild',
|
name: 'TestGuild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
groupPlan = group;
|
publicGuild = group;
|
||||||
groupData = {
|
groupData = {
|
||||||
_id: groupPlan._id,
|
_id: publicGuild._id,
|
||||||
categories: [],
|
categories: [],
|
||||||
id: groupPlan._id,
|
id: publicGuild._id,
|
||||||
type: groupPlan.type,
|
type: publicGuild.type,
|
||||||
privacy: groupPlan.privacy,
|
privacy: publicGuild.privacy,
|
||||||
name: groupPlan.name,
|
name: publicGuild.name,
|
||||||
summary: groupPlan.name,
|
summary: publicGuild.name,
|
||||||
leader: groupPlan.leader._id,
|
leader: publicGuild.leader._id,
|
||||||
};
|
};
|
||||||
|
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
userData = {
|
userData = {
|
||||||
_id: groupPlan.leader._id,
|
_id: publicGuild.leader._id,
|
||||||
id: groupPlan.leader._id,
|
id: publicGuild.leader._id,
|
||||||
profile: { name: user.profile.name },
|
profile: { name: user.profile.name },
|
||||||
auth: {
|
auth: {
|
||||||
local: {
|
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
|
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||||
nonMember = await generateUser();
|
nonMember = await generateUser();
|
||||||
|
|
||||||
challenge = await generateChallenge(user, group);
|
challenge = await generateChallenge(user, group);
|
||||||
challenge2 = await generateChallenge(user, group);
|
challenge2 = await generateChallenge(user, group);
|
||||||
await user.updateOne({ 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', () => {
|
context('all challenges', () => {
|
||||||
it('should return challenges user has joined', async () => {
|
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 });
|
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||||
expect(foundChallenge).to.exist;
|
expect(foundChallenge).to.exist;
|
||||||
@@ -83,13 +64,11 @@ describe('GET challenges/user', () => {
|
|||||||
expect(foundChallenge.group).to.eql(groupData);
|
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 challenges = await nonMember.get('/challenges/user?page=0');
|
||||||
|
|
||||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
expect(foundPublicChallenge).to.exist;
|
expect(foundChallenge2).to.not.exist;
|
||||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
|
||||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return challenges user has created', async () => {
|
it('should return challenges user has created', async () => {
|
||||||
@@ -121,10 +100,10 @@ describe('GET challenges/user', () => {
|
|||||||
it('should return newest challenges first', async () => {
|
it('should return newest challenges first', async () => {
|
||||||
let challenges = await user.get('/challenges/user?page=0');
|
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);
|
expect(foundChallengeIndex).to.eql(0);
|
||||||
|
|
||||||
const newChallenge = await generateChallenge(user, groupPlan);
|
const newChallenge = await generateChallenge(user, publicGuild);
|
||||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||||
|
|
||||||
challenges = await user.get('/challenges/user?page=0');
|
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 () => {
|
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 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;
|
expect(foundChallenge).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
|
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 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;
|
expect(foundChallenge).to.not.exist;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('my challenges', () => {
|
context('my challenges', () => {
|
||||||
it('should return challenges user has joined', async () => {
|
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 });
|
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||||
expect(foundChallenge).to.exist;
|
expect(foundChallenge).to.exist;
|
||||||
@@ -169,10 +177,6 @@ describe('GET challenges/user', () => {
|
|||||||
expect(foundChallenge2).to.exist;
|
expect(foundChallenge2).to.exist;
|
||||||
expect(foundChallenge2.leader).to.eql(userData);
|
expect(foundChallenge2.leader).to.eql(userData);
|
||||||
expect(foundChallenge2.group).to.eql(groupData);
|
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 () => {
|
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).to.exist;
|
||||||
expect(foundChallenge2.leader).to.eql(userData);
|
expect(foundChallenge2.leader).to.eql(userData);
|
||||||
expect(foundChallenge2.group).to.eql(groupData);
|
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 () => {
|
it('should not return challenges user has created if filter by not owned', async () => {
|
||||||
@@ -199,42 +199,38 @@ describe('GET challenges/user', () => {
|
|||||||
expect(foundChallenge1).to.not.exist;
|
expect(foundChallenge1).to.not.exist;
|
||||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
expect(foundChallenge2).to.not.exist;
|
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 () => {
|
it('should not return challenges in user groups', async () => {
|
||||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
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 });
|
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
expect(foundChallenge2).to.not.exist;
|
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', () => {
|
context('official challenge is present', () => {
|
||||||
let user; let officialChallenge; let unofficialChallenges; let
|
let user; let officialChallenge; let unofficialChallenges; let
|
||||||
group;
|
publicGuild;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
const { group, groupLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'TestGuild',
|
name: 'TestGuild',
|
||||||
summary: 'summary for TestGuild',
|
summary: 'summary for TestGuild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
await user.updateOne({
|
user = groupLeader;
|
||||||
|
publicGuild = group;
|
||||||
|
|
||||||
|
await user.update({
|
||||||
'permissions.challengeAdmin': true,
|
'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`);
|
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||||
|
|
||||||
challenges = await user.get('/challenges/user?page=0');
|
challenges = await user.get('/challenges/user?page=0');
|
||||||
@@ -298,17 +294,16 @@ describe('GET challenges/user', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'TestGuild',
|
name: 'TestGuild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
guild = group;
|
guild = group;
|
||||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||||
|
|
||||||
await user.updateOne({ balance: 20 });
|
await user.update({ balance: 20 });
|
||||||
|
|
||||||
for (let i = 0; i < 11; i += 1) {
|
for (let i = 0; i < 11; i += 1) {
|
||||||
let challenge = await generateChallenge(user, group); // eslint-disable-line
|
let challenge = await generateChallenge(user, group); // eslint-disable-line
|
||||||
|
|||||||
@@ -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.updateOne({ '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.updateOne({ '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 user = await generateUser();
|
||||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1);
|
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1);
|
||||||
const group = createAndPopulateGroup({
|
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 groupLeader;
|
||||||
let group;
|
let group;
|
||||||
let groupMember;
|
let groupMember;
|
||||||
@@ -75,11 +94,9 @@ describe('POST /challenges', () => {
|
|||||||
challenges: true,
|
challenges: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
groupLeader = await populatedGroup.groupLeader.sync();
|
groupLeader = await populatedGroup.groupLeader.sync();
|
||||||
await groupLeader.updateOne({ permissions: {} });
|
|
||||||
group = populatedGroup.group;
|
group = populatedGroup.group;
|
||||||
groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring
|
groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring
|
||||||
});
|
});
|
||||||
@@ -177,7 +194,7 @@ describe('POST /challenges', () => {
|
|||||||
const oldUserBalance = groupLeader.balance;
|
const oldUserBalance = groupLeader.balance;
|
||||||
const prize = 8;
|
const prize = 8;
|
||||||
|
|
||||||
await group.updateOne({ balance: 0 });
|
await group.update({ balance: 0 });
|
||||||
await groupLeader.post('/challenges', {
|
await groupLeader.post('/challenges', {
|
||||||
group: group._id,
|
group: group._id,
|
||||||
name: 'Test Challenge',
|
name: 'Test Challenge',
|
||||||
@@ -202,7 +219,7 @@ describe('POST /challenges', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('sets challenge as official if created by admin and official flag is set', async () => {
|
it('sets challenge as official if created by admin and official flag is set', async () => {
|
||||||
await groupLeader.updateOne({
|
await groupLeader.update({
|
||||||
permissions: {
|
permissions: {
|
||||||
challengeAdmin: true,
|
challengeAdmin: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -128,10 +128,10 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
|||||||
const oldBalance = winningUser.balance;
|
const oldBalance = winningUser.balance;
|
||||||
const oldLeaderBalance = (await groupLeader.sync()).balance;
|
const oldLeaderBalance = (await groupLeader.sync()).balance;
|
||||||
|
|
||||||
await winningUser.updateOne({
|
await winningUser.update({
|
||||||
'purchased.plan.customerId': 'group-plan',
|
'purchased.plan.customerId': 'group-plan',
|
||||||
});
|
});
|
||||||
await group.updateOne({
|
await group.update({
|
||||||
'leaderOnly.getGems': true,
|
'leaderOnly.getGems': true,
|
||||||
'purchased.plan.customerId': 123,
|
'purchased.plan.customerId': 123,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ describe('PUT /challenges/:challengeId', () => {
|
|||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
privateGuild = group;
|
privateGuild = group;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
@@ -9,30 +10,27 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
|||||||
admin;
|
admin;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
const { group, groupLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
leaderDetails: {
|
leaderDetails: {
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
balance: 10,
|
balance: 10,
|
||||||
},
|
},
|
||||||
members: 2,
|
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
groupWithChat = group;
|
groupWithChat = group;
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||||
message = message.message;
|
message = message.message;
|
||||||
userThatDidNotCreateChat = members[0]; // eslint-disable-line prefer-destructuring
|
userThatDidNotCreateChat = await generateUser();
|
||||||
admin = members[1]; // eslint-disable-line prefer-destructuring
|
admin = await generateUser({ 'permissions.moderator': true });
|
||||||
await admin.updateOne({ permissions: { moderator: true } });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Chat errors', () => {
|
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();
|
const fakeChatId = generateUUID();
|
||||||
await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
|
await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 404,
|
code: 404,
|
||||||
@@ -58,7 +56,7 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
|||||||
nextMessage = nextMessage.message;
|
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}`);
|
await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
|
||||||
|
|
||||||
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
createAndPopulateGroup,
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
@@ -11,22 +11,48 @@ describe('GET /groups/:groupId/chat', () => {
|
|||||||
user = await generateUser();
|
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', () => {
|
context('private Guild', () => {
|
||||||
let group;
|
let group;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
({ group } = await createAndPopulateGroup({
|
const leader = await generateUser({ balance: 2 });
|
||||||
groupDetails: {
|
|
||||||
name: 'test group',
|
group = await generateGroup(leader, {
|
||||||
type: 'guild',
|
name: 'test group',
|
||||||
privacy: 'private',
|
type: 'guild',
|
||||||
},
|
privacy: 'private',
|
||||||
members: 1,
|
}, {
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
chat: [
|
chat: [
|
||||||
'Hello',
|
'Hello',
|
||||||
'Welcome to the Guild',
|
'Welcome to the Guild',
|
||||||
],
|
],
|
||||||
}));
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error if user is not member of requested private group', async () => {
|
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 moment from 'moment';
|
||||||
|
import nconf from 'nconf';
|
||||||
import { IncomingWebhook } from '@slack/webhook';
|
import { IncomingWebhook } from '@slack/webhook';
|
||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
|
||||||
describe('POST /chat/:chatId/flag', () => {
|
describe('POST /chat/:chatId/flag', () => {
|
||||||
let user; let admin; let anotherUser; let newUser; let
|
let user; let admin; let anotherUser; let newUser; let
|
||||||
group; let members; let userToDelete;
|
group;
|
||||||
const TEST_MESSAGE = 'Test Message';
|
const TEST_MESSAGE = 'Test Message';
|
||||||
const USER_AGE_FOR_FLAGGING = 3;
|
const USER_AGE_FOR_FLAGGING = 3;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
({ group, groupLeader: user, members } = await createAndPopulateGroup({
|
user = await generateUser({ balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||||
groupDetails: {
|
admin = await generateUser({ balance: 1, 'permissions.moderator': true });
|
||||||
name: 'Test Guild',
|
anotherUser = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||||
type: 'guild',
|
newUser = await generateUser({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
|
||||||
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.updateOne({ permissions: {} });
|
|
||||||
await admin.updateOne({ permissions: { moderator: true } });
|
|
||||||
await anotherUser.updateOne({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
|
||||||
await newUser.updateOne({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
|
|
||||||
await userToDelete.updateOne({
|
|
||||||
'auth.timestamps.created': moment().subtract(1, 'days').toDate(),
|
|
||||||
'purchased.plan.dateTerminated': moment().subtract(1, 'minutes').toDate(),
|
|
||||||
});
|
|
||||||
|
|
||||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||||
|
|
||||||
|
group = await user.post('/groups', {
|
||||||
|
name: 'Test Guild',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'public',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -79,8 +69,8 @@ describe('POST /chat/:chatId/flag', () => {
|
|||||||
fallback: 'Flag Message',
|
fallback: 'Flag Message',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
|
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
|
||||||
title: 'Flag in Test Guild - (private guild)',
|
title: 'Flag in Test Guild',
|
||||||
title_link: undefined,
|
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||||
text: TEST_MESSAGE,
|
text: TEST_MESSAGE,
|
||||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
||||||
mrkdwn_in: [
|
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 () => {
|
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',
|
fallback: 'Flag Message',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
|
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
|
||||||
title: 'Flag in Test Guild - (private guild)',
|
title: 'Flag in Test Guild',
|
||||||
title_link: undefined,
|
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||||
text: TEST_MESSAGE,
|
text: TEST_MESSAGE,
|
||||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
|
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
|
||||||
mrkdwn_in: [
|
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 () => {
|
it('Flags a chat when the author\'s account was deleted', async () => {
|
||||||
const { message } = await userToDelete.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
const deletedUser = await generateUser({
|
||||||
await userToDelete.del('/user', {
|
'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',
|
password: 'password',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,28 +6,28 @@ import {
|
|||||||
|
|
||||||
describe('POST /chat/:chatId/like', () => {
|
describe('POST /chat/:chatId/like', () => {
|
||||||
let user;
|
let user;
|
||||||
let anotherUser;
|
|
||||||
let groupWithChat;
|
let groupWithChat;
|
||||||
let members;
|
|
||||||
const testMessage = 'Test Message';
|
const testMessage = 'Test Message';
|
||||||
|
let anotherUser;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
({ group: groupWithChat, groupLeader: user, members } = await createAndPopulateGroup({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'Test Guild',
|
name: 'Test Guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
leaderDetails: {
|
leaderDetails: {
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
balance: 10,
|
balance: 10,
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
[anotherUser] = members;
|
user = groupLeader;
|
||||||
await anotherUser.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
groupWithChat = group;
|
||||||
|
anotherUser = members[0]; // eslint-disable-line prefer-destructuring
|
||||||
|
await anotherUser.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Returns an error when chat message is not found', async () => {
|
it('Returns an error when chat message is not found', async () => {
|
||||||
|
|||||||
@@ -1,43 +1,52 @@
|
|||||||
import { IncomingWebhook } from '@slack/webhook';
|
import { IncomingWebhook } from '@slack/webhook';
|
||||||
|
import nconf from 'nconf';
|
||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
sleep,
|
sleep,
|
||||||
server,
|
server,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import {
|
import {
|
||||||
|
SPAM_MESSAGE_LIMIT,
|
||||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||||
|
TAVERN_ID,
|
||||||
} from '../../../../../website/server/models/group';
|
} 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';
|
import * as email from '../../../../../website/server/libs/email';
|
||||||
|
|
||||||
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
|
||||||
describe('POST /chat', () => {
|
describe('POST /chat', () => {
|
||||||
let user; let groupWithChat; let member; let
|
let user; let groupWithChat; let member; let
|
||||||
additionalMember;
|
additionalMember;
|
||||||
const testMessage = 'Test Message';
|
const testMessage = 'Test Message';
|
||||||
const testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE';
|
const testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE';
|
||||||
|
const testBannedWordMessage1 = 'TESTPLACEHOLDERSWEARWORDHERE1';
|
||||||
const testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE';
|
const testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE';
|
||||||
|
const testSlurMessage1 = 'TESTPLACEHOLDERSLURWORDHERE1';
|
||||||
|
const bannedWordErrorMessage = t('bannedWordUsed', { swearWordsUsed: testBannedWordMessage });
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'Test Guild',
|
name: 'Test Guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
members: 2,
|
members: 2,
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
}); // prevent tests accidentally throwing messageGroupChatSpam
|
}); // prevent tests accidentally throwing messageGroupChatSpam
|
||||||
groupWithChat = group;
|
groupWithChat = group;
|
||||||
[member, additionalMember] = members;
|
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||||
await member.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
additionalMember = members[1]; // eslint-disable-line prefer-destructuring
|
||||||
await additionalMember.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
await member.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||||
|
await additionalMember.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Returns an error when no message is provided', async () => {
|
it('Returns an error when no message is provided', async () => {
|
||||||
@@ -77,15 +86,35 @@ describe('POST /chat', () => {
|
|||||||
|
|
||||||
describe('mute user', () => {
|
describe('mute user', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
member.updateOne({ 'flags.chatRevoked': false });
|
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 () => {
|
it('does not error when chat privileges are revoked when sending a message to a private guild', async () => {
|
||||||
await member.updateOne({
|
const { group, members } = await createAndPopulateGroup({
|
||||||
'flags.chatRevoked': true,
|
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;
|
expect(message.message.id).to.exist;
|
||||||
});
|
});
|
||||||
@@ -101,7 +130,7 @@ describe('POST /chat', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const privatePartyMemberWithChatsRevoked = members[0];
|
const privatePartyMemberWithChatsRevoked = members[0];
|
||||||
await privatePartyMemberWithChatsRevoked.updateOne({
|
await privatePartyMemberWithChatsRevoked.update({
|
||||||
'flags.chatRevoked': true,
|
'flags.chatRevoked': true,
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
});
|
});
|
||||||
@@ -120,15 +149,57 @@ describe('POST /chat', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
member.updateOne({ 'flags.chatShadowMuted': false });
|
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 () => {
|
it('creates a chat with zero flagCount when sending a message to a private guild', async () => {
|
||||||
await member.updateOne({
|
const { group, members } = await createAndPopulateGroup({
|
||||||
'flags.chatShadowMuted': true,
|
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.id).to.exist;
|
||||||
expect(message.message.flagCount).to.eql(0);
|
expect(message.message.flagCount).to.eql(0);
|
||||||
@@ -145,7 +216,7 @@ describe('POST /chat', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userWithChatShadowMuted = members[0];
|
const userWithChatShadowMuted = members[0];
|
||||||
await userWithChatShadowMuted.updateOne({
|
await userWithChatShadowMuted.update({
|
||||||
'flags.chatShadowMuted': true,
|
'flags.chatShadowMuted': true,
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
});
|
});
|
||||||
@@ -155,9 +226,100 @@ describe('POST /chat', () => {
|
|||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
expect(message.message.flagCount).to.eql(0);
|
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', () => {
|
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 () => {
|
it('does not error when sending a chat message containing a banned word to a party', async () => {
|
||||||
const { group, members } = await createAndPopulateGroup({
|
const { group, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
@@ -167,7 +329,26 @@ describe('POST /chat', () => {
|
|||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
});
|
});
|
||||||
await members[0].updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
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 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 });
|
const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage });
|
||||||
|
|
||||||
@@ -175,7 +356,17 @@ describe('POST /chat', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not error when sending a chat message containing a banned word to a private guild', async () => {
|
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;
|
expect(message.message.id).to.exist;
|
||||||
});
|
});
|
||||||
@@ -189,7 +380,46 @@ describe('POST /chat', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
user.updateOne({ 'flags.chatRevoked': false });
|
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 () => {
|
it('allows slurs in private groups', async () => {
|
||||||
@@ -201,23 +431,34 @@ describe('POST /chat', () => {
|
|||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
});
|
});
|
||||||
await members[0].updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||||
|
|
||||||
const message = await members[0].post(`/groups/${group._id}/chat`, { message: testSlurMessage });
|
const message = await members[0].post(`/groups/${group._id}/chat`, { message: testSlurMessage });
|
||||||
|
|
||||||
expect(message.message.id).to.exist;
|
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 () => {
|
it('errors when user account is too young', async () => {
|
||||||
await user.updateOne({ 'auth.timestamps.created': new Date() });
|
const brandNewUser = await generateUser();
|
||||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: 'hi im new' }))
|
await expect(brandNewUser.post('/groups/habitrpg/chat', { message: 'hi im new' }))
|
||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
error: 'BadRequest',
|
error: 'BadRequest',
|
||||||
message: t('chatTemporarilyUnavailable'),
|
message: t('chatTemporarilyUnavailable'),
|
||||||
});
|
});
|
||||||
await user.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a chat', async () => {
|
it('creates a chat', async () => {
|
||||||
@@ -258,7 +499,7 @@ describe('POST /chat', () => {
|
|||||||
|
|
||||||
it('chat message with mentions - mention link should not count towards 3000 chars limit', async () => {
|
it('chat message with mentions - mention link should not count towards 3000 chars limit', async () => {
|
||||||
const memberUsername = 'memberUsername';
|
const memberUsername = 'memberUsername';
|
||||||
await member.updateOne({ 'auth.local.username': memberUsername });
|
await member.update({ 'auth.local.username': memberUsername });
|
||||||
|
|
||||||
const messageWithMentions = `hi @${memberUsername} 123456789
|
const messageWithMentions = `hi @${memberUsername} 123456789
|
||||||
123456789 123456789 123456789 123456789 123456789 123456789 89 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 12345678 END.`;
|
123456789 123456789 123456789 123456789 123456789 123456789 89 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 12345678 END.`;
|
||||||
@@ -278,43 +519,26 @@ describe('POST /chat', () => {
|
|||||||
const mount = 'test-mount';
|
const mount = 'test-mount';
|
||||||
const pet = 'test-pet';
|
const pet = 'test-pet';
|
||||||
const style = 'test-style';
|
const style = 'test-style';
|
||||||
await user.updateOne({
|
const userWithStyle = await generateUser({
|
||||||
'items.currentMount': mount,
|
'items.currentMount': mount,
|
||||||
'items.currentPet': pet,
|
'items.currentPet': pet,
|
||||||
'preferences.style': style,
|
'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.id).to.exist;
|
||||||
expect(message.message.userStyles.items.currentMount).to.eql(user.items.currentMount);
|
expect(message.message.userStyles.items.currentMount).to.eql(userWithStyle.items.currentMount);
|
||||||
expect(message.message.userStyles.items.currentPet).to.eql(user.items.currentPet);
|
expect(message.message.userStyles.items.currentPet).to.eql(userWithStyle.items.currentPet);
|
||||||
expect(message.message.userStyles.preferences.style).to.eql(user.preferences.style);
|
expect(message.message.userStyles.preferences.style).to.eql(userWithStyle.preferences.style);
|
||||||
expect(message.message.userStyles.preferences.hair).to.eql(user.preferences.hair);
|
expect(message.message.userStyles.preferences.hair).to.eql(userWithStyle.preferences.hair);
|
||||||
expect(message.message.userStyles.preferences.skin).to.eql(user.preferences.skin);
|
expect(message.message.userStyles.preferences.skin).to.eql(userWithStyle.preferences.skin);
|
||||||
expect(message.message.userStyles.preferences.shirt).to.eql(user.preferences.shirt);
|
expect(message.message.userStyles.preferences.shirt).to.eql(userWithStyle.preferences.shirt);
|
||||||
expect(message.message.userStyles.preferences.chair).to.eql(user.preferences.chair);
|
expect(message.message.userStyles.preferences.chair).to.eql(userWithStyle.preferences.chair);
|
||||||
expect(message.message.userStyles.preferences.background)
|
expect(message.message.userStyles.preferences.background)
|
||||||
.to.eql(user.preferences.background);
|
.to.eql(userWithStyle.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.updateOne({ '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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds backer info to chat', async () => {
|
it('adds backer info to chat', async () => {
|
||||||
@@ -323,11 +547,12 @@ describe('POST /chat', () => {
|
|||||||
tier: 800,
|
tier: 800,
|
||||||
tokensApplied: true,
|
tokensApplied: true,
|
||||||
};
|
};
|
||||||
await user.updateOne({
|
const backer = await generateUser({
|
||||||
backer: backerInfo,
|
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;
|
const messageBackerInfo = message.message.backer;
|
||||||
|
|
||||||
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
|
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
|
||||||
@@ -375,7 +600,7 @@ describe('POST /chat', () => {
|
|||||||
|
|
||||||
context('chat notifications', () => {
|
context('chat notifications', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
member.updateOne({ newMessages: {}, notifications: [] });
|
member.update({ newMessages: {}, notifications: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('notifies other users of new messages for a guild', async () => {
|
it('notifies other users of new messages for a guild', async () => {
|
||||||
@@ -407,5 +632,43 @@ describe('POST /chat', () => {
|
|||||||
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
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;
|
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({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
leaderDetails: {
|
leaderDetails: {
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
balance: 10,
|
balance: 10,
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
guild = group;
|
guild = group;
|
||||||
guildLeader = groupLeader;
|
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 = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' });
|
||||||
guildMessage = guildMessage.message;
|
guildMessage = guildMessage.message;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import config from '../../../../../config.json';
|
import config from '../../../../../config.json';
|
||||||
@@ -12,24 +13,21 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
|||||||
admin;
|
admin;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
const { group, groupLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
leaderDetails: {
|
leaderDetails: {
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
balance: 10,
|
balance: 10,
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
members: 2,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
groupWithChat = group;
|
groupWithChat = group;
|
||||||
author = groupLeader;
|
author = groupLeader;
|
||||||
[nonAdmin, admin] = members;
|
nonAdmin = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||||
await nonAdmin.updateOne({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
admin = await generateUser({ 'permissions.moderator': true });
|
||||||
await admin.updateOne({ 'permissions.moderator': true });
|
|
||||||
|
|
||||||
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||||
message = message.message;
|
message = message.message;
|
||||||
@@ -68,27 +66,22 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
|||||||
type: 'party',
|
type: 'party',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 2,
|
members: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
await members[0].updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||||
let privateMessage = await members[0].post(`/groups/${group._id}/chat`, { message: 'Some message' });
|
let privateMessage = await members[0].post(`/groups/${group._id}/chat`, { message: 'Some message' });
|
||||||
privateMessage = privateMessage.message;
|
privateMessage = privateMessage.message;
|
||||||
|
|
||||||
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/flag`);
|
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/flag`);
|
||||||
|
|
||||||
// first test that the flag was actually successful
|
// 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`);
|
let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||||
expect(messages[0].flagCount).to.eql(0);
|
expect(messages[0].flagCount).to.eql(5);
|
||||||
messages = await members[1].get(`/groups/${group._id}/chat`);
|
|
||||||
expect(messages.length).to.eql(0);
|
|
||||||
|
|
||||||
// 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`);
|
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);
|
expect(messages[0].flagCount).to.eql(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,7 +97,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
|||||||
const member = members[0];
|
const member = members[0];
|
||||||
|
|
||||||
// make member that can use skills
|
// make member that can use skills
|
||||||
await member.updateOne({
|
await member.update({
|
||||||
'stats.lvl': 100,
|
'stats.lvl': 100,
|
||||||
'stats.mp': 400,
|
'stats.mp': 400,
|
||||||
'stats.class': 'wizard',
|
'stats.class': 'wizard',
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ describe('GET /coupons/', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return the coupons in CSV format ordered by creation date', async () => {
|
it('should return the coupons in CSV format ordered by creation date', async () => {
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'permissions.coupons': true,
|
'permissions.coupons': true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe('POST /coupons/generate/:event', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns an error if user has no coupons permission', async () => {
|
it('returns an error if user has no coupons permission', async () => {
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'permissions.coupons': false,
|
'permissions.coupons': false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ describe('POST /coupons/generate/:event', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should generate coupons', async () => {
|
it('should generate coupons', async () => {
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'permissions.coupons': true,
|
'permissions.coupons': true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ describe('POST /debug/quest-progress', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('increases boss quest progress by 1000', async () => {
|
it('increases boss quest progress by 1000', async () => {
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'party.quest.key': 'whale',
|
'party.quest.key': 'whale',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ describe('POST /debug/quest-progress', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('increases collection quest progress by 300 items', async () => {
|
it('increases collection quest progress by 300 items', async () => {
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
'party.quest.key': 'evilsanta2',
|
'party.quest.key': 'evilsanta2',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
generateUser,
|
||||||
|
generateGroup,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('GET /group-plans', () => {
|
describe('GET /group-plans', () => {
|
||||||
@@ -7,15 +8,20 @@ describe('GET /group-plans', () => {
|
|||||||
let groupPlan;
|
let groupPlan;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
({ group: groupPlan, groupLeader: user } = await createAndPopulateGroup({
|
user = await generateUser({ balance: 4 });
|
||||||
groupDetails: {
|
groupPlan = await generateGroup(user,
|
||||||
name: 'group plan - is member',
|
{
|
||||||
|
name: 'public guild - is member',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
{
|
||||||
leaderDetails: { balance: 4 },
|
purchased: {
|
||||||
}));
|
plan: {
|
||||||
|
customerId: 'existings',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns group plans for the user', async () => {
|
it('returns group plans for the user', async () => {
|
||||||
|
|||||||
@@ -1,63 +1,70 @@
|
|||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
generateUser,
|
||||||
resetHabiticaDB,
|
resetHabiticaDB,
|
||||||
generateGroup,
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
import {
|
||||||
|
TAVERN_ID,
|
||||||
|
} from '../../../../../website/server/models/group';
|
||||||
|
import apiError from '../../../../../website/server/libs/apiError';
|
||||||
|
|
||||||
describe('GET /groups', () => {
|
describe('GET /groups', () => {
|
||||||
let user; let leader; let members;
|
let user;
|
||||||
let secondGroup; let secondLeader;
|
let userInGuild;
|
||||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 2;
|
const NUMBER_OF_PUBLIC_GUILDS = 2;
|
||||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 3;
|
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 = [{
|
const categories = [{
|
||||||
slug: 'newCat',
|
slug: 'newCat',
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
}];
|
}];
|
||||||
|
let publicGuildNotMember;
|
||||||
let privateGuildUserIsMemberOf;
|
let privateGuildUserIsMemberOf;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await resetHabiticaDB();
|
await resetHabiticaDB();
|
||||||
|
|
||||||
({
|
const leader = await generateUser({ balance: 10 });
|
||||||
group: privateGuildUserIsMemberOf,
|
user = await generateUser({ balance: 4 });
|
||||||
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.updateOne({ balance: 4 });
|
|
||||||
|
|
||||||
({ group: secondGroup, groupLeader: secondLeader } = await createAndPopulateGroup({
|
const publicGuildUserIsMemberOf = await generateGroup(leader, {
|
||||||
groupDetails: {
|
name: 'public guild - is member',
|
||||||
name: 'c++ coders',
|
type: 'guild',
|
||||||
type: 'guild',
|
privacy: 'public',
|
||||||
privacy: 'private',
|
summary: 'ohayou kombonwa',
|
||||||
},
|
description: 'oyasumi',
|
||||||
upgradeToGroupPlan: true,
|
});
|
||||||
}));
|
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] });
|
userInGuild = await generateUser({ guilds: [publicGuildUserIsMemberOf._id] });
|
||||||
await user.post(`/groups/${secondGroup._id}/join`);
|
|
||||||
|
|
||||||
await createAndPopulateGroup({
|
publicGuildNotMember = await generateGroup(leader, {
|
||||||
groupDetails: {
|
name: 'public guild - is not member',
|
||||||
name: 'private guild - is not member',
|
type: 'guild',
|
||||||
type: 'guild',
|
privacy: 'public',
|
||||||
privacy: 'private',
|
summary: 'Natsume Soseki',
|
||||||
},
|
description: 'Kinnosuke no Hondana',
|
||||||
upgradeToGroupPlan: true,
|
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, {
|
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 () => {
|
it('returns only the user\'s party when party passed in as query', async () => {
|
||||||
await expect(user.get('/groups?type=party'))
|
await expect(user.get('/groups?type=party'))
|
||||||
.to.eventually.have.a.lengthOf(1)
|
.to.eventually.have.a.lengthOf(1)
|
||||||
.and.to.have.nested.property('[0]');
|
.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 () => {
|
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
||||||
await expect(user.get('/groups?type=guilds'))
|
await expect(user.get('/groups?type=guilds'))
|
||||||
.to.eventually.have.a
|
.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 () => {
|
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 () => {
|
it('returns a list of groups user has access to', async () => {
|
||||||
await expect(user.get('/groups?type=privateGuilds,party'))
|
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
|
||||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
|
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW - 1); // -1 for no Tavern.
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('filters', () => {
|
it('returns a list of groups user has access to', async () => {
|
||||||
it('returns private guilds filtered by category', async () => {
|
const group = await generateGroup(user, {
|
||||||
const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
name: 'c++ coders',
|
||||||
|
type: 'guild',
|
||||||
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
privacy: 'public',
|
||||||
});
|
});
|
||||||
|
|
||||||
it('filters private guilds by size', async () => {
|
// search for 'c++ coders'
|
||||||
const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=0&search=c%2B%2B+coders'))
|
||||||
|
.to.eventually.have.lengthOf(1)
|
||||||
expect(guilds.length).to.equal(0);
|
.and.to.have.nested.property('[0]')
|
||||||
});
|
.and.to.have.property('_id', group._id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
generateUser,
|
generateUser,
|
||||||
generateGroup,
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
createAndPopulateGroup,
|
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('GET /groups/:groupId/invites', () => {
|
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 () => {
|
it('returns only first 30 invites by default (req.query.limit not specified)', async () => {
|
||||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
const leader = await generateUser({ balance: 4 });
|
||||||
groupDetails: {
|
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||||
type: 'guild',
|
|
||||||
privacy: 'private',
|
const invitesToGenerate = [];
|
||||||
name: generateUUID(),
|
for (let i = 0; i < 31; i += 1) {
|
||||||
},
|
invitesToGenerate.push(generateUser());
|
||||||
leaderDetails: { balance: 4 },
|
}
|
||||||
invites: 31,
|
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
upgradeToGroupPlan: true,
|
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||||
});
|
|
||||||
|
|
||||||
const res = await leader.get(`/groups/${group._id}/invites`);
|
const res = await leader.get(`/groups/${group._id}/invites`);
|
||||||
expect(res.length).to.equal(30);
|
expect(res.length).to.equal(30);
|
||||||
@@ -92,16 +90,8 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
}).timeout(10000);
|
}).timeout(10000);
|
||||||
|
|
||||||
it('returns an error if req.query.limit is over 60', async () => {
|
it('returns an error if req.query.limit is over 60', async () => {
|
||||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
const leader = await generateUser({ balance: 4 });
|
||||||
groupDetails: {
|
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||||
type: 'guild',
|
|
||||||
privacy: 'private',
|
|
||||||
name: generateUUID(),
|
|
||||||
},
|
|
||||||
leaderDetails: { balance: 4 },
|
|
||||||
invites: 1,
|
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
|
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -111,16 +101,8 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns an error if req.query.limit is under 1', async () => {
|
it('returns an error if req.query.limit is under 1', async () => {
|
||||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
const leader = await generateUser({ balance: 4 });
|
||||||
groupDetails: {
|
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||||
type: 'guild',
|
|
||||||
privacy: 'private',
|
|
||||||
name: generateUUID(),
|
|
||||||
},
|
|
||||||
leaderDetails: { balance: 4 },
|
|
||||||
invites: 1,
|
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
|
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -130,16 +112,8 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns an error if req.query.limit is not an integer', async () => {
|
it('returns an error if req.query.limit is not an integer', async () => {
|
||||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
const leader = await generateUser({ balance: 4 });
|
||||||
groupDetails: {
|
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||||
type: 'guild',
|
|
||||||
privacy: 'private',
|
|
||||||
name: generateUUID(),
|
|
||||||
},
|
|
||||||
leaderDetails: { balance: 4 },
|
|
||||||
invites: 1,
|
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
|
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -149,16 +123,15 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
||||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
const leader = await generateUser({ balance: 4 });
|
||||||
groupDetails: {
|
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||||
type: 'guild',
|
|
||||||
privacy: 'private',
|
const invitesToGenerate = [];
|
||||||
name: generateUUID(),
|
for (let i = 0; i < 31; i += 1) {
|
||||||
},
|
invitesToGenerate.push(generateUser());
|
||||||
leaderDetails: { balance: 4 },
|
}
|
||||||
invites: 31,
|
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
upgradeToGroupPlan: true,
|
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||||
});
|
|
||||||
|
|
||||||
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
||||||
expect(res.length).to.equal(14);
|
expect(res.length).to.equal(14);
|
||||||
@@ -176,20 +149,17 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
}).timeout(30000);
|
}).timeout(30000);
|
||||||
|
|
||||||
it('supports using req.query.lastId to get more invites', async function test () {
|
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
|
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||||
({ group, groupLeader: user, invitees } = await createAndPopulateGroup({
|
const leader = await generateUser({ balance: 4 });
|
||||||
groupDetails: {
|
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||||
type: 'guild',
|
|
||||||
privacy: 'private',
|
|
||||||
name: generateUUID(),
|
|
||||||
},
|
|
||||||
leaderDetails: { balance: 4 },
|
|
||||||
invites: 32,
|
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
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`);
|
const res = await user.get(`/groups/${group._id}/invites`);
|
||||||
expect(res.length).to.equal(30);
|
expect(res.length).to.equal(30);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
|
||||||
generateUser,
|
generateUser,
|
||||||
generateGroup,
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
@@ -76,15 +75,7 @@ describe('GET /groups/:groupId/members', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('req.query.includeAllPublicFields === true works with guilds', async () => {
|
it('req.query.includeAllPublicFields === true works with guilds', async () => {
|
||||||
let group;
|
const group = await generateGroup(user, { type: 'guild', name: generateUUID() });
|
||||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
|
||||||
type: 'guild',
|
|
||||||
privacy: 'private',
|
|
||||||
name: generateUUID(),
|
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
members: 1,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const [memberRes] = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`);
|
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
|
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 () {
|
it('supports using req.query.lastId to get more members', async function test () {
|
||||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||||
const { group, groupLeader: leader, members: generatedUsers } = await createAndPopulateGroup({
|
const leader = await generateUser({ balance: 4 });
|
||||||
type: 'guild',
|
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||||
privacy: 'private',
|
|
||||||
name: generateUUID(),
|
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
leaderDetails: { balance: 4 },
|
|
||||||
members: 57,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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 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);
|
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);
|
expect(res2.length).to.equal(28);
|
||||||
|
|
||||||
const resIds = res.concat(res2).map(member => member._id);
|
const resIds = res.concat(res2).map(member => member._id);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
|
|
||||||
describe('GET /groups/:id', () => {
|
describe('GET /groups/:id', () => {
|
||||||
const typesOfGroups = {};
|
const typesOfGroups = {};
|
||||||
|
typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' };
|
||||||
typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
|
typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
|
||||||
typesOfGroups.party = { type: 'party', privacy: 'private' };
|
typesOfGroups.party = { type: 'party', privacy: 'private' };
|
||||||
|
|
||||||
@@ -23,11 +24,10 @@ describe('GET /groups/:id', () => {
|
|||||||
const groupData = await createAndPopulateGroup({
|
const groupData = await createAndPopulateGroup({
|
||||||
members: 30,
|
members: 30,
|
||||||
groupDetails,
|
groupDetails,
|
||||||
upgradeToGroupPlan: groupDetails.type === 'guild',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
leader = groupData.groupLeader;
|
leader = groupData.groupLeader;
|
||||||
[member] = groupData.members;
|
member = groupData.members[0]; // eslint-disable-line prefer-destructuring
|
||||||
createdGroup = groupData.group;
|
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', () => {
|
context('Non-member of a private guild', () => {
|
||||||
let nonMember; let
|
let nonMember; let
|
||||||
createdGroup;
|
createdGroup;
|
||||||
@@ -61,7 +89,6 @@ describe('GET /groups/:id', () => {
|
|||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
createdGroup = groupData.group;
|
createdGroup = groupData.group;
|
||||||
@@ -153,7 +180,7 @@ describe('GET /groups/:id', () => {
|
|||||||
it('removes non-existent guild from user\'s guild list', async () => {
|
it('removes non-existent guild from user\'s guild list', async () => {
|
||||||
const guildId = generateUUID();
|
const guildId = generateUUID();
|
||||||
|
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
guilds: [guildId, generateUUID()],
|
guilds: [guildId, generateUUID()],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -173,7 +200,7 @@ describe('GET /groups/:id', () => {
|
|||||||
it('removes non-existent party from user\'s party object', async () => {
|
it('removes non-existent party from user\'s party object', async () => {
|
||||||
const partyId = generateUUID();
|
const partyId = generateUUID();
|
||||||
|
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
party: { _id: partyId },
|
party: { _id: partyId },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -191,7 +218,7 @@ describe('GET /groups/:id', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('Flagged messages', () => {
|
context('Flagged messages', () => {
|
||||||
let group; let members;
|
let group;
|
||||||
|
|
||||||
const chat1 = {
|
const chat1 = {
|
||||||
id: 'chat1',
|
id: 'chat1',
|
||||||
@@ -241,7 +268,7 @@ describe('GET /groups/:id', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'test guild',
|
name: 'test guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
chat: [
|
chat: [
|
||||||
chat1,
|
chat1,
|
||||||
chat2,
|
chat2,
|
||||||
@@ -250,11 +277,9 @@ describe('GET /groups/:id', () => {
|
|||||||
chat5,
|
chat5,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
members: 1,
|
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
({ group, members } = groupData);
|
group = groupData.group;
|
||||||
|
|
||||||
await group.addChat([chat1, chat2, chat3, chat4, chat5]);
|
await group.addChat([chat1, chat2, chat3, chat4, chat5]);
|
||||||
});
|
});
|
||||||
@@ -262,8 +287,8 @@ describe('GET /groups/:id', () => {
|
|||||||
context('non-admin', () => {
|
context('non-admin', () => {
|
||||||
let nonAdmin;
|
let nonAdmin;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
[nonAdmin] = members;
|
nonAdmin = await generateUser();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not include messages with a flag count of 2 or greater', async () => {
|
it('does not include messages with a flag count of 2 or greater', async () => {
|
||||||
@@ -289,8 +314,9 @@ describe('GET /groups/:id', () => {
|
|||||||
let admin;
|
let admin;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
[admin] = members;
|
admin = await generateUser({
|
||||||
await admin.updateOne({ permissions: { moderator: true } });
|
'permissions.moderator': true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('includes all messages', async () => {
|
it('includes all messages', async () => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} 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';
|
import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '../../../../../website/common/script/constants';
|
||||||
|
|
||||||
describe('POST /group', () => {
|
describe('POST /group', () => {
|
||||||
@@ -34,8 +35,8 @@ describe('POST /group', () => {
|
|||||||
|
|
||||||
it('sets the group leader to the user who created the group', async () => {
|
it('sets the group leader to the user who created the group', async () => {
|
||||||
const group = await user.post('/groups', {
|
const group = await user.post('/groups', {
|
||||||
name: 'Test Party',
|
name: 'Test Public Guild',
|
||||||
type: 'party',
|
type: 'guild',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(group.leader).to.eql({
|
expect(group.leader).to.eql({
|
||||||
@@ -50,7 +51,7 @@ describe('POST /group', () => {
|
|||||||
const name = 'Test Group';
|
const name = 'Test Group';
|
||||||
const group = await user.post('/groups', {
|
const group = await user.post('/groups', {
|
||||||
name,
|
name,
|
||||||
type: 'party',
|
type: 'guild',
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedGroup = await user.get(`/groups/${group._id}`);
|
const updatedGroup = await user.get(`/groups/${group._id}`);
|
||||||
@@ -63,7 +64,7 @@ describe('POST /group', () => {
|
|||||||
const summary = 'Test Summary';
|
const summary = 'Test Summary';
|
||||||
const group = await user.post('/groups', {
|
const group = await user.post('/groups', {
|
||||||
name,
|
name,
|
||||||
type: 'party',
|
type: 'guild',
|
||||||
summary,
|
summary,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ describe('POST /group', () => {
|
|||||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_GUILDS + 1);
|
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_GUILDS + 1);
|
||||||
await expect(user.post('/groups', {
|
await expect(user.post('/groups', {
|
||||||
name,
|
name,
|
||||||
type: 'party',
|
type: 'guild',
|
||||||
summary,
|
summary,
|
||||||
})).to.eventually.be.rejected.and.eql({
|
})).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
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', () => {
|
context('Parties', () => {
|
||||||
const partyName = 'Test Party';
|
const partyName = 'Test Party';
|
||||||
const partyType = 'party';
|
const partyType = 'party';
|
||||||
@@ -110,7 +262,7 @@ describe('POST /group', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('creates a party when the user has no chat privileges', async () => {
|
it('creates a party when the user has no chat privileges', async () => {
|
||||||
await user.updateOne({ 'flags.chatRevoked': true });
|
await user.update({ 'flags.chatRevoked': true });
|
||||||
const party = await user.post('/groups', {
|
const party = await user.post('/groups', {
|
||||||
name: partyName,
|
name: partyName,
|
||||||
type: partyType,
|
type: partyType,
|
||||||
@@ -120,7 +272,7 @@ describe('POST /group', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not require gems to create a party', async () => {
|
it('does not require gems to create a party', async () => {
|
||||||
await user.updateOne({ balance: 0 });
|
await user.update({ balance: 0 });
|
||||||
|
|
||||||
const party = await user.post('/groups', {
|
const party = await user.post('/groups', {
|
||||||
name: partyName,
|
name: partyName,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { v4 as generateUUID } from 'uuid';
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
|
checkExistence,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
@@ -18,24 +19,81 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Joining a private guild', () => {
|
context('Joining a public guild', () => {
|
||||||
let user;
|
let user; let joiningUser; let
|
||||||
let invitedUser;
|
publicGuild;
|
||||||
let guild;
|
|
||||||
let invitees;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
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: {
|
groupDetails: {
|
||||||
name: 'Test Guild',
|
name: 'Test Guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
invites: 1,
|
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 () => {
|
it('returns error when user is not invited to private guild', async () => {
|
||||||
@@ -78,7 +136,7 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not increment basilist quest count to inviter with basilist when joining a guild', async () => {
|
it('does not increment basilist quest count to inviter with basilist when joining a guild', async () => {
|
||||||
await user.updateOne({ 'items.quests.basilist': 1 });
|
await user.update({ 'items.quests.basilist': 1 });
|
||||||
|
|
||||||
await invitedUser.post(`/groups/${guild._id}/join`);
|
await invitedUser.post(`/groups/${guild._id}/join`);
|
||||||
|
|
||||||
@@ -125,7 +183,7 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
|
|
||||||
party = group;
|
party = group;
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
[invitedUser] = invitees;
|
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when user is not invited to party', async () => {
|
it('returns error when user is not invited to party', async () => {
|
||||||
@@ -146,7 +204,7 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Issue #12291: accepting a redundant party invite will let the user stay in the party', async () => {
|
it('Issue #12291: accepting a redundant party invite will let the user stay in the party', async () => {
|
||||||
await invitedUser.updateOne({
|
await invitedUser.update({
|
||||||
'party._id': party._id,
|
'party._id': party._id,
|
||||||
});
|
});
|
||||||
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||||
@@ -193,15 +251,56 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('increments basilist quest item count to inviter when joining a party', async () => {
|
it('increments basilist quest item count to inviter when joining a party', async () => {
|
||||||
await user.updateOne({ 'items.quests.basilist': 1 });
|
await user.update({ 'items.quests.basilist': 1 });
|
||||||
|
|
||||||
await invitedUser.post(`/groups/${party._id}/join`);
|
await invitedUser.post(`/groups/${party._id}/join`);
|
||||||
|
|
||||||
await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 2);
|
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 () => {
|
it('invites joining member to active quest', async () => {
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
[`items.quests.${PET_QUEST}`]: 1,
|
[`items.quests.${PET_QUEST}`]: 1,
|
||||||
});
|
});
|
||||||
await user.post(`/groups/${party._id}/quests/invite/${PET_QUEST}`);
|
await user.post(`/groups/${party._id}/quests/invite/${PET_QUEST}`);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
generateChallenge,
|
generateChallenge,
|
||||||
checkExistence,
|
checkExistence,
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
|
sleep,
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} 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';
|
import calculateSubscriptionTerminationDate from '../../../../../website/server/libs/payments/calculateSubscriptionTerminationDate';
|
||||||
|
|
||||||
describe('POST /groups/:groupId/leave', () => {
|
describe('POST /groups/:groupId/leave', () => {
|
||||||
let groupToLeave;
|
const typesOfGroups = {
|
||||||
let leader;
|
'public guild': { type: 'guild', privacy: 'public' },
|
||||||
let member;
|
'private guild': { type: 'guild', privacy: 'private' },
|
||||||
let members;
|
party: { type: 'party', privacy: 'private' },
|
||||||
let memberCount;
|
};
|
||||||
|
|
||||||
context('Leaving a Group Plan', () => {
|
each(typesOfGroups, (groupDetails, groupType) => {
|
||||||
beforeEach(async () => {
|
context(`Leaving a ${groupType}`, () => {
|
||||||
({ group: groupToLeave, groupLeader: leader, members } = await createAndPopulateGroup({
|
let groupToLeave;
|
||||||
type: 'guild',
|
let leader;
|
||||||
privacy: 'private',
|
let member;
|
||||||
members: 1,
|
let memberCount;
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
[member] = members;
|
|
||||||
memberCount = groupToLeave.memberCount;
|
|
||||||
await leader.updateOne({ '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;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
challenge = await generateChallenge(leader, groupToLeave);
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
await member.post(`/challenges/${challenge._id}/join`);
|
groupDetails,
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
groupToLeave = group;
|
||||||
text: 'test habit',
|
leader = groupLeader;
|
||||||
type: 'habit',
|
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 () => {
|
it(`lets user leave a ${groupType}`, 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`);
|
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 () => {
|
it(`sets a new group leader when leader leaves a ${groupType}`, async () => {
|
||||||
await member.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
|
||||||
const userWithChallengeTasks = await member.get('/user');
|
await groupToLeave.sync();
|
||||||
|
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
expect(groupToLeave.leader).to.equal(member._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
it('removes new messages for that group from user', async () => {
|
||||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
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', () => {
|
context('Leaving a group as the last member', () => {
|
||||||
let invitees;
|
context('private guild', () => {
|
||||||
let invitedUser;
|
let privateGuild;
|
||||||
|
let leader;
|
||||||
|
let invitedUser;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
({
|
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||||
group: groupToLeave,
|
groupDetails: {
|
||||||
groupLeader: leader,
|
name: 'Test Private Guild',
|
||||||
members,
|
type: 'guild',
|
||||||
invitees,
|
},
|
||||||
} = await createAndPopulateGroup({
|
invites: 1,
|
||||||
type: 'party',
|
leaderDetails: {
|
||||||
privacy: 'private',
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
members: 1,
|
balance: 10,
|
||||||
invites: 1,
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
[member] = members;
|
privateGuild = group;
|
||||||
[invitedUser] = invitees;
|
leader = groupLeader;
|
||||||
memberCount = groupToLeave.memberCount;
|
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||||
await leader.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('prevents non members from leaving', async () => {
|
await leader.post(`/groups/${group._id}/chat`, { message: 'Some message' });
|
||||||
const user = await generateUser();
|
});
|
||||||
await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
|
|
||||||
code: 404,
|
it('removes a group when the last member leaves', async () => {
|
||||||
error: 'NotFound',
|
await leader.post(`/groups/${privateGuild._id}/leave`);
|
||||||
message: t('groupNotFound'),
|
|
||||||
|
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 () => {
|
context('public guild', () => {
|
||||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
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;
|
publicGuild = group;
|
||||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
leader = groupLeader;
|
||||||
await groupToLeave.sync();
|
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
});
|
||||||
|
|
||||||
|
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 () => {
|
context('party', () => {
|
||||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
let party;
|
||||||
|
let leader;
|
||||||
|
let invitedUser;
|
||||||
|
|
||||||
await groupToLeave.sync();
|
beforeEach(async () => {
|
||||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||||
expect(groupToLeave.leader).to.equal(member._id);
|
groupDetails: {
|
||||||
});
|
name: 'Test Party',
|
||||||
|
type: 'party',
|
||||||
|
},
|
||||||
|
invites: 1,
|
||||||
|
});
|
||||||
|
|
||||||
it('removes new messages for that group from user', async () => {
|
party = group;
|
||||||
await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
leader = groupLeader;
|
||||||
await member.sync();
|
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;
|
it('removes a group when the last member leaves a party', async () => {
|
||||||
expect(member.newMessages[groupToLeave._id]).to.not.be.empty;
|
await leader.post(`/groups/${party._id}/leave`);
|
||||||
|
|
||||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
await expect(checkExistence('party', party._id)).to.eventually.equal(false);
|
||||||
await member.sync();
|
});
|
||||||
|
|
||||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
it('removes invitations when the last member leaves a party', async () => {
|
||||||
expect(member.newMessages[groupToLeave._id]).to.be.undefined;
|
await leader.post(`/groups/${party._id}/leave`);
|
||||||
});
|
|
||||||
|
|
||||||
it('removes a party when the last member leaves', async () => {
|
const userWithoutInvitation = await invitedUser.get('/user');
|
||||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
|
||||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
|
||||||
|
|
||||||
await expect(checkExistence('party', groupToLeave._id)).to.eventually.equal(false);
|
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deletes non existent party from user when user tries to leave', async () => {
|
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({});
|
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) => {
|
each(typesOfGroups, (groupDetails, groupType) => {
|
||||||
context(`Leaving a group plan when the group is a ${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 groupWithPlan;
|
||||||
|
let leader;
|
||||||
|
let member;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
({ group: groupWithPlan, groupLeader: leader, members } = await createAndPopulateGroup({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails,
|
groupDetails,
|
||||||
members: 1,
|
members: 1,
|
||||||
upgradeToGroupPlan: true,
|
});
|
||||||
}));
|
leader = groupLeader;
|
||||||
[member] = members;
|
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||||
|
groupWithPlan = group;
|
||||||
const userWithFreePlan = await User.findById(leader._id).exec();
|
const userWithFreePlan = await User.findById(leader._id).exec();
|
||||||
|
|
||||||
// Create subscription
|
// Create subscription
|
||||||
@@ -302,22 +321,46 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
await member.sync();
|
await member.sync();
|
||||||
expect(member.purchased.plan.dateTerminated).to.exist;
|
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) => {
|
each(typesOfGroups, (groupDetails, groupType) => {
|
||||||
context(`Leaving a group with extraMonths left plan when the group is a ${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;
|
const extraMonths = 12;
|
||||||
let groupWithPlan;
|
let groupWithPlan;
|
||||||
|
let member;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
({ group: groupWithPlan, members } = await createAndPopulateGroup({
|
const { group, members } = await createAndPopulateGroup({
|
||||||
groupDetails,
|
groupDetails,
|
||||||
members: 1,
|
members: 1,
|
||||||
upgradeToGroupPlan: true,
|
upgradeToGroupPlan: true,
|
||||||
}));
|
});
|
||||||
[member] = members;
|
[member] = members;
|
||||||
await member.updateOne({
|
groupWithPlan = group;
|
||||||
|
await member.update({
|
||||||
'purchased.plan.extraMonths': extraMonths,
|
'purchased.plan.extraMonths': extraMonths,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,43 @@ import {
|
|||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('POST /group/:groupId/reject-invite', () => {
|
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', () => {
|
context('Rejecting a private guild invite', () => {
|
||||||
let invitedUser; let
|
let invitedUser; let
|
||||||
guild;
|
guild;
|
||||||
@@ -17,7 +54,6 @@ describe('POST /group/:groupId/reject-invite', () => {
|
|||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
invites: 1,
|
invites: 1,
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
guild = group;
|
guild = group;
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
},
|
},
|
||||||
invites: 1,
|
invites: 1,
|
||||||
members: 2,
|
members: 2,
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
guild = group;
|
guild = group;
|
||||||
@@ -130,11 +129,9 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
it('sends email to removed user', async () => {
|
it('sends email to removed user', async () => {
|
||||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
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][0]._id).to.eql(member._id);
|
||||||
expect(email.sendTxn.args[0][1]).to.eql('kicked-from-guild');
|
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');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -212,7 +209,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
|
|
||||||
it('removes user from quest when removing user from party after quest starts', async () => {
|
it('removes user from quest when removing user from party after quest starts', async () => {
|
||||||
const petQuest = 'whale';
|
const petQuest = 'whale';
|
||||||
await partyLeader.updateOne({
|
await partyLeader.update({
|
||||||
[`items.quests.${petQuest}`]: 1,
|
[`items.quests.${petQuest}`]: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -234,7 +231,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
|
|
||||||
it('removes user from quest when removing user from party before quest starts', async () => {
|
it('removes user from quest when removing user from party before quest starts', async () => {
|
||||||
const petQuest = 'whale';
|
const petQuest = 'whale';
|
||||||
await partyLeader.updateOne({
|
await partyLeader.update({
|
||||||
[`items.quests.${petQuest}`]: 1,
|
[`items.quests.${petQuest}`]: 1,
|
||||||
});
|
});
|
||||||
await partyInvitedUser.post(`/groups/${party._id}/join`);
|
await partyInvitedUser.post(`/groups/${party._id}/join`);
|
||||||
@@ -257,7 +254,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
|
|
||||||
it('prevents user from being removed if they are the quest owner', async () => {
|
it('prevents user from being removed if they are the quest owner', async () => {
|
||||||
const petQuest = 'whale';
|
const petQuest = 'whale';
|
||||||
await partyMember.updateOne({
|
await partyMember.update({
|
||||||
[`items.quests.${petQuest}`]: 1,
|
[`items.quests.${petQuest}`]: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
|
||||||
generateUser,
|
generateUser,
|
||||||
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
const INVITES_LIMIT = 100;
|
const INVITES_LIMIT = 100;
|
||||||
const PARTY_LIMIT_MEMBERS = 30;
|
const PARTY_LIMIT_MEMBERS = 29;
|
||||||
const MAX_EMAIL_INVITES_BY_USER = 200;
|
const MAX_EMAIL_INVITES_BY_USER = 200;
|
||||||
|
|
||||||
describe('Post /groups/:groupId/invite', () => {
|
describe('Post /groups/:groupId/invite', () => {
|
||||||
let inviter;
|
let inviter;
|
||||||
let group;
|
let group;
|
||||||
const groupName = 'Test Party';
|
const groupName = 'Test Public Guild';
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
inviter = await generateUser({ balance: 4 });
|
inviter = await generateUser({ balance: 4 });
|
||||||
group = await inviter.post('/groups', {
|
group = await inviter.post('/groups', {
|
||||||
name: groupName,
|
name: groupName,
|
||||||
type: 'party',
|
type: 'guild',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,66 +48,54 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when recipient has blocked the senders', async () => {
|
|
||||||
const inviterNoBlocks = await inviter.updateOne({ '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 () => {
|
it('invites a user to a group by username', async () => {
|
||||||
const userToInvite = await generateUser();
|
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],
|
usernames: [userToInvite.auth.local.lowerCaseUsername],
|
||||||
});
|
})).to.eventually.deep.equal([{
|
||||||
expect(response).to.be.an('Array');
|
id: group._id,
|
||||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
name: groupName,
|
||||||
expect(response[0]._id).to.be.a('String');
|
inviter: inviter._id,
|
||||||
expect(response[0].id).to.eql(group._id);
|
publicGuild: false,
|
||||||
expect(response[0].name).to.eql(groupName);
|
}]);
|
||||||
expect(response[0].inviter).to.eql(inviter._id);
|
|
||||||
|
|
||||||
await expect(userToInvite.get('/user'))
|
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 () => {
|
it('invites multiple users to a group by uuid', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
const userToInvite2 = await generateUser();
|
const userToInvite2 = await generateUser();
|
||||||
|
|
||||||
const response = await (inviter.post(`/groups/${group._id}/invite`, {
|
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||||
usernames: [
|
usernames: [
|
||||||
userToInvite.auth.local.lowerCaseUsername,
|
userToInvite.auth.local.lowerCaseUsername,
|
||||||
userToInvite2.auth.local.lowerCaseUsername,
|
userToInvite2.auth.local.lowerCaseUsername,
|
||||||
],
|
],
|
||||||
}));
|
})).to.eventually.deep.equal([
|
||||||
expect(response).to.be.an('Array');
|
{
|
||||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
id: group._id,
|
||||||
expect(response[0]._id).to.be.a('String');
|
name: groupName,
|
||||||
expect(response[0].id).to.eql(group._id);
|
inviter: inviter._id,
|
||||||
expect(response[0].name).to.eql(groupName);
|
publicGuild: false,
|
||||||
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');
|
id: group._id,
|
||||||
expect(response[1].id).to.eql(group._id);
|
name: groupName,
|
||||||
expect(response[1].name).to.eql(groupName);
|
inviter: inviter._id,
|
||||||
expect(response[1].inviter).to.eql(inviter._id);
|
publicGuild: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
await expect(userToInvite.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.parties[0].id', group._id);
|
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('user id invites', () => {
|
describe('user id invites', () => {
|
||||||
it('returns an error when inviter has no chat privileges', async () => {
|
it('returns an error when inviter has no chat privileges', async () => {
|
||||||
const inviterMuted = await inviter.updateOne({ 'flags.chatRevoked': true });
|
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
@@ -197,7 +185,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when recipient has blocked the senders', async () => {
|
it('returns error when recipient has blocked the senders', async () => {
|
||||||
const inviterNoBlocks = await inviter.updateOne({ 'inbox.blocks': [] });
|
const inviterNoBlocks = await inviter.update({ 'inbox.blocks': [] });
|
||||||
const userWithBlockedInviter = await generateUser({ 'inbox.blocks': [inviter._id] });
|
const userWithBlockedInviter = await generateUser({ 'inbox.blocks': [inviter._id] });
|
||||||
await expect(inviterNoBlocks.post(`/groups/${group._id}/invite`, {
|
await expect(inviterNoBlocks.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userWithBlockedInviter._id],
|
uuids: [userWithBlockedInviter._id],
|
||||||
@@ -212,42 +200,42 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
it('invites a user to a group by uuid', async () => {
|
it('invites a user to a group by uuid', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
|
|
||||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
});
|
})).to.eventually.deep.equal([{
|
||||||
expect(response).to.be.an('Array');
|
id: group._id,
|
||||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
name: groupName,
|
||||||
expect(response[0]._id).to.be.a('String');
|
inviter: inviter._id,
|
||||||
expect(response[0].id).to.eql(group._id);
|
publicGuild: false,
|
||||||
expect(response[0].name).to.eql(groupName);
|
}]);
|
||||||
expect(response[0].inviter).to.eql(inviter._id);
|
|
||||||
|
|
||||||
await expect(userToInvite.get('/user'))
|
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 () => {
|
it('invites multiple users to a group by uuid', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
const userToInvite2 = 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],
|
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');
|
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns an error when inviting multiple users and a user is not found', async () => {
|
it('returns an error when inviting multiple users and a user is not found', async () => {
|
||||||
@@ -269,7 +257,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
const testInvite = { name: 'test', email: 'test@habitica.com' };
|
const testInvite = { name: 'test', email: 'test@habitica.com' };
|
||||||
|
|
||||||
it('returns an error when inviter has no chat privileges', async () => {
|
it('returns an error when inviter has no chat privileges', async () => {
|
||||||
const inviterMuted = await inviter.updateOne({ 'flags.chatRevoked': true });
|
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||||
emails: [testInvite],
|
emails: [testInvite],
|
||||||
inviter: 'inviter name',
|
inviter: 'inviter name',
|
||||||
@@ -336,8 +324,12 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
invitesSent: MAX_EMAIL_INVITES_BY_USER,
|
invitesSent: MAX_EMAIL_INVITES_BY_USER,
|
||||||
balance: 4,
|
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],
|
emails: [testInvite],
|
||||||
inviter: 'inviter name',
|
inviter: 'inviter name',
|
||||||
}))
|
}))
|
||||||
@@ -413,15 +405,15 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
const invitedUser = await newUser.get('/user');
|
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;
|
expect(invite).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invites user to group with cancelled plan', async () => {
|
it('invites marks invite with cancelled plan', async () => {
|
||||||
let cancelledPlanGroup;
|
const cancelledPlanGroup = await generateGroup(inviter, {
|
||||||
({ group: cancelledPlanGroup, groupLeader: inviter } = await createAndPopulateGroup({
|
type: 'guild',
|
||||||
upgradeToGroupPlan: true,
|
name: generateUUID(),
|
||||||
}));
|
});
|
||||||
await cancelledPlanGroup.createCancelledSubscription();
|
await cancelledPlanGroup.createCancelledSubscription();
|
||||||
|
|
||||||
const newUser = await generateUser();
|
const newUser = await generateUser();
|
||||||
@@ -431,15 +423,15 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
const invitedUser = await newUser.get('/user');
|
const invitedUser = await newUser.get('/user');
|
||||||
|
|
||||||
expect(invitedUser.invitations.parties[0].id).to.equal(cancelledPlanGroup._id);
|
expect(invitedUser.invitations.guilds[0].id).to.equal(cancelledPlanGroup._id);
|
||||||
expect(invitedUser.invitations.parties[0].cancelledPlan).to.be.true;
|
expect(invitedUser.invitations.guilds[0].cancelledPlan).to.be.true;
|
||||||
expect(invite).to.exist;
|
expect(invite).to.exist;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('party invites', () => {
|
describe('guild invites', () => {
|
||||||
it('returns an error when inviter has no chat privileges', async () => {
|
it('returns an error when inviter has no chat privileges', async () => {
|
||||||
const inviterMuted = await inviter.updateOne({ 'flags.chatRevoked': true });
|
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
@@ -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();
|
const userToInvite = await generateUser();
|
||||||
await inviter.post(`/groups/${group._id}/invite`, {
|
await inviter.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
@@ -459,6 +451,96 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
|
|
||||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
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({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
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 () => {
|
it('returns an error when invited user is already in a party of more than 1 member', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
const userToInvite2 = 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],
|
uuids: [userToInvite._id, userToInvite2._id],
|
||||||
});
|
});
|
||||||
await userToInvite.post(`/groups/${group._id}/join`);
|
await userToInvite.post(`/groups/${party._id}/join`);
|
||||||
await userToInvite2.post(`/groups/${group._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],
|
uuids: [userToInvite._id],
|
||||||
}))
|
}))
|
||||||
.to.eventually.be.rejected.and.eql({
|
.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
|
// Create another inviter
|
||||||
const inviter2 = await generateUser();
|
const inviter2 = await generateUser();
|
||||||
|
|
||||||
@@ -500,7 +595,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Invite to first party
|
// Invite to first party
|
||||||
await inviter.post(`/groups/${group._id}/invite`, {
|
await inviter.post(`/groups/${party._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -513,65 +608,49 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
const invitedUser = await userToInvite.get('/user');
|
const invitedUser = await userToInvite.get('/user');
|
||||||
|
|
||||||
expect(invitedUser.invitations.parties.length).to.equal(2);
|
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);
|
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({
|
const userToInvite = await generateUser({
|
||||||
party: { _id: generateUUID() },
|
party: { _id: generateUUID() },
|
||||||
});
|
});
|
||||||
|
|
||||||
await inviter.post(`/groups/${group._id}/invite`, {
|
await inviter.post(`/groups/${party._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
});
|
});
|
||||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(group._id);
|
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._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,
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows 30 members in a party', async () => {
|
it('allows 30 members in a party', async () => {
|
||||||
const invitesToGenerate = [];
|
const invitesToGenerate = [];
|
||||||
// Generate 10 new invites
|
// Generate 29 users to invite (29 + leader = 30 members)
|
||||||
for (let i = 1; i < 10; i += 1) {
|
for (let i = 0; i < PARTY_LIMIT_MEMBERS - 1; i += 1) {
|
||||||
invitesToGenerate.push(generateUser());
|
invitesToGenerate.push(generateUser());
|
||||||
}
|
}
|
||||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
// Invite users
|
// Invite users
|
||||||
expect(await partyLeader.post(`/groups/${group._id}/invite`, {
|
expect(await inviter.post(`/groups/${party._id}/invite`, {
|
||||||
uuids: generatedInvites.map(invite => invite._id),
|
uuids: generatedInvites.map(invite => invite._id),
|
||||||
})).to.be.an('array');
|
})).to.be.an('array');
|
||||||
}).timeout(10000);
|
}).timeout(10000);
|
||||||
|
|
||||||
it('does not allow >30 members in a party', async () => {
|
it('does not allow 30+ members in a party', async () => {
|
||||||
const invitesToGenerate = [];
|
const invitesToGenerate = [];
|
||||||
// Generate 11 invites
|
// Generate 30 users to invite (30 + leader = 31 members)
|
||||||
for (let i = 1; i < 11; i += 1) {
|
for (let i = 0; i < PARTY_LIMIT_MEMBERS; i += 1) {
|
||||||
invitesToGenerate.push(generateUser());
|
invitesToGenerate.push(generateUser());
|
||||||
}
|
}
|
||||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
// Invite users
|
// Invite users
|
||||||
await expect(partyLeader.post(`/groups/${group._id}/invite`, {
|
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||||
uuids: generatedInvites.map(invite => invite._id),
|
uuids: generatedInvites.map(invite => invite._id),
|
||||||
}))
|
}))
|
||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
error: 'BadRequest',
|
error: 'BadRequest',
|
||||||
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS }),
|
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS + 1 }),
|
||||||
});
|
});
|
||||||
}).timeout(10000);
|
}).timeout(10000);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ describe('POST /group/:groupId/add-manager', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: groupName,
|
name: groupName,
|
||||||
type: groupType,
|
type: groupType,
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
groupToUpdate = group;
|
groupToUpdate = group;
|
||||||
|
|||||||
@@ -23,11 +23,10 @@ describe('PUT /group', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: groupName,
|
name: groupName,
|
||||||
type: groupType,
|
type: groupType,
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
categories: groupCategories,
|
categories: groupCategories,
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
adminUser = await generateUser({ 'permissions.moderator': true });
|
adminUser = await generateUser({ 'permissions.moderator': true });
|
||||||
groupToUpdate = group;
|
groupToUpdate = group;
|
||||||
@@ -107,28 +106,14 @@ describe('PUT /group', () => {
|
|||||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not allow a leader to change leader of active group plan', async () => {
|
it('allows a leader to change leaders', async () => {
|
||||||
await expect(leader.put(`/groups/${groupToUpdate._id}`, {
|
const updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
||||||
name: groupUpdatedName,
|
name: groupUpdatedName,
|
||||||
leader: nonLeader._id,
|
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._id).to.eql(nonLeader._id);
|
||||||
expect(updatedGroup.leader.profile.name).to.eql(members[0].profile.name);
|
expect(updatedGroup.leader.profile.name).to.eql(nonLeader.profile.name);
|
||||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -137,16 +122,15 @@ describe('PUT /group', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'public guild',
|
name: 'public guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateGroupDetails = {
|
const updateGroupDetails = {
|
||||||
id: group._id,
|
id: group._id,
|
||||||
name: 'public guild',
|
name: 'public guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
bannedWordsAllowed: true,
|
bannedWordsAllowed: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,11 +150,9 @@ describe('PUT /group', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'public guild',
|
name: 'public guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
await groupLeader.updateOne({ permissions: {} });
|
|
||||||
|
|
||||||
const updateGroupDetails = {
|
const updateGroupDetails = {
|
||||||
id: group._id,
|
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,149 +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([
|
|
||||||
'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 */
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -181,8 +181,7 @@ describe('POST /members/transfer-gems', () => {
|
|||||||
const updatedSender = await userToSendMessage.get('/user');
|
const updatedSender = await userToSendMessage.get('/user');
|
||||||
|
|
||||||
const sendersMessageInReceiversInbox = findMessage(
|
const sendersMessageInReceiversInbox = findMessage(
|
||||||
updatedReceiver.inbox.messages,
|
updatedReceiver.inbox.messages, userToSendMessage._id,
|
||||||
userToSendMessage._id,
|
|
||||||
);
|
);
|
||||||
const sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
const sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||||
|
|
||||||
@@ -213,8 +212,7 @@ describe('POST /members/transfer-gems', () => {
|
|||||||
const updatedSender = await userToSendMessage.get('/user');
|
const updatedSender = await userToSendMessage.get('/user');
|
||||||
|
|
||||||
const sendersMessageInReceiversInbox = findMessage(
|
const sendersMessageInReceiversInbox = findMessage(
|
||||||
updatedReceiver.inbox.messages,
|
updatedReceiver.inbox.messages, userToSendMessage._id,
|
||||||
userToSendMessage._id,
|
|
||||||
);
|
);
|
||||||
const sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
const sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||||
|
|
||||||
@@ -235,10 +233,10 @@ describe('POST /members/transfer-gems', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('sends transfer gems message in each participant\'s language', async () => {
|
it('sends transfer gems message in each participant\'s language', async () => {
|
||||||
await receiver.updateOne({
|
await receiver.update({
|
||||||
'preferences.language': 'es',
|
'preferences.language': 'es',
|
||||||
});
|
});
|
||||||
await userToSendMessage.updateOne({
|
await userToSendMessage.update({
|
||||||
'preferences.language': 'cs',
|
'preferences.language': 'cs',
|
||||||
});
|
});
|
||||||
await userToSendMessage.post('/members/transfer-gems', {
|
await userToSendMessage.post('/members/transfer-gems', {
|
||||||
@@ -250,8 +248,7 @@ describe('POST /members/transfer-gems', () => {
|
|||||||
const updatedSender = await userToSendMessage.get('/user');
|
const updatedSender = await userToSendMessage.get('/user');
|
||||||
|
|
||||||
const sendersMessageInReceiversInbox = findMessage(
|
const sendersMessageInReceiversInbox = findMessage(
|
||||||
updatedReceiver.inbox.messages,
|
updatedReceiver.inbox.messages, userToSendMessage._id,
|
||||||
userToSendMessage._id,
|
|
||||||
);
|
);
|
||||||
const sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
const sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe('POST /notifications/:notificationId/read', () => {
|
|||||||
const id = generateUUID();
|
const id = generateUUID();
|
||||||
const id2 = generateUUID();
|
const id2 = generateUUID();
|
||||||
|
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
notifications: [{
|
notifications: [{
|
||||||
id,
|
id,
|
||||||
type: 'DROPS_ENABLED',
|
type: 'DROPS_ENABLED',
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe('POST /notifications/:notificationId/see', () => {
|
|||||||
const id = generateUUID();
|
const id = generateUUID();
|
||||||
const id2 = generateUUID();
|
const id2 = generateUUID();
|
||||||
|
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
notifications: [{
|
notifications: [{
|
||||||
id,
|
id,
|
||||||
type: 'DROPS_ENABLED',
|
type: 'DROPS_ENABLED',
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ describe('POST /notifications/read', () => {
|
|||||||
const id2 = generateUUID();
|
const id2 = generateUUID();
|
||||||
const id3 = generateUUID();
|
const id3 = generateUUID();
|
||||||
|
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
notifications: [{
|
notifications: [{
|
||||||
id,
|
id,
|
||||||
type: 'DROPS_ENABLED',
|
type: 'DROPS_ENABLED',
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ describe('POST /notifications/see', () => {
|
|||||||
const id2 = generateUUID();
|
const id2 = generateUUID();
|
||||||
const id3 = generateUUID();
|
const id3 = generateUUID();
|
||||||
|
|
||||||
await user.updateOne({
|
await user.update({
|
||||||
notifications: [{
|
notifications: [{
|
||||||
id,
|
id,
|
||||||
type: 'DROPS_ENABLED',
|
type: 'DROPS_ENABLED',
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe('Prevent multiple notifications', () => {
|
|||||||
|
|
||||||
for (let i = 0; i < 4; i += 1) {
|
for (let i = 0; i < 4; i += 1) {
|
||||||
for (let memberIndex = 0; memberIndex < partyMembers.length; memberIndex += 1) {
|
for (let memberIndex = 0; memberIndex < partyMembers.length; memberIndex += 1) {
|
||||||
await partyMembers[memberIndex].updateOne({ 'auth.timestamps.created': new Date('2022-01-01') }); // eslint-disable-line no-await-in-loop
|
await partyMembers[memberIndex].update({ 'auth.timestamps.created': new Date('2022-01-01') }); // eslint-disable-line no-await-in-loop
|
||||||
multipleChatMessages.push(
|
multipleChatMessages.push(
|
||||||
partyMembers[memberIndex].post(`/groups/${party._id}/chat`, { message: `Message ${i}_${memberIndex}` }),
|
partyMembers[memberIndex].post(`/groups/${party._id}/chat`, { message: `Message ${i}_${memberIndex}` }),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
|
||||||
generateUser,
|
generateUser,
|
||||||
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||||
@@ -50,21 +50,22 @@ describe('payments : amazon #subscribeCancel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('cancels a group subscription', async () => {
|
it('cancels a group subscription', async () => {
|
||||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
user = await generateUser({
|
||||||
groupDetails: {
|
'profile.name': 'sender',
|
||||||
name: 'test group',
|
'purchased.plan.customerId': 'customer-id',
|
||||||
type: 'guild',
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
privacy: 'private',
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
},
|
balance: 2,
|
||||||
leaderDetails: {
|
});
|
||||||
'profile.name': 'sender',
|
|
||||||
'purchased.plan.customerId': 'customer-id',
|
group = await generateGroup(user, {
|
||||||
'purchased.plan.planId': 'basic_3mo',
|
name: 'test group',
|
||||||
'purchased.plan.lastBillingDate': new Date(),
|
type: 'guild',
|
||||||
balance: 2,
|
privacy: 'public',
|
||||||
},
|
'purchased.plan.customerId': 'customer-id',
|
||||||
upgradeToGroupPlan: true,
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
}));
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ describe('payments - amazon - #subscribe', () => {
|
|||||||
|
|
||||||
group = await generateGroup(user, {
|
group = await generateGroup(user, {
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'party',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'public',
|
||||||
'purchased.plan.customerId': 'customer-id',
|
'purchased.plan.customerId': 'customer-id',
|
||||||
'purchased.plan.planId': 'basic_3mo',
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
'purchased.plan.lastBillingDate': new Date(),
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
|
|||||||
@@ -45,10 +45,11 @@ describe('payments : apple #subscribe', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(subscribeStub).to.be.calledOnce;
|
expect(subscribeStub).to.be.calledOnce;
|
||||||
expect(subscribeStub.args[0][0]._id).to.eql(user._id);
|
expect(subscribeStub.args[0][0]).to.eql(sku);
|
||||||
expect(subscribeStub.args[0][1]).to.eql('receipt');
|
expect(subscribeStub.args[0][1]._id).to.eql(user._id);
|
||||||
expect(subscribeStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
|
expect(subscribeStub.args[0][2]).to.eql('receipt');
|
||||||
expect(subscribeStub.args[0][2]['x-api-user']).to.eql(user._id);
|
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;
|
let verifyStub;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
verifyStub = sinon.stub(applePayments, 'verifyPurchase').resolves({});
|
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
applePayments.verifyPurchase.restore();
|
applePayments.verifyGemPurchase.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('makes a purchase', async () => {
|
it('makes a purchase', async () => {
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ describe('payments : google #verify', () => {
|
|||||||
let verifyStub;
|
let verifyStub;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
verifyStub = sinon.stub(googlePayments, 'verifyPurchase').resolves({});
|
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
googlePayments.verifyPurchase.restore();
|
googlePayments.verifyGemPurchase.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('makes a purchase', async () => {
|
it('makes a purchase', async () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
createAndPopulateGroup,
|
|
||||||
} from '../../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||||
|
|
||||||
@@ -48,21 +48,22 @@ describe('payments - stripe - #subscribeCancel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('cancels a group subscription', async () => {
|
it('cancels a group subscription', async () => {
|
||||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
user = await generateUser({
|
||||||
groupDetails: {
|
'profile.name': 'sender',
|
||||||
name: 'test group',
|
'purchased.plan.customerId': 'customer-id',
|
||||||
type: 'guild',
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
privacy: 'private',
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
},
|
balance: 2,
|
||||||
leaderDetails: {
|
});
|
||||||
'profile.name': 'sender',
|
|
||||||
'purchased.plan.customerId': 'customer-id',
|
group = await generateGroup(user, {
|
||||||
'purchased.plan.planId': 'basic_3mo',
|
name: 'test group',
|
||||||
'purchased.plan.lastBillingDate': new Date(),
|
type: 'guild',
|
||||||
balance: 2,
|
privacy: 'public',
|
||||||
},
|
'purchased.plan.customerId': 'customer-id',
|
||||||
upgradeToGroupPlan: true,
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
}));
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
|||||||
leader = groupLeader;
|
leader = groupLeader;
|
||||||
partyMembers = members;
|
partyMembers = members;
|
||||||
|
|
||||||
await leader.updateOne({
|
await leader.update({
|
||||||
[`items.quests.${PET_QUEST}`]: 1,
|
[`items.quests.${PET_QUEST}`]: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -53,7 +53,6 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
|||||||
it('does not accept quest for a guild', async () => {
|
it('does not accept quest for a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`))
|
await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`))
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
|||||||
leader = groupLeader;
|
leader = groupLeader;
|
||||||
partyMembers = members;
|
partyMembers = members;
|
||||||
|
|
||||||
await leader.updateOne({
|
await leader.update({
|
||||||
[`items.quests.${PET_QUEST}`]: 1,
|
[`items.quests.${PET_QUEST}`]: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -43,7 +43,6 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
|||||||
it('does not force start quest for a guild', async () => {
|
it('does not force start quest for a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/force-start`))
|
await expect(guildLeader.post(`/groups/${guild._id}/quests/force-start`))
|
||||||
@@ -93,7 +92,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
|||||||
context('successfully force starting a quest', () => {
|
context('successfully force starting a quest', () => {
|
||||||
it('allows quest leader to force start quest', async () => {
|
it('allows quest leader to force start quest', async () => {
|
||||||
const questLeader = partyMembers[0];
|
const questLeader = partyMembers[0];
|
||||||
await questLeader.updateOne({ [`items.quests.${PET_QUEST}`]: 1 });
|
await questLeader.update({ [`items.quests.${PET_QUEST}`]: 1 });
|
||||||
await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||||
|
|
||||||
await questLeader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
await questLeader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
||||||
@@ -105,7 +104,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
|||||||
|
|
||||||
it('allows group leader to force start quest', async () => {
|
it('allows group leader to force start quest', async () => {
|
||||||
const questLeader = partyMembers[0];
|
const questLeader = partyMembers[0];
|
||||||
await questLeader.updateOne({ [`items.quests.${PET_QUEST}`]: 1 });
|
await questLeader.update({ [`items.quests.${PET_QUEST}`]: 1 });
|
||||||
await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||||
|
|
||||||
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
||||||
@@ -177,7 +176,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
|||||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||||
const notInPartyUser = await generateUser();
|
const notInPartyUser = await generateUser();
|
||||||
|
|
||||||
await questingGroup.updateOne({
|
await questingGroup.update({
|
||||||
[`quest.members.${notInPartyUser._id}`]: true,
|
[`quest.members.${notInPartyUser._id}`]: true,
|
||||||
});
|
});
|
||||||
await questingGroup.sync();
|
await questingGroup.sync();
|
||||||
@@ -217,7 +216,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
|||||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||||
|
|
||||||
await questingGroup.updateOne({
|
await questingGroup.update({
|
||||||
[`quest.members.${partyMemberThatRejects._id}`]: false,
|
[`quest.members.${partyMemberThatRejects._id}`]: false,
|
||||||
[`quest.members.${partyMemberThatIgnores._id}`]: null,
|
[`quest.members.${partyMemberThatIgnores._id}`]: null,
|
||||||
});
|
});
|
||||||
@@ -236,7 +235,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
|||||||
|
|
||||||
it('allows group leader to force start quest and verifies chat', async () => {
|
it('allows group leader to force start quest and verifies chat', async () => {
|
||||||
const questLeader = partyMembers[0];
|
const questLeader = partyMembers[0];
|
||||||
await questLeader.updateOne({ [`items.quests.${PET_QUEST}`]: 1 });
|
await questLeader.update({ [`items.quests.${PET_QUEST}`]: 1 });
|
||||||
await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||||
|
|
||||||
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
|
||||||
|
|||||||
@@ -51,13 +51,14 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not issue invites for Guilds', async () => {
|
it('does not issue invites for Guilds', async () => {
|
||||||
const { group, groupLeader } = await createAndPopulateGroup({
|
const { group } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'public' },
|
||||||
members: 1,
|
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,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('guildQuestsNotSupported'),
|
message: t('guildQuestsNotSupported'),
|
||||||
@@ -87,8 +88,8 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
|||||||
const leaderUpdate = {};
|
const leaderUpdate = {};
|
||||||
leaderUpdate[`items.quests.${PET_QUEST}`] = 1;
|
leaderUpdate[`items.quests.${PET_QUEST}`] = 1;
|
||||||
|
|
||||||
await leader.updateOne(leaderUpdate);
|
await leader.update(leaderUpdate);
|
||||||
await questingGroup.updateOne({ 'quest.key': QUEST_IN_PROGRESS });
|
await questingGroup.update({ 'quest.key': QUEST_IN_PROGRESS });
|
||||||
|
|
||||||
await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
@@ -104,8 +105,8 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
|||||||
memberUpdate[`items.quests.${PET_QUEST}`] = 1;
|
memberUpdate[`items.quests.${PET_QUEST}`] = 1;
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
leader.updateOne(memberUpdate),
|
leader.update(memberUpdate),
|
||||||
member.updateOne(memberUpdate),
|
member.update(memberUpdate),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -202,7 +203,7 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
|||||||
leaderUpdate[`items.quests.${LEVELED_QUEST}`] = 1;
|
leaderUpdate[`items.quests.${LEVELED_QUEST}`] = 1;
|
||||||
leaderUpdate['stats.lvl'] = LEVELED_QUEST_REQ - 1;
|
leaderUpdate['stats.lvl'] = LEVELED_QUEST_REQ - 1;
|
||||||
|
|
||||||
await leader.updateOne(leaderUpdate);
|
await leader.update(leaderUpdate);
|
||||||
|
|
||||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${LEVELED_QUEST}`);
|
await leader.post(`/groups/${questingGroup._id}/quests/invite/${LEVELED_QUEST}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
|||||||
leader = groupLeader;
|
leader = groupLeader;
|
||||||
partyMembers = members;
|
partyMembers = members;
|
||||||
|
|
||||||
await leader.updateOne({
|
await leader.update({
|
||||||
[`items.quests.${PET_QUEST}`]: 1,
|
[`items.quests.${PET_QUEST}`]: 1,
|
||||||
});
|
});
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
@@ -52,7 +52,6 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
|||||||
it('returns an error when group is a guild', async () => {
|
it('returns an error when group is a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`))
|
await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`))
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
|||||||
leader = groupLeader;
|
leader = groupLeader;
|
||||||
partyMembers = members;
|
partyMembers = members;
|
||||||
|
|
||||||
await leader.updateOne({
|
await leader.update({
|
||||||
[`items.quests.${PET_QUEST}`]: 1,
|
[`items.quests.${PET_QUEST}`]: 1,
|
||||||
});
|
});
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
@@ -52,7 +52,6 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
|||||||
it('returns an error when group is a guild', async () => {
|
it('returns an error when group is a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`))
|
await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`))
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ describe('POST /groups/:groupId/quests/leave', () => {
|
|||||||
leader = groupLeader;
|
leader = groupLeader;
|
||||||
partyMembers = members;
|
partyMembers = members;
|
||||||
|
|
||||||
await leader.updateOne({
|
await leader.update({
|
||||||
[`items.quests.${PET_QUEST}`]: 1,
|
[`items.quests.${PET_QUEST}`]: 1,
|
||||||
});
|
});
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
@@ -51,7 +51,6 @@ describe('POST /groups/:groupId/quests/leave', () => {
|
|||||||
it('returns an error when group is a guild', async () => {
|
it('returns an error when group is a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`))
|
await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`))
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
|||||||
leader = groupLeader;
|
leader = groupLeader;
|
||||||
partyMembers = members;
|
partyMembers = members;
|
||||||
|
|
||||||
await leader.updateOne({
|
await leader.update({
|
||||||
[`items.quests.${PET_QUEST}`]: 1,
|
[`items.quests.${PET_QUEST}`]: 1,
|
||||||
});
|
});
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
@@ -53,7 +53,6 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
|||||||
it('returns an error when group is a guild', async () => {
|
it('returns an error when group is a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
upgradeToGroupPlan: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`))
|
await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user