mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-23 02:49:46 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f8fd4ae350 | |||
| 21f69e4d7c | |||
| ee09b1b60e | |||
| 7f1d332441 | |||
| b370a28c7a | |||
| 493f90fe07 | |||
| bbf537421a | |||
| 15cf900caa | |||
| 3c9771dd0e | |||
| 1236b55664 | |||
| ad7d4ac89e | |||
| 094c5b54d6 |
+2
-6
@@ -1,10 +1,6 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'habitrpg/lib/node',
|
||||
'habitrpg/lib/node'
|
||||
],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
name: job setup
|
||||
description: 'Sets the shared steps for each job'
|
||||
|
||||
inputs:
|
||||
node-version:
|
||||
description: 'The Node version to be setup'
|
||||
required: true
|
||||
node-env:
|
||||
description: 'Node-Env for CI'
|
||||
required: true
|
||||
package-install-cmd:
|
||||
description: 'CI or install or custom to skip post install and unneeded builds'
|
||||
required: false
|
||||
default: 'ci'
|
||||
install-website-package:
|
||||
description: 'if package-install-cmd skipped post install, you can trigger an installation of website'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
website-package-install-cmd:
|
||||
description: 'CI or install or custom to skip post install and unneeded builds'
|
||||
required: false
|
||||
default: 'ci'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Use Node.js ${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
- name: Cache multiple paths
|
||||
id: cache-package
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-${{ inputs.node-version }}-${{ inputs.node-env }}-${{ inputs.install-website-package }}-${{ hashFiles('**/package.json') }}
|
||||
|
||||
- name: Create dummy config.json
|
||||
shell: bash
|
||||
run: cp config.json.example config.json
|
||||
|
||||
- name: npm install
|
||||
if: steps.cache-package.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
npm ${{ inputs.package-install-cmd }}
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: ${{ inputs.node-env }}
|
||||
|
||||
- name: npm install website
|
||||
if: ${{ steps.cache-package.outputs.cache-hit != 'true' && inputs.install-website-package != 'false' }}
|
||||
shell: bash
|
||||
working-directory: ./website/client
|
||||
run: |
|
||||
npm ${{ inputs.website-package-install-cmd }}
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: ${{ inputs.node-env }}
|
||||
@@ -104,6 +104,42 @@ updates:
|
||||
- 7.7.0
|
||||
- 7.8.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
|
||||
versions:
|
||||
- 3.10.0
|
||||
|
||||
+75
-93
@@ -1,6 +1,13 @@
|
||||
name: Test
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'release'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -15,17 +22,16 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
node-env: 'test'
|
||||
package-install-cmd: 'ci --ignore-scripts'
|
||||
install-website-package: 'true'
|
||||
website-package-install-cmd: 'ci --ignore-scripts'
|
||||
|
||||
- run: npm run lint-no-fix
|
||||
apidoc:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -36,17 +42,14 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
node-env: 'test'
|
||||
package-install-cmd: 'ci --ignore-scripts'
|
||||
|
||||
- run: npm run apidoc
|
||||
sanity:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -57,19 +60,16 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
node-env: 'test'
|
||||
package-install-cmd: 'ci --ignore-scripts'
|
||||
|
||||
- run: npm run test:sanity
|
||||
|
||||
|
||||
common:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -79,17 +79,14 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
node-env: 'test'
|
||||
package-install-cmd: 'ci --ignore-scripts'
|
||||
|
||||
- run: npm run test:common
|
||||
content:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -100,19 +97,16 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
node-env: 'test'
|
||||
package-install-cmd: 'ci --ignore-scripts'
|
||||
|
||||
- run: npm run test:content
|
||||
|
||||
|
||||
api-unit:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -123,22 +117,20 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-env: 'test'
|
||||
package-install-cmd: 'ci'
|
||||
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.3.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
|
||||
- run: npm run test:api:unit
|
||||
env:
|
||||
REQUIRES_SERVER=true: true
|
||||
@@ -152,22 +144,20 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-env: 'test'
|
||||
package-install-cmd: 'ci --ignore-scripts'
|
||||
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.3.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
|
||||
- run: npm run test:api-v3:integration
|
||||
env:
|
||||
REQUIRES_SERVER=true: true
|
||||
@@ -181,22 +171,20 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-env: 'test'
|
||||
package-install-cmd: 'ci --ignore-scripts'
|
||||
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.3.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
|
||||
- run: npm run test:api-v4:integration
|
||||
env:
|
||||
REQUIRES_SERVER=true: true
|
||||
@@ -210,17 +198,16 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
node-env: 'test'
|
||||
package-install-cmd: 'ci --ignore-scripts'
|
||||
install-website-package: 'true'
|
||||
website-package-install-cmd: 'ci --ignore-scripts'
|
||||
|
||||
- run: npm run test:unit
|
||||
working-directory: ./website/client
|
||||
|
||||
@@ -233,14 +220,9 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Setup Shared Job Steps
|
||||
uses: './.github/actions/shared-job-steps'
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm install
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: production
|
||||
node-env: 'production'
|
||||
|
||||
+1
-1
@@ -87,5 +87,5 @@
|
||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||
"REDIS_PORT": "1234",
|
||||
"REDIS_PASSWORD": "12345678",
|
||||
"TRUSTED_DOMAINS": "localhost,habitica.com"
|
||||
"TRUSTED_DOMAINS": "https://localhost,https://habitica.com"
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ gulp.task('build:prepare-mongo', async () => {
|
||||
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
|
||||
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) {
|
||||
const stringChunk = chunk.toString();
|
||||
|
||||
@@ -2,6 +2,7 @@ import mongoose from 'mongoose';
|
||||
import nconf from 'nconf';
|
||||
import repl from 'repl';
|
||||
import gulp from 'gulp';
|
||||
import logger from '../website/server/libs/logger';
|
||||
import {
|
||||
getDevelopmentConnectionUrl,
|
||||
getDefaultConnectionOptions,
|
||||
@@ -38,6 +39,10 @@ const improveRepl = context => {
|
||||
mongoose.connect(
|
||||
connectionUrl,
|
||||
mongooseOptions,
|
||||
err => {
|
||||
if (err) throw err;
|
||||
logger.info('Connected with Mongoose');
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+14
-24
@@ -59,15 +59,13 @@ gulp.task('test:prepare:mongo', cb => {
|
||||
const mongooseOptions = getDefaultConnectionOptions();
|
||||
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
||||
|
||||
mongoose.connect(connectionUrl, mongooseOptions)
|
||||
.then(() => mongoose.connection.dropDatabase())
|
||||
.then(() => mongoose.connection.close()).then(() => {
|
||||
cb();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
|
||||
throw err;
|
||||
mongoose.connect(connectionUrl, mongooseOptions, err => {
|
||||
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
|
||||
return mongoose.connection.dropDatabase(err2 => {
|
||||
if (err2) return cb(err2);
|
||||
return mongoose.connection.close(cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
}));
|
||||
|
||||
gulp.task('test:content', gulp.series(
|
||||
'test:prepare:build',
|
||||
runInChildProcess(CONTENT_TEST_COMMAND, LIMIT_MAX_BUFFER_OPTIONS),
|
||||
));
|
||||
gulp.task('test:content', gulp.series('test:prepare:build',
|
||||
runInChildProcess(CONTENT_TEST_COMMAND, LIMIT_MAX_BUFFER_OPTIONS)));
|
||||
|
||||
gulp.task('test:content:clean', 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);
|
||||
}));
|
||||
|
||||
gulp.task(
|
||||
'test:api:unit:run',
|
||||
runInChildProcess(integrationTestCommand('test/api/unit', 'coverage/api-unit')),
|
||||
);
|
||||
gulp.task('test:api:unit:run',
|
||||
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-v3:integration', gulp.series(
|
||||
'test:prepare:mongo',
|
||||
gulp.task('test:api-v3:integration', gulp.series('test:prepare:mongo',
|
||||
runInChildProcess(
|
||||
integrationTestCommand('test/api/v3/integration', 'coverage/api-v3-integration'),
|
||||
LIMIT_MAX_BUFFER_OPTIONS,
|
||||
),
|
||||
));
|
||||
)));
|
||||
|
||||
gulp.task('test:api-v3:integration:watch', () => gulp.watch([
|
||||
'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',
|
||||
));
|
||||
|
||||
gulp.task('test:api-v4:integration', gulp.series(
|
||||
'test:prepare:mongo',
|
||||
gulp.task('test:api-v4:integration', gulp.series('test:prepare:mongo',
|
||||
runInChildProcess(
|
||||
integrationTestCommand('test/api/v4', 'api-v4-integration'),
|
||||
LIMIT_MAX_BUFFER_OPTIONS,
|
||||
),
|
||||
));
|
||||
)));
|
||||
|
||||
gulp.task('test:api-v4:integration:separate-server', runInChildProcess(
|
||||
'mocha test/api/v4 --recursive --require ./test/helpers/start-server',
|
||||
|
||||
+1
-1
Submodule habitica-images updated: e6d883bc2e...306496f9de
@@ -3,6 +3,6 @@ module.exports = {
|
||||
root: false,
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
'no-use-before-define': ['error', { functions: false }],
|
||||
},
|
||||
};
|
||||
'no-use-before-define': ['error', { functions: false }]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
groupList = groupList.concat(user.guilds);
|
||||
|
||||
const subscribedGroup = await Group.findOne(
|
||||
{
|
||||
_id: { $in: groupList },
|
||||
'purchased.plan.planId': 'group_monthly',
|
||||
'purchased.plan.dateTerminated': null,
|
||||
},
|
||||
{ _id: 1 },
|
||||
);
|
||||
const subscribedGroup = await Group.findOne({
|
||||
_id: { $in: groupList },
|
||||
'purchased.plan.planId': 'group_monthly',
|
||||
'purchased.plan.dateTerminated': null,
|
||||
},
|
||||
{ _id: 1 });
|
||||
|
||||
if (subscribedGroup) {
|
||||
User.update({ _id: user._id }, { $set: { 'items.mounts.Jackalope-RoyalPurple': true } }).exec();
|
||||
|
||||
@@ -51,8 +51,7 @@ function getAchievementUpdate (newUser, oldUser) {
|
||||
// Rebirth level
|
||||
if (achievementsUpdate.rebirthLevel) {
|
||||
achievementsUpdate.rebirthLevel = Math.max(
|
||||
achievementsUpdate.rebirthLevel,
|
||||
oldAchievements.rebirthLevel,
|
||||
achievementsUpdate.rebirthLevel, oldAchievements.rebirthLevel,
|
||||
);
|
||||
} else if (oldAchievements.rebirthLevel) {
|
||||
achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel;
|
||||
|
||||
@@ -16,7 +16,6 @@ async function updateUser (user) {
|
||||
|
||||
if (count % progressCount === 0) {
|
||||
console.warn(`${count} ${user._id}`);
|
||||
// eslint-disable-next-line no-promise-executor-return
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
}
|
||||
|
||||
|
||||
Generated
+2232
-4409
File diff suppressed because it is too large
Load Diff
+28
-26
@@ -1,37 +1,37 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "5.16.1",
|
||||
"version": "4.274.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/register": "^7.22.15",
|
||||
"@babel/core": "^7.22.5",
|
||||
"@babel/preset-env": "^7.22.5",
|
||||
"@babel/register": "^7.22.5",
|
||||
"@google-cloud/trace-agent": "^7.1.2",
|
||||
"@parse/node-apn": "^5.2.3",
|
||||
"@parse/node-apn": "^5.1.3",
|
||||
"@slack/webhook": "^6.1.0",
|
||||
"accepts": "^1.3.8",
|
||||
"amazon-payments": "^0.2.9",
|
||||
"amplitude": "^6.0.0",
|
||||
"apidoc": "^0.54.0",
|
||||
"apple-auth": "^1.0.9",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bcrypt": "^5.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"bootstrap": "^4.6.2",
|
||||
"bootstrap": "^4.6.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-session": "^2.0.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"csv-stringify": "^5.6.5",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-habitrpg": "^6.2.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"express": "^4.18.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"express-validator": "^5.2.0",
|
||||
"glob": "^8.1.0",
|
||||
"got": "^11.8.6",
|
||||
"got": "^11.8.3",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
@@ -39,42 +39,44 @@
|
||||
"gulp.spritesmith": "^6.13.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"helmet": "^4.6.0",
|
||||
"image-size": "^1.0.2",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"js2xmlparser": "^5.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwks-rsa": "^2.1.5",
|
||||
"lodash": "^4.17.21",
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^7.6.3",
|
||||
"mongoose": "^5.13.7",
|
||||
"morgan": "^1.10.0",
|
||||
"nconf": "^0.12.1",
|
||||
"nconf": "^0.12.0",
|
||||
"node-gcm": "^1.0.5",
|
||||
"nodemon": "^2.0.20",
|
||||
"on-headers": "^1.0.2",
|
||||
"passport": "^0.5.3",
|
||||
"passport": "^0.5.0",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-google-oauth2": "^0.2.0",
|
||||
"passport-google-oauth20": "2.0.0",
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"pp-ipn": "^1.1.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"rate-limiter-flexible": "^2.4.2",
|
||||
"rate-limiter-flexible": "^2.4.0",
|
||||
"redis": "^3.1.2",
|
||||
"regenerator-runtime": "^0.13.11",
|
||||
"remove-markdown": "^0.5.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.2.2",
|
||||
"stripe": "^12.18.0",
|
||||
"superagent": "^8.1.2",
|
||||
"stripe": "^12.9.0",
|
||||
"superagent": "^8.0.9",
|
||||
"universal-analytics": "^0.5.3",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.11.0",
|
||||
"winston": "^3.10.0",
|
||||
"winston-loggly-bulk": "^3.3.0",
|
||||
"xml2js": "^0.6.2"
|
||||
"validator": "^13.9.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^3.9.0",
|
||||
"winston-loggly-bulk": "^3.2.1",
|
||||
"xml2js": "^0.6.0"
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
@@ -103,16 +105,16 @@
|
||||
"client:unit": "cd website/client && npm run test:unit",
|
||||
"start": "gulp nodemon",
|
||||
"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",
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.4.0",
|
||||
"axios": "^1.3.6",
|
||||
"chai": "^4.3.7",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
"chalk": "^5.3.0",
|
||||
"chalk": "^5.2.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
@@ -120,7 +122,7 @@
|
||||
"monk": "^7.3.4",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.7.7",
|
||||
"sinon": "^15.2.0",
|
||||
"sinon": "^15.1.2",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -44,7 +44,6 @@ async function deleteHabiticaData (user, email) {
|
||||
{ _id: user._id },
|
||||
{ $set: set },
|
||||
);
|
||||
// eslint-disable-next-line no-promise-executor-return
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const response = await axios.delete(
|
||||
`${BASE_URL}/api/v3/user`,
|
||||
@@ -97,7 +96,6 @@ async function processEmailAddress (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));
|
||||
return Promise.all(users.map(user => (async () => {
|
||||
await deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
|
||||
|
||||
+2
-7
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
'habitrpg/lib/mocha',
|
||||
@@ -9,9 +7,6 @@ module.exports = {
|
||||
chai: true,
|
||||
expect: true,
|
||||
sinon: true,
|
||||
sandbox: true,
|
||||
sandbox: true
|
||||
},
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ describe('bug-report', () => {
|
||||
_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(result).to.deep.equal({
|
||||
|
||||
@@ -227,7 +227,7 @@ describe('Password Utilities', () => {
|
||||
expiresAt: moment().subtract({ minutes: 1 }),
|
||||
}));
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
@@ -264,7 +264,7 @@ describe('Password Utilities', () => {
|
||||
expiresAt: moment().add({ days: 1 }),
|
||||
}));
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'auth.local.passwordResetCode': 'invalid',
|
||||
});
|
||||
|
||||
@@ -280,7 +280,7 @@ describe('Password Utilities', () => {
|
||||
expiresAt: moment().add({ days: 1 }),
|
||||
}));
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'auth.local.passwordResetCode': code,
|
||||
});
|
||||
|
||||
|
||||
@@ -342,12 +342,10 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||
|
||||
await applePayments.subscribe(
|
||||
user,
|
||||
await applePayments.subscribe(user,
|
||||
receipt,
|
||||
headers,
|
||||
nextPaymentProcessing,
|
||||
);
|
||||
nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -389,12 +387,10 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
sub = common.content.subscriptionBlocks[newOption.subKey];
|
||||
|
||||
await applePayments.subscribe(
|
||||
user,
|
||||
await applePayments.subscribe(user,
|
||||
receipt,
|
||||
headers,
|
||||
nextPaymentProcessing,
|
||||
);
|
||||
nextPaymentProcessing);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -521,7 +517,9 @@ describe('Apple Payments', () => {
|
||||
|
||||
const secondUser = new User();
|
||||
await secondUser.save();
|
||||
await expect(applePayments.subscribe(secondUser, receipt, headers, nextPaymentProcessing))
|
||||
await expect(applePayments.subscribe(
|
||||
secondUser, receipt, headers, nextPaymentProcessing,
|
||||
))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -561,7 +559,9 @@ describe('Apple Payments', () => {
|
||||
|
||||
const thirdUser = new User();
|
||||
await thirdUser.save();
|
||||
await expect(applePayments.subscribe(thirdUser, receipt, headers, nextPaymentProcessing))
|
||||
await expect(applePayments.subscribe(
|
||||
thirdUser, receipt, headers, nextPaymentProcessing,
|
||||
))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
|
||||
@@ -1382,6 +1382,18 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(1);
|
||||
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 +1599,10 @@ describe('payments/index', () => {
|
||||
it('sends gem donation message in each participant\'s language', async () => {
|
||||
// TODO using english for both users because other languages are not loaded
|
||||
// for api.buyGems
|
||||
await recipient.updateOne({
|
||||
await recipient.update({
|
||||
'preferences.language': 'en',
|
||||
});
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'preferences.language': 'en',
|
||||
});
|
||||
await api.buyGems(data);
|
||||
|
||||
@@ -53,9 +53,11 @@ describe('cron middleware', () => {
|
||||
cronMiddleware(req, res, err => {
|
||||
if (err) return reject(err);
|
||||
|
||||
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
||||
expect(foundTask).to.not.exist;
|
||||
resolve();
|
||||
Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
|
||||
if (secondErr) return reject(err);
|
||||
expect(secondErr).to.not.exist;
|
||||
expect(taskFound).to.not.exist;
|
||||
return resolve();
|
||||
});
|
||||
|
||||
return null;
|
||||
@@ -76,8 +78,10 @@ describe('cron middleware', () => {
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, err => {
|
||||
if (err) return reject(err);
|
||||
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
||||
expect(foundTask).to.exist;
|
||||
Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
|
||||
if (secondErr) return reject(secondErr);
|
||||
expect(secondErr).to.not.exist;
|
||||
expect(taskFound).to.exist;
|
||||
return resolve();
|
||||
});
|
||||
return null;
|
||||
@@ -99,8 +103,10 @@ describe('cron middleware', () => {
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, err => {
|
||||
if (err) return reject(err);
|
||||
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
||||
expect(foundTask).to.not.exist;
|
||||
Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
|
||||
if (secondErr) return reject(secondErr);
|
||||
expect(secondErr).to.not.exist;
|
||||
expect(taskFound).to.not.exist;
|
||||
return resolve();
|
||||
});
|
||||
return null;
|
||||
@@ -164,7 +170,8 @@ describe('cron middleware', () => {
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, 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);
|
||||
return resolve();
|
||||
});
|
||||
@@ -181,7 +188,8 @@ describe('cron middleware', () => {
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, 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);
|
||||
return resolve();
|
||||
});
|
||||
@@ -216,7 +224,8 @@ describe('cron middleware', () => {
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, 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);
|
||||
return resolve();
|
||||
});
|
||||
@@ -229,7 +238,7 @@ describe('cron middleware', () => {
|
||||
await user.save();
|
||||
|
||||
const updatedUser = user.toObject();
|
||||
updatedUser.matchedCount = 0;
|
||||
updatedUser.nMatched = 0;
|
||||
|
||||
sandbox.spy(cronLib, 'recoverCron');
|
||||
|
||||
@@ -260,7 +269,7 @@ describe('cron middleware', () => {
|
||||
it('cronSignature less than an hour ago should error', async () => {
|
||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||
const now = new Date();
|
||||
await User.updateOne({
|
||||
await User.update({
|
||||
_id: user._id,
|
||||
}, {
|
||||
$set: {
|
||||
@@ -282,7 +291,7 @@ describe('cron middleware', () => {
|
||||
it('cronSignature longer than an hour ago should allow cron', async () => {
|
||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||
const now = new Date();
|
||||
await User.updateOne({
|
||||
await User.update({
|
||||
_id: user._id,
|
||||
}, {
|
||||
$set: {
|
||||
|
||||
@@ -1358,7 +1358,7 @@ describe('Group Model', () => {
|
||||
|
||||
describe('#sendChat', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(User, 'updateOne');
|
||||
sandbox.spy(User, 'update');
|
||||
sandbox.spy(User, 'updateMany');
|
||||
});
|
||||
|
||||
@@ -1450,7 +1450,7 @@ describe('Group Model', () => {
|
||||
|
||||
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', () => {
|
||||
@@ -1458,7 +1458,7 @@ describe('Group Model', () => {
|
||||
|
||||
party.sendChat({ message: 'message' });
|
||||
|
||||
expect(User.updateMany).to.not.be.called;
|
||||
expect(User.update).to.not.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('NewsPost Model', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
// Delete all existing posts from the database
|
||||
await NewsPost.deleteMany();
|
||||
await NewsPost.remove();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -116,7 +116,7 @@ describe('NewsPost Model', () => {
|
||||
_id: v4(), publishDate: new Date(), published: true,
|
||||
};
|
||||
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
|
||||
const newPost = await NewsPost.create({
|
||||
|
||||
@@ -221,8 +221,7 @@ describe('Task Model', () => {
|
||||
|
||||
it('returns task by alias', async () => {
|
||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||
[taskWithAlias.alias],
|
||||
user._id,
|
||||
[taskWithAlias.alias], user._id,
|
||||
);
|
||||
|
||||
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
|
||||
@@ -230,8 +229,7 @@ describe('Task Model', () => {
|
||||
|
||||
it('returns multiple tasks', async () => {
|
||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||
[taskWithAlias.alias, secondTask._id],
|
||||
user._id,
|
||||
[taskWithAlias.alias, secondTask._id], user._id,
|
||||
);
|
||||
|
||||
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 () => {
|
||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||
[taskWithAlias.alias, taskWithAlias._id],
|
||||
user._id,
|
||||
[taskWithAlias.alias, taskWithAlias._id], user._id,
|
||||
);
|
||||
|
||||
expect(foundTasks.length).to.eql(1);
|
||||
|
||||
@@ -188,7 +188,7 @@ describe('User Model', () => {
|
||||
it('removes invalid tags when loading the user', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
$set: {
|
||||
tags: [
|
||||
null, // invalid, not an object
|
||||
@@ -212,7 +212,7 @@ describe('User Model', () => {
|
||||
it('removes invalid push devices when loading the user', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
$set: {
|
||||
pushDevices: [
|
||||
null, // invalid, not an object
|
||||
@@ -236,7 +236,7 @@ describe('User Model', () => {
|
||||
it('removes duplicate push devices when loading the user', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
$set: {
|
||||
pushDevices: [
|
||||
{ type: 'android', regId: '1234' },
|
||||
@@ -258,7 +258,7 @@ describe('User Model', () => {
|
||||
it('removes invalid notifications when loading the user', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
$set: {
|
||||
notifications: [
|
||||
null, // invalid, not an object
|
||||
@@ -284,7 +284,7 @@ describe('User Model', () => {
|
||||
it('removes multiple NEW_CHAT_MESSAGE for the same group', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
$set: {
|
||||
notifications: [
|
||||
{
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('DELETE /challenges/:challengeId', () => {
|
||||
const testTask = _.find(tasks, task => task.text === taskText);
|
||||
|
||||
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 challengeLeader;
|
||||
let group;
|
||||
@@ -31,14 +84,14 @@ describe('GET /challenges/:challengeId', () => {
|
||||
const populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
members = populatedGroup.members;
|
||||
|
||||
[challengeLeader, otherMember] = members;
|
||||
challengeLeader = members[0]; // eslint-disable-line prefer-destructuring
|
||||
otherMember = members[1]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
challenge = await generateChallenge(challengeLeader, group);
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
||||
});
|
||||
|
||||
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 sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
|
||||
const splitRes = res.split('\n');
|
||||
|
||||
@@ -71,18 +71,42 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('populates only some fields', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', privacy: 'private', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
it('works with challenges belonging to public guild', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const challenge = await generateChallenge(leader, group);
|
||||
await leader.post(`/challenges/${challenge._id}/join`);
|
||||
const res = await user.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: { name: user.profile.name },
|
||||
_id: leader._id,
|
||||
id: leader._id,
|
||||
profile: { name: leader.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
username: leader.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('populates only some fields', async () => {
|
||||
const anotherUser = await generateUser({ balance: 3 });
|
||||
const group = await generateGroup(anotherUser, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const challenge = await generateChallenge(anotherUser, group);
|
||||
await anotherUser.post(`/challenges/${challenge._id}/join`);
|
||||
const res = await user.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: anotherUser._id,
|
||||
id: anotherUser._id,
|
||||
profile: { name: anotherUser.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: anotherUser.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
|
||||
+14
@@ -72,6 +72,20 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('works with challenges belonging to a public guild', async () => {
|
||||
const groupLeader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(groupLeader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
const challenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
const taskText = 'Test Text';
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{ type: 'habit', text: taskText }]);
|
||||
|
||||
const memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`);
|
||||
expect(memberProgress).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile', 'tasks']);
|
||||
expect(memberProgress.profile).to.have.all.keys(['name']);
|
||||
expect(memberProgress.tasks.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('returns the member tasks for the challenges', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
|
||||
@@ -7,7 +7,117 @@ import {
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('GET challenges/groups/:groupId', () => {
|
||||
context('Group Plan', () => {
|
||||
context('Public Guild', () => {
|
||||
let publicGuild; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
user = groupLeader;
|
||||
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
});
|
||||
|
||||
it('should return group challenges for non member with populated leader', async () => {
|
||||
const challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return group challenges for member with populated leader', async () => {
|
||||
const challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return newest challenges first', async () => {
|
||||
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('Private Guild', () => {
|
||||
let privateGuild; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
|
||||
@@ -18,7 +128,6 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
privateGuild = group;
|
||||
@@ -77,6 +186,68 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let publicGuild; let user; let officialChallenge; let unofficialChallenges;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
publicGuild = group;
|
||||
|
||||
await user.update({
|
||||
'permissions.challengeAdmin': true,
|
||||
});
|
||||
|
||||
officialChallenge = await generateChallenge(user, group, {
|
||||
categories: [{
|
||||
name: 'habitica_official',
|
||||
slug: 'habitica_official',
|
||||
}],
|
||||
});
|
||||
await user.post(`/challenges/${officialChallenge._id}/join`);
|
||||
|
||||
// We add 10 extra challenges to test whether the official challenge
|
||||
// (the oldest) makes it to the front page.
|
||||
unofficialChallenges = [];
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const challenge = await generateChallenge(user, group); // eslint-disable-line
|
||||
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
|
||||
unofficialChallenges.push(challenge);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
const challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
|
||||
it('should return newest challenges first, after official ones', async () => {
|
||||
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
unofficialChallenges.forEach((chal, index) => {
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id });
|
||||
expect(foundChallengeIndex).to.eql(10 - index);
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
context('Party', () => {
|
||||
let party; let user; let nonMember; let challenge; let
|
||||
challenge2;
|
||||
@@ -186,7 +357,7 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
await user.updateOne({ balance: 0.5 });
|
||||
await user.update({ balance: 0.5 });
|
||||
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
||||
|
||||
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 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,
|
||||
generateChallenge,
|
||||
createAndPopulateGroup,
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('GET challenges/user', () => {
|
||||
context('no official challenges', () => {
|
||||
let user; let member; let nonMember; let challenge; let challenge2; let publicChallenge;
|
||||
let groupPlan; let userData; let groupData; let tavern; let tavernData;
|
||||
let user; let member; let nonMember; let challenge; let challenge2;
|
||||
let publicGuild; let userData; let groupData;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupPlan = group;
|
||||
publicGuild = group;
|
||||
groupData = {
|
||||
_id: groupPlan._id,
|
||||
_id: publicGuild._id,
|
||||
categories: [],
|
||||
id: groupPlan._id,
|
||||
type: groupPlan.type,
|
||||
privacy: groupPlan.privacy,
|
||||
name: groupPlan.name,
|
||||
summary: groupPlan.name,
|
||||
leader: groupPlan.leader._id,
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
};
|
||||
|
||||
user = groupLeader;
|
||||
userData = {
|
||||
_id: groupPlan.leader._id,
|
||||
id: groupPlan.leader._id,
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
auth: {
|
||||
local: {
|
||||
@@ -51,31 +46,17 @@ describe('GET challenges/user', () => {
|
||||
},
|
||||
};
|
||||
|
||||
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
||||
tavernData = {
|
||||
_id: TAVERN_ID,
|
||||
categories: [],
|
||||
id: TAVERN_ID,
|
||||
type: tavern.type,
|
||||
privacy: tavern.privacy,
|
||||
name: tavern.name,
|
||||
summary: tavern.name,
|
||||
leader: tavern.leader._id,
|
||||
};
|
||||
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.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', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await member.get('/challenges/user?page=0');
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
@@ -83,13 +64,11 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge.group).to.eql(groupData);
|
||||
});
|
||||
|
||||
it('should return public challenges', async () => {
|
||||
it('should not return challenges a non-member has not joined', async () => {
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.exist;
|
||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
|
||||
it('should return challenges user has created', async () => {
|
||||
@@ -121,10 +100,10 @@ describe('GET challenges/user', () => {
|
||||
it('should return newest challenges first', async () => {
|
||||
let challenges = await user.get('/challenges/user?page=0');
|
||||
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: publicChallenge._id });
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
|
||||
const newChallenge = await generateChallenge(user, groupPlan);
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user?page=0');
|
||||
@@ -134,23 +113,52 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
const privateChallenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
const privateChallenge = await generateChallenge(groupLeader, group, {
|
||||
categories: [{
|
||||
name: 'academics',
|
||||
slug: 'academics',
|
||||
}],
|
||||
});
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
const challenges = await nonMember.get('/challenges/user?page=0&categories=academics&owned=not_owned');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
context('my challenges', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
const challenges = await nonMember.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
@@ -169,10 +177,6 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.exist;
|
||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||
});
|
||||
|
||||
it('should return challenges user has created if filter by owned', async () => {
|
||||
@@ -186,10 +190,6 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.exist;
|
||||
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||
});
|
||||
|
||||
it('should not return challenges user has created if filter by not owned', async () => {
|
||||
@@ -199,42 +199,38 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallenge1).to.not.exist;
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges in user groups', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.not.exist;
|
||||
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return public challenges', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||
expect(foundPublicChallenge).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let user; let officialChallenge; let unofficialChallenges; let
|
||||
group;
|
||||
publicGuild;
|
||||
|
||||
before(async () => {
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
summary: 'summary for TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
|
||||
await user.updateOne({
|
||||
user = groupLeader;
|
||||
publicGuild = group;
|
||||
|
||||
await user.update({
|
||||
'permissions.challengeAdmin': true,
|
||||
});
|
||||
|
||||
@@ -275,7 +271,7 @@ describe('GET challenges/user', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, group);
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user?page=0');
|
||||
@@ -298,17 +294,16 @@ describe('GET challenges/user', () => {
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
guild = group;
|
||||
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) {
|
||||
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 summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1);
|
||||
const group = createAndPopulateGroup({
|
||||
@@ -58,7 +77,7 @@ describe('POST /challenges', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('creating a Challenge for a Group Plan', () => {
|
||||
context('Creating a challenge for a valid group', () => {
|
||||
let groupLeader;
|
||||
let group;
|
||||
let groupMember;
|
||||
@@ -75,11 +94,9 @@ describe('POST /challenges', () => {
|
||||
challenges: true,
|
||||
},
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupLeader = await populatedGroup.groupLeader.sync();
|
||||
await groupLeader.updateOne({ permissions: {} });
|
||||
group = populatedGroup.group;
|
||||
groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
@@ -177,7 +194,7 @@ describe('POST /challenges', () => {
|
||||
const oldUserBalance = groupLeader.balance;
|
||||
const prize = 8;
|
||||
|
||||
await group.updateOne({ balance: 0 });
|
||||
await group.update({ balance: 0 });
|
||||
await groupLeader.post('/challenges', {
|
||||
group: group._id,
|
||||
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 () => {
|
||||
await groupLeader.updateOne({
|
||||
await groupLeader.update({
|
||||
permissions: {
|
||||
challengeAdmin: true,
|
||||
},
|
||||
|
||||
+2
-2
@@ -128,10 +128,10 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
||||
const oldBalance = winningUser.balance;
|
||||
const oldLeaderBalance = (await groupLeader.sync()).balance;
|
||||
|
||||
await winningUser.updateOne({
|
||||
await winningUser.update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
});
|
||||
await group.updateOne({
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
'purchased.plan.customerId': 123,
|
||||
});
|
||||
|
||||
@@ -18,7 +18,6 @@ describe('PUT /challenges/:challengeId', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
privateGuild = group;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -9,30 +10,27 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
admin;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupWithChat = group;
|
||||
user = groupLeader;
|
||||
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||
message = message.message;
|
||||
userThatDidNotCreateChat = members[0]; // eslint-disable-line prefer-destructuring
|
||||
admin = members[1]; // eslint-disable-line prefer-destructuring
|
||||
await admin.updateOne({ permissions: { moderator: true } });
|
||||
userThatDidNotCreateChat = await generateUser();
|
||||
admin = await generateUser({ 'permissions.moderator': true });
|
||||
});
|
||||
|
||||
context('Chat errors', () => {
|
||||
it('returns an error if message does not exist', async () => {
|
||||
it('returns an error is message does not exist', async () => {
|
||||
const fakeChatId = generateUUID();
|
||||
await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
@@ -58,7 +56,7 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
nextMessage = nextMessage.message;
|
||||
});
|
||||
|
||||
it('allows creator to delete their message', async () => {
|
||||
it('allows creator to delete a their message', async () => {
|
||||
await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
|
||||
|
||||
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -11,22 +11,48 @@ describe('GET /groups/:groupId/chat', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
context('public Guild', () => {
|
||||
let group;
|
||||
|
||||
before(async () => {
|
||||
const leader = await generateUser({ balance: 2 });
|
||||
|
||||
group = await generateGroup(leader, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}, {
|
||||
chat: [
|
||||
{ text: 'Hello', flags: {}, id: 1 },
|
||||
{ text: 'Welcome to the Guild', flags: {}, id: 2 },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns Guild chat', async () => {
|
||||
const chat = await user.get(`/groups/${group._id}/chat`);
|
||||
|
||||
expect(chat[0].id).to.eql(group.chat[0].id);
|
||||
expect(chat[1].id).to.eql(group.chat[1].id);
|
||||
});
|
||||
});
|
||||
|
||||
context('private Guild', () => {
|
||||
let group;
|
||||
|
||||
before(async () => {
|
||||
({ group } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
const leader = await generateUser({ balance: 2 });
|
||||
|
||||
group = await generateGroup(leader, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
}, {
|
||||
chat: [
|
||||
'Hello',
|
||||
'Welcome to the Guild',
|
||||
],
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error if user is not member of requested private group', async () => {
|
||||
|
||||
@@ -1,42 +1,32 @@
|
||||
import find from 'lodash/find';
|
||||
import { find } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
describe('POST /chat/:chatId/flag', () => {
|
||||
let user; let admin; let anotherUser; let newUser; let
|
||||
group; let members; let userToDelete;
|
||||
group;
|
||||
const TEST_MESSAGE = 'Test Message';
|
||||
const USER_AGE_FOR_FLAGGING = 3;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group, groupLeader: user, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate(),
|
||||
},
|
||||
members: 4,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
[admin, anotherUser, newUser, userToDelete] = members;
|
||||
await user.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(),
|
||||
});
|
||||
|
||||
user = await generateUser({ balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
admin = await generateUser({ balance: 1, 'permissions.moderator': true });
|
||||
anotherUser = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
newUser = await generateUser({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||
|
||||
group = await user.post('/groups', {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -79,8 +69,8 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild - (private guild)',
|
||||
title_link: undefined,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
||||
mrkdwn_in: [
|
||||
@@ -88,7 +78,7 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
/* eslint-ensable camelcase */
|
||||
});
|
||||
|
||||
it('Does not increment message flag count and sends different message to moderator Slack when user is new', async () => {
|
||||
@@ -114,8 +104,8 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild - (private guild)',
|
||||
title_link: undefined,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
|
||||
mrkdwn_in: [
|
||||
@@ -123,12 +113,15 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
/* eslint-ensable camelcase */
|
||||
});
|
||||
|
||||
it('Flags a chat when the author\'s account was deleted', async () => {
|
||||
const { message } = await userToDelete.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
||||
await userToDelete.del('/user', {
|
||||
const deletedUser = await generateUser({
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
const { message } = await deletedUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
||||
await deletedUser.del('/user', {
|
||||
password: 'password',
|
||||
});
|
||||
|
||||
|
||||
@@ -6,28 +6,28 @@ import {
|
||||
|
||||
describe('POST /chat/:chatId/like', () => {
|
||||
let user;
|
||||
let anotherUser;
|
||||
let groupWithChat;
|
||||
let members;
|
||||
const testMessage = 'Test Message';
|
||||
let anotherUser;
|
||||
|
||||
before(async () => {
|
||||
({ group: groupWithChat, groupLeader: user, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
|
||||
[anotherUser] = members;
|
||||
await anotherUser.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
user = groupLeader;
|
||||
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 () => {
|
||||
|
||||
@@ -1,43 +1,52 @@
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import nconf from 'nconf';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
sleep,
|
||||
server,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import {
|
||||
SPAM_MESSAGE_LIMIT,
|
||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import { CHAT_FLAG_FROM_SHADOW_MUTE, MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
describe('POST /chat', () => {
|
||||
let user; let groupWithChat; let member; let
|
||||
additionalMember;
|
||||
const testMessage = 'Test Message';
|
||||
const testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE';
|
||||
const testBannedWordMessage1 = 'TESTPLACEHOLDERSWEARWORDHERE1';
|
||||
const testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE';
|
||||
const testSlurMessage1 = 'TESTPLACEHOLDERSLURWORDHERE1';
|
||||
const bannedWordErrorMessage = t('bannedWordUsed', { swearWordsUsed: testBannedWordMessage });
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
user = groupLeader;
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
}); // prevent tests accidentally throwing messageGroupChatSpam
|
||||
groupWithChat = group;
|
||||
[member, additionalMember] = members;
|
||||
await member.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
await additionalMember.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
additionalMember = members[1]; // eslint-disable-line prefer-destructuring
|
||||
await member.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
await additionalMember.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
it('Returns an error when no message is provided', async () => {
|
||||
@@ -77,15 +86,35 @@ describe('POST /chat', () => {
|
||||
|
||||
describe('mute user', () => {
|
||||
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 () => {
|
||||
await member.updateOne({
|
||||
'flags.chatRevoked': true,
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Private Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const privateGuildMemberWithChatsRevoked = members[0];
|
||||
await privateGuildMemberWithChatsRevoked.update({
|
||||
'flags.chatRevoked': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await privateGuildMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
@@ -101,7 +130,7 @@ describe('POST /chat', () => {
|
||||
});
|
||||
|
||||
const privatePartyMemberWithChatsRevoked = members[0];
|
||||
await privatePartyMemberWithChatsRevoked.updateOne({
|
||||
await privatePartyMemberWithChatsRevoked.update({
|
||||
'flags.chatRevoked': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
@@ -120,15 +149,57 @@ describe('POST /chat', () => {
|
||||
|
||||
afterEach(() => {
|
||||
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 () => {
|
||||
await member.updateOne({
|
||||
'flags.chatShadowMuted': true,
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Private Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const userWithChatShadowMuted = members[0];
|
||||
await userWithChatShadowMuted.update({
|
||||
'flags.chatShadowMuted': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
@@ -145,7 +216,7 @@ describe('POST /chat', () => {
|
||||
});
|
||||
|
||||
const userWithChatShadowMuted = members[0];
|
||||
await userWithChatShadowMuted.updateOne({
|
||||
await userWithChatShadowMuted.update({
|
||||
'flags.chatShadowMuted': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
@@ -155,9 +226,100 @@ describe('POST /chat', () => {
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('creates a chat with zero flagCount when non-shadow-muted user sends a message to a public guild', async () => {
|
||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.flagCount).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('banned word', () => {
|
||||
it('returns an error when chat message contains a banned word in tavern', async () => {
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: testBannedWordMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when chat message contains a banned word in a public guild', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await expect(members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is part of a phrase', async () => {
|
||||
const wordInPhrase = `phrase ${testBannedWordMessage} end`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is surrounded by non alphabet characters', async () => {
|
||||
const wordInPhrase = `_!${testBannedWordMessage}@_`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is typed in mixed case', async () => {
|
||||
const substrLength = Math.floor(testBannedWordMessage.length / 2);
|
||||
const chatMessage = testBannedWordMessage.substring(0, substrLength).toLowerCase()
|
||||
+ testBannedWordMessage.substring(substrLength).toUpperCase();
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedWordUsed', { swearWordsUsed: chatMessage }),
|
||||
});
|
||||
});
|
||||
|
||||
it('checks error message has all the banned words used, regardless of case', async () => {
|
||||
const testBannedWords = [
|
||||
testBannedWordMessage.toUpperCase(),
|
||||
testBannedWordMessage1.toLowerCase(),
|
||||
];
|
||||
const chatMessage = `Mixing ${testBannedWords[0]} and ${testBannedWords[1]} is bad for you.`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected
|
||||
.and.have.property('message')
|
||||
.that.includes(testBannedWords.join(', '));
|
||||
});
|
||||
|
||||
it('does not error when bad word is suffix of a word', async () => {
|
||||
const wordAsSuffix = `prefix${testBannedWordMessage}`;
|
||||
const message = await user.post('/groups/habitrpg/chat', { message: wordAsSuffix });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when bad word is prefix of a word', async () => {
|
||||
const wordAsPrefix = `${testBannedWordMessage}suffix`;
|
||||
const message = await user.post('/groups/habitrpg/chat', { message: wordAsPrefix });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when sending a chat message containing a banned word to a party', async () => {
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
@@ -167,7 +329,26 @@ describe('POST /chat', () => {
|
||||
},
|
||||
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 });
|
||||
|
||||
@@ -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 () => {
|
||||
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;
|
||||
});
|
||||
@@ -189,7 +380,46 @@ describe('POST /chat', () => {
|
||||
|
||||
afterEach(() => {
|
||||
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 () => {
|
||||
@@ -201,23 +431,34 @@ describe('POST /chat', () => {
|
||||
},
|
||||
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 });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('errors when slur is typed in mixed case', async () => {
|
||||
const substrLength = Math.floor(testSlurMessage1.length / 2);
|
||||
const chatMessage = testSlurMessage1.substring(0, substrLength).toLowerCase()
|
||||
+ testSlurMessage1.substring(substrLength).toUpperCase();
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when user account is too young', async () => {
|
||||
await user.updateOne({ 'auth.timestamps.created': new Date() });
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: 'hi im new' }))
|
||||
const brandNewUser = await generateUser();
|
||||
await expect(brandNewUser.post('/groups/habitrpg/chat', { message: 'hi im new' }))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('chatTemporarilyUnavailable'),
|
||||
});
|
||||
await user.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const memberUsername = 'memberUsername';
|
||||
await member.updateOne({ 'auth.local.username': memberUsername });
|
||||
await member.update({ 'auth.local.username': memberUsername });
|
||||
|
||||
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.`;
|
||||
@@ -278,42 +519,54 @@ describe('POST /chat', () => {
|
||||
const mount = 'test-mount';
|
||||
const pet = 'test-pet';
|
||||
const style = 'test-style';
|
||||
await user.updateOne({
|
||||
const userWithStyle = await generateUser({
|
||||
'items.currentMount': mount,
|
||||
'items.currentPet': pet,
|
||||
'preferences.style': style,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.currentMount).to.eql(user.items.currentMount);
|
||||
expect(message.message.userStyles.items.currentPet).to.eql(user.items.currentPet);
|
||||
expect(message.message.userStyles.preferences.style).to.eql(user.preferences.style);
|
||||
expect(message.message.userStyles.preferences.hair).to.eql(user.preferences.hair);
|
||||
expect(message.message.userStyles.preferences.skin).to.eql(user.preferences.skin);
|
||||
expect(message.message.userStyles.preferences.shirt).to.eql(user.preferences.shirt);
|
||||
expect(message.message.userStyles.preferences.chair).to.eql(user.preferences.chair);
|
||||
expect(message.message.userStyles.items.currentMount).to.eql(userWithStyle.items.currentMount);
|
||||
expect(message.message.userStyles.items.currentPet).to.eql(userWithStyle.items.currentPet);
|
||||
expect(message.message.userStyles.preferences.style).to.eql(userWithStyle.preferences.style);
|
||||
expect(message.message.userStyles.preferences.hair).to.eql(userWithStyle.preferences.hair);
|
||||
expect(message.message.userStyles.preferences.skin).to.eql(userWithStyle.preferences.skin);
|
||||
expect(message.message.userStyles.preferences.shirt).to.eql(userWithStyle.preferences.shirt);
|
||||
expect(message.message.userStyles.preferences.chair).to.eql(userWithStyle.preferences.chair);
|
||||
expect(message.message.userStyles.preferences.background)
|
||||
.to.eql(user.preferences.background);
|
||||
.to.eql(userWithStyle.preferences.background);
|
||||
});
|
||||
|
||||
it('creates equipped to user styles', async () => {
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const userWithStyle = await generateUser({
|
||||
'preferences.costume': false,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await userWithStyle.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);
|
||||
.to.eql(userWithStyle.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 userWithStyle = await generateUser({
|
||||
'preferences.costume': true,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.gear.costume).to.eql(user.items.gear.costume);
|
||||
expect(message.message.userStyles.items.gear.costume).to.eql(userWithStyle.items.gear.costume);
|
||||
expect(message.message.userStyles.items.gear.equipped).to.not.exist;
|
||||
});
|
||||
|
||||
@@ -323,11 +576,12 @@ describe('POST /chat', () => {
|
||||
tier: 800,
|
||||
tokensApplied: true,
|
||||
};
|
||||
await user.updateOne({
|
||||
const backer = await generateUser({
|
||||
backer: backerInfo,
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
});
|
||||
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const message = await backer.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const messageBackerInfo = message.message.backer;
|
||||
|
||||
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
|
||||
@@ -375,7 +629,7 @@ describe('POST /chat', () => {
|
||||
|
||||
context('chat notifications', () => {
|
||||
beforeEach(() => {
|
||||
member.updateOne({ newMessages: {}, notifications: [] });
|
||||
member.update({ newMessages: {}, notifications: [] });
|
||||
});
|
||||
|
||||
it('notifies other users of new messages for a guild', async () => {
|
||||
@@ -407,5 +661,43 @@ describe('POST /chat', () => {
|
||||
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
||||
expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id)).to.exist;
|
||||
});
|
||||
|
||||
it('does not notify other users of a new message that is already hidden from shadow-muting', async () => {
|
||||
await user.update({ 'flags.chatShadowMuted': true });
|
||||
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||
const memberWithNotification = await member.get('/user');
|
||||
|
||||
await user.update({ 'flags.chatShadowMuted': false });
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.not.exist;
|
||||
expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id)).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
context('Spam prevention', () => {
|
||||
it('Returns an error when the user has been posting too many messages', async () => {
|
||||
// Post as many messages are needed to reach the spam limit
|
||||
for (let i = 0; i < SPAM_MESSAGE_LIMIT; i += 1) {
|
||||
const result = await additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
|
||||
expect(result.message.id).to.exist;
|
||||
}
|
||||
|
||||
await expect(additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageGroupChatSpam'),
|
||||
});
|
||||
});
|
||||
|
||||
it('contributor should not receive spam alert', async () => {
|
||||
const userSocialite = await member.update({ 'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL });
|
||||
|
||||
// Post 1 more message than the spam limit to ensure they do not reach the limit
|
||||
for (let i = 0; i < SPAM_MESSAGE_LIMIT + 1; i += 1) {
|
||||
const result = await userSocialite.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
|
||||
expect(result.message.id).to.exist;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,19 +12,18 @@ describe('POST /groups/:id/chat/seen', () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
guildLeader = groupLeader;
|
||||
[guildMember] = members;
|
||||
guildMember = members[0]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' });
|
||||
guildMessage = guildMessage.message;
|
||||
|
||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import config from '../../../../../config.json';
|
||||
@@ -12,24 +13,21 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
admin;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
members: 2,
|
||||
});
|
||||
|
||||
groupWithChat = group;
|
||||
author = groupLeader;
|
||||
[nonAdmin, admin] = members;
|
||||
await nonAdmin.updateOne({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
await admin.updateOne({ 'permissions.moderator': true });
|
||||
nonAdmin = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
admin = await generateUser({ 'permissions.moderator': true });
|
||||
|
||||
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||
message = message.message;
|
||||
@@ -71,7 +69,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
members: 2,
|
||||
});
|
||||
|
||||
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' });
|
||||
privateMessage = privateMessage.message;
|
||||
|
||||
@@ -104,7 +102,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
const member = members[0];
|
||||
|
||||
// make member that can use skills
|
||||
await member.updateOne({
|
||||
await member.update({
|
||||
'stats.lvl': 100,
|
||||
'stats.mp': 400,
|
||||
'stats.class': 'wizard',
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('GET /coupons/', () => {
|
||||
});
|
||||
|
||||
it('should return the coupons in CSV format ordered by creation date', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'permissions.coupons': true,
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('POST /coupons/generate/:event', () => {
|
||||
});
|
||||
|
||||
it('returns an error if user has no coupons permission', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'permissions.coupons': false,
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('POST /coupons/generate/:event', () => {
|
||||
});
|
||||
|
||||
it('should generate coupons', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'permissions.coupons': true,
|
||||
});
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('POST /debug/quest-progress', () => {
|
||||
});
|
||||
|
||||
it('increases boss quest progress by 1000', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'party.quest.key': 'whale',
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('POST /debug/quest-progress', () => {
|
||||
});
|
||||
|
||||
it('increases collection quest progress by 300 items', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'party.quest.key': 'evilsanta2',
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /group-plans', () => {
|
||||
@@ -7,15 +8,20 @@ describe('GET /group-plans', () => {
|
||||
let groupPlan;
|
||||
|
||||
before(async () => {
|
||||
({ group: groupPlan, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'group plan - is member',
|
||||
user = await generateUser({ balance: 4 });
|
||||
groupPlan = await generateGroup(user,
|
||||
{
|
||||
name: 'public guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
leaderDetails: { balance: 4 },
|
||||
}));
|
||||
{
|
||||
purchased: {
|
||||
plan: {
|
||||
customerId: 'existings',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns group plans for the user', async () => {
|
||||
|
||||
@@ -1,63 +1,70 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
resetHabiticaDB,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import {
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('GET /groups', () => {
|
||||
let user; let leader; let members;
|
||||
let secondGroup; let secondLeader;
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 2;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 3;
|
||||
let user;
|
||||
let userInGuild;
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 2;
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2;
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
|
||||
const GUILD_PER_PAGE = 30;
|
||||
const categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
let publicGuildNotMember;
|
||||
let privateGuildUserIsMemberOf;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
|
||||
({
|
||||
group: privateGuildUserIsMemberOf,
|
||||
groupLeader: leader,
|
||||
members,
|
||||
} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
categories,
|
||||
},
|
||||
leaderDetails: {
|
||||
balance: 10,
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
[user] = members;
|
||||
await user.updateOne({ balance: 4 });
|
||||
const leader = await generateUser({ balance: 10 });
|
||||
user = await generateUser({ balance: 4 });
|
||||
|
||||
({ group: secondGroup, groupLeader: secondLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'c++ coders',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
const publicGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
name: 'public guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
summary: 'ohayou kombonwa',
|
||||
description: 'oyasumi',
|
||||
});
|
||||
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
|
||||
|
||||
await secondLeader.post(`/groups/${secondGroup._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${secondGroup._id}/join`);
|
||||
userInGuild = await generateUser({ guilds: [publicGuildUserIsMemberOf._id] });
|
||||
|
||||
await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'private guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
publicGuildNotMember = await generateGroup(leader, {
|
||||
name: 'public guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
summary: 'Natsume Soseki',
|
||||
description: 'Kinnosuke no Hondana',
|
||||
categories,
|
||||
});
|
||||
|
||||
privateGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
categories,
|
||||
});
|
||||
await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`);
|
||||
|
||||
await generateGroup(leader, {
|
||||
name: 'private guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
await generateGroup(leader, {
|
||||
@@ -91,16 +98,172 @@ describe('GET /groups', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only the tavern when tavern passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=tavern'))
|
||||
.to.eventually.have.a.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]')
|
||||
.and.to.have.property('_id', TAVERN_ID);
|
||||
});
|
||||
|
||||
it('returns only the user\'s party when party passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=party'))
|
||||
.to.eventually.have.a.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]');
|
||||
});
|
||||
|
||||
it('returns all public guilds when publicGuilds passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=publicGuilds'))
|
||||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
|
||||
});
|
||||
|
||||
describe('filters', () => {
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
const guilds = await user.get(`/groups?type=publicGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(publicGuildNotMember._id);
|
||||
});
|
||||
|
||||
it('returns private guilds filtered by category', async () => {
|
||||
const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
||||
});
|
||||
|
||||
it('filters public guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
const guilds = await user.get('/groups?type=publicGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('filters private guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('filters public guilds by leader role', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&leader=true');
|
||||
expect(guilds.length).to.equal(NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER);
|
||||
});
|
||||
|
||||
it('filters public guilds by member role', async () => {
|
||||
const guilds = await userInGuild.get('/groups?type=publicGuilds&member=true');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].name).to.have.string('is member');
|
||||
});
|
||||
|
||||
it('filters public guilds by single-word search term', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=kom');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
|
||||
});
|
||||
|
||||
it('filters public guilds by single-word search term left and right-padded by spaces', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=++++ohayou+kombonwa+++++');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
|
||||
});
|
||||
|
||||
it('filters public guilds by two-words search term separated by multiple spaces', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=kinnosuke+++++hon');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].description).to.have.string('Kinnosuke');
|
||||
});
|
||||
});
|
||||
|
||||
describe('public guilds pagination', () => {
|
||||
it('req.query.paginate must be a boolean string', async () => {
|
||||
await expect(user.get('/groups?paginate=aString&type=publicGuilds'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('req.query.paginate can only be true when req.query.type includes publicGuilds', async () => {
|
||||
await expect(user.get('/groups?paginate=true&type=notPublicGuilds'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: apiError('guildsOnlyPaginate'),
|
||||
});
|
||||
});
|
||||
|
||||
it('req.query.page can\'t be negative', async () => {
|
||||
await expect(user.get('/groups?paginate=true&page=-1&type=publicGuilds'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 30 guilds per page ordered by number of members', async () => {
|
||||
await user.update({ balance: 9000 });
|
||||
const delay = () => new Promise(resolve => setTimeout(resolve, 40));
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < 60; i += 1) {
|
||||
promises.push(generateGroup(user, {
|
||||
name: `public guild ${i} - is member`,
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}));
|
||||
await delay(); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
|
||||
const groups = await Promise.all(promises);
|
||||
|
||||
// update group number 32 and not the first to make sure sorting works
|
||||
await groups[32].update({ name: 'guild with most members', memberCount: 199 });
|
||||
await groups[33].update({ name: 'guild with less members', memberCount: -100 });
|
||||
|
||||
const page0 = await expect(user.get('/groups?type=publicGuilds&paginate=true'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
expect(page0[0].name).to.equal('guild with most members');
|
||||
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
const page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
|
||||
// 1 created now, 4 by other tests, -1 for no more tavern.
|
||||
.to.eventually.have.a.lengthOf(1 + 4 - 1);
|
||||
expect(page2[3].name).to.equal('guild with less members');
|
||||
}).timeout(10000);
|
||||
});
|
||||
|
||||
it('makes sure that the tavern doesn\'t show up when guilds is passed as a query', async () => {
|
||||
const guilds = await user.get('/groups?type=guilds');
|
||||
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('makes sure that the tavern doesn\'t show up when publicGuilds is passed as a query', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds');
|
||||
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=guilds'))
|
||||
.to.eventually.have.a
|
||||
.lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
});
|
||||
|
||||
it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => {
|
||||
@@ -109,21 +272,21 @@ describe('GET /groups', () => {
|
||||
});
|
||||
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
await expect(user.get('/groups?type=privateGuilds,party'))
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
|
||||
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW - 1); // -1 for no Tavern.
|
||||
});
|
||||
|
||||
describe('filters', () => {
|
||||
it('returns private guilds filtered by category', async () => {
|
||||
const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
const group = await generateGroup(user, {
|
||||
name: 'c++ coders',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
it('filters private guilds by size', async () => {
|
||||
const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
// search for 'c++ coders'
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=0&search=c%2B%2B+coders'))
|
||||
.to.eventually.have.lengthOf(1)
|
||||
.and.to.have.nested.property('[0]')
|
||||
.and.to.have.property('_id', group._id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /groups/:groupId/invites', () => {
|
||||
@@ -72,16 +71,15 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns only first 30 invites by default (req.query.limit not specified)', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 31,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||
|
||||
const res = await leader.get(`/groups/${group._id}/invites`);
|
||||
expect(res.length).to.equal(30);
|
||||
@@ -92,16 +90,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
}).timeout(10000);
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -111,16 +101,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -130,16 +112,8 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -149,16 +123,15 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
|
||||
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
||||
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 31,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||
|
||||
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
||||
expect(res.length).to.equal(14);
|
||||
@@ -176,20 +149,17 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
}).timeout(30000);
|
||||
|
||||
it('supports using req.query.lastId to get more invites', async function test () {
|
||||
let group; let invitees;
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
({ group, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
},
|
||||
leaderDetails: { balance: 4 },
|
||||
invites: 32,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const expectedIds = invitees.map(generatedInvite => generatedInvite._id);
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 32; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate); // Group has 32 invites
|
||||
const expectedIds = generatedInvites.map(generatedInvite => generatedInvite._id);
|
||||
await user.post(`/groups/${group._id}/invite`, { uuids: expectedIds });
|
||||
|
||||
const res = await user.get(`/groups/${group._id}/invites`);
|
||||
expect(res.length).to.equal(30);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
@@ -76,15 +75,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
});
|
||||
|
||||
it('req.query.includeAllPublicFields === true works with guilds', async () => {
|
||||
let group;
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
upgradeToGroupPlan: true,
|
||||
members: 1,
|
||||
}));
|
||||
|
||||
const group = await generateGroup(user, { type: 'guild', name: generateUUID() });
|
||||
const [memberRes] = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`);
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
@@ -215,20 +206,20 @@ describe('GET /groups/:groupId/members', () => {
|
||||
|
||||
it('supports using req.query.lastId to get more members', async function test () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
const { group, groupLeader: leader, members: generatedUsers } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: generateUUID(),
|
||||
upgradeToGroupPlan: true,
|
||||
leaderDetails: { balance: 4 },
|
||||
members: 57,
|
||||
});
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const usersToGenerate = [];
|
||||
for (let i = 0; i < 57; i += 1) {
|
||||
usersToGenerate.push(generateUser({ guilds: [group._id] }));
|
||||
}
|
||||
// Group has 59 members (1 is the leader)
|
||||
const generatedUsers = await Promise.all(usersToGenerate);
|
||||
const expectedIds = [leader._id].concat(generatedUsers.map(generatedUser => generatedUser._id));
|
||||
|
||||
const res = await leader.get(`/groups/${group._id}/members`);
|
||||
const res = await user.get(`/groups/${group._id}/members`);
|
||||
expect(res.length).to.equal(30);
|
||||
const res2 = await leader.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`);
|
||||
const res2 = await user.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`);
|
||||
expect(res2.length).to.equal(28);
|
||||
|
||||
const resIds = res.concat(res2).map(member => member._id);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
|
||||
describe('GET /groups/:id', () => {
|
||||
const typesOfGroups = {};
|
||||
typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' };
|
||||
typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
|
||||
typesOfGroups.party = { type: 'party', privacy: 'private' };
|
||||
|
||||
@@ -23,11 +24,10 @@ describe('GET /groups/:id', () => {
|
||||
const groupData = await createAndPopulateGroup({
|
||||
members: 30,
|
||||
groupDetails,
|
||||
upgradeToGroupPlan: groupDetails.type === 'guild',
|
||||
});
|
||||
|
||||
leader = groupData.groupLeader;
|
||||
[member] = groupData.members;
|
||||
member = groupData.members[0]; // eslint-disable-line prefer-destructuring
|
||||
createdGroup = groupData.group;
|
||||
});
|
||||
|
||||
@@ -49,6 +49,34 @@ describe('GET /groups/:id', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-member of a public guild', () => {
|
||||
let nonMember; let
|
||||
createdGroup;
|
||||
|
||||
before(async () => {
|
||||
const groupData = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
nonMember = await generateUser();
|
||||
});
|
||||
|
||||
it('returns the group object for a non-member', async () => {
|
||||
const group = await nonMember.get(`/groups/${createdGroup._id}`);
|
||||
|
||||
expect(group._id).to.eql(createdGroup._id);
|
||||
expect(group.name).to.eql(createdGroup.name);
|
||||
expect(group.type).to.eql(createdGroup.type);
|
||||
expect(group.privacy).to.eql(createdGroup.privacy);
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-member of a private guild', () => {
|
||||
let nonMember; let
|
||||
createdGroup;
|
||||
@@ -61,7 +89,6 @@ describe('GET /groups/:id', () => {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
@@ -153,7 +180,7 @@ describe('GET /groups/:id', () => {
|
||||
it('removes non-existent guild from user\'s guild list', async () => {
|
||||
const guildId = generateUUID();
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
guilds: [guildId, generateUUID()],
|
||||
});
|
||||
|
||||
@@ -173,7 +200,7 @@ describe('GET /groups/:id', () => {
|
||||
it('removes non-existent party from user\'s party object', async () => {
|
||||
const partyId = generateUUID();
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
party: { _id: partyId },
|
||||
});
|
||||
|
||||
@@ -191,7 +218,7 @@ describe('GET /groups/:id', () => {
|
||||
});
|
||||
|
||||
context('Flagged messages', () => {
|
||||
let group; let members;
|
||||
let group;
|
||||
|
||||
const chat1 = {
|
||||
id: 'chat1',
|
||||
@@ -241,7 +268,7 @@ describe('GET /groups/:id', () => {
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
chat: [
|
||||
chat1,
|
||||
chat2,
|
||||
@@ -250,11 +277,9 @@ describe('GET /groups/:id', () => {
|
||||
chat5,
|
||||
],
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
({ group, members } = groupData);
|
||||
group = groupData.group;
|
||||
|
||||
await group.addChat([chat1, chat2, chat3, chat4, chat5]);
|
||||
});
|
||||
@@ -262,8 +287,8 @@ describe('GET /groups/:id', () => {
|
||||
context('non-admin', () => {
|
||||
let nonAdmin;
|
||||
|
||||
beforeEach(() => {
|
||||
[nonAdmin] = members;
|
||||
beforeEach(async () => {
|
||||
nonAdmin = await generateUser();
|
||||
});
|
||||
|
||||
it('does not include messages with a flag count of 2 or greater', async () => {
|
||||
@@ -289,8 +314,9 @@ describe('GET /groups/:id', () => {
|
||||
let admin;
|
||||
|
||||
beforeEach(async () => {
|
||||
[admin] = members;
|
||||
await admin.updateOne({ permissions: { moderator: true } });
|
||||
admin = await generateUser({
|
||||
'permissions.moderator': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('includes all messages', async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('POST /group', () => {
|
||||
@@ -34,8 +35,8 @@ describe('POST /group', () => {
|
||||
|
||||
it('sets the group leader to the user who created the group', async () => {
|
||||
const group = await user.post('/groups', {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
expect(group.leader).to.eql({
|
||||
@@ -50,7 +51,7 @@ describe('POST /group', () => {
|
||||
const name = 'Test Group';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
const updatedGroup = await user.get(`/groups/${group._id}`);
|
||||
@@ -63,7 +64,7 @@ describe('POST /group', () => {
|
||||
const summary = 'Test Summary';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
summary,
|
||||
});
|
||||
|
||||
@@ -77,7 +78,7 @@ describe('POST /group', () => {
|
||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_GUILDS + 1);
|
||||
await expect(user.post('/groups', {
|
||||
name,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
summary,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
@@ -87,6 +88,157 @@ describe('POST /group', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Guilds', () => {
|
||||
it('returns an error when a user with insufficient funds attempts to create a guild', async () => {
|
||||
await user.update({ balance: 0 });
|
||||
|
||||
await expect(
|
||||
user.post('/groups', {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
}),
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageInsufficientGems'),
|
||||
});
|
||||
});
|
||||
|
||||
it('adds guild to user\'s list of guilds', async () => {
|
||||
const guild = await user.post('/groups', {
|
||||
name: 'some guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.guilds).to.include(guild._id);
|
||||
});
|
||||
|
||||
it('awards the Joined Guild achievement', async () => {
|
||||
await user.post('/groups', {
|
||||
name: 'some guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.achievements.joinedGuild).to.eql(true);
|
||||
});
|
||||
|
||||
context('public guild', () => {
|
||||
it('creates a group', async () => {
|
||||
const groupName = 'Test Public Guild';
|
||||
const groupType = 'guild';
|
||||
const groupPrivacy = 'public';
|
||||
|
||||
const publicGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(publicGuild._id).to.exist;
|
||||
expect(publicGuild.name).to.equal(groupName);
|
||||
expect(publicGuild.type).to.equal(groupType);
|
||||
expect(publicGuild.memberCount).to.equal(1);
|
||||
expect(publicGuild.privacy).to.equal(groupPrivacy);
|
||||
expect(publicGuild.leader).to.eql({
|
||||
_id: user._id,
|
||||
profile: {
|
||||
name: user.profile.name,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when a user with no chat privileges attempts to create a public guild', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
|
||||
await expect(
|
||||
user.post('/groups', {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}),
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('private guild', () => {
|
||||
const groupName = 'Test Private Guild';
|
||||
const groupType = 'guild';
|
||||
const groupPrivacy = 'private';
|
||||
|
||||
it('creates a group', async () => {
|
||||
const privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild._id).to.exist;
|
||||
expect(privateGuild.name).to.equal(groupName);
|
||||
expect(privateGuild.type).to.equal(groupType);
|
||||
expect(privateGuild.memberCount).to.equal(1);
|
||||
expect(privateGuild.privacy).to.equal(groupPrivacy);
|
||||
expect(privateGuild.leader).to.eql({
|
||||
_id: user._id,
|
||||
profile: {
|
||||
name: user.profile.name,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a private guild when the user has no chat privileges', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
const privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild._id).to.exist;
|
||||
});
|
||||
|
||||
it('deducts gems from user and adds them to guild bank', async () => {
|
||||
const privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild.balance).to.eql(1);
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.balance).to.eql(user.balance - 1);
|
||||
});
|
||||
|
||||
it('does not deduct the gems from user when guild creation fails', async () => {
|
||||
const stub = sinon.stub(Group.prototype, 'save').rejects();
|
||||
const promise = user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
await expect(promise).to.eventually.be.rejected;
|
||||
|
||||
const updatedUser = await user.get('/user');
|
||||
|
||||
expect(updatedUser.balance).to.eql(user.balance);
|
||||
|
||||
stub.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Parties', () => {
|
||||
const partyName = 'Test Party';
|
||||
const partyType = 'party';
|
||||
@@ -110,7 +262,7 @@ describe('POST /group', () => {
|
||||
});
|
||||
|
||||
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', {
|
||||
name: partyName,
|
||||
type: partyType,
|
||||
@@ -120,7 +272,7 @@ describe('POST /group', () => {
|
||||
});
|
||||
|
||||
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', {
|
||||
name: partyName,
|
||||
|
||||
@@ -18,24 +18,81 @@ describe('POST /group/:groupId/join', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('Joining a private guild', () => {
|
||||
let user;
|
||||
let invitedUser;
|
||||
let guild;
|
||||
let invitees;
|
||||
context('Joining a public guild', () => {
|
||||
let user; let joiningUser; let
|
||||
publicGuild;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group: guild, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
user = groupLeader;
|
||||
joiningUser = await generateUser();
|
||||
});
|
||||
|
||||
it('allows non-invited users to join public guilds', async () => {
|
||||
const res = await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get('/user')).to.eventually.have.property('guilds').to.include(publicGuild._id);
|
||||
expect(res.leader._id).to.eql(user._id);
|
||||
expect(res.leader.profile.name).to.eql(user.profile.name);
|
||||
});
|
||||
|
||||
it('returns an error if user was already a member', async () => {
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
await expect(joiningUser.post(`/groups/${publicGuild._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('youAreAlreadyInGroup'),
|
||||
});
|
||||
});
|
||||
|
||||
it('promotes joining member in a public empty guild to leader', async () => {
|
||||
await user.post(`/groups/${publicGuild._id}/leave`);
|
||||
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.nested.property('leader._id', joiningUser._id);
|
||||
});
|
||||
|
||||
it('increments memberCount when joining guilds', async () => {
|
||||
const oldMemberCount = publicGuild.memberCount;
|
||||
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
|
||||
});
|
||||
|
||||
it('awards Joined Guild achievement', async () => {
|
||||
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||
|
||||
await expect(joiningUser.get('/user')).to.eventually.have.nested.property('achievements.joinedGuild', true);
|
||||
});
|
||||
});
|
||||
|
||||
context('Joining a private guild', () => {
|
||||
let user; let invitedUser; let
|
||||
guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
|
||||
[invitedUser] = invitees;
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('returns error when user is not invited to private guild', async () => {
|
||||
@@ -78,7 +135,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
});
|
||||
|
||||
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`);
|
||||
|
||||
@@ -125,7 +182,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
|
||||
party = group;
|
||||
user = groupLeader;
|
||||
[invitedUser] = invitees;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('returns error when user is not invited to party', async () => {
|
||||
@@ -146,7 +203,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||
@@ -193,7 +250,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
});
|
||||
|
||||
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`);
|
||||
|
||||
@@ -201,7 +258,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
});
|
||||
|
||||
it('invites joining member to active quest', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
});
|
||||
await user.post(`/groups/${party._id}/quests/invite/${PET_QUEST}`);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
generateChallenge,
|
||||
checkExistence,
|
||||
createAndPopulateGroup,
|
||||
sleep,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
@@ -13,187 +14,253 @@ import payments from '../../../../../website/server/libs/payments/payments';
|
||||
import calculateSubscriptionTerminationDate from '../../../../../website/server/libs/payments/calculateSubscriptionTerminationDate';
|
||||
|
||||
describe('POST /groups/:groupId/leave', () => {
|
||||
let groupToLeave;
|
||||
let leader;
|
||||
let member;
|
||||
let members;
|
||||
let memberCount;
|
||||
const typesOfGroups = {
|
||||
'public guild': { type: 'guild', privacy: 'public' },
|
||||
'private guild': { type: 'guild', privacy: 'private' },
|
||||
party: { type: 'party', privacy: 'private' },
|
||||
};
|
||||
|
||||
context('Leaving a Group Plan', () => {
|
||||
beforeEach(async () => {
|
||||
({ group: groupToLeave, groupLeader: leader, members } = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
|
||||
[member] = members;
|
||||
memberCount = groupToLeave.memberCount;
|
||||
await leader.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;
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a ${groupType}`, () => {
|
||||
let groupToLeave;
|
||||
let leader;
|
||||
let member;
|
||||
let memberCount;
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
groupToLeave = group;
|
||||
leader = groupLeader;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
memberCount = group.memberCount;
|
||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
|
||||
it('prevents non members from leaving', async () => {
|
||||
const user = await generateUser();
|
||||
await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('removes all challenge tasks when keep parameter is set to remove', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
|
||||
|
||||
const userWithoutChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('keeps all challenge tasks when keep parameter is not set', async () => {
|
||||
it(`lets user leave a ${groupType}`, async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
const userThatLeftGroup = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
expect(userThatLeftGroup.guilds).to.be.empty;
|
||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
});
|
||||
|
||||
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
||||
it(`sets a new group leader when leader leaves a ${groupType}`, async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
expect(groupToLeave.leader).to.equal(member._id);
|
||||
});
|
||||
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
await sleep(0.5);
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.be.undefined;
|
||||
});
|
||||
|
||||
context('with challenges', () => {
|
||||
let challenge;
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await leader.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await sleep(0.5);
|
||||
});
|
||||
|
||||
it('removes all challenge tasks when keep parameter is set to remove', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
|
||||
|
||||
const userWithoutChallengeTasks = await leader.get('/user');
|
||||
|
||||
expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('keeps all challenge tasks when keep parameter is not set', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await leader.get('/user');
|
||||
|
||||
// @TODO find elegant way to assert against the task existing
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
||||
|
||||
const userWithChallengeTasks = await leader.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
});
|
||||
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await leader.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents quest leader from leaving a groupToLeave');
|
||||
it('prevents a user from leaving during an active quest');
|
||||
});
|
||||
});
|
||||
|
||||
context('Leaving a Party', () => {
|
||||
let invitees;
|
||||
let invitedUser;
|
||||
context('Leaving a group as the last member', () => {
|
||||
context('private guild', () => {
|
||||
let privateGuild;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
({
|
||||
group: groupToLeave,
|
||||
groupLeader: leader,
|
||||
members,
|
||||
invitees,
|
||||
} = await createAndPopulateGroup({
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
members: 1,
|
||||
invites: 1,
|
||||
}));
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Private Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
invites: 1,
|
||||
leaderDetails: {
|
||||
'auth.timestamps.created': new Date('2022-01-01'),
|
||||
balance: 10,
|
||||
},
|
||||
});
|
||||
|
||||
[member] = members;
|
||||
[invitedUser] = invitees;
|
||||
memberCount = groupToLeave.memberCount;
|
||||
await leader.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||
});
|
||||
privateGuild = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
it('prevents non members from leaving', async () => {
|
||||
const user = await generateUser();
|
||||
await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
await leader.post(`/groups/${group._id}/chat`, { message: 'Some message' });
|
||||
});
|
||||
|
||||
it('removes a group when the last member leaves', async () => {
|
||||
await leader.post(`/groups/${privateGuild._id}/leave`);
|
||||
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
it('removes invitations when the last member leaves', async () => {
|
||||
await leader.post(`/groups/${privateGuild._id}/leave`);
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.guilds).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
it('lets user leave', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
context('public guild', () => {
|
||||
let publicGuild;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
|
||||
const userThatLeftGroup = await member.get('/user');
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
|
||||
expect(userThatLeftGroup.guilds).to.be.empty;
|
||||
expect(userThatLeftGroup.party._id).to.not.exist;
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
publicGuild = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('keeps the group when the last member leaves', async () => {
|
||||
await leader.post(`/groups/${publicGuild._id}/leave`);
|
||||
|
||||
await expect(checkExistence('groups', publicGuild._id)).to.eventually.equal(true);
|
||||
});
|
||||
|
||||
it('keeps the invitations when the last member leaves a public guild', async () => {
|
||||
await leader.post(`/groups/${publicGuild._id}/leave`);
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.guilds).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('deletes non existent guild from user when user tries to leave', async () => {
|
||||
const nonExistentGuildId = generateUUID();
|
||||
const userWithNonExistentGuild = await generateUser({ guilds: [nonExistentGuildId] });
|
||||
expect(userWithNonExistentGuild.guilds).to.contain(nonExistentGuildId);
|
||||
|
||||
await expect(userWithNonExistentGuild.post(`/groups/${nonExistentGuildId}/leave`))
|
||||
.to.eventually.be.rejected;
|
||||
|
||||
await userWithNonExistentGuild.sync();
|
||||
|
||||
expect(userWithNonExistentGuild.guilds).to.not.contain(nonExistentGuildId);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets a new group leader when leader leaves', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
context('party', () => {
|
||||
let party;
|
||||
let leader;
|
||||
let invitedUser;
|
||||
|
||||
await groupToLeave.sync();
|
||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||
expect(groupToLeave.leader).to.equal(member._id);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
await member.sync();
|
||||
party = group;
|
||||
leader = groupLeader;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
it('removes a group when the last member leaves a party', async () => {
|
||||
await leader.post(`/groups/${party._id}/leave`);
|
||||
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await member.sync();
|
||||
await expect(checkExistence('party', party._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
||||
expect(member.newMessages[groupToLeave._id]).to.be.undefined;
|
||||
});
|
||||
it('removes invitations when the last member leaves a party', async () => {
|
||||
await leader.post(`/groups/${party._id}/leave`);
|
||||
|
||||
it('removes a party when the last member leaves', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
await expect(checkExistence('party', groupToLeave._id)).to.eventually.equal(false);
|
||||
});
|
||||
|
||||
it('removes invitations when the last member leaves a party', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes non existent party from user when user tries to leave', async () => {
|
||||
@@ -208,71 +275,23 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
|
||||
expect(userWithNonExistentParty.party).to.eql({});
|
||||
});
|
||||
|
||||
context('with challenges', () => {
|
||||
let challenge;
|
||||
|
||||
beforeEach(async () => {
|
||||
challenge = await generateChallenge(leader, groupToLeave);
|
||||
await member.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await leader.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
});
|
||||
|
||||
it('removes all challenge tasks when keep parameter is set to remove', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
|
||||
|
||||
const userWithoutChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
|
||||
it('keeps all challenge tasks when keep parameter is not set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' });
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||
});
|
||||
|
||||
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||
|
||||
const userWithChallengeTasks = await member.get('/user');
|
||||
|
||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const typesOfGroups = {
|
||||
'private guild': { type: 'guild', privacy: 'private' },
|
||||
party: { type: 'party', privacy: 'private' },
|
||||
};
|
||||
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a group plan when the group is a ${groupType}`, () => {
|
||||
if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans
|
||||
let groupWithPlan;
|
||||
let leader;
|
||||
let member;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group: groupWithPlan, groupLeader: leader, members } = await createAndPopulateGroup({
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
[member] = members;
|
||||
});
|
||||
leader = groupLeader;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
groupWithPlan = group;
|
||||
const userWithFreePlan = await User.findById(leader._id).exec();
|
||||
|
||||
// Create subscription
|
||||
@@ -302,22 +321,46 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.dateTerminated).to.exist;
|
||||
});
|
||||
|
||||
it('preserves the free subscription when leaving a any other group without a plan', async () => {
|
||||
// Joining a guild without a group plan
|
||||
const { group: groupWithNoPlan } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Group Without Plan',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
await member.post(`/groups/${groupWithNoPlan._id}/join`);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.planId).to.equal('group_plan_auto');
|
||||
expect(member.purchased.plan.dateTerminated).to.not.exist;
|
||||
|
||||
// Leaving the guild without a group plan
|
||||
await member.post(`/groups/${groupWithNoPlan._id}/leave`);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.dateTerminated).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
each(typesOfGroups, (groupDetails, groupType) => {
|
||||
context(`Leaving a group with extraMonths left plan when the group is a ${groupType}`, () => {
|
||||
if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans
|
||||
const extraMonths = 12;
|
||||
let groupWithPlan;
|
||||
let member;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group: groupWithPlan, members } = await createAndPopulateGroup({
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
});
|
||||
[member] = members;
|
||||
await member.updateOne({
|
||||
groupWithPlan = group;
|
||||
await member.update({
|
||||
'purchased.plan.extraMonths': extraMonths,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,43 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /group/:groupId/reject-invite', () => {
|
||||
context('Rejecting a public guild invite', () => {
|
||||
let publicGuild; let
|
||||
invitedUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, invitees } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
invites: 1,
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
||||
});
|
||||
|
||||
it('returns error when user is not invited', async () => {
|
||||
const userWithoutInvite = await generateUser();
|
||||
|
||||
await expect(userWithoutInvite.post(`/groups/${publicGuild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageGroupRequiresInvite'),
|
||||
});
|
||||
});
|
||||
|
||||
it('clears invitation from user', async () => {
|
||||
await invitedUser.post(`/groups/${publicGuild._id}/reject-invite`);
|
||||
|
||||
await expect(invitedUser.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.guilds')
|
||||
.to.not.include({ id: publicGuild._id });
|
||||
});
|
||||
});
|
||||
|
||||
context('Rejecting a private guild invite', () => {
|
||||
let invitedUser; let
|
||||
guild;
|
||||
@@ -17,7 +54,6 @@ describe('POST /group/:groupId/reject-invite', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
invites: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
|
||||
@@ -25,7 +25,6 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
},
|
||||
invites: 1,
|
||||
members: 2,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
@@ -130,11 +129,9 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
it('sends email to removed user', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledTwice;
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.eql(member._id);
|
||||
expect(email.sendTxn.args[0][1]).to.eql('kicked-from-guild');
|
||||
expect(email.sendTxn.args[1][0]._id).to.eql(member._id);
|
||||
expect(email.sendTxn.args[1][1]).to.eql('group-member-removed');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -212,7 +209,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
|
||||
it('removes user from quest when removing user from party after quest starts', async () => {
|
||||
const petQuest = 'whale';
|
||||
await partyLeader.updateOne({
|
||||
await partyLeader.update({
|
||||
[`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 () => {
|
||||
const petQuest = 'whale';
|
||||
await partyLeader.updateOne({
|
||||
await partyLeader.update({
|
||||
[`items.quests.${petQuest}`]: 1,
|
||||
});
|
||||
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 () => {
|
||||
const petQuest = 'whale';
|
||||
await partyMember.updateOne({
|
||||
await partyMember.update({
|
||||
[`items.quests.${petQuest}`]: 1,
|
||||
});
|
||||
|
||||
|
||||
@@ -3,23 +3,24 @@ import nconf from 'nconf';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
const INVITES_LIMIT = 100;
|
||||
const PARTY_LIMIT_MEMBERS = 30;
|
||||
const PARTY_LIMIT_MEMBERS = 29;
|
||||
const MAX_EMAIL_INVITES_BY_USER = 200;
|
||||
|
||||
describe('Post /groups/:groupId/invite', () => {
|
||||
let inviter;
|
||||
let group;
|
||||
const groupName = 'Test Party';
|
||||
const groupName = 'Test Public Guild';
|
||||
|
||||
beforeEach(async () => {
|
||||
inviter = await generateUser({ balance: 4 });
|
||||
group = await inviter.post('/groups', {
|
||||
name: groupName,
|
||||
type: 'party',
|
||||
type: 'guild',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,7 +50,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
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] });
|
||||
await expect(inviterNoBlocks.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userWithBlockedInviter.auth.local.lowerCaseUsername],
|
||||
@@ -64,50 +65,51 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('invites a user to a group by username', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [userToInvite.auth.local.lowerCaseUsername],
|
||||
});
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
})).to.eventually.deep.equal([{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
}]);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
.to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
|
||||
it('invites multiple users to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
|
||||
const response = await (inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
usernames: [
|
||||
userToInvite.auth.local.lowerCaseUsername,
|
||||
userToInvite2.auth.local.lowerCaseUsername,
|
||||
],
|
||||
}));
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
expect(response[1]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[1]._id).to.be.a('String');
|
||||
expect(response[1].id).to.eql(group._id);
|
||||
expect(response[1].name).to.eql(groupName);
|
||||
expect(response[1].inviter).to.eql(inviter._id);
|
||||
})).to.eventually.deep.equal([
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('user id invites', () => {
|
||||
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();
|
||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
@@ -197,7 +199,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
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] });
|
||||
await expect(inviterNoBlocks.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userWithBlockedInviter._id],
|
||||
@@ -212,42 +214,42 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('invites a user to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
})).to.eventually.deep.equal([{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
}]);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
.to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
.to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
|
||||
it('invites multiple users to a group by uuid', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
|
||||
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id, userToInvite2._id],
|
||||
});
|
||||
})).to.eventually.deep.equal([
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(response).to.be.an('Array');
|
||||
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[0]._id).to.be.a('String');
|
||||
expect(response[0].id).to.eql(group._id);
|
||||
expect(response[0].name).to.eql(groupName);
|
||||
expect(response[0].inviter).to.eql(inviter._id);
|
||||
expect(response[1]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||
expect(response[1]._id).to.be.a('String');
|
||||
expect(response[1].id).to.eql(group._id);
|
||||
expect(response[1].name).to.eql(groupName);
|
||||
expect(response[1].inviter).to.eql(inviter._id);
|
||||
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
||||
});
|
||||
|
||||
it('returns an error when inviting multiple users and a user is not found', async () => {
|
||||
@@ -269,7 +271,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
const testInvite = { name: 'test', email: 'test@habitica.com' };
|
||||
|
||||
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`, {
|
||||
emails: [testInvite],
|
||||
inviter: 'inviter name',
|
||||
@@ -336,8 +338,12 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
invitesSent: MAX_EMAIL_INVITES_BY_USER,
|
||||
balance: 4,
|
||||
});
|
||||
const tmpGroup = await inviterWithMax.post('/groups', {
|
||||
name: groupName,
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
await expect(inviterWithMax.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviterWithMax.post(`/groups/${tmpGroup._id}/invite`, {
|
||||
emails: [testInvite],
|
||||
inviter: 'inviter name',
|
||||
}))
|
||||
@@ -413,15 +419,15 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
const invitedUser = await newUser.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(group._id);
|
||||
expect(invitedUser.invitations.guilds[0].id).to.equal(group._id);
|
||||
expect(invite).to.exist;
|
||||
});
|
||||
|
||||
it('invites user to group with cancelled plan', async () => {
|
||||
let cancelledPlanGroup;
|
||||
({ group: cancelledPlanGroup, groupLeader: inviter } = await createAndPopulateGroup({
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
it('invites marks invite with cancelled plan', async () => {
|
||||
const cancelledPlanGroup = await generateGroup(inviter, {
|
||||
type: 'guild',
|
||||
name: generateUUID(),
|
||||
});
|
||||
await cancelledPlanGroup.createCancelledSubscription();
|
||||
|
||||
const newUser = await generateUser();
|
||||
@@ -431,15 +437,15 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
const invitedUser = await newUser.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(cancelledPlanGroup._id);
|
||||
expect(invitedUser.invitations.parties[0].cancelledPlan).to.be.true;
|
||||
expect(invitedUser.invitations.guilds[0].id).to.equal(cancelledPlanGroup._id);
|
||||
expect(invitedUser.invitations.guilds[0].cancelledPlan).to.be.true;
|
||||
expect(invite).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('party invites', () => {
|
||||
describe('guild invites', () => {
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
const inviterMuted = await inviter.updateOne({ 'flags.chatRevoked': true });
|
||||
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||
const userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
@@ -451,7 +457,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user has a pending invitation to the party', async () => {
|
||||
it('returns an error when invited user is already invited to the group', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
@@ -459,6 +465,96 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('userAlreadyInvitedToGroup', { userId: userToInvite._id, username: userToInvite.profile.name }),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user is already in the group', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
await userToInvite.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('userAlreadyInGroup', { userId: userToInvite._id, username: userToInvite.profile.name }),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows 30+ members in a guild', async () => {
|
||||
const invitesToGenerate = [];
|
||||
// Generate 30 users to invite (30 + leader = 31 members)
|
||||
for (let i = 0; i < PARTY_LIMIT_MEMBERS; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
expect(await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
})).to.be.an('array');
|
||||
}).timeout(10000);
|
||||
|
||||
// @TODO: Add this after we are able to mock the group plan route
|
||||
xit('returns an error when a non-leader invites to a group plan', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
|
||||
const nonGroupLeader = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [nonGroupLeader._id],
|
||||
});
|
||||
await nonGroupLeader.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(nonGroupLeader.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanInviteToGroupPlan'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('party invites', () => {
|
||||
let party;
|
||||
|
||||
beforeEach(async () => {
|
||||
party = await inviter.post('/groups', {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||
const userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user has a pending invitation to the party', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
@@ -470,13 +566,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
it('returns an error when invited user is already in a party of more than 1 member', async () => {
|
||||
const userToInvite = await generateUser();
|
||||
const userToInvite2 = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id, userToInvite2._id],
|
||||
});
|
||||
await userToInvite.post(`/groups/${group._id}/join`);
|
||||
await userToInvite2.post(`/groups/${group._id}/join`);
|
||||
await userToInvite.post(`/groups/${party._id}/join`);
|
||||
await userToInvite2.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
@@ -500,7 +596,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
// Invite to first party
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
@@ -513,27 +609,28 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
const invitedUser = await userToInvite.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties.length).to.equal(2);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(group._id);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(party._id);
|
||||
expect(invitedUser.invitations.parties[1].id).to.equal(party2._id);
|
||||
});
|
||||
|
||||
it('allows inviting a user if party id is not associated with a real party', async () => {
|
||||
it('allow inviting a user if party id is not associated with a real party', async () => {
|
||||
const userToInvite = await generateUser({
|
||||
party: { _id: generateUUID() },
|
||||
});
|
||||
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(group._id);
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('party size limits', () => {
|
||||
let party;
|
||||
let partyLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ group, groupLeader: partyLeader } = await createAndPopulateGroup({
|
||||
group = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Party',
|
||||
type: 'party',
|
||||
@@ -541,7 +638,9 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
},
|
||||
// Generate party with 20 members
|
||||
members: PARTY_LIMIT_MEMBERS - 10,
|
||||
}));
|
||||
});
|
||||
party = group.group;
|
||||
partyLeader = group.groupLeader;
|
||||
});
|
||||
|
||||
it('allows 30 members in a party', async () => {
|
||||
@@ -552,7 +651,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
expect(await partyLeader.post(`/groups/${group._id}/invite`, {
|
||||
expect(await partyLeader.post(`/groups/${party._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
})).to.be.an('array');
|
||||
}).timeout(10000);
|
||||
@@ -565,13 +664,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
// Invite users
|
||||
await expect(partyLeader.post(`/groups/${group._id}/invite`, {
|
||||
await expect(partyLeader.post(`/groups/${party._id}/invite`, {
|
||||
uuids: generatedInvites.map(invite => invite._id),
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS }),
|
||||
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS + 1 }),
|
||||
});
|
||||
}).timeout(10000);
|
||||
});
|
||||
|
||||
@@ -17,10 +17,9 @@ describe('POST /group/:groupId/add-manager', () => {
|
||||
groupDetails: {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
groupToUpdate = group;
|
||||
|
||||
@@ -23,11 +23,10 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
categories: groupCategories,
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
adminUser = await generateUser({ 'permissions.moderator': true });
|
||||
groupToUpdate = group;
|
||||
@@ -107,28 +106,14 @@ describe('PUT /group', () => {
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
it('does not allow a leader to change leader of active group plan', async () => {
|
||||
await expect(leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
it('allows a leader to change leaders', async () => {
|
||||
const updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
name: groupUpdatedName,
|
||||
leader: nonLeader._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotChangeLeaderWithActiveGroupPlan'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a leader of a party to change leaders', async () => {
|
||||
const { group: party, groupLeader: partyLeader, members } = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
});
|
||||
const updatedGroup = await partyLeader.put(`/groups/${party._id}`, {
|
||||
name: groupUpdatedName,
|
||||
leader: members[0]._id,
|
||||
});
|
||||
|
||||
expect(updatedGroup.leader._id).to.eql(members[0]._id);
|
||||
expect(updatedGroup.leader.profile.name).to.eql(members[0].profile.name);
|
||||
expect(updatedGroup.leader._id).to.eql(nonLeader._id);
|
||||
expect(updatedGroup.leader.profile.name).to.eql(nonLeader.profile.name);
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
@@ -137,16 +122,15 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
const updateGroupDetails = {
|
||||
id: group._id,
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
bannedWordsAllowed: true,
|
||||
};
|
||||
|
||||
@@ -166,11 +150,9 @@ describe('PUT /group', () => {
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
await groupLeader.updateOne({ permissions: {} });
|
||||
|
||||
const updateGroupDetails = {
|
||||
id: group._id,
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /members/:memberId/clear-flags', () => {
|
||||
let reporter;
|
||||
let admin;
|
||||
let moderator;
|
||||
|
||||
beforeEach(async () => {
|
||||
reporter = await generateUser();
|
||||
admin = await generateUser({ permissions: { userSupport: true } });
|
||||
moderator = await generateUser({ permissions: { moderator: true } });
|
||||
await reporter.post(`/members/${admin._id}/flag`);
|
||||
});
|
||||
|
||||
context('error cases', () => {
|
||||
it('returns error when memberId is not a UUID', async () => {
|
||||
await expect(moderator.post('/members/gribbly/clear-flags'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when member with UUID is not found', async () => {
|
||||
const randomId = generateUUID();
|
||||
|
||||
await expect(moderator.post(`/members/${randomId}/clear-flags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', { userId: randomId }),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when requesting user is not a moderator', async () => {
|
||||
await expect(reporter.post(`/members/${admin._id}/clear-flags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Only a moderator may clear reports from a profile.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('valid request', () => {
|
||||
it('removes a single flag from user', async () => {
|
||||
await expect(moderator.post(`/members/${admin._id}/clear-flags`)).to.eventually.be.ok;
|
||||
const updatedTarget = await admin.get(`/hall/heroes/${admin._id}`);
|
||||
expect(updatedTarget.profile.flags).to.eql({});
|
||||
});
|
||||
|
||||
it('removes multiple flags from user', async () => {
|
||||
await moderator.post(`/members/${admin._id}/flag`);
|
||||
await expect(moderator.post(`/members/${admin._id}/clear-flags`)).to.eventually.be.ok;
|
||||
const updatedTarget = await admin.get(`/hall/heroes/${admin._id}`);
|
||||
expect(updatedTarget.profile.flags).to.eql({});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,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 sendersMessageInReceiversInbox = findMessage(
|
||||
updatedReceiver.inbox.messages,
|
||||
userToSendMessage._id,
|
||||
updatedReceiver.inbox.messages, userToSendMessage._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 sendersMessageInReceiversInbox = findMessage(
|
||||
updatedReceiver.inbox.messages,
|
||||
userToSendMessage._id,
|
||||
updatedReceiver.inbox.messages, userToSendMessage._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 () => {
|
||||
await receiver.updateOne({
|
||||
await receiver.update({
|
||||
'preferences.language': 'es',
|
||||
});
|
||||
await userToSendMessage.updateOne({
|
||||
await userToSendMessage.update({
|
||||
'preferences.language': 'cs',
|
||||
});
|
||||
await userToSendMessage.post('/members/transfer-gems', {
|
||||
@@ -250,8 +248,7 @@ describe('POST /members/transfer-gems', () => {
|
||||
const updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
const sendersMessageInReceiversInbox = findMessage(
|
||||
updatedReceiver.inbox.messages,
|
||||
userToSendMessage._id,
|
||||
updatedReceiver.inbox.messages, userToSendMessage._id,
|
||||
);
|
||||
const sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ describe('POST /notifications/:notificationId/read', () => {
|
||||
const id = generateUUID();
|
||||
const id2 = generateUUID();
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('POST /notifications/:notificationId/see', () => {
|
||||
const id = generateUUID();
|
||||
const id2 = generateUUID();
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('POST /notifications/read', () => {
|
||||
const id2 = generateUUID();
|
||||
const id3 = generateUUID();
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('POST /notifications/see', () => {
|
||||
const id2 = generateUUID();
|
||||
const id3 = generateUUID();
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('Prevent multiple notifications', () => {
|
||||
|
||||
for (let i = 0; i < 4; i += 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(
|
||||
partyMembers[memberIndex].post(`/groups/${party._id}/chat`, { message: `Message ${i}_${memberIndex}` }),
|
||||
);
|
||||
|
||||
+17
-16
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||
@@ -50,21 +50,22 @@ describe('payments : amazon #subscribeCancel', () => {
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
});
|
||||
|
||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ describe('payments - amazon - #subscribe', () => {
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
|
||||
+17
-16
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
translate as t,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
@@ -48,21 +48,22 @@ describe('payments - stripe - #subscribeCancel', () => {
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
leaderDetails: {
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
},
|
||||
upgradeToGroupPlan: true,
|
||||
}));
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
group = await generateGroup(user, {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
});
|
||||
|
||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
||||
leader = groupLeader;
|
||||
partyMembers = members;
|
||||
|
||||
await leader.updateOne({
|
||||
await leader.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
});
|
||||
});
|
||||
@@ -53,7 +53,6 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
||||
it('does not accept quest for a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`))
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
leader = groupLeader;
|
||||
partyMembers = members;
|
||||
|
||||
await leader.updateOne({
|
||||
await leader.update({
|
||||
[`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 () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
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', () => {
|
||||
it('allows quest leader to force start quest', async () => {
|
||||
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/force-start`);
|
||||
@@ -105,7 +104,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
|
||||
|
||||
it('allows group leader to force start quest', async () => {
|
||||
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 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}`);
|
||||
const notInPartyUser = await generateUser();
|
||||
|
||||
await questingGroup.updateOne({
|
||||
await questingGroup.update({
|
||||
[`quest.members.${notInPartyUser._id}`]: true,
|
||||
});
|
||||
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 partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
|
||||
await questingGroup.updateOne({
|
||||
await questingGroup.update({
|
||||
[`quest.members.${partyMemberThatRejects._id}`]: false,
|
||||
[`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 () => {
|
||||
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 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 () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
const { group } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'public' },
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||
const alternateGroup = group;
|
||||
|
||||
await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('guildQuestsNotSupported'),
|
||||
@@ -87,8 +88,8 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
const leaderUpdate = {};
|
||||
leaderUpdate[`items.quests.${PET_QUEST}`] = 1;
|
||||
|
||||
await leader.updateOne(leaderUpdate);
|
||||
await questingGroup.updateOne({ 'quest.key': QUEST_IN_PROGRESS });
|
||||
await leader.update(leaderUpdate);
|
||||
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({
|
||||
code: 401,
|
||||
@@ -104,8 +105,8 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
memberUpdate[`items.quests.${PET_QUEST}`] = 1;
|
||||
|
||||
await Promise.all([
|
||||
leader.updateOne(memberUpdate),
|
||||
member.updateOne(memberUpdate),
|
||||
leader.update(memberUpdate),
|
||||
member.update(memberUpdate),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -202,7 +203,7 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
leaderUpdate[`items.quests.${LEVELED_QUEST}`] = 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}`);
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
leader = groupLeader;
|
||||
partyMembers = members;
|
||||
|
||||
await leader.updateOne({
|
||||
await leader.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
});
|
||||
user = await generateUser();
|
||||
@@ -52,7 +52,6 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`))
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
leader = groupLeader;
|
||||
partyMembers = members;
|
||||
|
||||
await leader.updateOne({
|
||||
await leader.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
});
|
||||
user = await generateUser();
|
||||
@@ -52,7 +52,6 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`))
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('POST /groups/:groupId/quests/leave', () => {
|
||||
leader = groupLeader;
|
||||
partyMembers = members;
|
||||
|
||||
await leader.updateOne({
|
||||
await leader.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
});
|
||||
user = await generateUser();
|
||||
@@ -51,7 +51,6 @@ describe('POST /groups/:groupId/quests/leave', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`))
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
||||
leader = groupLeader;
|
||||
partyMembers = members;
|
||||
|
||||
await leader.updateOne({
|
||||
await leader.update({
|
||||
[`items.quests.${PET_QUEST}`]: 1,
|
||||
});
|
||||
user = await generateUser();
|
||||
@@ -53,7 +53,6 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
||||
it('returns an error when group is a guild', async () => {
|
||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`))
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('GET /shops/market', () => {
|
||||
});
|
||||
|
||||
it('can purchase anything returned from the shops object using the /user/purchase route', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
balance: 99999999,
|
||||
'stats.gp': 99999999,
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('GET /shops/time-travelers', () => {
|
||||
});
|
||||
|
||||
it('returns active shop notes and imageName if user has trinkets', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'purchased.plan.consecutive.trinkets': 1,
|
||||
});
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('GET /shops/time-travelers', () => {
|
||||
});
|
||||
|
||||
it('does not return mystery sets that are already owned', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'items.gear.owned': {
|
||||
head_mystery_201606: true, // eslint-disable-line camelcase
|
||||
armor_mystery_201606: true, // eslint-disable-line camelcase
|
||||
@@ -72,7 +72,7 @@ describe('GET /shops/time-travelers', () => {
|
||||
});
|
||||
|
||||
it('does not return pets and mounts that user already owns', async () => {
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'items.mounts': {
|
||||
'MantisShrimp-Base': true,
|
||||
},
|
||||
|
||||
@@ -86,7 +86,7 @@ describe('DELETE /tasks/:id', () => {
|
||||
it('does not send task activity webhooks if task is not user owned', async () => {
|
||||
const uuid = generateUUID();
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
balance: 10,
|
||||
});
|
||||
const guild = await generateGroup(user);
|
||||
|
||||
@@ -154,7 +154,7 @@ describe('GET /tasks/user', () => {
|
||||
|
||||
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
const timezoneOffset = 420;
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'preferences.dayStart': 0,
|
||||
'preferences.timezoneOffset': timezoneOffset,
|
||||
});
|
||||
@@ -181,7 +181,7 @@ describe('GET /tasks/user', () => {
|
||||
|
||||
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
const timezoneOffset = 240;
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'preferences.dayStart': 0,
|
||||
'preferences.timezoneOffset': timezoneOffset,
|
||||
});
|
||||
@@ -208,7 +208,7 @@ describe('GET /tasks/user', () => {
|
||||
|
||||
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
const timezoneOffset = 540;
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'preferences.dayStart': 0,
|
||||
'preferences.timezoneOffset': timezoneOffset,
|
||||
});
|
||||
|
||||
@@ -106,7 +106,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
|
||||
const initialLvl = user.stats.lvl;
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'stats.exp': 3000,
|
||||
});
|
||||
const task = await user.post('/tasks/user', {
|
||||
|
||||
@@ -104,6 +104,7 @@ describe('POST /tasks/unlink-all/:challengeId', () => {
|
||||
id: challenge._id,
|
||||
shortName: challenge.shortName,
|
||||
broken: 'CHALLENGE_DELETED',
|
||||
winner: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -90,9 +90,7 @@ describe('POST /tasks/unlink-one/:taskId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// These tests are causing trouble. Race condition?
|
||||
|
||||
xit('unlinks a task from a challenge and saves it on keep=keep', async () => {
|
||||
it('unlinks a task from a challenge and saves it on keep=keep', async () => {
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
|
||||
let [daily] = await user.get('/tasks/user');
|
||||
await user.del(`/challenges/${challenge._id}`);
|
||||
@@ -101,7 +99,7 @@ describe('POST /tasks/unlink-one/:taskId', () => {
|
||||
expect(daily.challenge).to.eql({});
|
||||
});
|
||||
|
||||
xit('unlinks a task from a challenge and deletes it on keep=remove', async () => {
|
||||
it('unlinks a task from a challenge and deletes it on keep=remove', async () => {
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
|
||||
const [daily] = await user.get('/tasks/user');
|
||||
await user.del(`/challenges/${challenge._id}`);
|
||||
|
||||
@@ -200,7 +200,7 @@ describe('PUT /tasks/:id', () => {
|
||||
it('does not send task activity webhooks if task is not user owned', async () => {
|
||||
const uuid = generateUUID();
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
balance: 10,
|
||||
});
|
||||
const guild = await generateGroup(user);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||
@@ -7,12 +8,8 @@ describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||
guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
user = await generateUser({ balance: 1 });
|
||||
guild = await generateGroup(user, { type: 'guild' }, { 'purchased.plan.customerId': 'group-unlimited' });
|
||||
});
|
||||
|
||||
it('can move task to new position', async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
find,
|
||||
each,
|
||||
map,
|
||||
} from 'lodash';
|
||||
@@ -48,7 +49,6 @@ describe('DELETE /user', () => {
|
||||
});
|
||||
|
||||
it('deletes the user', async () => {
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(true);
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
@@ -158,12 +158,11 @@ describe('DELETE /user', () => {
|
||||
});
|
||||
|
||||
it('deletes the user with a legacy sha1 password', async () => {
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(true);
|
||||
const textPassword = 'mySecretPassword';
|
||||
const salt = sha1MakeSalt();
|
||||
const sha1HashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
'auth.local.hashed_password': sha1HashedPassword,
|
||||
'auth.local.passwordHashMethod': 'sha1',
|
||||
'auth.local.salt': salt,
|
||||
@@ -199,6 +198,95 @@ describe('DELETE /user', () => {
|
||||
await expect(checkExistence('party', party._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('last member of a private guild', () => {
|
||||
let privateGuild;
|
||||
|
||||
beforeEach(async () => {
|
||||
privateGuild = await generateGroup(user, {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes guild when user is the only member', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is leader of', () => {
|
||||
let guild; let oldLeader; let
|
||||
newLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
newLeader = members[0]; // eslint-disable-line prefer-destructuring
|
||||
oldLeader = groupLeader;
|
||||
});
|
||||
|
||||
it('chooses new group leader for any group user was the leader of', async () => {
|
||||
await oldLeader.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
const updatedGuild = await newLeader.get(`/groups/${guild._id}`);
|
||||
|
||||
expect(updatedGuild.leader).to.exist;
|
||||
expect(updatedGuild.leader._id).to.not.eql(oldLeader._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is a part of', () => {
|
||||
let group1; let group2; let userToDelete; let
|
||||
otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
userToDelete = await generateUser({ balance: 10 });
|
||||
|
||||
group1 = await generateGroup(userToDelete, {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
const { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
});
|
||||
|
||||
group2 = group;
|
||||
otherUser = members[0]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
await userToDelete.post(`/groups/${group2._id}/join`);
|
||||
});
|
||||
|
||||
it('removes user from all groups user was a part of', async () => {
|
||||
await userToDelete.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
const updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`);
|
||||
const updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`);
|
||||
const userInGroup = find(updatedGroup2Members, member => member._id === userToDelete._id);
|
||||
|
||||
expect(updatedGroup1Members).to.be.empty;
|
||||
expect(updatedGroup2Members).to.not.be.empty;
|
||||
expect(userInGroup).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('user with Google auth', async () => {
|
||||
@@ -213,7 +301,6 @@ describe('DELETE /user', () => {
|
||||
});
|
||||
|
||||
it('deletes a Google user', async () => {
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(true);
|
||||
await user.del('/user', {
|
||||
password: DELETE_CONFIRMATION,
|
||||
});
|
||||
@@ -233,7 +320,6 @@ describe('DELETE /user', () => {
|
||||
});
|
||||
|
||||
it('deletes a Apple user', async () => {
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(true);
|
||||
await user.del('/user', {
|
||||
password: DELETE_CONFIRMATION,
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('GET /user/anonymized', () => {
|
||||
text: 'Clark Kent',
|
||||
},
|
||||
});
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
newMessages: ['some', 'new', 'messages'],
|
||||
'profile.name': 'profile',
|
||||
'purchased.plan': 'purchased plan',
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('POST /user/move-pinned-item/:path/move/to/:position', () => {
|
||||
// For this test put seasonal items at the end so they stay out of the way
|
||||
testPinnedItemsOrder = testPinnedItemsOrder.concat(officialPinnedItemPaths);
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
pinnedItems: testPinnedItems,
|
||||
pinnedItemsOrder: testPinnedItemsOrder,
|
||||
});
|
||||
@@ -96,7 +96,7 @@ describe('POST /user/move-pinned-item/:path/move/to/:position', () => {
|
||||
'potion',
|
||||
];
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
pinnedItems: testPinnedItems,
|
||||
pinnedItemsOrder: testPinnedItemsOrder,
|
||||
});
|
||||
@@ -138,7 +138,7 @@ describe('POST /user/move-pinned-item/:path/move/to/:position', () => {
|
||||
'potion',
|
||||
];
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
pinnedItems: testPinnedItems,
|
||||
pinnedItemsOrder: testPinnedItemsOrder,
|
||||
});
|
||||
@@ -171,7 +171,7 @@ describe('POST /user/move-pinned-item/:path/move/to/:position', () => {
|
||||
// add item to pinned
|
||||
officialPinnedItems.push({ type: 'armoire', path: 'armoire' });
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
pinnedItems: testPinnedItems,
|
||||
pinnedItemsOrder: testPinnedItemsOrder,
|
||||
});
|
||||
@@ -201,7 +201,7 @@ describe('POST /user/move-pinned-item/:path/move/to/:position', () => {
|
||||
'potion',
|
||||
];
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
pinnedItems: testPinnedItems,
|
||||
pinnedItemsOrder: testPinnedItemsOrder,
|
||||
});
|
||||
@@ -225,7 +225,7 @@ describe('POST /user/move-pinned-item/:path/move/to/:position', () => {
|
||||
'potion',
|
||||
];
|
||||
|
||||
await user.updateOne({
|
||||
await user.update({
|
||||
pinnedItems: testPinnedItems,
|
||||
pinnedItemsOrder: testPinnedItemsOrder,
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user