Compare commits

..

1 Commits

Author SHA1 Message Date
Phillip Thelen
a018588021 Banning a user now automatically hides all their posts 2022-12-09 13:02:49 +01:00
2849 changed files with 318589 additions and 76246 deletions

View File

@@ -1,11 +1,6 @@
/* eslint-disable import/no-commonjs */
module.exports = { module.exports = {
root: true, root: true,
extends: [ extends: [
'habitrpg/lib/node', 'habitrpg/lib/node'
], ],
rules: { }
'prefer-regex-literals': 'warn',
'import/no-extraneous-dependencies': 'off',
},
};

186
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,186 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
time: "06:00"
timezone: Europe/Rome
open-pull-requests-limit: 99
ignore:
- dependency-name: express-validator
versions:
- 6.10.0
- 6.10.1
- 6.9.2
- dependency-name: "@babel/core"
versions:
- 7.12.13
- 7.12.16
- 7.12.17
- 7.13.1
- 7.13.10
- 7.13.13
- 7.13.14
- 7.13.15
- 7.13.8
- dependency-name: redis
versions:
- 3.1.0
- dependency-name: stripe
versions:
- 8.134.0
- 8.135.0
- 8.137.0
- 8.138.0
- 8.140.0
- 8.142.0
- dependency-name: "@babel/register"
versions:
- 7.12.13
- 7.13.14
- 7.13.8
- dependency-name: mongoose
versions:
- 5.11.14
- 5.11.15
- 5.11.16
- 5.11.17
- 5.11.18
- 5.11.19
- 5.12.0
- 5.12.1
- 5.12.2
- 5.12.3
- dependency-name: jwks-rsa
versions:
- 1.12.3
- 2.0.1
- 2.0.2
- dependency-name: "@babel/preset-env"
versions:
- 7.12.13
- 7.12.16
- 7.12.17
- 7.13.10
- 7.13.12
- 7.13.8
- 7.13.9
- dependency-name: image-size
versions:
- 0.9.4
- 0.9.5
- 0.9.7
- dependency-name: winston-loggly-bulk
versions:
- 3.2.0
- dependency-name: chai
versions:
- 4.3.0
- 4.3.3
- dependency-name: mocha
versions:
- 8.2.1
- 8.3.0
- 8.3.1
- dependency-name: "@google-cloud/trace-agent"
versions:
- 5.1.2
- dependency-name: monk
versions:
- 7.3.3
- package-ecosystem: npm
directory: "/website/client"
schedule:
interval: weekly
time: "06:00"
timezone: Europe/Rome
open-pull-requests-limit: 99
ignore:
- dependency-name: eslint-plugin-vue
versions:
- 7.5.0
- 7.6.0
- 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
- 3.10.1
- 3.9.0
- 3.9.1
- dependency-name: bootstrap
versions:
- 4.6.0
- dependency-name: y18n
versions:
- 4.0.1
- dependency-name: hellojs
versions:
- 1.18.8
- 1.19.2
- dependency-name: chai
versions:
- 4.3.0
- 4.3.3
- dependency-name: amplitude-js
versions:
- 7.4.2
- 7.4.3
- 7.4.4
- dependency-name: pug
versions:
- 3.0.2
- dependency-name: sass
versions:
- 1.32.6
- 1.32.7
- 1.32.8
- dependency-name: "@vue/test-utils"
versions:
- 1.1.2
- 1.1.3
- dependency-name: intro.js
versions:
- 3.2.1
- 3.3.1
- dependency-name: sass-loader
versions:
- 10.1.1

View File

@@ -10,20 +10,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm i npm ci
env: env:
CI: true CI: true
NODE_ENV: test NODE_ENV: test
@@ -32,20 +31,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm i npm ci
env: env:
CI: true CI: true
NODE_ENV: test NODE_ENV: test
@@ -54,20 +52,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm i npm ci
env: env:
CI: true CI: true
NODE_ENV: test NODE_ENV: test
@@ -77,20 +74,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm i npm ci
env: env:
CI: true CI: true
NODE_ENV: test NODE_ENV: test
@@ -99,20 +95,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm i npm ci
env: env:
CI: true CI: true
NODE_ENV: test NODE_ENV: test
@@ -122,14 +117,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
mongodb-version: [4.2] mongodb-version: [4.2]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set - name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
@@ -137,11 +132,10 @@ jobs:
with: with:
mongodb-version: ${{ matrix.mongodb-version }} mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs mongodb-replica-set: rs
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm i npm ci
env: env:
CI: true CI: true
NODE_ENV: test NODE_ENV: test
@@ -152,14 +146,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
mongodb-version: [4.2] mongodb-version: [4.2]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set - name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
@@ -167,11 +161,10 @@ jobs:
with: with:
mongodb-version: ${{ matrix.mongodb-version }} mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs mongodb-replica-set: rs
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm i npm ci
env: env:
CI: true CI: true
NODE_ENV: test NODE_ENV: test
@@ -182,14 +175,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
mongodb-version: [4.2] mongodb-version: [4.2]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set - name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
@@ -197,11 +190,10 @@ jobs:
with: with:
mongodb-version: ${{ matrix.mongodb-version }} mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs mongodb-replica-set: rs
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm i npm ci
env: env:
CI: true CI: true
NODE_ENV: test NODE_ENV: test
@@ -213,20 +205,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm i npm ci
env: env:
CI: true CI: true
NODE_ENV: test NODE_ENV: test
@@ -237,16 +228,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [21.x] node-version: [14.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo apt-get -y install libkrb5-dev
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |

1
.gitignore vendored
View File

@@ -40,7 +40,6 @@ yarn.lock
!.elasticbeanstalk/*.global.yml !.elasticbeanstalk/*.global.yml
/.vscode /.vscode
habitica.code-workspace
# webstorm fake webpack for path intellisense # webstorm fake webpack for path intellisense
webpack.webstorm.config webpack.webstorm.config

2
.nvmrc
View File

@@ -1 +1 @@
20 14

30
Dockerfile Normal file
View File

@@ -0,0 +1,30 @@
FROM node:14
ENV ADMIN_EMAIL admin@habitica.com
ENV EMAILS_COMMUNITY_MANAGER_EMAIL admin@habitica.com
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
ENV BASE_URL https://habitica.com
ENV FACEBOOK_KEY 128307497299777
ENV GA_ID UA-33510635-1
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
ENV LOGGLY_CLIENT_TOKEN ab5663bf-241f-4d14-8783-7d80db77089a
ENV NODE_ENV production
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
ENV APPLE_AUTH_CLIENT_ID 9Q9SMRMCNN.com.habitrpg.ios.Habitica
# Install global packages
RUN npm install -g gulp-cli mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch release --depth 1 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN git config --global url."https://".insteadOf git://
RUN npm set unsafe-perm true
RUN npm install
# Start Habitica
EXPOSE 80 8080 36612
CMD ["node", "./website/transpiled-babel/index.js"]

View File

@@ -1,4 +1,4 @@
FROM node:20 FROM node:14
# Install global packages # Install global packages
RUN npm install -g gulp-cli mocha RUN npm install -g gulp-cli mocha

View File

@@ -1,7 +1,7 @@
Habitica ![Build Status](https://github.com/HabitRPG/habitica/workflows/Test/badge.svg) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE) Habitica ![Build Status](https://github.com/HabitRPG/habitica/workflows/Test/badge.svg) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
=============== ===============
[Habitica](https://habitica.com) is an open-source habit-building program that treats your life like a role-playing game. Level up as you succeed, lose HP as you fail, and earn money to buy weapons and armor. [Habitica](https://habitica.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor.
**We need more programmers!** Your assistance will be greatly appreciated. The wiki pages below and the additional pages they link to will tell you how to get started on contributing code and where you can go to seek further help or ask questions: **We need more programmers!** Your assistance will be greatly appreciated. The wiki pages below and the additional pages they link to will tell you how to get started on contributing code and where you can go to seek further help or ask questions:
* [Guidance for Blacksmiths](https://habitica.fandom.com/wiki/Guidance_for_Blacksmiths) - an introduction to the technologies used and how the software is organized. * [Guidance for Blacksmiths](https://habitica.fandom.com/wiki/Guidance_for_Blacksmiths) - an introduction to the technologies used and how the software is organized.

View File

@@ -86,6 +86,5 @@
"RATE_LIMITER_ENABLED": "false", "RATE_LIMITER_ENABLED": "false",
"REDIS_HOST": "aaabbbcccdddeeefff", "REDIS_HOST": "aaabbbcccdddeeefff",
"REDIS_PORT": "1234", "REDIS_PORT": "1234",
"REDIS_PASSWORD": "12345678", "REDIS_PASSWORD": "12345678"
"TRUSTED_DOMAINS": "localhost,https://habitica.com"
} }

View File

@@ -51,7 +51,7 @@ gulp.task('build:prepare-mongo', async () => {
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
// use run-rs without --keep, kill it as soon as the replica set starts // use run-rs without --keep, kill it as soon as the replica set starts
const runRsProcess = spawn('run-rs', ['-v', '4.1.1', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']); const runRsProcess = spawn('run-rs', ['-v', '4.2.8', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
for await (const chunk of runRsProcess.stdout) { for await (const chunk of runRsProcess.stdout) {
const stringChunk = chunk.toString(); const stringChunk = chunk.toString();

View File

@@ -2,6 +2,7 @@ import mongoose from 'mongoose';
import nconf from 'nconf'; import nconf from 'nconf';
import repl from 'repl'; import repl from 'repl';
import gulp from 'gulp'; import gulp from 'gulp';
import logger from '../website/server/libs/logger';
import { import {
getDevelopmentConnectionUrl, getDevelopmentConnectionUrl,
getDefaultConnectionOptions, getDefaultConnectionOptions,
@@ -38,6 +39,10 @@ const improveRepl = context => {
mongoose.connect( mongoose.connect(
connectionUrl, connectionUrl,
mongooseOptions, mongooseOptions,
err => {
if (err) throw err;
logger.info('Connected with Mongoose');
},
); );
}; };

View File

@@ -59,15 +59,13 @@ gulp.task('test:prepare:mongo', cb => {
const mongooseOptions = getDefaultConnectionOptions(); const mongooseOptions = getDefaultConnectionOptions();
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI); const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
mongoose.connect(connectionUrl, mongooseOptions) mongoose.connect(connectionUrl, mongooseOptions, err => {
.then(() => mongoose.connection.dropDatabase()) if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
.then(() => mongoose.connection.close()).then(() => { return mongoose.connection.dropDatabase(err2 => {
cb(); if (err2) return cb(err2);
}) return mongoose.connection.close(cb);
.catch(err => {
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
throw err;
}); });
});
}); });
gulp.task('test:prepare:server', gulp.series('test:prepare:mongo', done => { gulp.task('test:prepare:server', gulp.series('test:prepare:mongo', done => {
@@ -118,10 +116,8 @@ gulp.task('test:common:safe', gulp.series('test:prepare:build', cb => {
pipe(runner); pipe(runner);
})); }));
gulp.task('test:content', gulp.series( gulp.task('test:content', gulp.series('test:prepare:build',
'test:prepare:build', runInChildProcess(CONTENT_TEST_COMMAND, LIMIT_MAX_BUFFER_OPTIONS)));
runInChildProcess(CONTENT_TEST_COMMAND, LIMIT_MAX_BUFFER_OPTIONS),
));
gulp.task('test:content:clean', cb => { gulp.task('test:content:clean', cb => {
pipe(exec(testBin(CONTENT_TEST_COMMAND), LIMIT_MAX_BUFFER_OPTIONS, () => cb())); pipe(exec(testBin(CONTENT_TEST_COMMAND), LIMIT_MAX_BUFFER_OPTIONS, () => cb()));
@@ -146,20 +142,16 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', cb => {
pipe(runner); pipe(runner);
})); }));
gulp.task( gulp.task('test:api:unit:run',
'test:api:unit:run', runInChildProcess(integrationTestCommand('test/api/unit', 'coverage/api-unit')));
runInChildProcess(integrationTestCommand('test/api/unit', 'coverage/api-unit')),
);
gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done()))); gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done())));
gulp.task('test:api-v3:integration', gulp.series( gulp.task('test:api-v3:integration', gulp.series('test:prepare:mongo',
'test:prepare:mongo',
runInChildProcess( runInChildProcess(
integrationTestCommand('test/api/v3/integration', 'coverage/api-v3-integration'), integrationTestCommand('test/api/v3/integration', 'coverage/api-v3-integration'),
LIMIT_MAX_BUFFER_OPTIONS, LIMIT_MAX_BUFFER_OPTIONS,
), )));
));
gulp.task('test:api-v3:integration:watch', () => gulp.watch([ gulp.task('test:api-v3:integration:watch', () => gulp.watch([
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js', 'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
@@ -172,13 +164,11 @@ gulp.task('test:api-v3:integration:separate-server', runInChildProcess(
'LOAD_SERVER=0', 'LOAD_SERVER=0',
)); ));
gulp.task('test:api-v4:integration', gulp.series( gulp.task('test:api-v4:integration', gulp.series('test:prepare:mongo',
'test:prepare:mongo',
runInChildProcess( runInChildProcess(
integrationTestCommand('test/api/v4', 'api-v4-integration'), integrationTestCommand('test/api/v4', 'api-v4-integration'),
LIMIT_MAX_BUFFER_OPTIONS, LIMIT_MAX_BUFFER_OPTIONS,
), )));
));
gulp.task('test:api-v4:integration:separate-server', runInChildProcess( gulp.task('test:api-v4:integration:separate-server', runInChildProcess(
'mocha test/api/v4 --recursive --require ./test/helpers/start-server', 'mocha test/api/v4 --recursive --require ./test/helpers/start-server',

View File

@@ -3,6 +3,6 @@ module.exports = {
root: false, root: false,
rules: { rules: {
'no-console': 0, 'no-console': 0,
'no-use-before-define': ['error', { functions: false }], 'no-use-before-define': ['error', { functions: false }]
}, }
}; }

View File

@@ -61,7 +61,7 @@ async function updateUser (user) {
export default async function processUsers () { export default async function processUsers () {
let query = { let query = {
migration: {$ne: MIGRATION_NAME}, // migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2021-01-01')}, 'auth.timestamps.loggedin': {$gt: new Date('2021-01-01')},
}; };

View File

@@ -105,7 +105,7 @@ async function updateUser (user) {
export default async function processUsers () { export default async function processUsers () {
let query = { let query = {
migration: { $ne: MIGRATION_NAME }, // migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') }, 'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') },
}; };

View File

@@ -145,7 +145,7 @@ async function updateUser (user) {
export default async function processUsers () { export default async function processUsers () {
let query = { let query = {
migration: { $ne: MIGRATION_NAME }, // migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') }, 'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') },
}; };

View File

@@ -105,7 +105,7 @@ async function updateUser (user) {
export default async function processUsers () { export default async function processUsers () {
let query = { let query = {
migration: { $ne: MIGRATION_NAME }, // migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') }, 'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') },
}; };

View File

@@ -95,7 +95,7 @@ async function updateUser (user) {
export default async function processUsers () { export default async function processUsers () {
let query = { let query = {
migration: { $ne: MIGRATION_NAME }, // migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2022-01-01') }, 'auth.timestamps.loggedin': { $gt: new Date('2022-01-01') },
}; };

View File

@@ -86,7 +86,7 @@ async function updateUser (user) {
export default async function processUsers () { export default async function processUsers () {
let query = { let query = {
migration: { $ne: MIGRATION_NAME }, // migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2022-01-01') }, 'auth.timestamps.loggedin': { $gt: new Date('2022-01-01') },
}; };

View File

@@ -1,108 +0,0 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20221213_pet_group_achievements';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {
migration: MIGRATION_NAME,
};
if (user && user.items && user.items.pets) {
const pets = user.items.pets;
if (pets['BearCub-Base']
&& pets['BearCub-CottonCandyBlue']
&& pets['BearCub-CottonCandyPink']
&& pets['BearCub-Desert']
&& pets['BearCub-Golden']
&& pets['BearCub-Red']
&& pets['BearCub-Shade']
&& pets['BearCub-Skeleton']
&& pets['BearCub-White']
&& pets['BearCub-Zombie']
&& pets['Fox-Base']
&& pets['Fox-CottonCandyBlue']
&& pets['Fox-CottonCandyPink']
&& pets['Fox-Desert']
&& pets['Fox-Golden']
&& pets['Fox-Red']
&& pets['Fox-Shade']
&& pets['Fox-Skeleton']
&& pets['Fox-White']
&& pets['Fox-Zombie']
&& pets['Penguin-Base']
&& pets['Penguin-CottonCandyBlue']
&& pets['Penguin-CottonCandyPink']
&& pets['Penguin-Desert']
&& pets['Penguin-Golden']
&& pets['Penguin-Red']
&& pets['Penguin-Shade']
&& pets['Penguin-Skeleton']
&& pets['Penguin-White']
&& pets['Penguin-Zombie']
&& pets['Whale-Base']
&& pets['Whale-CottonCandyBlue']
&& pets['Whale-CottonCandyPink']
&& pets['Whale-Desert']
&& pets['Whale-Golden']
&& pets['Whale-Red']
&& pets['Whale-Shade']
&& pets['Whale-Skeleton']
&& pets['Whale-White']
&& pets['Whale-Zombie']
&& pets['Wolf-Base']
&& pets['Wolf-CottonCandyBlue']
&& pets['Wolf-CottonCandyPink']
&& pets['Wolf-Desert']
&& pets['Wolf-Golden']
&& pets['Wolf-Red']
&& pets['Wolf-Shade']
&& pets['Wolf-Skeleton']
&& pets['Wolf-White']
&& pets['Wolf-Zombie']) {
set['achievements.polarPro'] = true;
}
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({ _id: user._id }, { $set: set }).exec();
}
export default async function processUsers () {
let query = {
migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2022-11-01') },
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1]._id,
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -1,144 +0,0 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20221227_nye';
import { model as User } from '../../../website/server/models/user';
import { v4 as uuid } from 'uuid';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = { migration: MIGRATION_NAME };
let push;
if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
set['items.gear.owned.head_special_nye2022'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2022',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
set['items.gear.owned.head_special_nye2021'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2021',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
set['items.gear.owned.head_special_nye2020'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2020',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
set['items.gear.owned.head_special_nye2019'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2019',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
set['items.gear.owned.head_special_nye2018'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2018',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
set['items.gear.owned.head_special_nye2017'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2017',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
set['items.gear.owned.head_special_nye2016'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2016',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
set['items.gear.owned.head_special_nye2015'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2015',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
set['items.gear.owned.head_special_nye2014'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2014',
_id: uuid(),
},
];
} else {
set['items.gear.owned.head_special_nye'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye',
_id: uuid(),
},
];
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
}
export default async function processUsers () {
let query = {
'auth.timestamps.loggedin': {$gt: new Date('2022-12-01')},
migration: {$ne: MIGRATION_NAME},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -1,88 +0,0 @@
/* eslint-disable no-console */
import { v4 as uuid } from 'uuid';
import { model as User } from '../../../website/server/models/user';
const MIGRATION_NAME = '20230123_habit_birthday';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
const inc = { 'balance': 5 };
const set = {};
const push = {};
set.migration = MIGRATION_NAME;
if (typeof user.items.gear.owned.armor_special_birthday2022 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2023'] = true;
} else if (typeof user.items.gear.owned.armor_special_birthday2021 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2022'] = true;
} else if (typeof user.items.gear.owned.armor_special_birthday2020 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2021'] = true;
} else if (typeof user.items.gear.owned.armor_special_birthday2019 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2020'] = true;
} else if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2019'] = true;
} else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2018'] = true;
} else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2017'] = true;
} else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2016'] = true;
} else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') {
set['items.gear.owned.armor_special_birthday2015'] = true;
} else {
set['items.gear.owned.armor_special_birthday'] = true;
}
push.notifications = {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_head_special_nye',
title: 'Birthday Bash Day 1!',
text: 'Enjoy your new Birthday Robe and 20 Gems on us!',
destination: 'equipment',
},
seen: false,
};
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: push}).exec();
}
export default async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -1,69 +0,0 @@
/* eslint-disable no-console */
import { v4 as uuid } from 'uuid';
import { model as User } from '../../../website/server/models/user';
const MIGRATION_NAME = '20230127_habit_birthday_day5';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
const set = {};
const push = {};
set.migration = MIGRATION_NAME;
set['items.gear.owned.back_special_anniversary'] = true;
set['items.gear.owned.body_special_anniversary'] = true;
set['items.gear.owned.eyewear_special_anniversary'] = true;
push.notifications = {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_head_special_nye',
title: 'Birthday Bash Day 5!',
text: 'Come celebrate by wearing your new Habitica Hero Cape, Collar, and Mask!',
destination: 'equipment',
},
seen: false,
};
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set, $push: push}).exec();
}
export default async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -1,79 +0,0 @@
/* eslint-disable no-console */
import { v4 as uuid } from 'uuid';
import { model as User } from '../../../website/server/models/user';
const MIGRATION_NAME = '20230201_habit_birthday_day10';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
const set = {
migration: MIGRATION_NAME,
'purchased.background.birthday_bash': true,
};
const push = {
notifications: {
type: 'ITEM_RECEIVED',
data: {
icon: 'notif_head_special_nye',
title: 'Birthday Bash Day 10!',
text: 'Join in for the end of our birthday celebrations with 10th Birthday background, Cake, and achievement!',
destination: 'backgrounds',
},
seen: false,
},
};
const inc = {
'items.food.Cake_Skeleton': 1,
'items.food.Cake_Base': 1,
'items.food.Cake_CottonCandyBlue': 1,
'items.food.Cake_CottonCandyPink': 1,
'items.food.Cake_Shade': 1,
'items.food.Cake_White': 1,
'items.food.Cake_Golden': 1,
'items.food.Cake_Zombie': 1,
'items.food.Cake_Desert': 1,
'items.food.Cake_Red': 1,
'achievements.habitBirthdays': 1,
};
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set, $push: push, $inc: inc }).exec();
}
export default async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -1,158 +0,0 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20230522_pet_group_achievements';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {
migration: MIGRATION_NAME,
};
if (user && user.items && user.items.pets) {
const pets = user.items.pets;
if (pets['Parrot-Base']
&& pets['Parrot-CottonCandyBlue']
&& pets['Parrot-CottonCandyPink']
&& pets['Parrot-Desert']
&& pets['Parrot-Golden']
&& pets['Parrot-Red']
&& pets['Parrot-Shade']
&& pets['Parrot-Skeleton']
&& pets['Parrot-White']
&& pets['Parrot-Zombie']
&& pets['Rooster-Base']
&& pets['Rooster-CottonCandyBlue']
&& pets['Rooster-CottonCandyPink']
&& pets['Rooster-Desert']
&& pets['Rooster-Golden']
&& pets['Rooster-Red']
&& pets['Rooster-Shade']
&& pets['Rooster-Skeleton']
&& pets['Rooster-White']
&& pets['Rooster-Zombie']
&& pets['Triceratops-Base']
&& pets['Triceratops-CottonCandyBlue']
&& pets['Triceratops-CottonCandyPink']
&& pets['Triceratops-Desert']
&& pets['Triceratops-Golden']
&& pets['Triceratops-Red']
&& pets['Triceratops-Shade']
&& pets['Triceratops-Skeleton']
&& pets['Triceratops-White']
&& pets['Triceratops-Zombie']
&& pets['TRex-Base']
&& pets['TRex-CottonCandyBlue']
&& pets['TRex-CottonCandyPink']
&& pets['TRex-Desert']
&& pets['TRex-Golden']
&& pets['TRex-Red']
&& pets['TRex-Shade']
&& pets['TRex-Skeleton']
&& pets['TRex-White']
&& pets['TRex-Zombie']
&& pets['Pterodactyl-Base']
&& pets['Pterodactyl-CottonCandyBlue']
&& pets['Pterodactyl-CottonCandyPink']
&& pets['Pterodactyl-Desert']
&& pets['Pterodactyl-Golden']
&& pets['Pterodactyl-Red']
&& pets['Pterodactyl-Shade']
&& pets['Pterodactyl-Skeleton']
&& pets['Pterodactyl-White']
&& pets['Pterodactyl-Zombie']
&& pets['Owl-Base']
&& pets['Owl-CottonCandyBlue']
&& pets['Owl-CottonCandyPink']
&& pets['Owl-Desert']
&& pets['Owl-Golden']
&& pets['Owl-Red']
&& pets['Owl-Shade']
&& pets['Owl-Skeleton']
&& pets['Owl-White']
&& pets['Owl-Zombie']
&& pets['Velociraptor-Base']
&& pets['Velociraptor-CottonCandyBlue']
&& pets['Velociraptor-CottonCandyPink']
&& pets['Velociraptor-Desert']
&& pets['Velociraptor-Golden']
&& pets['Velociraptor-Red']
&& pets['Velociraptor-Shade']
&& pets['Velociraptor-Skeleton']
&& pets['Velociraptor-White']
&& pets['Velociraptor-Zombie']
&& pets['Penguin-Base']
&& pets['Penguin-CottonCandyBlue']
&& pets['Penguin-CottonCandyPink']
&& pets['Penguin-Desert']
&& pets['Penguin-Golden']
&& pets['Penguin-Red']
&& pets['Penguin-Shade']
&& pets['Penguin-Skeleton']
&& pets['Penguin-White']
&& pets['Penguin-Zombie']
&& pets['Falcon-Base']
&& pets['Falcon-CottonCandyBlue']
&& pets['Falcon-CottonCandyPink']
&& pets['Falcon-Desert']
&& pets['Falcon-Golden']
&& pets['Falcon-Red']
&& pets['Falcon-Shade']
&& pets['Falcon-Skeleton']
&& pets['Falcon-White']
&& pets['Falcon-Zombie']
&& pets['Peacock-Base']
&& pets['Peacock-CottonCandyBlue']
&& pets['Peacock-CottonCandyPink']
&& pets['Peacock-Desert']
&& pets['Peacock-Golden']
&& pets['Peacock-Red']
&& pets['Peacock-Shade']
&& pets['Peacock-Skeleton']
&& pets['Peacock-White']
&& pets['Peacock-Zombie']) {
set['achievements.dinosaurDynasty'] = true;
}
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({ _id: user._id }, { $set: set }).exec();
}
export default async function processUsers () {
let query = {
migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2023-04-15') },
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1]._id,
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -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
}
};

View File

@@ -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, weve 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, weve 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, weve 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, weve 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, weve 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, weve 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
}
};

View File

@@ -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
}
};

View File

@@ -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
}
};

View File

@@ -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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received a Veteran Pet!',
text: 'To commemorate being here for a new era of Habitica, weve 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: 'Youve received the Heroic Set!',
text: 'To commemorate your hard work as a contributor, weve 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
}
};

View File

@@ -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
}
};

View File

@@ -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
}
};

View File

@@ -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
}
};

View File

@@ -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
}
};

View File

@@ -1,89 +0,0 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '202403_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['GuineaPig-Zombie'] > 0
&& pets['GuineaPig-Skeleton'] > 0
&& pets['GuineaPig-Base'] > 0
&& pets['GuineaPig-Desert'] > 0
&& pets['GuineaPig-Red'] > 0
&& pets['GuineaPig-Shade'] > 0
&& pets['GuineaPig-White']> 0
&& pets['GuineaPig-Golden'] > 0
&& pets['GuineaPig-CottonCandyBlue'] > 0
&& pets['GuineaPig-CottonCandyPink'] > 0
&& pets['Squirrel-Zombie'] > 0
&& pets['Squirrel-Skeleton'] > 0
&& pets['Squirrel-Base'] > 0
&& pets['Squirrel-Desert'] > 0
&& pets['Squirrel-Red'] > 0
&& pets['Squirrel-Shade'] > 0
&& pets['Squirrel-White'] > 0
&& pets['Squirrel-Golden'] > 0
&& pets['Squirrel-CottonCandyBlue'] > 0
&& pets['Squirrel-CottonCandyPink'] > 0
&& pets['Rat-Zombie'] > 0
&& pets['Rat-Skeleton'] > 0
&& pets['Rat-Base'] > 0
&& pets['Rat-Desert'] > 0
&& pets['Rat-Red'] > 0
&& pets['Rat-Shade'] > 0
&& pets['Rat-White'] > 0
&& pets['Rat-Golden'] > 0
&& pets['Rat-CottonCandyBlue'] > 0
&& pets['Rat-CottonCandyPink'] > 0 ) {
set['achievements.rodentRuler'] = 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('2024-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
}
};

View File

@@ -1,99 +0,0 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '202405_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['LionCub-Zombie'] > 0
&& pets['LionCub-Skeleton'] > 0
&& pets['LionCub-Base'] > 0
&& pets['LionCub-Desert'] > 0
&& pets['LionCub-Red'] > 0
&& pets['LionCub-Shade'] > 0
&& pets['LionCub-White']> 0
&& pets['LionCub-Golden'] > 0
&& pets['LionCub-CottonCandyBlue'] > 0
&& pets['LionCub-CottonCandyPink'] > 0
&& pets['TigerCub-Zombie'] > 0
&& pets['TigerCub-Skeleton'] > 0
&& pets['TigerCub-Base'] > 0
&& pets['TigerCub-Desert'] > 0
&& pets['TigerCub-Red'] > 0
&& pets['TigerCub-Shade'] > 0
&& pets['TigerCub-White'] > 0
&& pets['TigerCub-Golden'] > 0
&& pets['TigerCub-CottonCandyBlue'] > 0
&& pets['TigerCub-CottonCandyPink'] > 0
&& pets['Sabretooth-Zombie'] > 0
&& pets['Sabretooth-Skeleton'] > 0
&& pets['Sabretooth-Base'] > 0
&& pets['Sabretooth-Desert'] > 0
&& pets['Sabretooth-Red'] > 0
&& pets['Sabretooth-Shade'] > 0
&& pets['Sabretooth-White'] > 0
&& pets['Sabretooth-Golden'] > 0
&& pets['Sabretooth-CottonCandyBlue'] > 0
&& pets['Sabretooth-CottonCandyPink'] > 0
&& pets['Cheetah-Zombie'] > 0
&& pets['Cheetah-Skeleton'] > 0
&& pets['Cheetah-Base'] > 0
&& pets['Cheetah-Desert'] > 0
&& pets['Cheetah-Red'] > 0
&& pets['Cheetah-Shade'] > 0
&& pets['Cheetah-White'] > 0
&& pets['Cheetah-Golden'] > 0
&& pets['Cheetah-CottonCandyBlue'] > 0
&& pets['Cheetah-CottonCandyPink'] > 0 ) {
set['achievements.cats'] = 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('2024-03-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
}
};

View File

@@ -0,0 +1,118 @@
let migrationName = '20180904_takeThis.js'; // Update per month
let authorName = 'Sabe'; // in case script author needs to know when their ...
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/*
* Award Take This ladder items to participants in this month's challenge
*/
import monk from 'monk';
import nconf from 'nconf';
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: migrationName},
challenges: {$in: ['1044ec0c-4a85-48c5-9f36-d51c0c62c7d3']}, // Update per month
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
'items.gear.owned',
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
let set = {};
let push;
if (typeof user.items.gear.owned.back_special_takeThis !== 'undefined') {
set = {migration: migrationName};
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
set = {migration: migrationName, 'items.gear.owned.back_special_takeThis': false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', _id: monk.id()}};
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
set = {migration: migrationName, 'items.gear.owned.body_special_takeThis': false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', _id: monk.id()}};
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
set = {migration: migrationName, 'items.gear.owned.head_special_takeThis': false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', _id: monk.id()}};
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
set = {migration: migrationName, 'items.gear.owned.armor_special_takeThis': false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', _id: monk.id()}};
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
set = {migration: migrationName, 'items.gear.owned.weapon_special_takeThis': false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', _id: monk.id()}};
} else {
set = {migration: migrationName, 'items.gear.owned.shield_special_takeThis': false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', _id: monk.id()}};
}
if (push) {
dbUsers.update({_id: user._id}, {$set: set, $push: push});
} else {
dbUsers.update({_id: user._id}, {$set: set});
}
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
}
function displayData () {
console.warn(`\n${ count } users processed\n`);
return exiting(0);
}
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
}
}
process.exit(code);
}
module.exports = processUsers;

10
migrations/csvexport.py Normal file
View File

@@ -0,0 +1,10 @@
import csv
with open(r"/home/slappybag/Documents/SurveyScrape.csv") as f:
reader = csv.reader(f, delimiter=',', quotechar='"')
column = []
for row in reader:
if row:
column.append(row[4])
print column

View File

@@ -21,14 +21,12 @@ async function handOutJackalopes () {
if (user.party._id) groupList.push(user.party._id); if (user.party._id) groupList.push(user.party._id);
groupList = groupList.concat(user.guilds); groupList = groupList.concat(user.guilds);
const subscribedGroup = await Group.findOne( const subscribedGroup = await Group.findOne({
{ _id: { $in: groupList },
_id: { $in: groupList }, 'purchased.plan.planId': 'group_monthly',
'purchased.plan.planId': 'group_monthly', 'purchased.plan.dateTerminated': null,
'purchased.plan.dateTerminated': null, },
}, { _id: 1 });
{ _id: 1 },
);
if (subscribedGroup) { if (subscribedGroup) {
User.update({ _id: user._id }, { $set: { 'items.mounts.Jackalope-RoyalPurple': true } }).exec(); User.update({ _id: user._id }, { $set: { 'items.mounts.Jackalope-RoyalPurple': true } }).exec();

View File

@@ -51,8 +51,7 @@ function getAchievementUpdate (newUser, oldUser) {
// Rebirth level // Rebirth level
if (achievementsUpdate.rebirthLevel) { if (achievementsUpdate.rebirthLevel) {
achievementsUpdate.rebirthLevel = Math.max( achievementsUpdate.rebirthLevel = Math.max(
achievementsUpdate.rebirthLevel, achievementsUpdate.rebirthLevel, oldAchievements.rebirthLevel,
oldAchievements.rebirthLevel,
); );
} else if (oldAchievements.rebirthLevel) { } else if (oldAchievements.rebirthLevel) {
achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel; achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel;

View File

@@ -16,7 +16,6 @@ async function updateUser (user) {
if (count % progressCount === 0) { if (count % progressCount === 0) {
console.warn(`${count} ${user._id}`); console.warn(`${count} ${user._id}`);
// eslint-disable-next-line no-promise-executor-return
await new Promise(resolve => setTimeout(resolve, 5000)); await new Promise(resolve => setTimeout(resolve, 5000));
} }

View File

@@ -51,7 +51,7 @@ async function updateUser (user) {
if (count % progressCount === 0) console.warn(`${count} ${user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.updateOne({ _id: user._id }, { $set: set }).exec(); return User.update({ _id: user._id }, { $set: set }).exec();
} }
export default async function processUsers () { export default async function processUsers () {

View File

@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
import { model as User } from '../../website/server/models/user'; import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20240314_pi_day'; const MIGRATION_NAME = '20220314_pi_day';
const progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
@@ -54,7 +54,7 @@ async function updateUser (user) {
export default async function processUsers () { export default async function processUsers () {
const query = { const query = {
migration: { $ne: MIGRATION_NAME }, migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2023-02-15') }, 'auth.timestamps.loggedin': { $gt: new Date('2022-02-15') },
}; };
const fields = { const fields = {

25657
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +1,37 @@
{ {
"name": "habitica", "name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.", "description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.24.2", "version": "4.251.0",
"main": "./website/server/index.js", "main": "./website/server/index.js",
"dependencies": { "dependencies": {
"@babel/core": "^7.22.10", "@babel/core": "^7.19.6",
"@babel/preset-env": "^7.22.10", "@babel/preset-env": "^7.20.2",
"@babel/register": "^7.22.15", "@babel/register": "^7.18.9",
"@google-cloud/trace-agent": "^7.1.2", "@google-cloud/trace-agent": "^7.1.2",
"@parse/node-apn": "^5.2.3", "@parse/node-apn": "^5.1.3",
"@slack/webhook": "^6.1.0", "@slack/webhook": "^6.1.0",
"accepts": "^1.3.8", "accepts": "^1.3.8",
"amazon-payments": "^0.2.9", "amazon-payments": "^0.2.9",
"amplitude": "^6.0.0", "amplitude": "^6.0.0",
"apidoc": "^0.54.0", "apidoc": "^0.53.1",
"apple-auth": "^1.0.9", "apple-auth": "^1.0.7",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.1",
"bootstrap": "^4.6.2", "bootstrap": "^4.6.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-session": "^2.0.0", "cookie-session": "^2.0.0",
"coupon-code": "^0.4.5", "coupon-code": "^0.4.5",
"csv-stringify": "^5.6.5", "csv-stringify": "^5.6.5",
"cwait": "^1.1.1", "cwait": "^1.1.1",
"domain-middleware": "~0.1.0", "domain-middleware": "~0.1.0",
"eslint": "^8.55.0", "eslint": "^6.8.0",
"eslint-config-habitrpg": "^6.2.3", "eslint-config-habitrpg": "^6.2.0",
"eslint-plugin-mocha": "^5.0.0", "eslint-plugin-mocha": "^5.0.0",
"express": "^4.19.2", "express": "^4.18.2",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0", "express-validator": "^5.2.0",
"glob": "^8.1.0", "glob": "^8.0.3",
"got": "^11.8.6", "got": "^11.8.3",
"gulp": "^4.0.0", "gulp": "^4.0.0",
"gulp-babel": "^8.0.0", "gulp-babel": "^8.0.0",
"gulp-imagemin": "^7.1.0", "gulp-imagemin": "^7.1.0",
@@ -39,47 +39,49 @@
"gulp.spritesmith": "^6.13.0", "gulp.spritesmith": "^6.13.0",
"habitica-markdown": "^3.0.0", "habitica-markdown": "^3.0.0",
"helmet": "^4.6.0", "helmet": "^4.6.0",
"image-size": "^1.0.2",
"in-app-purchase": "^1.11.3", "in-app-purchase": "^1.11.3",
"js2xmlparser": "^5.0.0", "js2xmlparser": "^5.0.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^8.5.1",
"jwks-rsa": "^2.1.5", "jwks-rsa": "^2.1.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merge-stream": "^2.0.0", "merge-stream": "^2.0.0",
"method-override": "^3.0.0", "method-override": "^3.0.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"moment-recur": "^1.0.7", "moment-recur": "^1.0.7",
"mongoose": "^7.6.3", "mongoose": "^5.13.7",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"nconf": "^0.12.1", "nconf": "^0.12.0",
"node-gcm": "^1.0.5", "node-gcm": "^1.0.5",
"nodemon": "^2.0.20",
"on-headers": "^1.0.2", "on-headers": "^1.0.2",
"passport": "^0.5.3", "passport": "^0.6.0",
"passport-facebook": "^3.0.0", "passport-facebook": "^3.0.0",
"passport-google-oauth2": "^0.2.0", "passport-google-oauth2": "^0.2.0",
"passport-google-oauth20": "2.0.0", "passport-google-oauth20": "2.0.0",
"paypal-rest-sdk": "^1.8.1", "paypal-rest-sdk": "^1.8.1",
"pp-ipn": "^1.1.0", "pp-ipn": "^1.1.0",
"ps-tree": "^1.0.0", "ps-tree": "^1.0.0",
"rate-limiter-flexible": "^2.4.2", "rate-limiter-flexible": "^2.4.0",
"redis": "^3.1.2", "redis": "^3.1.2",
"regenerator-runtime": "^0.13.11",
"remove-markdown": "^0.5.0", "remove-markdown": "^0.5.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"short-uuid": "^4.2.2", "short-uuid": "^4.2.2",
"stripe": "^12.18.0", "stripe": "^10.13.0",
"superagent": "^8.1.2", "superagent": "^8.0.5",
"universal-analytics": "^0.5.3", "universal-analytics": "^0.5.3",
"useragent": "^2.1.9", "useragent": "^2.1.9",
"uuid": "^9.0.0", "uuid": "^8.3.2",
"validator": "^13.11.0", "validator": "^13.7.0",
"winston": "^3.10.0", "vinyl-buffer": "^1.0.1",
"winston-loggly-bulk": "^3.3.0", "winston": "^3.8.2",
"xml2js": "^0.6.2" "winston-loggly-bulk": "^3.2.1",
"xml2js": "^0.4.23"
}, },
"private": true, "private": true,
"engines": { "engines": {
"node": "20", "node": "^14",
"npm": "^10" "npm": "^6"
}, },
"scripts": { "scripts": {
"lint": "eslint --ext .js --fix . && cd website/client && npm run lint", "lint": "eslint --ext .js --fix . && cd website/client && npm run lint",
@@ -103,16 +105,16 @@
"client:unit": "cd website/client && npm run test:unit", "client:unit": "cd website/client && npm run test:unit",
"start": "gulp nodemon", "start": "gulp nodemon",
"debug": "gulp nodemon --inspect", "debug": "gulp nodemon --inspect",
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet", "mongo:dev": "run-rs -v 4.2.8 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install", "postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
"apidoc": "gulp apidoc" "apidoc": "gulp apidoc"
}, },
"devDependencies": { "devDependencies": {
"axios": "^1.4.0", "axios": "^0.27.2",
"chai": "^4.3.7", "chai": "^4.3.7",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chai-moment": "^0.1.0", "chai-moment": "^0.1.0",
"chalk": "^5.3.0", "chalk": "^5.1.2",
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.3",
"expect.js": "^0.3.1", "expect.js": "^0.3.1",
"istanbul": "^1.1.0-alpha.1", "istanbul": "^1.1.0-alpha.1",
@@ -120,8 +122,9 @@
"monk": "^7.3.4", "monk": "^7.3.4",
"require-again": "^2.0.0", "require-again": "^2.0.0",
"run-rs": "^0.7.7", "run-rs": "^0.7.7",
"sinon": "^15.2.0", "sinon": "^14.0.2",
"sinon-chai": "^3.7.0", "sinon-chai": "^3.7.0",
"sinon-stub-promise": "^4.0.0" "sinon-stub-promise": "^4.0.0"
} },
"optionalDependencies": {}
} }

View File

@@ -44,7 +44,6 @@ async function deleteHabiticaData (user, email) {
{ _id: user._id }, { _id: user._id },
{ $set: set }, { $set: set },
); );
// eslint-disable-next-line no-promise-executor-return
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
const response = await axios.delete( const response = await axios.delete(
`${BASE_URL}/api/v3/user`, `${BASE_URL}/api/v3/user`,
@@ -97,7 +96,6 @@ async function processEmailAddress (email) {
return console.log(`No users found with email address ${email}`); return console.log(`No users found with email address ${email}`);
} }
// eslint-disable-next-line no-promise-executor-return
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
return Promise.all(users.map(user => (async () => { return Promise.all(users.map(user => (async () => {
await deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop await deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs */
module.exports = { module.exports = {
extends: [ extends: [
'habitrpg/lib/mocha', 'habitrpg/lib/mocha',
@@ -9,9 +7,6 @@ module.exports = {
chai: true, chai: true,
expect: true, expect: true,
sinon: true, sinon: true,
sandbox: true, sandbox: true
}, },
rules: { }
'import/no-extraneous-dependencies': 'off',
},
};

View File

@@ -1,4 +1,4 @@
import { apiError } from '../../../../website/server/libs/apiError'; import apiError from '../../../../website/server/libs/apiError';
describe('API Messages', () => { describe('API Messages', () => {
const message = 'Only public guilds support pagination.'; const message = 'Only public guilds support pagination.';

View File

@@ -26,7 +26,9 @@ describe('bug-report', () => {
_id: userId, _id: userId,
}); });
const result = await bugReportLogic(user, userMail, userMessage, userAgent); const result = await bugReportLogic(
user, userMail, userMessage, userAgent,
);
expect(emailLib.sendTxn).to.be.called; expect(emailLib.sendTxn).to.be.called;
expect(result).to.deep.equal({ expect(result).to.deep.equal({

View File

@@ -231,16 +231,13 @@ describe('cron', async () => {
}, },
}); });
// user1 has a 1-month recurring subscription starting today // user1 has a 1-month recurring subscription starting today
beforeEach(async () => { user1.purchased.plan.customerId = 'subscribedId';
user1.purchased.plan.customerId = 'subscribedId'; user1.purchased.plan.dateUpdated = moment().toDate();
user1.purchased.plan.dateUpdated = moment().toDate(); user1.purchased.plan.planId = 'basic';
user1.purchased.plan.planId = 'basic'; user1.purchased.plan.consecutive.count = 0;
user1.purchased.plan.consecutive.count = 0; user1.purchased.plan.consecutive.offset = 0;
user1.purchased.plan.perkMonthCount = 0; user1.purchased.plan.consecutive.trinkets = 0;
user1.purchased.plan.consecutive.offset = 0; user1.purchased.plan.consecutive.gemCapExtra = 0;
user1.purchased.plan.consecutive.trinkets = 0;
user1.purchased.plan.consecutive.gemCapExtra = 0;
});
it('does not increment consecutive benefits after the first month', async () => { it('does not increment consecutive benefits after the first month', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
@@ -274,24 +271,6 @@ describe('cron', async () => {
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0); expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
}); });
it('increments consecutive benefits after the second month if they also received a 1 month gift subscription', async () => {
user1.purchased.plan.perkMonthCount = 1;
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days')
.toDate());
// Add 1 month to simulate what happens a month after the subscription was created.
// Add 2 days so that we're sure we're not affected by any start-of-month effects
// e.g., from time zone oddness.
await cron({
user: user1, tasksByType, daysMissed, analytics,
});
expect(user1.purchased.plan.perkMonthCount).to.equal(0);
expect(user1.purchased.plan.consecutive.count).to.equal(2);
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
});
it('increments consecutive benefits after the third month', async () => { it('increments consecutive benefits after the third month', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
.add(2, 'days') .add(2, 'days')
@@ -336,30 +315,6 @@ describe('cron', async () => {
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3); expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15); expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
}); });
it('initializes plan.perkMonthCount if necessary', async () => {
user.purchased.plan.perkMonthCount = undefined;
clock = sinon.useFakeTimers(moment(user.purchased.plan.dateUpdated)
.utcOffset(0)
.startOf('month')
.add(1, 'months')
.add(2, 'days')
.toDate());
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.perkMonthCount).to.equal(1);
user.purchased.plan.perkMonthCount = undefined;
user.purchased.plan.consecutive.count = 8;
clock.restore();
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days')
.toDate());
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.perkMonthCount).to.equal(2);
});
}); });
describe('for a 3-month recurring subscription', async () => { describe('for a 3-month recurring subscription', async () => {
@@ -375,16 +330,13 @@ describe('cron', async () => {
}, },
}); });
// user3 has a 3-month recurring subscription starting today // user3 has a 3-month recurring subscription starting today
beforeEach(async () => { user3.purchased.plan.customerId = 'subscribedId';
user3.purchased.plan.customerId = 'subscribedId'; user3.purchased.plan.dateUpdated = moment().toDate();
user3.purchased.plan.dateUpdated = moment().toDate(); user3.purchased.plan.planId = 'basic_3mo';
user3.purchased.plan.planId = 'basic_3mo'; user3.purchased.plan.consecutive.count = 0;
user3.purchased.plan.perkMonthCount = 0; user3.purchased.plan.consecutive.offset = 3;
user3.purchased.plan.consecutive.count = 0; user3.purchased.plan.consecutive.trinkets = 1;
user3.purchased.plan.consecutive.offset = 3; user3.purchased.plan.consecutive.gemCapExtra = 5;
user3.purchased.plan.consecutive.trinkets = 1;
user3.purchased.plan.consecutive.gemCapExtra = 5;
});
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
@@ -438,21 +390,6 @@ describe('cron', async () => {
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
}); });
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
user3.purchased.plan.perkMonthCount = 2;
user3.purchased.plan.consecutive.trinkets = 1;
user3.purchased.plan.consecutive.gemCapExtra = 5;
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.perkMonthCount).to.equal(2);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
});
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => { it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months')
.add(2, 'days') .add(2, 'days')
@@ -519,16 +456,13 @@ describe('cron', async () => {
}, },
}); });
// user6 has a 6-month recurring subscription starting today // user6 has a 6-month recurring subscription starting today
beforeEach(async () => { user6.purchased.plan.customerId = 'subscribedId';
user6.purchased.plan.customerId = 'subscribedId'; user6.purchased.plan.dateUpdated = moment().toDate();
user6.purchased.plan.dateUpdated = moment().toDate(); user6.purchased.plan.planId = 'google_6mo';
user6.purchased.plan.planId = 'google_6mo'; user6.purchased.plan.consecutive.count = 0;
user6.purchased.plan.perkMonthCount = 0; user6.purchased.plan.consecutive.offset = 6;
user6.purchased.plan.consecutive.count = 0; user6.purchased.plan.consecutive.trinkets = 2;
user6.purchased.plan.consecutive.offset = 6; user6.purchased.plan.consecutive.gemCapExtra = 10;
user6.purchased.plan.consecutive.trinkets = 2;
user6.purchased.plan.consecutive.gemCapExtra = 10;
});
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
@@ -569,19 +503,6 @@ describe('cron', async () => {
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20); expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
}); });
it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => {
user6.purchased.plan.perkMonthCount = 2;
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
.add(2, 'days')
.toDate());
await cron({
user: user6, tasksByType, daysMissed, analytics,
});
expect(user6.purchased.plan.perkMonthCount).to.equal(2);
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
});
it('increments consecutive benefits the month after the third paid period has started', async () => { it('increments consecutive benefits the month after the third paid period has started', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
.add(2, 'days') .add(2, 'days')

View File

@@ -44,7 +44,7 @@ describe('mongodb', () => {
const mongoLibOverride = requireAgain(pathToMongoLib); const mongoLibOverride = requireAgain(pathToMongoLib);
const options = mongoLibOverride.getDefaultConnectionOptions(); const options = mongoLibOverride.getDefaultConnectionOptions();
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology']); expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology', 'keepAlive', 'keepAliveInitialDelay']);
}); });
}); });
}); });

View File

@@ -227,7 +227,7 @@ describe('Password Utilities', () => {
expiresAt: moment().subtract({ minutes: 1 }), expiresAt: moment().subtract({ minutes: 1 }),
})); }));
await user.updateOne({ await user.update({
'auth.local.passwordResetCode': code, 'auth.local.passwordResetCode': code,
}); });
@@ -264,7 +264,7 @@ describe('Password Utilities', () => {
expiresAt: moment().add({ days: 1 }), expiresAt: moment().add({ days: 1 }),
})); }));
await user.updateOne({ await user.update({
'auth.local.passwordResetCode': 'invalid', 'auth.local.passwordResetCode': 'invalid',
}); });
@@ -280,7 +280,7 @@ describe('Password Utilities', () => {
expiresAt: moment().add({ days: 1 }), expiresAt: moment().add({ days: 1 }),
})); }));
await user.updateOne({ await user.update({
'auth.local.passwordResetCode': code, 'auth.local.passwordResetCode': code,
}); });

View File

@@ -2,7 +2,7 @@ import { model as User } from '../../../../../../website/server/models/user';
import amzLib from '../../../../../../website/server/libs/payments/amazon'; import amzLib from '../../../../../../website/server/libs/payments/amazon';
import payments from '../../../../../../website/server/libs/payments/payments'; import payments from '../../../../../../website/server/libs/payments/payments';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
import { apiError } from '../../../../../../website/server/libs/apiError'; import apiError from '../../../../../../website/server/libs/apiError';
import * as gems from '../../../../../../website/server/libs/payments/gems'; import * as gems from '../../../../../../website/server/libs/payments/gems';
const { i18n } = common; const { i18n } = common;
@@ -17,7 +17,7 @@ describe('Amazon Payments - Checkout', () => {
let closeOrderReferenceSpy; let closeOrderReferenceSpy;
let paymentBuyGemsStub; let paymentBuyGemsStub;
let paymentCreateSubscriptionStub; let paymentCreateSubscritionStub;
let amount = gemsBlock.price / 100; let amount = gemsBlock.price / 100;
function expectOrderReferenceSpy () { function expectOrderReferenceSpy () {
@@ -85,8 +85,8 @@ describe('Amazon Payments - Checkout', () => {
paymentBuyGemsStub = sinon.stub(payments, 'buyGems'); paymentBuyGemsStub = sinon.stub(payments, 'buyGems');
paymentBuyGemsStub.resolves({}); paymentBuyGemsStub.resolves({});
paymentCreateSubscriptionStub = sinon.stub(payments, 'createSubscription'); paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription');
paymentCreateSubscriptionStub.resolves({}); paymentCreateSubscritionStub.resolves({});
sinon.stub(common, 'uuid').returns('uuid-generated'); sinon.stub(common, 'uuid').returns('uuid-generated');
sandbox.stub(gems, 'validateGiftMessage'); sandbox.stub(gems, 'validateGiftMessage');
@@ -109,7 +109,6 @@ describe('Amazon Payments - Checkout', () => {
user, user,
paymentMethod, paymentMethod,
headers, headers,
sku: undefined,
}; };
if (gift) { if (gift) {
expectedArgs.gift = gift; expectedArgs.gift = gift;
@@ -216,14 +215,13 @@ describe('Amazon Payments - Checkout', () => {
}); });
gift.member = receivingUser; gift.member = receivingUser;
expect(paymentCreateSubscriptionStub).to.be.calledOnce; expect(paymentCreateSubscritionStub).to.be.calledOnce;
expect(paymentCreateSubscriptionStub).to.be.calledWith({ expect(paymentCreateSubscritionStub).to.be.calledWith({
user, user,
paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT, paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT,
headers, headers,
gift, gift,
gemsBlock: undefined, gemsBlock: undefined,
sku: undefined,
}); });
expectAmazonStubs(); expectAmazonStubs();
}); });

View File

@@ -12,10 +12,10 @@ const { i18n } = common;
describe('Apple Payments', () => { describe('Apple Payments', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
describe('verifyPurchase', () => { describe('verifyGemPurchase', () => {
let sku; let user; let token; let receipt; let let sku; let user; let token; let receipt; let
headers; headers;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuySkuStub; let let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuyGemsStub; let
iapGetPurchaseDataStub; let validateGiftMessageStub; iapGetPurchaseDataStub; let validateGiftMessageStub;
beforeEach(() => { beforeEach(() => {
@@ -29,15 +29,14 @@ describe('Apple Payments', () => {
.resolves(); .resolves();
iapValidateStub = sinon.stub(iap, 'validate') iapValidateStub = sinon.stub(iap, 'validate')
.resolves({}); .resolves({});
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true); iapIsValidatedStub = sinon.stub(iap, 'isValidated')
sinon.stub(iap, 'isExpired').returns(false); .returns(true);
sinon.stub(iap, 'isCanceled').returns(false);
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ .returns([{
productId: 'com.habitrpg.ios.Habitica.21gems', productId: 'com.habitrpg.ios.Habitica.21gems',
transactionId: token, transactionId: token,
}]); }]);
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({}); paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage'); validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
}); });
@@ -45,10 +44,8 @@ describe('Apple Payments', () => {
iap.setup.restore(); iap.setup.restore();
iap.validate.restore(); iap.validate.restore();
iap.isValidated.restore(); iap.isValidated.restore();
iap.isExpired.restore();
iap.isCanceled.restore();
iap.getPurchaseData.restore(); iap.getPurchaseData.restore();
payments.buySkuItem.restore(); payments.buyGems.restore();
gems.validateGiftMessage.restore(); gems.validateGiftMessage.restore();
}); });
@@ -57,7 +54,7 @@ describe('Apple Payments', () => {
iapIsValidatedStub = sinon.stub(iap, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false); .returns(false);
await expect(applePayments.verifyPurchase({ user, receipt, headers })) await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -69,7 +66,7 @@ describe('Apple Payments', () => {
iapGetPurchaseDataStub.restore(); iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData').returns([]); iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData').returns([]);
await expect(applePayments.verifyPurchase({ user, receipt, headers })) await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -79,7 +76,7 @@ describe('Apple Payments', () => {
it('errors if the user cannot purchase gems', async () => { it('errors if the user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false); sinon.stub(user, 'canGetGems').resolves(false);
await expect(applePayments.verifyPurchase({ user, receipt, headers })) await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -97,16 +94,14 @@ describe('Apple Payments', () => {
productId: 'badProduct', productId: 'badProduct',
transactionId: token, transactionId: token,
}]); }]);
paymentBuySkuStub.restore();
await expect(applePayments.verifyPurchase({ user, receipt, headers })) await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 400, httpCode: 401,
name: 'BadRequest', name: 'NotAuthorized',
message: applePayments.constants.RESPONSE_INVALID_ITEM, message: applePayments.constants.RESPONSE_INVALID_ITEM,
}); });
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
user.canGetGems.restore(); user.canGetGems.restore();
}); });
@@ -143,7 +138,7 @@ describe('Apple Payments', () => {
}]); }]);
sinon.stub(user, 'canGetGems').resolves(true); sinon.stub(user, 'canGetGems').resolves(true);
await applePayments.verifyPurchase({ user, receipt, headers }); await applePayments.verifyGemPurchase({ user, receipt, headers });
expect(iapSetupStub).to.be.calledOnce; expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce; expect(iapValidateStub).to.be.calledOnce;
@@ -153,13 +148,13 @@ describe('Apple Payments', () => {
expect(iapGetPurchaseDataStub).to.be.calledOnce; expect(iapGetPurchaseDataStub).to.be.calledOnce;
expect(validateGiftMessageStub).to.not.be.called; expect(validateGiftMessageStub).to.not.be.called;
expect(paymentBuySkuStub).to.be.calledOnce; expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuySkuStub).to.be.calledWith({ expect(paymentBuyGemsStub).to.be.calledWith({
user, user,
gift: undefined,
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
sku: gemTest.productId, gemsBlock: common.content.gems[gemTest.gemsBlock],
headers, headers,
gift: undefined,
}); });
expect(user.canGetGems).to.be.calledOnce; expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore(); user.canGetGems.restore();
@@ -178,7 +173,7 @@ describe('Apple Payments', () => {
}]); }]);
const gift = { uuid: receivingUser._id }; const gift = { uuid: receivingUser._id };
await applePayments.verifyPurchase({ await applePayments.verifyGemPurchase({
user, gift, receipt, headers, user, gift, receipt, headers,
}); });
@@ -192,16 +187,18 @@ describe('Apple Payments', () => {
expect(validateGiftMessageStub).to.be.calledOnce; expect(validateGiftMessageStub).to.be.calledOnce;
expect(validateGiftMessageStub).to.be.calledWith(gift, user); expect(validateGiftMessageStub).to.be.calledWith(gift, user);
expect(paymentBuySkuStub).to.be.calledOnce; expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuySkuStub).to.be.calledWith({ expect(paymentBuyGemsStub).to.be.calledWith({
user, user,
gift: {
uuid: receivingUser._id,
member: sinon.match({ _id: receivingUser._id }),
},
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
sku: 'com.habitrpg.ios.Habitica.4gems',
headers, headers,
gift: {
type: 'gems',
gems: { amount: 4 },
member: sinon.match({ _id: receivingUser._id }),
uuid: receivingUser._id,
},
gemsBlock: common.content.gems['4gems'],
}); });
}); });
}); });
@@ -221,7 +218,6 @@ describe('Apple Payments', () => {
headers = {}; headers = {};
receipt = `{"token": "${token}"}`; receipt = `{"token": "${token}"}`;
nextPaymentProcessing = moment.utc().add({ days: 2 }); nextPaymentProcessing = moment.utc().add({ days: 2 });
user = new User();
iapSetupStub = sinon.stub(iap, 'setup') iapSetupStub = sinon.stub(iap, 'setup')
.resolves(); .resolves();
@@ -232,17 +228,14 @@ describe('Apple Payments', () => {
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ .returns([{
expirationDate: moment.utc().subtract({ day: 1 }).toDate(), expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
purchaseDate: moment.utc().valueOf(),
productId: sku, productId: sku,
transactionId: token, transactionId: token,
}, { }, {
expirationDate: moment.utc().add({ day: 1 }).toDate(), expirationDate: moment.utc().add({ day: 1 }).toDate(),
purchaseDate: moment.utc().valueOf(),
productId: 'wrongsku', productId: 'wrongsku',
transactionId: token, transactionId: token,
}, { }, {
expirationDate: moment.utc().add({ day: 1 }).toDate(), expirationDate: moment.utc().add({ day: 1 }).toDate(),
purchaseDate: moment.utc().valueOf(),
productId: sku, productId: sku,
transactionId: token, transactionId: token,
}]); }]);
@@ -257,12 +250,21 @@ describe('Apple Payments', () => {
if (payments.createSubscription.restore) payments.createSubscription.restore(); if (payments.createSubscription.restore) payments.createSubscription.restore();
}); });
it('should throw an error if sku is empty', async () => {
await expect(applePayments.subscribe('', user, receipt, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({
httpCode: 400,
name: 'BadRequest',
message: i18n.t('missingSubscriptionCode'),
});
});
it('should throw an error if receipt is invalid', async () => { it('should throw an error if receipt is invalid', async () => {
iap.isValidated.restore(); iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iap, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false); .returns(false);
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing)) await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -293,15 +295,13 @@ describe('Apple Payments', () => {
iap.getPurchaseData.restore(); iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ .returns([{
expirationDate: moment.utc().add({ day: 2 }).toDate(), expirationDate: moment.utc().add({ day: 1 }).toDate(),
purchaseDate: new Date(),
productId: option.sku, productId: option.sku,
transactionId: token, transactionId: token,
originalTransactionId: token,
}]); }]);
sub = common.content.subscriptionBlocks[option.subKey]; sub = common.content.subscriptionBlocks[option.subKey];
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); await applePayments.subscribe(option.sku, user, receipt, headers, nextPaymentProcessing);
expect(iapSetupStub).to.be.calledOnce; expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce; expect(iapValidateStub).to.be.calledOnce;
@@ -321,253 +321,21 @@ describe('Apple Payments', () => {
nextPaymentProcessing, nextPaymentProcessing,
}); });
}); });
if (option !== subOptions[3]) {
const newOption = subOptions[3];
it(`upgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => {
const oldSub = common.content.subscriptionBlocks[option.subKey];
oldSub.logic = 'refundAndRepay';
user.profile.name = 'sender';
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
user.purchased.plan.customerId = token;
user.purchased.plan.planId = option.subKey;
user.purchased.plan.additionalData = receipt;
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
expirationDate: moment.utc().add({ day: 2 }).toDate(),
purchaseDate: moment.utc().valueOf(),
productId: newOption.sku,
transactionId: `${token}new`,
originalTransactionId: token,
}]);
sub = common.content.subscriptionBlocks[newOption.subKey];
await applePayments.subscribe(
user,
receipt,
headers,
nextPaymentProcessing,
);
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(iapGetPurchaseDataStub).to.be.calledOnce;
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
expect(paymentsCreateSubscritionStub).to.be.calledWith({
user,
customerId: token,
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
sub,
headers,
additionalData: receipt,
nextPaymentProcessing,
updatedFrom: oldSub,
});
});
}
if (option !== subOptions[0]) {
const newOption = subOptions[0];
it(`downgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => {
const oldSub = common.content.subscriptionBlocks[option.subKey];
user.profile.name = 'sender';
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
user.purchased.plan.customerId = token;
user.purchased.plan.planId = option.subKey;
user.purchased.plan.additionalData = receipt;
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
expirationDate: moment.utc().add({ day: 2 }).toDate(),
purchaseDate: moment.utc().valueOf(),
productId: newOption.sku,
transactionId: `${token}new`,
originalTransactionId: token,
}]);
sub = common.content.subscriptionBlocks[newOption.subKey];
await applePayments.subscribe(
user,
receipt,
headers,
nextPaymentProcessing,
);
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(iapGetPurchaseDataStub).to.be.calledOnce;
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
expect(paymentsCreateSubscritionStub).to.be.calledWith({
user,
customerId: token,
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
sub,
headers,
additionalData: receipt,
nextPaymentProcessing,
updatedFrom: oldSub,
});
});
}
}); });
it('uses the most recent subscription data', async () => { it('errors when a user is already subscribed', async () => {
iap.getPurchaseData.restore(); payments.createSubscription.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') user = new User();
.returns([{ await user.save();
expirationDate: moment.utc().add({ day: 4 }).toDate(),
purchaseDate: moment.utc().subtract({ day: 5 }).toDate(),
productId: 'com.habitrpg.ios.habitica.subscription.3month',
transactionId: `${token}oldest`,
originalTransactionId: `${token}evenOlder`,
}, {
expirationDate: moment.utc().add({ day: 2 }).toDate(),
purchaseDate: moment.utc().subtract({ day: 1 }).toDate(),
productId: 'com.habitrpg.ios.habitica.subscription.12month',
transactionId: `${token}newest`,
originalTransactionId: `${token}newest`,
}, {
expirationDate: moment.utc().add({ day: 1 }).toDate(),
purchaseDate: moment.utc().subtract({ day: 2 }).toDate(),
productId: 'com.habitrpg.ios.habitica.subscription.6month',
transactionId: token,
originalTransactionId: token,
}]);
sub = common.content.subscriptionBlocks.basic_12mo;
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
expect(paymentsCreateSubscritionStub).to.be.calledOnce; await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
expect(paymentsCreateSubscritionStub).to.be.calledWith({ .to.eventually.be.rejected.and.to.eql({
user, httpCode: 401,
customerId: `${token}newest`, name: 'NotAuthorized',
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, message: applePayments.constants.RESPONSE_ALREADY_USED,
sub, });
headers,
additionalData: receipt,
nextPaymentProcessing,
});
});
describe('does not apply multiple times', async () => {
it('errors when a user is using the same subscription', async () => {
payments.createSubscription.restore();
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
expirationDate: moment.utc().add({ day: 1 }).toDate(),
purchaseDate: moment.utc().toDate(),
productId: sku,
transactionId: token,
originalTransactionId: token,
}]);
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: applePayments.constants.RESPONSE_ALREADY_USED,
});
});
it('errors when a user is using a rebill of the same subscription', async () => {
user = new User();
await user.save();
payments.createSubscription.restore();
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
expirationDate: moment.utc().add({ day: 1 }).toDate(),
purchaseDate: moment.utc().toDate(),
productId: sku,
transactionId: `${token}renew`,
originalTransactionId: token,
}]);
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: applePayments.constants.RESPONSE_ALREADY_USED,
});
});
it('errors when a different user is using the subscription', async () => {
user = new User();
await user.save();
payments.createSubscription.restore();
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
expirationDate: moment.utc().add({ day: 1 }).toDate(),
purchaseDate: moment.utc().toDate(),
productId: sku,
transactionId: token,
originalTransactionId: token,
}]);
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
const secondUser = new User();
await secondUser.save();
await expect(applePayments.subscribe(secondUser, receipt, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: applePayments.constants.RESPONSE_ALREADY_USED,
});
});
it('errors when a multiple users exist using the subscription', async () => {
user = new User();
await user.save();
payments.createSubscription.restore();
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
expirationDate: moment.utc().add({ day: 1 }).toDate(),
purchaseDate: moment.utc().toDate(),
productId: sku,
transactionId: token,
originalTransactionId: token,
}]);
await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing);
const secondUser = new User();
secondUser.purchased.plan = user.purchased.plan;
secondUser.purchased.plan.dateTerminate = new Date();
secondUser.save();
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
expirationDate: moment.utc().add({ day: 1 }).toDate(),
purchaseDate: moment.utc().toDate(),
productId: sku,
transactionId: `${token}new`,
originalTransactionId: token,
}]);
const thirdUser = new User();
await thirdUser.save();
await expect(applePayments.subscribe(thirdUser, receipt, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: applePayments.constants.RESPONSE_ALREADY_USED,
});
});
}); });
}); });
@@ -592,9 +360,9 @@ describe('Apple Payments', () => {
}); });
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ expirationDate: expirationDate.toDate() }]); .returns([{ expirationDate: expirationDate.toDate() }]);
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true); iapIsValidatedStub = sinon.stub(iap, 'isValidated')
sinon.stub(iap, 'isCanceled').returns(false); .returns(true);
sinon.stub(iap, 'isExpired').returns(true);
user = new User(); user = new User();
user.profile.name = 'sender'; user.profile.name = 'sender';
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE; user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
@@ -609,8 +377,6 @@ describe('Apple Payments', () => {
iap.setup.restore(); iap.setup.restore();
iap.validate.restore(); iap.validate.restore();
iap.isValidated.restore(); iap.isValidated.restore();
iap.isExpired.restore();
iap.isCanceled.restore();
iap.getPurchaseData.restore(); iap.getPurchaseData.restore();
payments.cancelSubscription.restore(); payments.cancelSubscription.restore();
}); });
@@ -630,8 +396,6 @@ describe('Apple Payments', () => {
iap.getPurchaseData.restore(); iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]); .returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]);
iap.isExpired.restore();
sinon.stub(iap, 'isExpired').returns(false);
await expect(applePayments.cancelSubscribe(user, headers)) await expect(applePayments.cancelSubscribe(user, headers))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
@@ -654,38 +418,7 @@ describe('Apple Payments', () => {
}); });
}); });
it('should cancel a cancelled subscription with termination date in the future', async () => { it('should cancel a user subscription', async () => {
const futureDate = expirationDate.add({ day: 1 });
iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ expirationDate: futureDate }]);
iap.isExpired.restore();
sinon.stub(iap, 'isExpired').returns(false);
iap.isCanceled.restore();
sinon.stub(iap, 'isCanceled').returns(true);
await applePayments.cancelSubscribe(user, headers);
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({
expirationDate: futureDate,
});
expect(iapGetPurchaseDataStub).to.be.calledOnce;
expect(paymentCancelSubscriptionSpy).to.be.calledOnce;
expect(paymentCancelSubscriptionSpy).to.be.calledWith({
user,
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
nextBill: futureDate.toDate(),
headers,
});
});
it('should cancel an expired subscription', async () => {
await applePayments.cancelSubscribe(user, headers); await applePayments.cancelSubscribe(user, headers);
expect(iapSetupStub).to.be.calledOnce; expect(iapSetupStub).to.be.calledOnce;

View File

@@ -12,11 +12,11 @@ const { i18n } = common;
describe('Google Payments', () => { describe('Google Payments', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
describe('verifyPurchase', () => { describe('verifyGemPurchase', () => {
let sku; let user; let token; let receipt; let signature; let let sku; let user; let token; let receipt; let signature; let
headers; headers; const gemsBlock = common.content.gems['21gems'];
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
paymentBuySkuStub; let validateGiftMessageStub; paymentBuyGemsStub; let validateGiftMessageStub;
beforeEach(() => { beforeEach(() => {
sku = 'com.habitrpg.android.habitica.iap.21gems'; sku = 'com.habitrpg.android.habitica.iap.21gems';
@@ -27,10 +27,11 @@ describe('Google Payments', () => {
iapSetupStub = sinon.stub(iap, 'setup') iapSetupStub = sinon.stub(iap, 'setup')
.resolves(); .resolves();
iapValidateStub = sinon.stub(iap, 'validate').resolves({ productId: sku }); iapValidateStub = sinon.stub(iap, 'validate')
.resolves({});
iapIsValidatedStub = sinon.stub(iap, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true); .returns(true);
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({}); paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage'); validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
}); });
@@ -38,7 +39,7 @@ describe('Google Payments', () => {
iap.setup.restore(); iap.setup.restore();
iap.validate.restore(); iap.validate.restore();
iap.isValidated.restore(); iap.isValidated.restore();
payments.buySkuItem.restore(); payments.buyGems.restore();
gems.validateGiftMessage.restore(); gems.validateGiftMessage.restore();
}); });
@@ -47,7 +48,7 @@ describe('Google Payments', () => {
iapIsValidatedStub = sinon.stub(iap, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false); .returns(false);
await expect(googlePayments.verifyPurchase({ await expect(googlePayments.verifyGemPurchase({
user, receipt, signature, headers, user, receipt, signature, headers,
})) }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
@@ -59,25 +60,21 @@ describe('Google Payments', () => {
it('should throw an error if productId is invalid', async () => { it('should throw an error if productId is invalid', async () => {
receipt = `{"token": "${token}", "productId": "invalid"}`; receipt = `{"token": "${token}", "productId": "invalid"}`;
iapValidateStub.restore();
iapValidateStub = sinon.stub(iap, 'validate').resolves({});
paymentBuySkuStub.restore(); await expect(googlePayments.verifyGemPurchase({
await expect(googlePayments.verifyPurchase({
user, receipt, signature, headers, user, receipt, signature, headers,
})) }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 400, httpCode: 401,
name: 'BadRequest', name: 'NotAuthorized',
message: googlePayments.constants.RESPONSE_INVALID_ITEM, message: googlePayments.constants.RESPONSE_INVALID_ITEM,
}); });
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
}); });
it('should throw an error if user cannot purchase gems', async () => { it('should throw an error if user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false); sinon.stub(user, 'canGetGems').resolves(false);
await expect(googlePayments.verifyPurchase({ await expect(googlePayments.verifyGemPurchase({
user, receipt, signature, headers, user, receipt, signature, headers,
})) }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
@@ -91,7 +88,7 @@ describe('Google Payments', () => {
it('purchases gems', async () => { it('purchases gems', async () => {
sinon.stub(user, 'canGetGems').resolves(true); sinon.stub(user, 'canGetGems').resolves(true);
await googlePayments.verifyPurchase({ await googlePayments.verifyGemPurchase({
user, receipt, signature, headers, user, receipt, signature, headers,
}); });
@@ -104,17 +101,15 @@ describe('Google Payments', () => {
signature, signature,
}); });
expect(iapIsValidatedStub).to.be.calledOnce; expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith( expect(iapIsValidatedStub).to.be.calledWith({});
{ productId: sku },
);
expect(paymentBuySkuStub).to.be.calledOnce; expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuySkuStub).to.be.calledWith({ expect(paymentBuyGemsStub).to.be.calledWith({
user, user,
gift: undefined,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE, paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
sku, gemsBlock,
headers, headers,
gift: undefined,
}); });
expect(user.canGetGems).to.be.calledOnce; expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore(); user.canGetGems.restore();
@@ -125,7 +120,7 @@ describe('Google Payments', () => {
await receivingUser.save(); await receivingUser.save();
const gift = { uuid: receivingUser._id }; const gift = { uuid: receivingUser._id };
await googlePayments.verifyPurchase({ await googlePayments.verifyGemPurchase({
user, gift, receipt, signature, headers, user, gift, receipt, signature, headers,
}); });
@@ -139,20 +134,20 @@ describe('Google Payments', () => {
signature, signature,
}); });
expect(iapIsValidatedStub).to.be.calledOnce; expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith( expect(iapIsValidatedStub).to.be.calledWith({});
{ productId: sku },
);
expect(paymentBuySkuStub).to.be.calledOnce; expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuySkuStub).to.be.calledWith({ expect(paymentBuyGemsStub).to.be.calledWith({
user, user,
gift: {
uuid: receivingUser._id,
member: sinon.match({ _id: receivingUser._id }),
},
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE, paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
sku, gemsBlock,
headers, headers,
gift: {
type: 'gems',
gems: { amount: 21 },
member: sinon.match({ _id: receivingUser._id }),
uuid: receivingUser._id,
},
}); });
}); });
}); });

View File

@@ -203,28 +203,6 @@ describe('payments/index', () => {
expect(recipient.purchased.plan.dateCreated).to.exist; expect(recipient.purchased.plan.dateCreated).to.exist;
}); });
it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => {
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.exist;
await api.createSubscription(data);
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.exist;
});
it('keeps plan.dateCreated when changing subscription type', async () => {
await api.createSubscription(data);
const initialDate = recipient.purchased.plan.dateCreated;
await api.createSubscription(data);
expect(recipient.purchased.plan.dateCreated).to.eql(initialDate);
});
it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => {
await api.createSubscription(data);
const initialDate = recipient.purchased.plan.dateCurrentTypeCreated;
await api.createSubscription(data);
expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
});
it('does not change plan.customerId if it already exists', async () => { it('does not change plan.customerId if it already exists', async () => {
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
data.customerId = 'purchaserCustomerId'; data.customerId = 'purchaserCustomerId';
@@ -235,116 +213,6 @@ describe('payments/index', () => {
expect(recipient.purchased.plan.customerId).to.eql('customer-id'); expect(recipient.purchased.plan.customerId).to.eql('customer-id');
}); });
it('sets plan.perkMonthCount to 1 if user is not subscribed', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 1;
recipient.purchased.plan.customerId = undefined;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
});
it('sets plan.perkMonthCount to 1 if field is not initialized', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = -1;
recipient.purchased.plan.customerId = undefined;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
});
it('sets plan.perkMonthCount to 1 if user had previous count but lapsed subscription', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 2;
recipient.purchased.plan.customerId = undefined;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
});
it('adds to plan.perkMonthCount if user is already subscribed', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 1;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(1);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
});
it('awards perks if plan.perkMonthCount reaches 3 with existing subscription', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 2;
data.sub.key = 'basic_earned';
data.gift.subscription.key = 'basic_earned';
data.gift.subscription.months = 1;
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
});
it('awards perks if plan.perkMonthCount reaches 3 without existing subscription', async () => {
recipient.purchased.plan.perkMonthCount = 0;
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
});
it('awards perks if plan.perkMonthCount reaches 3 without initialized field', async () => {
expect(recipient.purchased.plan.perkMonthCount).to.eql(-1);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(0);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
});
it('awards perks if plan.perkMonthCount goes over 3', async () => {
recipient.purchased.plan = plan;
recipient.purchased.plan.perkMonthCount = 2;
data.sub.key = 'basic_earned';
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.perkMonthCount).to.eql(2);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5);
});
it('sets plan.customerId to "Gift" if it does not already exist', async () => { it('sets plan.customerId to "Gift" if it does not already exist', async () => {
expect(recipient.purchased.plan.customerId).to.not.exist; expect(recipient.purchased.plan.customerId).to.not.exist;
@@ -511,7 +379,6 @@ describe('payments/index', () => {
expect(user.purchased.plan.customerId).to.eql('customer-id'); expect(user.purchased.plan.customerId).to.eql('customer-id');
expect(user.purchased.plan.dateUpdated).to.exist; expect(user.purchased.plan.dateUpdated).to.exist;
expect(user.purchased.plan.gemsBought).to.eql(0); expect(user.purchased.plan.gemsBought).to.eql(0);
expect(user.purchased.plan.perkMonthCount).to.eql(0);
expect(user.purchased.plan.paymentMethod).to.eql('Payment Method'); expect(user.purchased.plan.paymentMethod).to.eql('Payment Method');
expect(user.purchased.plan.extraMonths).to.eql(0); expect(user.purchased.plan.extraMonths).to.eql(0);
expect(user.purchased.plan.dateTerminated).to.eql(null); expect(user.purchased.plan.dateTerminated).to.eql(null);
@@ -519,63 +386,6 @@ describe('payments/index', () => {
expect(user.purchased.plan.dateCreated).to.exist; expect(user.purchased.plan.dateCreated).to.exist;
}); });
it('sets plan.dateCreated if it did not previously exist', async () => {
expect(user.purchased.plan.dateCreated).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.dateCreated).to.exist;
});
it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => {
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.dateCurrentTypeCreated).to.exist;
});
it('keeps plan.dateCreated when changing subscription type', async () => {
await api.createSubscription(data);
const initialDate = user.purchased.plan.dateCreated;
await api.createSubscription(data);
expect(user.purchased.plan.dateCreated).to.eql(initialDate);
});
it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => {
await api.createSubscription(data);
const initialDate = user.purchased.plan.dateCurrentTypeCreated;
await api.createSubscription(data);
expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate);
});
it('keeps plan.perkMonthCount when changing subscription type', async () => {
await api.createSubscription(data);
user.purchased.plan.perkMonthCount = 2;
await api.createSubscription(data);
expect(user.purchased.plan.perkMonthCount).to.eql(2);
});
it('sets plan.perkMonthCount to zero when creating new monthly subscription', async () => {
user.purchased.plan.perkMonthCount = 2;
await api.createSubscription(data);
expect(user.purchased.plan.perkMonthCount).to.eql(0);
});
it('sets plan.perkMonthCount to zero when creating new 3 month subscription', async () => {
user.purchased.plan.perkMonthCount = 2;
await api.createSubscription(data);
expect(user.purchased.plan.perkMonthCount).to.eql(0);
});
it('updates plan.consecutive.offset when changing subscription type', async () => {
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.offset).to.eql(3);
data.sub.key = 'basic_6mo';
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.offset).to.eql(6);
});
it('awards the Royal Purple Jackalope pet', async () => { it('awards the Royal Purple Jackalope pet', async () => {
await api.createSubscription(data); await api.createSubscription(data);
@@ -655,89 +465,6 @@ describe('payments/index', () => {
}, },
}); });
}); });
context('Upgrades subscription', () => {
it('from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.customerId).to.eql('customer-id');
const created = user.purchased.plan.dateCreated;
const updated = user.purchased.plan.dateUpdated;
data.sub.key = 'basic_6mo';
data.updatedFrom = { key: 'basic_earned' };
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.dateCreated).to.eql(created);
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
expect(user.purchased.plan.customerId).to.eql('customer-id');
});
it('from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.customerId).to.eql('customer-id');
const created = user.purchased.plan.dateCreated;
const updated = user.purchased.plan.dateUpdated;
data.sub.key = 'basic_12mo';
data.updatedFrom = { key: 'basic_3mo' };
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.dateCreated).to.eql(created);
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
expect(user.purchased.plan.customerId).to.eql('customer-id');
});
});
context('Downgrades subscription', () => {
it('from basic_6mo to basic_earned', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.customerId).to.eql('customer-id');
const created = user.purchased.plan.dateCreated;
const updated = user.purchased.plan.dateUpdated;
data.sub.key = 'basic_earned';
data.updatedFrom = { key: 'basic_6mo' };
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.dateCreated).to.eql(created);
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
expect(user.purchased.plan.customerId).to.eql('customer-id');
});
it('from basic_12mo to basic_3mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
data.sub.key = 'basic_12mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.customerId).to.eql('customer-id');
const created = user.purchased.plan.dateCreated;
const updated = user.purchased.plan.dateUpdated;
data.sub.key = 'basic_3mo';
data.updatedFrom = { key: 'basic_12mo' };
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.dateCreated).to.eql(created);
expect(user.purchased.plan.dateUpdated).to.not.eql(updated);
expect(user.purchased.plan.customerId).to.eql('customer-id');
});
});
}); });
context('Block subscription perks', () => { context('Block subscription perks', () => {
@@ -748,19 +475,9 @@ describe('payments/index', () => {
}); });
it('does not add to plans.consecutive.offset if 1 month subscription', async () => { it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
data.sub.key = 'basic_earned';
await api.createSubscription(data); await api.createSubscription(data);
expect(user.purchased.plan.consecutive.offset).to.eql(0); expect(user.purchased.plan.extraMonths).to.eql(0);
});
it('resets plans.consecutive.offset if 1 month subscription', async () => {
user.purchased.plan.consecutive.offset = 1;
await user.save();
data.sub.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.offset).to.eql(0);
}); });
it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => { it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => {
@@ -771,6 +488,7 @@ describe('payments/index', () => {
it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => { it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => {
data.sub.key = 'basic_6mo'; data.sub.key = 'basic_6mo';
await api.createSubscription(data); await api.createSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
@@ -778,6 +496,7 @@ describe('payments/index', () => {
it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => { it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => {
data.sub.key = 'basic_12mo'; data.sub.key = 'basic_12mo';
await api.createSubscription(data); await api.createSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
@@ -813,532 +532,6 @@ describe('payments/index', () => {
expect(user.purchased.plan.consecutive.trinkets).to.eql(4); expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
}); });
context('Upgrades subscription', () => {
context('Using payDifference logic', () => {
beforeEach(async () => {
data.updatedFrom = { logic: 'payDifference' };
});
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
});
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
});
});
context('Using payFull logic', () => {
beforeEach(async () => {
data.updatedFrom = { logic: 'payFull' };
});
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
});
});
context('Using refundAndRepay logic', () => {
let clock;
beforeEach(async () => {
clock = sinon.useFakeTimers(new Date('2022-01-01'));
data.updatedFrom = { logic: 'refundAndRepay' };
});
context('Upgrades within first half of subscription', () => {
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-01-10'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-02-05'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-01-08'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-01-31'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
});
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-01-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
clock.restore();
clock = sinon.useFakeTimers(new Date('2024-01-08'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-08-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
});
it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-07-31'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
});
});
context('Upgrades within second half of subscription', () => {
it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-01-20'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-02-24'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-01-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-05-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-03-03'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
data.sub.key = 'basic_6mo';
data.updatedFrom.key = 'basic_earned';
clock.restore();
clock = sinon.useFakeTimers(new Date('2022-05-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_6mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2023-05-28'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_3mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
data.sub.key = 'basic_12mo';
data.updatedFrom.key = 'basic_3mo';
clock.restore();
clock = sinon.useFakeTimers(new Date('2023-09-03'));
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(5);
});
});
afterEach(async () => {
if (clock !== null) clock.restore();
});
});
});
context('Downgrades subscription', () => {
it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
data.sub.key = 'basic_earned';
data.updatedFrom = { key: 'basic_6mo' };
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10);
});
it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
data.sub.key = 'basic_12mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
data.sub.key = 'basic_3mo';
data.updatedFrom = { key: 'basic_12mo' };
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20);
});
it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_6mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
data.sub.key = 'basic_earned';
data.updatedFrom = { key: 'basic_6mo' };
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_earned');
expect(user.purchased.plan.consecutive.trinkets).to.eql(2);
});
it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => {
expect(user.purchased.plan.planId).to.not.exist;
data.sub.key = 'basic_12mo';
await api.createSubscription(data);
expect(user.purchased.plan.planId).to.eql('basic_12mo');
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
data.sub.key = 'basic_3mo';
data.updatedFrom = { key: 'basic_12mo' };
await api.createSubscription(data);
expect(user.purchased.plan.consecutive.trinkets).to.eql(4);
});
});
}); });
context('Mystery Items', () => { context('Mystery Items', () => {
@@ -1382,6 +575,18 @@ describe('payments/index', () => {
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(1); expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(1);
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605'); expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
}); });
it('does not award mystery item when user already has the item in the mystery box', async () => {
user.purchased.plan.mysteryItems = [mayMysteryItem];
sandbox.spy(user.purchased.plan.mysteryItems, 'push');
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
await api.createSubscription(data);
expect(user.purchased.plan.mysteryItems.push).to.be.calledOnce;
expect(user.purchased.plan.mysteryItems.push).to.be.calledWith('head_mystery_201605');
});
}); });
}); });
@@ -1587,10 +792,10 @@ describe('payments/index', () => {
it('sends gem donation message in each participant\'s language', async () => { it('sends gem donation message in each participant\'s language', async () => {
// TODO using english for both users because other languages are not loaded // TODO using english for both users because other languages are not loaded
// for api.buyGems // for api.buyGems
await recipient.updateOne({ await recipient.update({
'preferences.language': 'en', 'preferences.language': 'en',
}); });
await user.updateOne({ await user.update({
'preferences.language': 'en', 'preferences.language': 'en',
}); });
await api.buyGems(data); await api.buyGems(data);

View File

@@ -4,7 +4,7 @@ import nconf from 'nconf';
import paypalPayments from '../../../../../../website/server/libs/payments/paypal'; import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
import { model as User } from '../../../../../../website/server/models/user'; import { model as User } from '../../../../../../website/server/models/user';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
import { apiError } from '../../../../../../website/server/libs/apiError'; import apiError from '../../../../../../website/server/libs/apiError';
import * as gems from '../../../../../../website/server/libs/payments/gems'; import * as gems from '../../../../../../website/server/libs/payments/gems';
const BASE_URL = nconf.get('BASE_URL'); const BASE_URL = nconf.get('BASE_URL');

View File

@@ -1,40 +0,0 @@
import {
canBuySkuItem,
} from '../../../../../website/server/libs/payments/skuItem';
import { model as User } from '../../../../../website/server/models/user';
describe('payments/skuItems', () => {
let user;
let clock;
beforeEach(() => {
user = new User();
clock = null;
});
afterEach(() => {
if (clock !== null) clock.restore();
});
describe('#canBuySkuItem', () => {
it('returns true for random sku', () => {
expect(canBuySkuItem('something', user)).to.be.true;
});
describe('#gryphatrice', () => {
const sku = 'Pet-Gryphatrice-Jubilant';
it('returns true during birthday week', () => {
clock = sinon.useFakeTimers(new Date('2023-01-31'));
expect(canBuySkuItem(sku, user)).to.be.true;
});
it('returns false outside of birthday week', () => {
clock = sinon.useFakeTimers(new Date('2023-01-20'));
expect(canBuySkuItem(sku, user)).to.be.false;
});
it('returns false if user already owns it', () => {
clock = sinon.useFakeTimers(new Date('2023-02-01'));
user.items.pets['Gryphatrice-Jubilant'] = 5;
expect(canBuySkuItem(sku, user)).to.be.false;
});
});
});
});

View File

@@ -1,4 +1,4 @@
import { apiError } from '../../../../../../website/server/libs/apiError'; import apiError from '../../../../../../website/server/libs/apiError';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
import { import {
getOneTimePaymentInfo, getOneTimePaymentInfo,

View File

@@ -53,9 +53,11 @@ describe('cron middleware', () => {
cronMiddleware(req, res, err => { cronMiddleware(req, res, err => {
if (err) return reject(err); if (err) return reject(err);
Tasks.Task.findOne({ _id: task }).then(foundTask => { Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
expect(foundTask).to.not.exist; if (secondErr) return reject(err);
resolve(); expect(secondErr).to.not.exist;
expect(taskFound).to.not.exist;
return resolve();
}); });
return null; return null;
@@ -76,8 +78,10 @@ describe('cron middleware', () => {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
cronMiddleware(req, res, err => { cronMiddleware(req, res, err => {
if (err) return reject(err); if (err) return reject(err);
Tasks.Task.findOne({ _id: task }).then(foundTask => { Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
expect(foundTask).to.exist; if (secondErr) return reject(secondErr);
expect(secondErr).to.not.exist;
expect(taskFound).to.exist;
return resolve(); return resolve();
}); });
return null; return null;
@@ -99,8 +103,10 @@ describe('cron middleware', () => {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
cronMiddleware(req, res, err => { cronMiddleware(req, res, err => {
if (err) return reject(err); if (err) return reject(err);
Tasks.Task.findOne({ _id: task }).then(foundTask => { Tasks.Task.findOne({ _id: task }, (secondErr, taskFound) => {
expect(foundTask).to.not.exist; if (secondErr) return reject(secondErr);
expect(secondErr).to.not.exist;
expect(taskFound).to.not.exist;
return resolve(); return resolve();
}); });
return null; return null;
@@ -164,7 +170,8 @@ describe('cron middleware', () => {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
cronMiddleware(req, res, err => { cronMiddleware(req, res, err => {
if (err) return reject(err); if (err) return reject(err);
return User.findOne({ _id: user._id }).then(updatedUser => { return User.findOne({ _id: user._id }, (secondErr, updatedUser) => {
if (secondErr) return reject(secondErr);
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore); expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
return resolve(); return resolve();
}); });
@@ -181,7 +188,8 @@ describe('cron middleware', () => {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
cronMiddleware(req, res, err => { cronMiddleware(req, res, err => {
if (err) return reject(err); if (err) return reject(err);
return Tasks.Task.findOne({ _id: todo._id }).then(todoFound => { return Tasks.Task.findOne({ _id: todo._id }, (secondErr, todoFound) => {
if (secondErr) return reject(secondErr);
expect(todoFound.value).to.be.lessThan(todoValueBefore); expect(todoFound.value).to.be.lessThan(todoValueBefore);
return resolve(); return resolve();
}); });
@@ -216,7 +224,8 @@ describe('cron middleware', () => {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
cronMiddleware(req, res, err => { cronMiddleware(req, res, err => {
if (err) return reject(err); if (err) return reject(err);
return User.findOne({ _id: user._id }).then(updatedUser => { return User.findOne({ _id: user._id }, (secondErr, updatedUser) => {
if (secondErr) return reject(secondErr);
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore); expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
return resolve(); return resolve();
}); });
@@ -229,11 +238,11 @@ describe('cron middleware', () => {
await user.save(); await user.save();
const updatedUser = user.toObject(); const updatedUser = user.toObject();
updatedUser.matchedCount = 0; updatedUser.nMatched = 0;
sandbox.spy(cronLib, 'recoverCron'); sandbox.spy(cronLib, 'recoverCron');
sandbox.stub(User, 'updateOne') sandbox.stub(User, 'update')
.withArgs({ .withArgs({
_id: user._id, _id: user._id,
$or: [ $or: [
@@ -260,7 +269,7 @@ describe('cron middleware', () => {
it('cronSignature less than an hour ago should error', async () => { it('cronSignature less than an hour ago should error', async () => {
user.lastCron = moment(new Date()).subtract({ days: 2 }); user.lastCron = moment(new Date()).subtract({ days: 2 });
const now = new Date(); const now = new Date();
await User.updateOne({ await User.update({
_id: user._id, _id: user._id,
}, { }, {
$set: { $set: {
@@ -282,7 +291,7 @@ describe('cron middleware', () => {
it('cronSignature longer than an hour ago should allow cron', async () => { it('cronSignature longer than an hour ago should allow cron', async () => {
user.lastCron = moment(new Date()).subtract({ days: 2 }); user.lastCron = moment(new Date()).subtract({ days: 2 });
const now = new Date(); const now = new Date();
await User.updateOne({ await User.update({
_id: user._id, _id: user._id,
}, { }, {
$set: { $set: {

View File

@@ -6,7 +6,7 @@ import {
} from '../../../helpers/api-unit.helper'; } from '../../../helpers/api-unit.helper';
import { ensurePermission } from '../../../../website/server/middlewares/ensureAccessRight'; import { ensurePermission } from '../../../../website/server/middlewares/ensureAccessRight';
import { NotAuthorized } from '../../../../website/server/libs/errors'; import { NotAuthorized } from '../../../../website/server/libs/errors';
import { apiError } from '../../../../website/server/libs/apiError'; import apiError from '../../../../website/server/libs/apiError';
describe('ensure access middlewares', () => { describe('ensure access middlewares', () => {
let res; let req; let let res; let req; let

View File

@@ -6,7 +6,7 @@ import {
generateNext, generateNext,
} from '../../../helpers/api-unit.helper'; } from '../../../helpers/api-unit.helper';
import { Forbidden } from '../../../../website/server/libs/errors'; import { Forbidden } from '../../../../website/server/libs/errors';
import { apiError } from '../../../../website/server/libs/apiError'; import apiError from '../../../../website/server/libs/apiError';
function checkErrorThrown (next) { function checkErrorThrown (next) {
expect(next).to.have.been.calledOnce; expect(next).to.have.been.calledOnce;

View File

@@ -7,7 +7,7 @@ import {
generateNext, generateNext,
} from '../../../helpers/api-unit.helper'; } from '../../../helpers/api-unit.helper';
import { TooManyRequests } from '../../../../website/server/libs/errors'; import { TooManyRequests } from '../../../../website/server/libs/errors';
import { apiError } from '../../../../website/server/libs/apiError'; import apiError from '../../../../website/server/libs/apiError';
import logger from '../../../../website/server/libs/logger'; import logger from '../../../../website/server/libs/logger';
describe('rateLimiter middleware', () => { describe('rateLimiter middleware', () => {

View File

@@ -1358,8 +1358,7 @@ describe('Group Model', () => {
describe('#sendChat', () => { describe('#sendChat', () => {
beforeEach(() => { beforeEach(() => {
sandbox.spy(User, 'updateOne'); sandbox.spy(User, 'update');
sandbox.spy(User, 'updateMany');
}); });
it('formats message', () => { it('formats message', () => {
@@ -1414,8 +1413,8 @@ describe('Group Model', () => {
it('updates users about new messages in party', () => { it('updates users about new messages in party', () => {
party.sendChat({ message: 'message' }); party.sendChat({ message: 'message' });
expect(User.updateMany).to.be.calledOnce; expect(User.update).to.be.calledOnce;
expect(User.updateMany).to.be.calledWithMatch({ expect(User.update).to.be.calledWithMatch({
'party._id': party._id, 'party._id': party._id,
_id: { $ne: '' }, _id: { $ne: '' },
}); });
@@ -1428,8 +1427,8 @@ describe('Group Model', () => {
group.sendChat({ message: 'message' }); group.sendChat({ message: 'message' });
expect(User.updateMany).to.be.calledOnce; expect(User.update).to.be.calledOnce;
expect(User.updateMany).to.be.calledWithMatch({ expect(User.update).to.be.calledWithMatch({
guilds: group._id, guilds: group._id,
_id: { $ne: '' }, _id: { $ne: '' },
}); });
@@ -1438,8 +1437,8 @@ describe('Group Model', () => {
it('does not send update to user that sent the message', () => { it('does not send update to user that sent the message', () => {
party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } }); party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } });
expect(User.updateMany).to.be.calledOnce; expect(User.update).to.be.calledOnce;
expect(User.updateMany).to.be.calledWithMatch({ expect(User.update).to.be.calledWithMatch({
'party._id': party._id, 'party._id': party._id,
_id: { $ne: 'user-id' }, _id: { $ne: 'user-id' },
}); });
@@ -1450,7 +1449,7 @@ describe('Group Model', () => {
party.sendChat({ message: 'message' }); party.sendChat({ message: 'message' });
expect(User.updateMany).to.not.be.called; expect(User.update).to.not.be.called;
}); });
it('skips sending messages to the tavern', () => { it('skips sending messages to the tavern', () => {
@@ -1458,7 +1457,7 @@ describe('Group Model', () => {
party.sendChat({ message: 'message' }); party.sendChat({ message: 'message' });
expect(User.updateMany).to.not.be.called; expect(User.update).to.not.be.called;
}); });
}); });
@@ -1732,7 +1731,7 @@ describe('Group Model', () => {
}); });
it('updates participting members (not including user)', async () => { it('updates participting members (not including user)', async () => {
sandbox.spy(User, 'updateMany'); sandbox.spy(User, 'update');
await party.startQuest(nonParticipatingMember); await party.startQuest(nonParticipatingMember);
@@ -1740,7 +1739,7 @@ describe('Group Model', () => {
questLeader._id, participatingMember._id, sleepingParticipatingMember._id, questLeader._id, participatingMember._id, sleepingParticipatingMember._id,
]; ];
expect(User.updateMany).to.be.calledWith( expect(User.update).to.be.calledWith(
{ _id: { $in: members } }, { _id: { $in: members } },
{ {
$set: { $set: {
@@ -1753,11 +1752,11 @@ describe('Group Model', () => {
}); });
it('updates non-user quest leader and decrements quest scroll', async () => { it('updates non-user quest leader and decrements quest scroll', async () => {
sandbox.spy(User, 'updateOne'); sandbox.spy(User, 'update');
await party.startQuest(participatingMember); await party.startQuest(participatingMember);
expect(User.updateOne).to.be.calledWith( expect(User.update).to.be.calledWith(
{ _id: questLeader._id }, { _id: questLeader._id },
{ {
$inc: { $inc: {
@@ -1819,29 +1818,29 @@ describe('Group Model', () => {
}; };
it('doesn\'t retry successful operations', async () => { it('doesn\'t retry successful operations', async () => {
sandbox.stub(User, 'updateOne').returns(successfulMock); sandbox.stub(User, 'update').returns(successfulMock);
await party.finishQuest(quest); await party.finishQuest(quest);
expect(User.updateOne).to.be.calledThrice; expect(User.update).to.be.calledThrice;
}); });
it('stops retrying when a successful update has occurred', async () => { it('stops retrying when a successful update has occurred', async () => {
const updateStub = sandbox.stub(User, 'updateOne'); const updateStub = sandbox.stub(User, 'update');
updateStub.onCall(0).returns(failedMock); updateStub.onCall(0).returns(failedMock);
updateStub.returns(successfulMock); updateStub.returns(successfulMock);
await party.finishQuest(quest); await party.finishQuest(quest);
expect(User.updateOne.callCount).to.equal(4); expect(User.update.callCount).to.equal(4);
}); });
it('retries failed updates at most five times per user', async () => { it('retries failed updates at most five times per user', async () => {
sandbox.stub(User, 'updateOne').returns(failedMock); sandbox.stub(User, 'update').returns(failedMock);
await expect(party.finishQuest(quest)).to.eventually.be.rejected; await expect(party.finishQuest(quest)).to.eventually.be.rejected;
expect(User.updateOne.callCount).to.eql(15); // for 3 users expect(User.update.callCount).to.eql(15); // for 3 users
}); });
}); });
@@ -2088,17 +2087,17 @@ describe('Group Model', () => {
context('Party quests', () => { context('Party quests', () => {
it('updates participating members with rewards', async () => { it('updates participating members with rewards', async () => {
sandbox.spy(User, 'updateOne'); sandbox.spy(User, 'update');
await party.finishQuest(quest); await party.finishQuest(quest);
expect(User.updateOne).to.be.calledThrice; expect(User.update).to.be.calledThrice;
expect(User.updateOne).to.be.calledWithMatch({ expect(User.update).to.be.calledWithMatch({
_id: questLeader._id, _id: questLeader._id,
}); });
expect(User.updateOne).to.be.calledWithMatch({ expect(User.update).to.be.calledWithMatch({
_id: participatingMember._id, _id: participatingMember._id,
}); });
expect(User.updateOne).to.be.calledWithMatch({ expect(User.update).to.be.calledWithMatch({
_id: sleepingParticipatingMember._id, _id: sleepingParticipatingMember._id,
}); });
}); });
@@ -2173,11 +2172,11 @@ describe('Group Model', () => {
}); });
it('updates all users with rewards', async () => { it('updates all users with rewards', async () => {
sandbox.spy(User, 'updateMany'); sandbox.spy(User, 'update');
await party.finishQuest(tavernQuest); await party.finishQuest(tavernQuest);
expect(User.updateMany).to.be.calledOnce; expect(User.update).to.be.calledOnce;
expect(User.updateMany).to.be.calledWithMatch({}); expect(User.update).to.be.calledWithMatch({});
}); });
it('sets quest completed to the world quest key', async () => { it('sets quest completed to the world quest key', async () => {

View File

@@ -103,7 +103,7 @@ describe('NewsPost Model', () => {
beforeEach(async () => { beforeEach(async () => {
// Delete all existing posts from the database // Delete all existing posts from the database
await NewsPost.deleteMany(); await NewsPost.remove();
}); });
afterEach(() => { afterEach(() => {
@@ -116,7 +116,7 @@ describe('NewsPost Model', () => {
_id: v4(), publishDate: new Date(), published: true, _id: v4(), publishDate: new Date(), published: true,
}; };
NewsPost.updateLastNewsPost(previousPost); NewsPost.updateLastNewsPost(previousPost);
intervalId = refreshNewsPost(100); // refreshes every 100ms intervalId = refreshNewsPost(50); // refreshes every 50ms
await sleep(0.1); // wait 100ms to make sure the new post has a more recent publishDate await sleep(0.1); // wait 100ms to make sure the new post has a more recent publishDate
const newPost = await NewsPost.create({ const newPost = await NewsPost.create({

View File

@@ -221,8 +221,7 @@ describe('Task Model', () => {
it('returns task by alias', async () => { it('returns task by alias', async () => {
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias( const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
[taskWithAlias.alias], [taskWithAlias.alias], user._id,
user._id,
); );
expect(foundTasks[0].text).to.eql(taskWithAlias.text); expect(foundTasks[0].text).to.eql(taskWithAlias.text);
@@ -230,8 +229,7 @@ describe('Task Model', () => {
it('returns multiple tasks', async () => { it('returns multiple tasks', async () => {
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias( const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
[taskWithAlias.alias, secondTask._id], [taskWithAlias.alias, secondTask._id], user._id,
user._id,
); );
expect(foundTasks.length).to.eql(2); expect(foundTasks.length).to.eql(2);
@@ -241,8 +239,7 @@ describe('Task Model', () => {
it('returns a task only once if searched by both id and alias', async () => { it('returns a task only once if searched by both id and alias', async () => {
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias( const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
[taskWithAlias.alias, taskWithAlias._id], [taskWithAlias.alias, taskWithAlias._id], user._id,
user._id,
); );
expect(foundTasks.length).to.eql(1); expect(foundTasks.length).to.eql(1);

View File

@@ -188,7 +188,7 @@ describe('User Model', () => {
it('removes invalid tags when loading the user', async () => { it('removes invalid tags when loading the user', async () => {
let user = new User(); let user = new User();
await user.save(); await user.save();
await user.updateOne({ await user.update({
$set: { $set: {
tags: [ tags: [
null, // invalid, not an object null, // invalid, not an object
@@ -212,7 +212,7 @@ describe('User Model', () => {
it('removes invalid push devices when loading the user', async () => { it('removes invalid push devices when loading the user', async () => {
let user = new User(); let user = new User();
await user.save(); await user.save();
await user.updateOne({ await user.update({
$set: { $set: {
pushDevices: [ pushDevices: [
null, // invalid, not an object null, // invalid, not an object
@@ -236,7 +236,7 @@ describe('User Model', () => {
it('removes duplicate push devices when loading the user', async () => { it('removes duplicate push devices when loading the user', async () => {
let user = new User(); let user = new User();
await user.save(); await user.save();
await user.updateOne({ await user.update({
$set: { $set: {
pushDevices: [ pushDevices: [
{ type: 'android', regId: '1234' }, { type: 'android', regId: '1234' },
@@ -258,7 +258,7 @@ describe('User Model', () => {
it('removes invalid notifications when loading the user', async () => { it('removes invalid notifications when loading the user', async () => {
let user = new User(); let user = new User();
await user.save(); await user.save();
await user.updateOne({ await user.update({
$set: { $set: {
notifications: [ notifications: [
null, // invalid, not an object null, // invalid, not an object
@@ -284,7 +284,7 @@ describe('User Model', () => {
it('removes multiple NEW_CHAT_MESSAGE for the same group', async () => { it('removes multiple NEW_CHAT_MESSAGE for the same group', async () => {
let user = new User(); let user = new User();
await user.save(); await user.save();
await user.updateOne({ await user.update({
$set: { $set: {
notifications: [ notifications: [
{ {

View File

@@ -1,7 +1,7 @@
import { v4 as generateUUID } from 'uuid'; import { v4 as generateUUID } from 'uuid';
import { model as Webhook } from '../../../../website/server/models/webhook'; import { model as Webhook } from '../../../../website/server/models/webhook';
import { BadRequest } from '../../../../website/server/libs/errors'; import { BadRequest } from '../../../../website/server/libs/errors';
import { apiError } from '../../../../website/server/libs/apiError'; import apiError from '../../../../website/server/libs/apiError';
describe('Webhook Model', () => { describe('Webhook Model', () => {
context('Instance Methods', () => { context('Instance Methods', () => {

View File

@@ -87,7 +87,7 @@ describe('DELETE /challenges/:challengeId', () => {
const testTask = _.find(tasks, task => task.text === taskText); const testTask = _.find(tasks, task => task.text === taskText);
expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED'); expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED');
expect(testTask.challenge.winner).to.be.undefined; expect(testTask.challenge.winner).to.be.null;
}); });
}); });
}); });

View File

@@ -16,7 +16,60 @@ describe('GET /challenges/:challengeId', () => {
}); });
}); });
context('Group Plan', () => { context('public guild', () => {
let groupLeader;
let group;
let challenge;
let user;
beforeEach(async () => {
user = await generateUser();
const populatedGroup = await createAndPopulateGroup({
groupDetails: { type: 'guild', privacy: 'public' },
});
groupLeader = populatedGroup.groupLeader;
group = populatedGroup.group;
challenge = await generateChallenge(groupLeader, group);
await groupLeader.post(`/challenges/${challenge._id}/join`);
});
it('should return challenge data', async () => {
await challenge.sync();
const chal = await user.get(`/challenges/${challenge._id}`);
expect(chal.memberCount).to.equal(challenge.memberCount);
expect(chal.name).to.equal(challenge.name);
expect(chal._id).to.equal(challenge._id);
expect(chal.leader).to.eql({
_id: groupLeader._id,
id: groupLeader._id,
profile: { name: groupLeader.profile.name },
auth: {
local: {
username: groupLeader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(chal.group).to.eql({
_id: group._id,
categories: [],
id: group.id,
name: group.name,
summary: group.name,
type: group.type,
privacy: group.privacy,
leader: groupLeader.id,
});
});
});
context('private guild', () => {
let groupLeader; let groupLeader;
let challengeLeader; let challengeLeader;
let group; let group;
@@ -31,14 +84,14 @@ describe('GET /challenges/:challengeId', () => {
const populatedGroup = await createAndPopulateGroup({ const populatedGroup = await createAndPopulateGroup({
groupDetails: { type: 'guild', privacy: 'private' }, groupDetails: { type: 'guild', privacy: 'private' },
members: 2, members: 2,
upgradeToGroupPlan: true,
}); });
groupLeader = populatedGroup.groupLeader; groupLeader = populatedGroup.groupLeader;
group = populatedGroup.group; group = populatedGroup.group;
members = populatedGroup.members; members = populatedGroup.members;
[challengeLeader, otherMember] = members; challengeLeader = members[0]; // eslint-disable-line prefer-destructuring
otherMember = members[1]; // eslint-disable-line prefer-destructuring
challenge = await generateChallenge(challengeLeader, group); challenge = await generateChallenge(challengeLeader, group);
}); });

View File

@@ -74,7 +74,7 @@ describe('GET /challenges/:challengeId/export/csv', () => {
}); });
it('should successfully return when it contains erroneous residue user data', async () => { it('should successfully return when it contains erroneous residue user data', async () => {
await members[0].updateOne({ challenges: [] }); await members[0].update({ challenges: [] });
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`); const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id'); const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
const splitRes = res.split('\n'); const splitRes = res.split('\n');

View File

@@ -71,18 +71,42 @@ describe('GET /challenges/:challengeId/members', () => {
}); });
}); });
it('populates only some fields', async () => { it('works with challenges belonging to public guild', async () => {
const group = await generateGroup(user, { type: 'party', privacy: 'private', name: generateUUID() }); const leader = await generateUser({ balance: 4 });
const challenge = await generateChallenge(user, group); const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
await user.post(`/challenges/${challenge._id}/join`); const challenge = await generateChallenge(leader, group);
await leader.post(`/challenges/${challenge._id}/join`);
const res = await user.get(`/challenges/${challenge._id}/members`); const res = await user.get(`/challenges/${challenge._id}/members`);
expect(res[0]).to.eql({ expect(res[0]).to.eql({
_id: user._id, _id: leader._id,
id: user._id, id: leader._id,
profile: { name: user.profile.name }, profile: { name: leader.profile.name },
auth: { auth: {
local: { local: {
username: user.auth.local.username, username: leader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(res[0].profile).to.have.all.keys(['name']);
});
it('populates only some fields', async () => {
const anotherUser = await generateUser({ balance: 3 });
const group = await generateGroup(anotherUser, { type: 'guild', privacy: 'public', name: generateUUID() });
const challenge = await generateChallenge(anotherUser, group);
await anotherUser.post(`/challenges/${challenge._id}/join`);
const res = await user.get(`/challenges/${challenge._id}/members`);
expect(res[0]).to.eql({
_id: anotherUser._id,
id: anotherUser._id,
profile: { name: anotherUser.profile.name },
auth: {
local: {
username: anotherUser.auth.local.username,
}, },
}, },
flags: { flags: {

View File

@@ -72,6 +72,20 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
}); });
}); });
it('works with challenges belonging to a public guild', async () => {
const groupLeader = await generateUser({ balance: 4 });
const group = await generateGroup(groupLeader, { type: 'guild', privacy: 'public', name: generateUUID() });
const challenge = await generateChallenge(groupLeader, group);
await groupLeader.post(`/challenges/${challenge._id}/join`);
const taskText = 'Test Text';
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{ type: 'habit', text: taskText }]);
const memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`);
expect(memberProgress).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile', 'tasks']);
expect(memberProgress.profile).to.have.all.keys(['name']);
expect(memberProgress.tasks.length).to.equal(1);
});
it('returns the member tasks for the challenges', async () => { it('returns the member tasks for the challenges', async () => {
const group = await generateGroup(user, { type: 'party', name: generateUUID() }); const group = await generateGroup(user, { type: 'party', name: generateUUID() });
const challenge = await generateChallenge(user, group); const challenge = await generateChallenge(user, group);

View File

@@ -7,7 +7,117 @@ import {
import { TAVERN_ID } from '../../../../../website/common/script/constants'; import { TAVERN_ID } from '../../../../../website/common/script/constants';
describe('GET challenges/groups/:groupId', () => { describe('GET challenges/groups/:groupId', () => {
context('Group Plan', () => { context('Public Guild', () => {
let publicGuild; let user; let nonMember; let challenge; let
challenge2;
before(async () => {
const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestGuild',
type: 'guild',
privacy: 'public',
},
});
publicGuild = group;
user = groupLeader;
nonMember = await generateUser();
challenge = await generateChallenge(user, group);
await user.post(`/challenges/${challenge._id}/join`);
challenge2 = await generateChallenge(user, group);
await user.post(`/challenges/${challenge2._id}/join`);
});
it('should return group challenges for non member with populated leader', async () => {
const challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`);
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: { name: user.profile.name },
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: { name: user.profile.name },
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
it('should return group challenges for member with populated leader', async () => {
const challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: { name: user.profile.name },
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: { name: user.profile.name },
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
it('should return newest challenges first', async () => {
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
expect(foundChallengeIndex).to.eql(0);
const newChallenge = await generateChallenge(user, publicGuild);
await user.post(`/challenges/${newChallenge._id}/join`);
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
expect(foundChallengeIndex).to.eql(0);
});
});
context('Private Guild', () => {
let privateGuild; let user; let nonMember; let challenge; let let privateGuild; let user; let nonMember; let challenge; let
challenge2; challenge2;
@@ -18,7 +128,6 @@ describe('GET challenges/groups/:groupId', () => {
type: 'guild', type: 'guild',
privacy: 'private', privacy: 'private',
}, },
upgradeToGroupPlan: true,
}); });
privateGuild = group; privateGuild = group;
@@ -77,6 +186,68 @@ describe('GET challenges/groups/:groupId', () => {
}); });
}); });
context('official challenge is present', () => {
let publicGuild; let user; let officialChallenge; let unofficialChallenges;
before(async () => {
const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestGuild',
type: 'guild',
privacy: 'public',
},
});
user = groupLeader;
publicGuild = group;
await user.update({
'permissions.challengeAdmin': true,
});
officialChallenge = await generateChallenge(user, group, {
categories: [{
name: 'habitica_official',
slug: 'habitica_official',
}],
});
await user.post(`/challenges/${officialChallenge._id}/join`);
// We add 10 extra challenges to test whether the official challenge
// (the oldest) makes it to the front page.
unofficialChallenges = [];
for (let i = 0; i < 10; i += 1) {
const challenge = await generateChallenge(user, group); // eslint-disable-line
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
unofficialChallenges.push(challenge);
}
});
it('should return official challenges first', async () => {
const challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
expect(foundChallengeIndex).to.eql(0);
});
it('should return newest challenges first, after official ones', async () => {
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
unofficialChallenges.forEach((chal, index) => {
const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id });
expect(foundChallengeIndex).to.eql(10 - index);
});
const newChallenge = await generateChallenge(user, publicGuild);
await user.post(`/challenges/${newChallenge._id}/join`);
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
expect(foundChallengeIndex).to.eql(1);
});
});
context('Party', () => { context('Party', () => {
let party; let user; let nonMember; let challenge; let let party; let user; let nonMember; let challenge; let
challenge2; challenge2;
@@ -186,7 +357,7 @@ describe('GET challenges/groups/:groupId', () => {
before(async () => { before(async () => {
user = await generateUser(); user = await generateUser();
await user.updateOne({ balance: 0.5 }); await user.update({ balance: 0.5 });
tavern = await user.get(`/groups/${TAVERN_ID}`); tavern = await user.get(`/groups/${TAVERN_ID}`);
challenge = await generateChallenge(user, tavern, { prize: 1 }); challenge = await generateChallenge(user, tavern, { prize: 1 });
@@ -230,7 +401,7 @@ describe('GET challenges/groups/:groupId', () => {
}); });
}); });
it('should return tavern challenges using ID "habitrpg"', async () => { it('should return tavern challenges using ID "habitrpg', async () => {
const challenges = await user.get('/challenges/groups/habitrpg'); const challenges = await user.get('/challenges/groups/habitrpg');
const foundChallenge1 = _.find(challenges, { _id: challenge._id }); const foundChallenge1 = _.find(challenges, { _id: challenge._id });
@@ -264,58 +435,5 @@ describe('GET challenges/groups/:groupId', () => {
}, },
}); });
}); });
context('official challenge is present', () => {
let officialChallenge; let unofficialChallenges;
before(async () => {
await user.updateOne({
'permissions.challengeAdmin': true,
balance: 3,
});
officialChallenge = await generateChallenge(user, tavern, {
categories: [{
name: 'habitica_official',
slug: 'habitica_official',
}],
prize: 1,
});
await user.post(`/challenges/${officialChallenge._id}/join`);
// We add 10 extra challenges to test whether the official challenge
// (the oldest) makes it to the front page.
unofficialChallenges = [];
for (let i = 0; i < 10; i += 1) {
const challenge = await generateChallenge(user, tavern, { prize: 1 }); // eslint-disable-line
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
unofficialChallenges.push(challenge);
}
});
it('should return official challenges first', async () => {
const challenges = await user.get('/challenges/groups/habitrpg');
const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
expect(foundChallengeIndex).to.eql(0);
});
it('should return newest challenges first, after official ones', async () => {
let challenges = await user.get('/challenges/groups/habitrpg');
unofficialChallenges.forEach((chal, index) => {
const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id });
expect(foundChallengeIndex).to.eql(10 - index);
});
const newChallenge = await generateChallenge(user, tavern, { prize: 1 });
await user.post(`/challenges/${newChallenge._id}/join`);
challenges = await user.get('/challenges/groups/habitrpg');
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
expect(foundChallengeIndex).to.eql(1);
});
});
}); });
}); });

View File

@@ -2,44 +2,39 @@ import {
generateUser, generateUser,
generateChallenge, generateChallenge,
createAndPopulateGroup, createAndPopulateGroup,
resetHabiticaDB,
} from '../../../../helpers/api-integration/v3'; } from '../../../../helpers/api-integration/v3';
import { TAVERN_ID } from '../../../../../website/common/script/constants';
describe('GET challenges/user', () => { describe('GET challenges/user', () => {
context('no official challenges', () => { context('no official challenges', () => {
let user; let member; let nonMember; let challenge; let challenge2; let publicChallenge; let user; let member; let nonMember; let challenge; let challenge2;
let groupPlan; let userData; let groupData; let tavern; let tavernData; let publicGuild; let userData; let groupData;
before(async () => { before(async () => {
await resetHabiticaDB();
const { group, groupLeader, members } = await createAndPopulateGroup({ const { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: { groupDetails: {
name: 'TestGuild', name: 'TestGuild',
type: 'guild', type: 'guild',
privacy: 'private', privacy: 'public',
}, },
members: 1, members: 1,
upgradeToGroupPlan: true,
}); });
groupPlan = group; publicGuild = group;
groupData = { groupData = {
_id: groupPlan._id, _id: publicGuild._id,
categories: [], categories: [],
id: groupPlan._id, id: publicGuild._id,
type: groupPlan.type, type: publicGuild.type,
privacy: groupPlan.privacy, privacy: publicGuild.privacy,
name: groupPlan.name, name: publicGuild.name,
summary: groupPlan.name, summary: publicGuild.name,
leader: groupPlan.leader._id, leader: publicGuild.leader._id,
}; };
user = groupLeader; user = groupLeader;
userData = { userData = {
_id: groupPlan.leader._id, _id: publicGuild.leader._id,
id: groupPlan.leader._id, id: publicGuild.leader._id,
profile: { name: user.profile.name }, profile: { name: user.profile.name },
auth: { auth: {
local: { local: {
@@ -51,31 +46,17 @@ describe('GET challenges/user', () => {
}, },
}; };
tavern = await user.get(`/groups/${TAVERN_ID}`);
tavernData = {
_id: TAVERN_ID,
categories: [],
id: TAVERN_ID,
type: tavern.type,
privacy: tavern.privacy,
name: tavern.name,
summary: tavern.name,
leader: tavern.leader._id,
};
member = members[0]; // eslint-disable-line prefer-destructuring member = members[0]; // eslint-disable-line prefer-destructuring
nonMember = await generateUser(); nonMember = await generateUser();
challenge = await generateChallenge(user, group); challenge = await generateChallenge(user, group);
challenge2 = await generateChallenge(user, group); challenge2 = await generateChallenge(user, group);
await user.updateOne({ balance: 0.25 });
publicChallenge = await generateChallenge(user, tavern, { prize: 1 });
await member.post(`/challenges/${challenge._id}/join`); await nonMember.post(`/challenges/${challenge._id}/join`);
}); });
context('all challenges', () => { context('all challenges', () => {
it('should return challenges user has joined', async () => { it('should return challenges user has joined', async () => {
const challenges = await member.get('/challenges/user?page=0'); const challenges = await nonMember.get('/challenges/user?page=0');
const foundChallenge = _.find(challenges, { _id: challenge._id }); const foundChallenge = _.find(challenges, { _id: challenge._id });
expect(foundChallenge).to.exist; expect(foundChallenge).to.exist;
@@ -83,13 +64,11 @@ describe('GET challenges/user', () => {
expect(foundChallenge.group).to.eql(groupData); expect(foundChallenge.group).to.eql(groupData);
}); });
it('should return public challenges', async () => { it('should not return challenges a non-member has not joined', async () => {
const challenges = await nonMember.get('/challenges/user?page=0'); const challenges = await nonMember.get('/challenges/user?page=0');
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id }); const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundPublicChallenge).to.exist; expect(foundChallenge2).to.not.exist;
expect(foundPublicChallenge.leader).to.eql(userData);
expect(foundPublicChallenge.group).to.eql(tavernData);
}); });
it('should return challenges user has created', async () => { it('should return challenges user has created', async () => {
@@ -121,10 +100,10 @@ describe('GET challenges/user', () => {
it('should return newest challenges first', async () => { it('should return newest challenges first', async () => {
let challenges = await user.get('/challenges/user?page=0'); let challenges = await user.get('/challenges/user?page=0');
let foundChallengeIndex = _.findIndex(challenges, { _id: publicChallenge._id }); let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
expect(foundChallengeIndex).to.eql(0); expect(foundChallengeIndex).to.eql(0);
const newChallenge = await generateChallenge(user, groupPlan); const newChallenge = await generateChallenge(user, publicGuild);
await user.post(`/challenges/${newChallenge._id}/join`); await user.post(`/challenges/${newChallenge._id}/join`);
challenges = await user.get('/challenges/user?page=0'); challenges = await user.get('/challenges/user?page=0');
@@ -134,23 +113,52 @@ describe('GET challenges/user', () => {
}); });
it('should not return challenges user doesn\'t have access to', async () => { it('should not return challenges user doesn\'t have access to', async () => {
const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestPrivateGuild',
summary: 'summary for TestPrivateGuild',
type: 'guild',
privacy: 'private',
},
});
const privateChallenge = await generateChallenge(groupLeader, group);
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
const challenges = await nonMember.get('/challenges/user?page=0'); const challenges = await nonMember.get('/challenges/user?page=0');
const foundChallenge = _.find(challenges, { _id: challenge._id }); const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
expect(foundChallenge).to.not.exist; expect(foundChallenge).to.not.exist;
}); });
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => { it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestPrivateGuild',
summary: 'summary for TestPrivateGuild',
type: 'guild',
privacy: 'private',
},
});
const privateChallenge = await generateChallenge(groupLeader, group, {
categories: [{
name: 'academics',
slug: 'academics',
}],
});
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
const challenges = await nonMember.get('/challenges/user?page=0&categories=academics&owned=not_owned'); const challenges = await nonMember.get('/challenges/user?page=0&categories=academics&owned=not_owned');
const foundChallenge = _.find(challenges, { _id: challenge._id }); const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
expect(foundChallenge).to.not.exist; expect(foundChallenge).to.not.exist;
}); });
}); });
context('my challenges', () => { context('my challenges', () => {
it('should return challenges user has joined', async () => { it('should return challenges user has joined', async () => {
const challenges = await member.get(`/challenges/user?page=0&member=${true}`); const challenges = await nonMember.get(`/challenges/user?page=0&member=${true}`);
const foundChallenge = _.find(challenges, { _id: challenge._id }); const foundChallenge = _.find(challenges, { _id: challenge._id });
expect(foundChallenge).to.exist; expect(foundChallenge).to.exist;
@@ -169,10 +177,6 @@ describe('GET challenges/user', () => {
expect(foundChallenge2).to.exist; expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql(userData); expect(foundChallenge2.leader).to.eql(userData);
expect(foundChallenge2.group).to.eql(groupData); expect(foundChallenge2.group).to.eql(groupData);
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
expect(foundPublicChallenge).to.exist;
expect(foundPublicChallenge.leader).to.eql(userData);
expect(foundPublicChallenge.group).to.eql(tavernData);
}); });
it('should return challenges user has created if filter by owned', async () => { it('should return challenges user has created if filter by owned', async () => {
@@ -186,10 +190,6 @@ describe('GET challenges/user', () => {
expect(foundChallenge2).to.exist; expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql(userData); expect(foundChallenge2.leader).to.eql(userData);
expect(foundChallenge2.group).to.eql(groupData); expect(foundChallenge2.group).to.eql(groupData);
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
expect(foundPublicChallenge).to.exist;
expect(foundPublicChallenge.leader).to.eql(userData);
expect(foundPublicChallenge.group).to.eql(tavernData);
}); });
it('should not return challenges user has created if filter by not owned', async () => { it('should not return challenges user has created if filter by not owned', async () => {
@@ -199,42 +199,38 @@ describe('GET challenges/user', () => {
expect(foundChallenge1).to.not.exist; expect(foundChallenge1).to.not.exist;
const foundChallenge2 = _.find(challenges, { _id: challenge2._id }); const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.not.exist; expect(foundChallenge2).to.not.exist;
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
expect(foundPublicChallenge).to.not.exist;
}); });
it('should not return challenges in user groups', async () => { it('should not return challenges in user groups', async () => {
const challenges = await member.get(`/challenges/user?page=0&member=${true}`); const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.not.exist;
const foundChallenge2 = _.find(challenges, { _id: challenge2._id }); const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.not.exist; expect(foundChallenge2).to.not.exist;
}); });
it('should not return public challenges', async () => {
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
expect(foundPublicChallenge).to.not.exist;
});
}); });
}); });
context('official challenge is present', () => { context('official challenge is present', () => {
let user; let officialChallenge; let unofficialChallenges; let let user; let officialChallenge; let unofficialChallenges; let
group; publicGuild;
before(async () => { before(async () => {
({ group, groupLeader: user } = await createAndPopulateGroup({ const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: { groupDetails: {
name: 'TestGuild', name: 'TestGuild',
summary: 'summary for TestGuild', summary: 'summary for TestGuild',
type: 'guild', type: 'guild',
privacy: 'private', privacy: 'public',
}, },
upgradeToGroupPlan: true, });
}));
await user.updateOne({ user = groupLeader;
publicGuild = group;
await user.update({
'permissions.challengeAdmin': true, 'permissions.challengeAdmin': true,
}); });
@@ -275,7 +271,7 @@ describe('GET challenges/user', () => {
} }
}); });
const newChallenge = await generateChallenge(user, group); const newChallenge = await generateChallenge(user, publicGuild);
await user.post(`/challenges/${newChallenge._id}/join`); await user.post(`/challenges/${newChallenge._id}/join`);
challenges = await user.get('/challenges/user?page=0'); challenges = await user.get('/challenges/user?page=0');
@@ -298,17 +294,16 @@ describe('GET challenges/user', () => {
groupDetails: { groupDetails: {
name: 'TestGuild', name: 'TestGuild',
type: 'guild', type: 'guild',
privacy: 'private', privacy: 'public',
}, },
members: 1, members: 1,
upgradeToGroupPlan: true,
}); });
user = groupLeader; user = groupLeader;
guild = group; guild = group;
member = members[0]; // eslint-disable-line prefer-destructuring member = members[0]; // eslint-disable-line prefer-destructuring
await user.updateOne({ balance: 20 }); await user.update({ balance: 20 });
for (let i = 0; i < 11; i += 1) { for (let i = 0; i < 11; i += 1) {
let challenge = await generateChallenge(user, group); // eslint-disable-line let challenge = await generateChallenge(user, group); // eslint-disable-line

View File

@@ -1,79 +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 challengeGroup;
let challenge;
beforeEach(async () => {
const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestParty',
type: 'party',
privacy: 'private',
},
members: 1,
});
user = groupLeader;
challengeGroup = group;
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'),
});
});
it('returns an error when user tries to flag an official challenge', async () => {
await user.updateOne({
permissions: {
challengeAdmin: true,
},
});
challenge = await generateChallenge(user, challengeGroup, { official: true });
await expect(user.post(`/challenges/${challenge._id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageChallengeFlagOfficial'),
});
});
});

View File

@@ -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);
});
});

View File

@@ -42,7 +42,26 @@ describe('POST /challenges', () => {
}); });
}); });
it('returns error when creating a challenge with summary with greater than MAX_SUMMARY_SIZE_FOR_CHALLENGES characters', async () => { it('returns error when creating a challenge in a public guild and you are not a member of it', async () => {
const user = await generateUser();
const { group } = await createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
});
await expect(user.post('/challenges', {
group: group._id,
prize: 4,
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('mustBeGroupMember'),
});
});
it('return error when creating a challenge with summary with greater than MAX_SUMMARY_SIZE_FOR_CHALLENGES characters', async () => {
const user = await generateUser(); const user = await generateUser();
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1); const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1);
const group = createAndPopulateGroup({ const group = createAndPopulateGroup({
@@ -58,7 +77,7 @@ describe('POST /challenges', () => {
}); });
}); });
context('creating a Challenge for a Group Plan', () => { context('Creating a challenge for a valid group', () => {
let groupLeader; let groupLeader;
let group; let group;
let groupMember; let groupMember;
@@ -75,11 +94,9 @@ describe('POST /challenges', () => {
challenges: true, challenges: true,
}, },
}, },
upgradeToGroupPlan: true,
}); });
groupLeader = await populatedGroup.groupLeader.sync(); groupLeader = await populatedGroup.groupLeader.sync();
await groupLeader.updateOne({ permissions: {} });
group = populatedGroup.group; group = populatedGroup.group;
groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring
}); });
@@ -177,7 +194,7 @@ describe('POST /challenges', () => {
const oldUserBalance = groupLeader.balance; const oldUserBalance = groupLeader.balance;
const prize = 8; const prize = 8;
await group.updateOne({ balance: 0 }); await group.update({ balance: 0 });
await groupLeader.post('/challenges', { await groupLeader.post('/challenges', {
group: group._id, group: group._id,
name: 'Test Challenge', name: 'Test Challenge',
@@ -202,7 +219,7 @@ describe('POST /challenges', () => {
}); });
it('sets challenge as official if created by admin and official flag is set', async () => { it('sets challenge as official if created by admin and official flag is set', async () => {
await groupLeader.updateOne({ await groupLeader.update({
permissions: { permissions: {
challengeAdmin: true, challengeAdmin: true,
}, },
@@ -331,71 +348,5 @@ describe('POST /challenges', () => {
expect(updatedChallenge.summary).to.eql(summary); expect(updatedChallenge.summary).to.eql(summary);
}); });
it('sets categories for challenges', async () => {
const testCategory = { _id: '65c1172997c0b24600371ea9', slug: 'test', name: 'Test' };
const challenge = await groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
categories: [testCategory],
});
const updatedChallenge = await groupLeader.get(`/challenges/${challenge._id}`);
expect(updatedChallenge.categories).to.eql([testCategory]);
});
it('does not set habitica_official category for non-admins', async () => {
const testCategory = { _id: '65c1172997c0b24600371ea9', slug: 'habitica_official', name: 'habitica_official' };
await expect(groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
categories: [testCategory],
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('noPrivAccess'),
});
});
it('sets habitica_official category for admins', async () => {
await groupLeader.updateOne({
permissions: {
challengeAdmin: true,
},
});
const testCategory = { _id: '65c1172997c0b24600371ea9', slug: 'habitica_official', name: 'habitica_official' };
const challenge = await groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
categories: [testCategory],
});
const updatedChallenge = await groupLeader.get(`/challenges/${challenge._id}`);
expect(updatedChallenge.categories).to.eql([testCategory]);
});
it('sets official if the habitica_official category is set for admins', async () => {
await groupLeader.updateOne({
permissions: {
challengeAdmin: true,
},
});
const testCategory = { _id: '65c1172997c0b24600371ea9', slug: 'habitica_official', name: 'habitica_official' };
const challenge = await groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
categories: [testCategory],
});
const updatedChallenge = await groupLeader.get(`/challenges/${challenge._id}`);
expect(updatedChallenge.official).to.eql(true);
});
}); });
}); });

View File

@@ -128,10 +128,10 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
const oldBalance = winningUser.balance; const oldBalance = winningUser.balance;
const oldLeaderBalance = (await groupLeader.sync()).balance; const oldLeaderBalance = (await groupLeader.sync()).balance;
await winningUser.updateOne({ await winningUser.update({
'purchased.plan.customerId': 'group-plan', 'purchased.plan.customerId': 'group-plan',
}); });
await group.updateOne({ await group.update({
'leaderOnly.getGems': true, 'leaderOnly.getGems': true,
'purchased.plan.customerId': 123, 'purchased.plan.customerId': 123,
}); });

View File

@@ -18,7 +18,6 @@ describe('PUT /challenges/:challengeId', () => {
privacy: 'private', privacy: 'private',
}, },
members: 1, members: 1,
upgradeToGroupPlan: true,
}); });
privateGuild = group; privateGuild = group;

View File

@@ -1,6 +1,7 @@
import { v4 as generateUUID } from 'uuid'; import { v4 as generateUUID } from 'uuid';
import { import {
createAndPopulateGroup, createAndPopulateGroup,
generateUser,
translate as t, translate as t,
} from '../../../../helpers/api-integration/v3'; } from '../../../../helpers/api-integration/v3';
@@ -9,30 +10,27 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
admin; admin;
before(async () => { before(async () => {
const { group, groupLeader, members } = await createAndPopulateGroup({ const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: { groupDetails: {
type: 'guild', type: 'guild',
privacy: 'private', privacy: 'public',
}, },
leaderDetails: { leaderDetails: {
'auth.timestamps.created': new Date('2022-01-01'), 'auth.timestamps.created': new Date('2022-01-01'),
balance: 10, balance: 10,
}, },
members: 2,
upgradeToGroupPlan: true,
}); });
groupWithChat = group; groupWithChat = group;
user = groupLeader; user = groupLeader;
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' }); message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
message = message.message; message = message.message;
userThatDidNotCreateChat = members[0]; // eslint-disable-line prefer-destructuring userThatDidNotCreateChat = await generateUser();
admin = members[1]; // eslint-disable-line prefer-destructuring admin = await generateUser({ 'permissions.moderator': true });
await admin.updateOne({ permissions: { moderator: true } });
}); });
context('Chat errors', () => { context('Chat errors', () => {
it('returns an error if message does not exist', async () => { it('returns an error is message does not exist', async () => {
const fakeChatId = generateUUID(); const fakeChatId = generateUUID();
await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({ await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
code: 404, code: 404,
@@ -58,7 +56,7 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
nextMessage = nextMessage.message; nextMessage = nextMessage.message;
}); });
it('allows creator to delete their message', async () => { it('allows creator to delete a their message', async () => {
await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`); await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`); const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);

View File

@@ -1,6 +1,6 @@
import { import {
generateUser, generateUser,
createAndPopulateGroup, generateGroup,
translate as t, translate as t,
} from '../../../../helpers/api-integration/v3'; } from '../../../../helpers/api-integration/v3';
@@ -11,22 +11,48 @@ describe('GET /groups/:groupId/chat', () => {
user = await generateUser(); user = await generateUser();
}); });
context('public Guild', () => {
let group;
before(async () => {
const leader = await generateUser({ balance: 2 });
group = await generateGroup(leader, {
name: 'test group',
type: 'guild',
privacy: 'public',
}, {
chat: [
{ text: 'Hello', flags: {}, id: 1 },
{ text: 'Welcome to the Guild', flags: {}, id: 2 },
],
});
});
it('returns Guild chat', async () => {
const chat = await user.get(`/groups/${group._id}/chat`);
expect(chat[0].id).to.eql(group.chat[0].id);
expect(chat[1].id).to.eql(group.chat[1].id);
});
});
context('private Guild', () => { context('private Guild', () => {
let group; let group;
before(async () => { before(async () => {
({ group } = await createAndPopulateGroup({ const leader = await generateUser({ balance: 2 });
groupDetails: {
name: 'test group', group = await generateGroup(leader, {
type: 'guild', name: 'test group',
privacy: 'private', type: 'guild',
}, privacy: 'private',
members: 1, }, {
upgradeToGroupPlan: true,
chat: [ chat: [
'Hello', 'Hello',
'Welcome to the Guild', 'Welcome to the Guild',
], ],
})); });
}); });
it('returns error if user is not member of requested private group', async () => { it('returns error if user is not member of requested private group', async () => {

View File

@@ -1,42 +1,32 @@
import find from 'lodash/find'; import { find } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import nconf from 'nconf';
import { IncomingWebhook } from '@slack/webhook'; import { IncomingWebhook } from '@slack/webhook';
import { import {
createAndPopulateGroup, generateUser,
translate as t, translate as t,
} from '../../../../helpers/api-integration/v3'; } from '../../../../helpers/api-integration/v3';
const BASE_URL = nconf.get('BASE_URL');
describe('POST /chat/:chatId/flag', () => { describe('POST /chat/:chatId/flag', () => {
let user; let admin; let anotherUser; let newUser; let let user; let admin; let anotherUser; let newUser; let
group; let members; let userToDelete; group;
const TEST_MESSAGE = 'Test Message'; const TEST_MESSAGE = 'Test Message';
const USER_AGE_FOR_FLAGGING = 3; const USER_AGE_FOR_FLAGGING = 3;
beforeEach(async () => { beforeEach(async () => {
({ group, groupLeader: user, members } = await createAndPopulateGroup({ user = await generateUser({ balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
groupDetails: { admin = await generateUser({ balance: 1, 'permissions.moderator': true });
name: 'Test Guild', anotherUser = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
type: 'guild', newUser = await generateUser({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
privacy: 'private',
},
leaderDetails: {
'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate(),
},
members: 4,
upgradeToGroupPlan: true,
}));
[admin, anotherUser, newUser, userToDelete] = members;
await user.updateOne({ permissions: {} });
await admin.updateOne({ permissions: { moderator: true } });
await anotherUser.updateOne({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
await newUser.updateOne({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
await userToDelete.updateOne({
'auth.timestamps.created': moment().subtract(1, 'days').toDate(),
'purchased.plan.dateTerminated': moment().subtract(1, 'minutes').toDate(),
});
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve()); sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
group = await user.post('/groups', {
name: 'Test Guild',
type: 'guild',
privacy: 'public',
});
}); });
afterEach(() => { afterEach(() => {
@@ -79,8 +69,8 @@ describe('POST /chat/:chatId/flag', () => {
fallback: 'Flag Message', fallback: 'Flag Message',
color: 'danger', color: 'danger',
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`, author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
title: 'Flag in Test Guild - (private guild)', title: 'Flag in Test Guild',
title_link: undefined, title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE, text: TEST_MESSAGE,
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`, footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
mrkdwn_in: [ mrkdwn_in: [
@@ -88,7 +78,7 @@ describe('POST /chat/:chatId/flag', () => {
], ],
}], }],
}); });
/* eslint-enable camelcase */ /* eslint-ensable camelcase */
}); });
it('Does not increment message flag count and sends different message to moderator Slack when user is new', async () => { it('Does not increment message flag count and sends different message to moderator Slack when user is new', async () => {
@@ -114,8 +104,8 @@ describe('POST /chat/:chatId/flag', () => {
fallback: 'Flag Message', fallback: 'Flag Message',
color: 'danger', color: 'danger',
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`, author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
title: 'Flag in Test Guild - (private guild)', title: 'Flag in Test Guild',
title_link: undefined, title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE, text: TEST_MESSAGE,
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`, footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
mrkdwn_in: [ mrkdwn_in: [
@@ -123,12 +113,15 @@ describe('POST /chat/:chatId/flag', () => {
], ],
}], }],
}); });
/* eslint-enable camelcase */ /* eslint-ensable camelcase */
}); });
it('Flags a chat when the author\'s account was deleted', async () => { it('Flags a chat when the author\'s account was deleted', async () => {
const { message } = await userToDelete.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE }); const deletedUser = await generateUser({
await userToDelete.del('/user', { 'auth.timestamps.created': new Date('2022-01-01'),
});
const { message } = await deletedUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
await deletedUser.del('/user', {
password: 'password', password: 'password',
}); });

View File

@@ -6,28 +6,28 @@ import {
describe('POST /chat/:chatId/like', () => { describe('POST /chat/:chatId/like', () => {
let user; let user;
let anotherUser;
let groupWithChat; let groupWithChat;
let members;
const testMessage = 'Test Message'; const testMessage = 'Test Message';
let anotherUser;
before(async () => { before(async () => {
({ group: groupWithChat, groupLeader: user, members } = await createAndPopulateGroup({ const { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: { groupDetails: {
name: 'Test Guild', name: 'Test Guild',
type: 'guild', type: 'guild',
privacy: 'private', privacy: 'public',
}, },
members: 1, members: 1,
leaderDetails: { leaderDetails: {
'auth.timestamps.created': new Date('2022-01-01'), 'auth.timestamps.created': new Date('2022-01-01'),
balance: 10, balance: 10,
}, },
upgradeToGroupPlan: true, });
}));
[anotherUser] = members; user = groupLeader;
await anotherUser.updateOne({ 'auth.timestamps.created': new Date('2022-01-01') }); groupWithChat = group;
anotherUser = members[0]; // eslint-disable-line prefer-destructuring
await anotherUser.update({ 'auth.timestamps.created': new Date('2022-01-01') });
}); });
it('Returns an error when chat message is not found', async () => { it('Returns an error when chat message is not found', async () => {
@@ -39,21 +39,19 @@ describe('POST /chat/:chatId/like', () => {
}); });
}); });
it('Likes a chat', async () => { it('Returns an error when user tries to like their own message', async () => {
const message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
const likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`); await expect(user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`))
.to.eventually.be.rejected.and.eql({
expect(likeResult.likes[user._id]).to.equal(true); code: 404,
error: 'NotFound',
const groupWithChatLikes = await user.get(`/groups/${groupWithChat._id}`); message: t('messageGroupChatLikeOwnMessage'),
});
const messageToCheck = find(groupWithChatLikes.chat, { id: message.message.id });
expect(messageToCheck.likes[user._id]).to.equal(true);
}); });
it('Allows to likes their own chat message', async () => { it('Likes a chat', async () => {
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); const message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
const likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`); const likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`);

Some files were not shown because too many files have changed in this diff Show More