mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-11 19:19:43 -05:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c80cd83b18 |
+2
-1
@@ -1 +1,2 @@
|
||||
https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
https://github.com/stomita/heroku-buildpack-phantomjs.git
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
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
|
||||
+12
-36
@@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -22,13 +22,12 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run lint-no-fix
|
||||
apidoc:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -43,13 +42,12 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run apidoc
|
||||
sanity:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -64,14 +62,13 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:sanity
|
||||
|
||||
common:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -86,13 +83,12 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:common
|
||||
content:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -107,15 +103,13 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:content
|
||||
|
||||
api-unit:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
mongodb-version: [4.2]
|
||||
node-version: [12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -124,18 +118,13 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.3.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- run: sudo docker run --name mongo -d -p 27017:27017 mongo
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:api:unit
|
||||
env:
|
||||
REQUIRES_SERVER=true: true
|
||||
@@ -143,8 +132,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
mongodb-version: [4.2]
|
||||
node-version: [12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -153,18 +141,13 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.3.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- run: sudo docker run --name mongo -d -p 27017:27017 mongo
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:api-v3:integration
|
||||
env:
|
||||
REQUIRES_SERVER=true: true
|
||||
@@ -172,8 +155,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
mongodb-version: [4.2]
|
||||
node-version: [12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -182,18 +164,13 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.3.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- run: sudo docker run --name mongo -d -p 27017:27017 mongo
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:api-v4:integration
|
||||
env:
|
||||
REQUIRES_SERVER=true: true
|
||||
@@ -202,7 +179,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -217,6 +194,5 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:unit
|
||||
working-directory: ./website/client
|
||||
@@ -38,12 +38,7 @@ yarn.lock
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
!.elasticbeanstalk/*.global.yml
|
||||
|
||||
/.vscode
|
||||
|
||||
# webstorm fake webpack for path intellisense
|
||||
webpack.webstorm.config
|
||||
|
||||
# mongodb replica set for local dev
|
||||
mongodb-*.tgz
|
||||
/mongodb-data
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:14
|
||||
FROM node:12
|
||||
|
||||
ENV ADMIN_EMAIL admin@habitica.com
|
||||
ENV EMAILS_COMMUNITY_MANAGER_EMAIL admin@habitica.com
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:14
|
||||
FROM node:12
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"name": "Habitica V3 API Documentation",
|
||||
"title": "Habitica",
|
||||
"url": "https://habitica.com",
|
||||
"header": {
|
||||
"title": "Introduction",
|
||||
"filename": "apidoc/header.md"
|
||||
},
|
||||
"template": {
|
||||
"withCompare": false
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
This webpage includes the documentation for version 3 of the [Habitica](https://habitica.com) API.
|
||||
|
||||
If you're developing a 3rd party tool that uses the Habitica API you should read the [Guidance for Comrades](https://habitica.fandom.com/wiki/Guidance_for_Comrades) and in particular the section called [Rules for Third-Party Tools](https://habitica.fandom.com/wiki/Guidance_for_Comrades#Rules_for_Third-Party_Tools) which includes suggestions on how to best use the API and the rules to follow when interacting with it.
|
||||
|
||||
# Attention: Sample forms are non-functional
|
||||
The sample forms were enabled [due to a change in the apidoc package](https://github.com/apidoc/apidoc/pull/915) and do not function, they will be disabled in a future release.
|
||||
+3
-8
@@ -32,8 +32,7 @@
|
||||
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
||||
"LOGGLY_TOKEN": "example-token",
|
||||
"MAINTENANCE_MODE": "false",
|
||||
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
|
||||
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
|
||||
"NODE_DB_URI": "mongodb://localhost:27017/habitrpg",
|
||||
"MONGODB_POOL_SIZE": "10",
|
||||
"NODE_ENV": "development",
|
||||
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
||||
@@ -71,7 +70,7 @@
|
||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||
"STRIPE_WEBHOOKS_ENDPOINT_SECRET": "111111",
|
||||
"TEST_DB_URI": "mongodb://localhost:27017/habitrpg_test",
|
||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
||||
"WEB_CONCURRENCY": 1,
|
||||
"SKIP_SSL_CHECK_KEY": "key",
|
||||
@@ -81,9 +80,5 @@
|
||||
"APPLE_AUTH_CLIENT_ID": "",
|
||||
"APPLE_AUTH_KEY_ID": "",
|
||||
"BLOCKED_IPS": "",
|
||||
"LOG_AMPLITUDE_EVENTS": "false",
|
||||
"RATE_LIMITER_ENABLED": "false",
|
||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||
"REDIS_PORT": "1234",
|
||||
"REDIS_PASSWORD": "12345678"
|
||||
"LOG_AMPLITUDE_EVENTS": "false"
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import gulp from 'gulp';
|
||||
import path from 'path';
|
||||
import babel from 'gulp-babel';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import spawn from 'cross-spawn'; // eslint-disable-line import/no-extraneous-dependencies
|
||||
import clean from 'rimraf';
|
||||
|
||||
gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js')
|
||||
.pipe(babel())
|
||||
@@ -31,67 +24,10 @@ gulp.task('build:prod', gulp.series(
|
||||
done => done(),
|
||||
));
|
||||
|
||||
// Due to this issue https://github.com/vkarpov15/run-rs/issues/45
|
||||
// When used on windows `run-rs` must first be run without the `--keep` option
|
||||
// in order to be setup correctly, afterwards it can be used.
|
||||
|
||||
const MONGO_PATH = path.join(__dirname, '/../mongodb-data/');
|
||||
|
||||
gulp.task('build:prepare-mongo', async () => {
|
||||
if (fs.existsSync(MONGO_PATH)) {
|
||||
// console.log('MongoDB data folder exists, skipping setup.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (os.platform() !== 'win32') {
|
||||
// console.log('Not on Windows, skipping MongoDB setup.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
|
||||
|
||||
// use run-rs without --keep, kill it as soon as the replica set starts
|
||||
const runRsProcess = spawn('run-rs', ['-v', '4.2.8', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
|
||||
|
||||
for await (const chunk of runRsProcess.stdout) {
|
||||
const stringChunk = chunk.toString();
|
||||
console.log(stringChunk); // eslint-disable-line no-console
|
||||
// kills the process after the replica set is setup
|
||||
if (stringChunk.includes('Started replica set')) {
|
||||
console.log('MongoDB setup correctly.'); // eslint-disable-line no-console
|
||||
runRsProcess.kill();
|
||||
}
|
||||
}
|
||||
|
||||
let error = '';
|
||||
for await (const chunk of runRsProcess.stderr) {
|
||||
const stringChunk = chunk.toString();
|
||||
error += stringChunk;
|
||||
}
|
||||
|
||||
const exitCode = await new Promise(resolve => {
|
||||
runRsProcess.on('close', resolve);
|
||||
});
|
||||
|
||||
if (exitCode || error.length > 0) {
|
||||
// remove any leftover files
|
||||
clean.sync(MONGO_PATH);
|
||||
|
||||
throw new Error(`Error running run-rs: ${error}`);
|
||||
}
|
||||
});
|
||||
|
||||
gulp.task('build:dev', gulp.series(
|
||||
'build:prepare-mongo',
|
||||
done => done(),
|
||||
));
|
||||
|
||||
const buildArgs = [];
|
||||
|
||||
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
|
||||
buildArgs.push('build:prod');
|
||||
} else if (process.env.NODE_ENV !== 'test') { // eslint-disable-line no-process-env
|
||||
buildArgs.push('build:dev');
|
||||
}
|
||||
|
||||
gulp.task('build', gulp.series(buildArgs, done => {
|
||||
|
||||
+6
-11
@@ -3,10 +3,6 @@ import nconf from 'nconf';
|
||||
import repl from 'repl';
|
||||
import gulp from 'gulp';
|
||||
import logger from '../website/server/libs/logger';
|
||||
import {
|
||||
getDevelopmentConnectionUrl,
|
||||
getDefaultConnectionOptions,
|
||||
} from '../website/server/libs/mongodb';
|
||||
|
||||
// Add additional properties to the repl's context
|
||||
const improveRepl = context => {
|
||||
@@ -30,14 +26,13 @@ const improveRepl = context => {
|
||||
context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require
|
||||
context.User = require('../website/server/models/user').model; // eslint-disable-line global-require
|
||||
|
||||
const IS_PROD = nconf.get('NODE_ENV') === 'production';
|
||||
const NODE_DB_URI = nconf.get('NODE_DB_URI');
|
||||
|
||||
const mongooseOptions = getDefaultConnectionOptions();
|
||||
const connectionUrl = IS_PROD ? NODE_DB_URI : getDevelopmentConnectionUrl(NODE_DB_URI);
|
||||
|
||||
const isProd = nconf.get('NODE_ENV') === 'production';
|
||||
const mongooseOptions = !isProd ? {} : {
|
||||
keepAlive: 1,
|
||||
connectTimeoutMS: 30000,
|
||||
};
|
||||
mongoose.connect(
|
||||
connectionUrl,
|
||||
nconf.get('NODE_DB_URI'),
|
||||
mongooseOptions,
|
||||
err => {
|
||||
if (err) throw err;
|
||||
|
||||
+104
-55
@@ -3,11 +3,9 @@ import { exec } from 'child_process';
|
||||
import gulp from 'gulp';
|
||||
import os from 'os';
|
||||
import nconf from 'nconf';
|
||||
import { pipe } from './taskHelper';
|
||||
import {
|
||||
getDevelopmentConnectionUrl,
|
||||
getDefaultConnectionOptions,
|
||||
} from '../website/server/libs/mongodb';
|
||||
pipe,
|
||||
} from './taskHelper';
|
||||
|
||||
// TODO rewrite
|
||||
|
||||
@@ -19,16 +17,15 @@ const TEST_DB_URI = nconf.get('TEST_DB_URI');
|
||||
const SANITY_TEST_COMMAND = 'npm run test:sanity';
|
||||
const COMMON_TEST_COMMAND = 'npm run test:common';
|
||||
const CONTENT_TEST_COMMAND = 'npm run test:content';
|
||||
const LIMIT_MAX_BUFFER_OPTIONS = { maxBuffer: 1024 * 500 };
|
||||
const CONTENT_OPTIONS = { maxBuffer: 1024 * 500 };
|
||||
|
||||
/* Helper method for reporting test summary */
|
||||
/* Helper methods for reporting test summary */
|
||||
const testResults = [];
|
||||
const testCount = (stdout, regexp) => {
|
||||
const match = stdout.match(regexp);
|
||||
return parseInt(match && (match[1] || 0), 10);
|
||||
};
|
||||
|
||||
/* Helper methods to correctly run child test processes */
|
||||
const testBin = (string, additionalEnvVariables = '') => {
|
||||
if (os.platform() === 'win32') {
|
||||
if (additionalEnvVariables !== '') {
|
||||
@@ -40,15 +37,6 @@ const testBin = (string, additionalEnvVariables = '') => {
|
||||
return `NODE_ENV=test ${additionalEnvVariables} ${string}`;
|
||||
};
|
||||
|
||||
function runInChildProcess (command, options = {}, envVariables = '') {
|
||||
return done => pipe(exec(testBin(command, envVariables), options, done));
|
||||
}
|
||||
|
||||
function integrationTestCommand (testDir, coverageDir) {
|
||||
return `istanbul cover --dir coverage/${coverageDir} --report lcovonly node_modules/mocha/bin/_mocha -- ${testDir} --recursive --require ./test/helpers/start-server`;
|
||||
}
|
||||
|
||||
/* Test task definitions */
|
||||
gulp.task('test:nodemon', gulp.series(done => {
|
||||
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env
|
||||
process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
|
||||
@@ -56,10 +44,7 @@ gulp.task('test:nodemon', gulp.series(done => {
|
||||
}, 'nodemon'));
|
||||
|
||||
gulp.task('test:prepare:mongo', cb => {
|
||||
const mongooseOptions = getDefaultConnectionOptions();
|
||||
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
||||
|
||||
mongoose.connect(connectionUrl, mongooseOptions, err => {
|
||||
mongoose.connect(TEST_DB_URI, err => {
|
||||
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
|
||||
return mongoose.connection.dropDatabase(err2 => {
|
||||
if (err2) return cb(err2);
|
||||
@@ -90,9 +75,31 @@ gulp.task('test:prepare', gulp.series(
|
||||
done => done(),
|
||||
));
|
||||
|
||||
gulp.task('test:sanity', runInChildProcess(SANITY_TEST_COMMAND));
|
||||
gulp.task('test:sanity', cb => {
|
||||
const runner = exec(
|
||||
testBin(SANITY_TEST_COMMAND),
|
||||
err => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
cb();
|
||||
},
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:common', gulp.series('test:prepare:build', runInChildProcess(COMMON_TEST_COMMAND)));
|
||||
gulp.task('test:common', gulp.series('test:prepare:build', cb => {
|
||||
const runner = exec(
|
||||
testBin(COMMON_TEST_COMMAND),
|
||||
err => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
cb();
|
||||
},
|
||||
);
|
||||
pipe(runner);
|
||||
}));
|
||||
|
||||
gulp.task('test:common:clean', cb => {
|
||||
pipe(exec(testBin(COMMON_TEST_COMMAND), () => cb()));
|
||||
@@ -116,11 +123,22 @@ gulp.task('test:common:safe', gulp.series('test:prepare:build', cb => {
|
||||
pipe(runner);
|
||||
}));
|
||||
|
||||
gulp.task('test:content', gulp.series('test:prepare:build',
|
||||
runInChildProcess(CONTENT_TEST_COMMAND, LIMIT_MAX_BUFFER_OPTIONS)));
|
||||
gulp.task('test:content', gulp.series('test:prepare:build', cb => {
|
||||
const runner = exec(
|
||||
testBin(CONTENT_TEST_COMMAND),
|
||||
CONTENT_OPTIONS,
|
||||
err => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
cb();
|
||||
},
|
||||
);
|
||||
pipe(runner);
|
||||
}));
|
||||
|
||||
gulp.task('test:content:clean', cb => {
|
||||
pipe(exec(testBin(CONTENT_TEST_COMMAND), LIMIT_MAX_BUFFER_OPTIONS, () => cb()));
|
||||
pipe(exec(testBin(CONTENT_TEST_COMMAND), CONTENT_OPTIONS, () => cb()));
|
||||
});
|
||||
|
||||
gulp.task('test:content:watch', gulp.series('test:content:clean', () => gulp.watch(['common/script/content/**', 'test/**'], gulp.series('test:content:clean', done => done()))));
|
||||
@@ -128,7 +146,7 @@ gulp.task('test:content:watch', gulp.series('test:content:clean', () => gulp.wat
|
||||
gulp.task('test:content:safe', gulp.series('test:prepare:build', cb => {
|
||||
const runner = exec(
|
||||
testBin(CONTENT_TEST_COMMAND),
|
||||
LIMIT_MAX_BUFFER_OPTIONS,
|
||||
CONTENT_OPTIONS,
|
||||
(err, stdout) => { // eslint-disable-line handle-callback-err
|
||||
testResults.push({
|
||||
suite: 'Content Specs\t',
|
||||
@@ -142,51 +160,82 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', cb => {
|
||||
pipe(runner);
|
||||
}));
|
||||
|
||||
gulp.task('test:api:unit:run',
|
||||
runInChildProcess(integrationTestCommand('test/api/unit', 'coverage/api-unit')));
|
||||
gulp.task('test:api:unit', done => {
|
||||
const runner = exec(
|
||||
testBin('istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
|
||||
err => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
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())));
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration', gulp.series('test:prepare:mongo',
|
||||
runInChildProcess(
|
||||
integrationTestCommand('test/api/v3/integration', 'coverage/api-v3-integration'),
|
||||
LIMIT_MAX_BUFFER_OPTIONS,
|
||||
)));
|
||||
gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done())));
|
||||
|
||||
gulp.task('test:api-v3:integration', done => {
|
||||
const runner = exec(
|
||||
testBin('istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
|
||||
{ maxBuffer: 500 * 1024 },
|
||||
err => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
done();
|
||||
},
|
||||
);
|
||||
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration:watch', () => gulp.watch([
|
||||
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
|
||||
'test/api/v3/integration/**/*',
|
||||
], gulp.series('test:api-v3:integration', done => done())));
|
||||
|
||||
gulp.task('test:api-v3:integration:separate-server', runInChildProcess(
|
||||
'mocha test/api/v3/integration --recursive --require ./test/helpers/start-server',
|
||||
LIMIT_MAX_BUFFER_OPTIONS,
|
||||
'LOAD_SERVER=0',
|
||||
));
|
||||
gulp.task('test:api-v3:integration:separate-server', done => {
|
||||
const runner = exec(
|
||||
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
|
||||
{ maxBuffer: 500 * 1024 },
|
||||
err => done(err),
|
||||
);
|
||||
|
||||
gulp.task('test:api-v4:integration', gulp.series('test:prepare:mongo',
|
||||
runInChildProcess(
|
||||
integrationTestCommand('test/api/v4', 'api-v4-integration'),
|
||||
LIMIT_MAX_BUFFER_OPTIONS,
|
||||
)));
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v4:integration:separate-server', runInChildProcess(
|
||||
'mocha test/api/v4 --recursive --require ./test/helpers/start-server',
|
||||
LIMIT_MAX_BUFFER_OPTIONS,
|
||||
'LOAD_SERVER=0',
|
||||
));
|
||||
gulp.task('test:api-v4:integration', done => {
|
||||
const runner = exec(
|
||||
testBin('istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
|
||||
{ maxBuffer: 500 * 1024 },
|
||||
err => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
done();
|
||||
},
|
||||
);
|
||||
|
||||
gulp.task('test:api:unit', gulp.series(
|
||||
'test:prepare:mongo',
|
||||
'test:api:unit:run',
|
||||
done => done(),
|
||||
));
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v4:integration:separate-server', done => {
|
||||
const runner = exec(
|
||||
testBin('mocha test/api/v4 --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
|
||||
{ maxBuffer: 500 * 1024 },
|
||||
err => done(err),
|
||||
);
|
||||
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test', gulp.series(
|
||||
'test:sanity',
|
||||
'test:content',
|
||||
'test:common',
|
||||
'test:api:unit:run',
|
||||
'test:api:unit',
|
||||
'test:api-v3:integration',
|
||||
'test:api-v4:integration',
|
||||
done => done(),
|
||||
|
||||
@@ -12,6 +12,7 @@ const SLACK_CONFIG = {
|
||||
const LOCALES = './website/common/locales/';
|
||||
const ENGLISH_LOCALE = `${LOCALES}en/`;
|
||||
|
||||
|
||||
function getArrayOfLanguages () {
|
||||
const languages = fs.readdirSync(LOCALES);
|
||||
languages.shift(); // Remove README.md from array of languages
|
||||
|
||||
+22
-22
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20210525_pet_color_achievements';
|
||||
const MIGRATION_NAME = '20200218_pet_color_achievements';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
@@ -14,31 +14,31 @@ async function updateUser (user) {
|
||||
|
||||
if (user && user.items && user.items.pets) {
|
||||
const pets = user.items.pets;
|
||||
if (pets['Wolf-CottonCandyBlue'] > 0
|
||||
&& pets['TigerCub-CottonCandyBlue'] > 0
|
||||
&& pets['PandaCub-CottonCandyBlue'] > 0
|
||||
&& pets['LionCub-CottonCandyBlue'] > 0
|
||||
&& pets['Fox-CottonCandyBlue'] > 0
|
||||
&& pets['FlyingPig-CottonCandyBlue'] > 0
|
||||
&& pets['Dragon-CottonCandyBlue'] > 0
|
||||
&& pets['Cactus-CottonCandyBlue'] > 0
|
||||
&& pets['BearCub-CottonCandyBlue'] > 0) {
|
||||
set['achievements.violetsAreBlue'] = true;
|
||||
if (pets['Wolf-CottonCandyPink'] > 0
|
||||
&& pets['TigerCub-CottonCandyPink'] > 0
|
||||
&& pets['PandaCub-CottonCandyPink'] > 0
|
||||
&& pets['LionCub-CottonCandyPink'] > 0
|
||||
&& pets['Fox-CottonCandyPink'] > 0
|
||||
&& pets['FlyingPig-CottonCandyPink'] > 0
|
||||
&& pets['Dragon-CottonCandyPink'] > 0
|
||||
&& pets['Cactus-CottonCandyPink'] > 0
|
||||
&& pets['BearCub-CottonCandyPink'] > 0) {
|
||||
set['achievements.tickledPink'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (user && user.items && user.items.mounts) {
|
||||
const mounts = user.items.mounts;
|
||||
if (mounts['Wolf-CottonCandyBlue']
|
||||
&& mounts['TigerCub-CottonCandyBlue']
|
||||
&& mounts['PandaCub-CottonCandyBlue']
|
||||
&& mounts['LionCub-CottonCandyBlue']
|
||||
&& mounts['Fox-CottonCandyBlue']
|
||||
&& mounts['FlyingPig-CottonCandyBlue']
|
||||
&& mounts['Dragon-CottonCandyBlue']
|
||||
&& mounts['Cactus-CottonCandyBlue']
|
||||
&& mounts['BearCub-CottonCandyBlue'] ) {
|
||||
set['achievements.wildBlueYonder'] = true;
|
||||
if (mounts['Wolf-CottonCandyPink']
|
||||
&& mounts['TigerCub-CottonCandyPink']
|
||||
&& mounts['PandaCub-CottonCandyPink']
|
||||
&& mounts['LionCub-CottonCandyPink']
|
||||
&& mounts['Fox-CottonCandyPink']
|
||||
&& mounts['FlyingPig-CottonCandyPink']
|
||||
&& mounts['Dragon-CottonCandyPink']
|
||||
&& mounts['Cactus-CottonCandyPink']
|
||||
&& mounts['BearCub-CottonCandyPink'] ) {
|
||||
set['achievements.rosyOutlook'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ async function updateUser (user) {
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2021-05-01') },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2020-02-01') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
@@ -1,59 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20200721_summer_splash_orcas';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
let set;
|
||||
|
||||
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME };
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.pets.Orca-Base': 5 };
|
||||
} else {
|
||||
set = { migration: MIGRATION_NAME, 'items.mounts.Orca-Base': 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('2020-06-21')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,87 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20200731_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 };
|
||||
} 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': false };
|
||||
push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.back_special_namingDay2020', _id: uuid() }};
|
||||
} 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': false };
|
||||
push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.body_special_namingDay2018', _id: uuid() }};
|
||||
} 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': false };
|
||||
push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.head_special_namingDay2017', _id: uuid() }};
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Gryphon-RoyalPurple'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.pets.Gryphon-RoyalPurple': 5 };
|
||||
} else {
|
||||
set = { migration: MIGRATION_NAME, 'items.mounts.Gryphon-RoyalPurple': true };
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
if (push) {
|
||||
return await User.update({ _id: user._id }, { $set: set, $inc: inc, $push: push }).exec();
|
||||
} else {
|
||||
return await User.update({ _id: user._id }, { $set: set, $inc: inc }).exec();
|
||||
}
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2020-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)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20200818_pet_color_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['Wolf-Golden'] > 0
|
||||
&& pets['TigerCub-Golden'] > 0
|
||||
&& pets['PandaCub-Golden'] > 0
|
||||
&& pets['LionCub-Golden'] > 0
|
||||
&& pets['Fox-Golden'] > 0
|
||||
&& pets['FlyingPig-Golden'] > 0
|
||||
&& pets['Dragon-Golden'] > 0
|
||||
&& pets['Cactus-Golden'] > 0
|
||||
&& pets['BearCub-Golden'] > 0) {
|
||||
set['achievements.goodAsGold'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (user && user.items && user.items.mounts) {
|
||||
const mounts = user.items.mounts;
|
||||
if (mounts['Wolf-Golden']
|
||||
&& mounts['TigerCub-Golden']
|
||||
&& mounts['PandaCub-Golden']
|
||||
&& mounts['LionCub-Golden']
|
||||
&& mounts['Fox-Golden']
|
||||
&& mounts['FlyingPig-Golden']
|
||||
&& mounts['Dragon-Golden']
|
||||
&& mounts['Cactus-Golden']
|
||||
&& mounts['BearCub-Golden'] ) {
|
||||
set['achievements.allThatGlitters'] = 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('2020-08-01') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20201020_pet_color_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['Wolf-Golden'] > 0
|
||||
&& pets['TigerCub-Skeleton'] > 0
|
||||
&& pets['PandaCub-Skeleton'] > 0
|
||||
&& pets['LionCub-Skeleton'] > 0
|
||||
&& pets['Fox-Skeleton'] > 0
|
||||
&& pets['FlyingPig-Skeleton'] > 0
|
||||
&& pets['Dragon-Skeleton'] > 0
|
||||
&& pets['Cactus-Skeleton'] > 0
|
||||
&& pets['BearCub-Skeleton'] > 0) {
|
||||
set['achievements.boneCollector'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (user && user.items && user.items.mounts) {
|
||||
const mounts = user.items.mounts;
|
||||
if (mounts['Wolf-Skeleton']
|
||||
&& mounts['TigerCub-Skeleton']
|
||||
&& mounts['PandaCub-Skeleton']
|
||||
&& mounts['LionCub-Skeleton']
|
||||
&& mounts['Fox-Skeleton']
|
||||
&& mounts['FlyingPig-Skeleton']
|
||||
&& mounts['Dragon-Skeleton']
|
||||
&& mounts['Cactus-Skeleton']
|
||||
&& mounts['BearCub-Skeleton'] ) {
|
||||
set['achievements.skeletonCrew'] = 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('2020-10-01') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Award Habitoween ladder items to participants in this month's Habitoween festivities
|
||||
*/
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const MIGRATION_NAME = '20201029_habitoween_ladder'; // Update when running in future years
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {};
|
||||
const inc = {
|
||||
'items.food.Candy_Skeleton': 1,
|
||||
'items.food.Candy_Base': 1,
|
||||
'items.food.Candy_CottonCandyBlue': 1,
|
||||
'items.food.Candy_CottonCandyPink': 1,
|
||||
'items.food.Candy_Shade': 1,
|
||||
'items.food.Candy_White': 1,
|
||||
'items.food.Candy_Golden': 1,
|
||||
'items.food.Candy_Zombie': 1,
|
||||
'items.food.Candy_Desert': 1,
|
||||
'items.food.Candy_Red': 1,
|
||||
};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Glow']) {
|
||||
set['items.pets.JackOLantern-RoyalPurple'] = 5;
|
||||
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Glow']) {
|
||||
set['items.mounts.JackOLantern-Glow'] = true;
|
||||
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Ghost']) {
|
||||
set['items.pets.JackOLantern-Glow'] = 5;
|
||||
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
|
||||
set['items.mounts.JackOLantern-Ghost'] = true;
|
||||
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
|
||||
set['items.pets.JackOLantern-Ghost'] = 5;
|
||||
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
|
||||
set['items.mounts.JackOLantern-Base'] = true;
|
||||
} else {
|
||||
set['items.pets.JackOLantern-Base'] = 5;
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2020-10-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],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Fix JackOLantern-Base for users that signed up recently
|
||||
*/
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const MIGRATION_NAME = '20201102_fix_habitoween'; // Update when running in future years
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
set['items.pets.JackOLantern-Base'] = 5;
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.created': {$gt: new Date('2020-10-26')},
|
||||
'items.pets.JackOLantern-Base': true,
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* All web users should be enrolled in the Drop Cap AB Test
|
||||
*/
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const MIGRATION_NAME = '20201103_drop_cap_ab_tweaks';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
const testGroup = Math.random();
|
||||
// Enroll 100% of users, splitting them 50/50
|
||||
const value = testGroup <= 0.50 ? 'drop-cap-notif-enabled' : 'drop-cap-notif-disabled';
|
||||
set['_ABtests.dropCapNotif'] = value;
|
||||
|
||||
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('2020-10-10')},
|
||||
'_ABtests.dropCapNotif': 'drop-cap-notif-not-enrolled',
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
_ABtests: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Fix dates in the database that were stored as $type string instead of Date
|
||||
*/
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const MIGRATION_NAME = '20201111_api_date';
|
||||
|
||||
import * as Tasks from '../../../website/server/models/task';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (todo) {
|
||||
count++;
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${todo._id}`);
|
||||
|
||||
const newDate = new Date(todo.date);
|
||||
if (isValidDate(newDate)) return;
|
||||
|
||||
return await Tasks.Task.update({_id: todo._id, type: 'todo'}, {$unset: {date: ''}}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
type: 'todo',
|
||||
date: {$exists: true},
|
||||
updatedAt: {$gt: new Date('2020-11-23')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
type: 1,
|
||||
date: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await Tasks.Task // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.select(fields)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate tasks found and modified.');
|
||||
console.warn(`\n${count} tasks processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
|
||||
function isValidDate(d) {
|
||||
return !isNaN(d.getTime());
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20201124_pet_color_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['Wolf-Red'] > 0
|
||||
&& pets['TigerCub-Red'] > 0
|
||||
&& pets['PandaCub-Red'] > 0
|
||||
&& pets['LionCub-Red'] > 0
|
||||
&& pets['Fox-Red'] > 0
|
||||
&& pets['FlyingPig-Red'] > 0
|
||||
&& pets['Dragon-Red'] > 0
|
||||
&& pets['Cactus-Red'] > 0
|
||||
&& pets['BearCub-Red'] > 0) {
|
||||
set['achievements.seeingRed'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (user && user.items && user.items.mounts) {
|
||||
const mounts = user.items.mounts;
|
||||
if (mounts['Wolf-Red']
|
||||
&& mounts['TigerCub-Red']
|
||||
&& mounts['PandaCub-Red']
|
||||
&& mounts['LionCub-Red']
|
||||
&& mounts['Fox-Red']
|
||||
&& mounts['FlyingPig-Red']
|
||||
&& mounts['Dragon-Red']
|
||||
&& mounts['Cactus-Red']
|
||||
&& mounts['BearCub-Red'] ) {
|
||||
set['achievements.redLetterDay'] = 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('2020-11-01') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,126 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20201126_harvest_feast';
|
||||
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++;
|
||||
|
||||
const set = {};
|
||||
let inc;
|
||||
let push;
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (typeof user.items.gear.owned.head_special_turkeyHelmGilded !== 'undefined') {
|
||||
inc = {
|
||||
'items.food.Pie_Base': 1,
|
||||
'items.food.Pie_CottonCandyBlue': 1,
|
||||
'items.food.Pie_CottonCandyPink': 1,
|
||||
'items.food.Pie_Desert': 1,
|
||||
'items.food.Pie_Golden': 1,
|
||||
'items.food.Pie_Red': 1,
|
||||
'items.food.Pie_Shade': 1,
|
||||
'items.food.Pie_Skeleton': 1,
|
||||
'items.food.Pie_Zombie': 1,
|
||||
'items.food.Pie_White': 1,
|
||||
}
|
||||
} else if (typeof user.items.gear.owned.armor_special_turkeyArmorBase !== 'undefined') {
|
||||
set['items.gear.owned.head_special_turkeyHelmGilded'] = false;
|
||||
set['items.gear.owned.armor_special_turkeyArmorGilded'] = false;
|
||||
set['items.gear.owned.back_special_turkeyTailGilded'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_turkeyHelmGilded',
|
||||
_id: uuid(),
|
||||
},
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.armor_special_turkeyArmorGilded',
|
||||
_id: uuid(),
|
||||
},
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.back_special_turkeyTailGilded',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) {
|
||||
set['items.gear.owned.head_special_turkeyHelmBase'] = false;
|
||||
set['items.gear.owned.armor_special_turkeyArmorBase'] = false;
|
||||
set['items.gear.owned.back_special_turkeyTailBase'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_turkeyHelmBase',
|
||||
_id: uuid(),
|
||||
},
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.armor_special_turkeyArmorBase',
|
||||
_id: uuid(),
|
||||
},
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.back_special_turkeyTailBase',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (user.items && user.items.pets && user.items.pets['Turkey-Gilded']) {
|
||||
set['items.mounts.Turkey-Gilded'] = true;
|
||||
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Base']) {
|
||||
set['items.pets.Turkey-Gilded'] = 5;
|
||||
} else if (user.items && user.items.pets && user.items.pets['Turkey-Base']) {
|
||||
set['items.mounts.Turkey-Base'] = true;
|
||||
} else {
|
||||
set['items.pets.Turkey-Base'] = 5;
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
if (inc) {
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set}).exec();
|
||||
} else if (push) {
|
||||
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||
} else {
|
||||
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('2019-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],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,126 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20201229_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_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('2020-12-01')},
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,94 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20210129_habit_birthday';
|
||||
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++;
|
||||
|
||||
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 = {};
|
||||
let push;
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (typeof user.items.gear.owned.armor_special_birthday2020 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2021'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2021', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2019 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2020'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2020', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2019'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2019', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2018'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2018', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2017'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2017', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2016'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2016', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2015'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2015', _id: uuid()}};
|
||||
} else {
|
||||
set['items.gear.owned.armor_special_birthday'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday', _id: uuid()}};
|
||||
}
|
||||
|
||||
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('2021-01-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],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,108 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20210216_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['Dragon-Base']
|
||||
&& pets['Dragon-CottonCandyBlue']
|
||||
&& pets['Dragon-CottonCandyPink']
|
||||
&& pets['Dragon-Desert']
|
||||
&& pets['Dragon-Golden']
|
||||
&& pets['Dragon-Red']
|
||||
&& pets['Dragon-Shade']
|
||||
&& pets['Dragon-Skeleton']
|
||||
&& pets['Dragon-White']
|
||||
&& pets['Dragon-Zombie']
|
||||
&& pets['FlyingPig-Base']
|
||||
&& pets['FlyingPig-CottonCandyBlue']
|
||||
&& pets['FlyingPig-CottonCandyPink']
|
||||
&& pets['FlyingPig-Desert']
|
||||
&& pets['FlyingPig-Golden']
|
||||
&& pets['FlyingPig-Red']
|
||||
&& pets['FlyingPig-Shade']
|
||||
&& pets['FlyingPig-Skeleton']
|
||||
&& pets['FlyingPig-White']
|
||||
&& pets['FlyingPig-Zombie']
|
||||
&& pets['Gryphon-Base']
|
||||
&& pets['Gryphon-CottonCandyBlue']
|
||||
&& pets['Gryphon-CottonCandyPink']
|
||||
&& pets['Gryphon-Desert']
|
||||
&& pets['Gryphon-Golden']
|
||||
&& pets['Gryphon-Red']
|
||||
&& pets['Gryphon-Shade']
|
||||
&& pets['Gryphon-Skeleton']
|
||||
&& pets['Gryphon-White']
|
||||
&& pets['Gryphon-Zombie']
|
||||
&& pets['SeaSerpent-Base']
|
||||
&& pets['SeaSerpent-CottonCandyBlue']
|
||||
&& pets['SeaSerpent-CottonCandyPink']
|
||||
&& pets['SeaSerpent-Desert']
|
||||
&& pets['SeaSerpent-Golden']
|
||||
&& pets['SeaSerpent-Red']
|
||||
&& pets['SeaSerpent-Shade']
|
||||
&& pets['SeaSerpent-Skeleton']
|
||||
&& pets['SeaSerpent-White']
|
||||
&& pets['SeaSerpent-Zombie']
|
||||
&& pets['Unicorn-Base']
|
||||
&& pets['Unicorn-CottonCandyBlue']
|
||||
&& pets['Unicorn-CottonCandyPink']
|
||||
&& pets['Unicorn-Desert']
|
||||
&& pets['Unicorn-Golden']
|
||||
&& pets['Unicorn-Red']
|
||||
&& pets['Unicorn-Shade']
|
||||
&& pets['Unicorn-Skeleton']
|
||||
&& pets['Unicorn-White']
|
||||
&& pets['Unicorn-Zombie']) {
|
||||
set['achievements.legendaryBestiary'] = 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('2021-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
|
||||
}
|
||||
};
|
||||
@@ -32,11 +32,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// CONFIGURATION:
|
||||
// - Change the uuid below to be the user's uuid.
|
||||
// - Change ALL instances of "todos" to "habits"/"dailys"/"rewards" as
|
||||
// needed. Do not miss any of them!
|
||||
|
||||
|
||||
const uuid = '30fb2640-7121-4968-ace5-f385e60ea6c5';
|
||||
|
||||
db.users.aggregate([
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// @authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||
// @authorUuid = ''; // ... own data is done
|
||||
|
||||
|
||||
/*
|
||||
* This migration moves chat off of groups and into their own model
|
||||
*/
|
||||
@@ -37,6 +38,7 @@ async function moveGroupChatToModel (skip = 0) {
|
||||
return chatpromises;
|
||||
});
|
||||
|
||||
|
||||
const reducedPromises = promises.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr); // eslint-disable-line no-param-reassign
|
||||
return acc;
|
||||
|
||||
@@ -45,7 +45,7 @@ async function fixGroupPlanMembers () {
|
||||
pause();
|
||||
groupPlanCount += 1;
|
||||
|
||||
const canonicalMemberCount = await dbUsers.countDocuments(
|
||||
const canonicalMemberCount = await dbUsers.count(
|
||||
{
|
||||
$or:
|
||||
[
|
||||
|
||||
@@ -25,6 +25,7 @@ const monk = require('monk'); // eslint-disable-line import/no-extraneous-depend
|
||||
|
||||
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
const query = {
|
||||
@@ -85,6 +86,7 @@ function updateUser (user) {
|
||||
|
||||
const set = { migration: migrationName, 'flags.armoireEmpty': false };
|
||||
|
||||
|
||||
if (user.flags.armoireEmpty) {
|
||||
// this user believes their armoire has no more items in it
|
||||
if (
|
||||
|
||||
@@ -121,4 +121,5 @@ function exiting (code, msg) {
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
|
||||
processUsers();
|
||||
|
||||
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20210314_pi_day';
|
||||
const MIGRATION_NAME = '20200314_pi_day';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
@@ -54,7 +54,7 @@ async function updateUser (user) {
|
||||
export default async function processUsers () {
|
||||
const query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2021-02-15') },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2020-02-15') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
|
||||
@@ -9,6 +9,7 @@ const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is do
|
||||
*/
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
|
||||
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = 'tag-challenge-field-string2bool';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
export default async function processUsers () {
|
||||
const query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
tags: {
|
||||
$elemMatch: {
|
||||
challenge: {
|
||||
$exists: true,
|
||||
$type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const users = await User.find(query)
|
||||
.sort({ _id: 1 })
|
||||
.limit(250)
|
||||
.select({ _id: 1, tags: 1 })
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
let requiresUpdate = false;
|
||||
|
||||
if (user && user.tags) {
|
||||
user.tags.forEach(tag => {
|
||||
if (tag && typeof tag.challenge === 'string') {
|
||||
requiresUpdate = true;
|
||||
if (tag.challenge === 'true') {
|
||||
tag.challenge = true;
|
||||
} else if (tag.challenge === 'false') {
|
||||
tag.challenge = false;
|
||||
} else {
|
||||
tag.challenge = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (requiresUpdate) {
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
tags: user.tags,
|
||||
};
|
||||
return User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Generated
+4508
-4674
File diff suppressed because it is too large
Load Diff
+41
-49
@@ -1,27 +1,26 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.195.0",
|
||||
"version": "4.144.1",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.13.16",
|
||||
"@babel/preset-env": "^7.14.1",
|
||||
"@babel/register": "^7.13.16",
|
||||
"@google-cloud/trace-agent": "^5.1.3",
|
||||
"@parse/node-apn": "^4.1.1",
|
||||
"@slack/webhook": "^6.0.0",
|
||||
"@babel/core": "^7.10.2",
|
||||
"@babel/preset-env": "^7.10.2",
|
||||
"@babel/register": "^7.10.1",
|
||||
"@google-cloud/trace-agent": "^5.0.0",
|
||||
"@slack/client": "^4.12.0",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.8",
|
||||
"amplitude": "^5.1.5",
|
||||
"apidoc": "^0.27.1",
|
||||
"apple-auth": "^1.0.7",
|
||||
"bcrypt": "^5.0.1",
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"apple-auth": "^1.0.6",
|
||||
"bcrypt": "^3.0.8",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^4.6.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-session": "^1.4.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"csv-stringify": "^5.6.2",
|
||||
"csv-stringify": "^5.5.0",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
@@ -31,56 +30,53 @@
|
||||
"express-basic-auth": "^1.1.5",
|
||||
"express-validator": "^5.2.0",
|
||||
"glob": "^7.1.6",
|
||||
"got": "^11.8.2",
|
||||
"got": "^10.7.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-imagemin": "^6.2.0",
|
||||
"gulp-nodemon": "^2.5.0",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"helmet": "^4.6.0",
|
||||
"image-size": "^1.0.0",
|
||||
"habitica-markdown": "^2.0.0",
|
||||
"helmet": "^3.22.0",
|
||||
"image-size": "^0.8.3",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"js2xmlparser": "^4.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "^2.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
"jwks-rsa": "^1.8.0",
|
||||
"lodash": "^4.17.15",
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"moment": "^2.26.0",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.12.7",
|
||||
"mongoose": "^5.9.18",
|
||||
"morgan": "^1.10.0",
|
||||
"nconf": "^0.11.2",
|
||||
"node-gcm": "^1.0.4",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
"on-headers": "^1.0.2",
|
||||
"passport": "^0.4.1",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-google-oauth2": "^0.2.0",
|
||||
"passport-google-oauth20": "2.0.0",
|
||||
"passport-google-oauth20": "1.0.0",
|
||||
"paypal-ipn": "3.0.0",
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"pp-ipn": "^1.1.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"rate-limiter-flexible": "^2.2.1",
|
||||
"redis": "^3.1.2",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"regenerator-runtime": "^0.13.5",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.1.0",
|
||||
"stripe": "^8.145.0",
|
||||
"superagent": "^6.1.0",
|
||||
"universal-analytics": "^0.4.23",
|
||||
"short-uuid": "^3.0.0",
|
||||
"stripe": "^7.15.0",
|
||||
"superagent": "^5.2.2",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.6.0",
|
||||
"uuid": "^3.4.0",
|
||||
"validator": "^11.0.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^3.3.3",
|
||||
"winston-loggly-bulk": "^3.2.1",
|
||||
"winston": "^3.2.1",
|
||||
"winston-loggly-bulk": "^3.1.0",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "^14",
|
||||
"node": "^12",
|
||||
"npm": "^6"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -105,25 +101,21 @@
|
||||
"client:unit": "cd website/client && npm run test:unit",
|
||||
"start": "gulp nodemon",
|
||||
"debug": "gulp nodemon --inspect",
|
||||
"mongo:dev": "run-rs -v 4.2.8 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||
"postinstall": "gulp build && cd website/client && npm install",
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"chai": "^4.3.4",
|
||||
"axios": "^0.19.2",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-moment": "^0.1.0",
|
||||
"chalk": "^4.1.1",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"chalk": "^3.0.0",
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^7.3.4",
|
||||
"monk": "^7.3.0",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.7.4",
|
||||
"sinon": "^10.0.0",
|
||||
"sinon-chai": "^3.6.0",
|
||||
"sinon": "^7.2.4",
|
||||
"sinon-chai": "^3.5.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
"optionalDependencies": {}
|
||||
|
||||
@@ -31,21 +31,20 @@ async function deleteAmplitudeData (userId, email) {
|
||||
console.log(`${userId} (${email}) Amplitude response: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
async function deleteHabiticaData (user, email) {
|
||||
const set = {
|
||||
'auth.blocked': false,
|
||||
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
|
||||
'auth.local.passwordHashMethod': 'bcrypt',
|
||||
};
|
||||
if (!user.auth.local.email) set['auth.local.email'] = `${user._id}@example.com`;
|
||||
const truncatedEmail = email.slice(0, email.indexOf('@'));
|
||||
await User.update(
|
||||
{ _id: user._id },
|
||||
{ $set: set },
|
||||
{
|
||||
$set: {
|
||||
'auth.local.email': user.auth.local.email ? email : `${truncatedEmail}@example.com`,
|
||||
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
|
||||
'auth.local.passwordHashMethod': 'bcrypt',
|
||||
},
|
||||
},
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const response = await axios.delete(
|
||||
`${BASE_URL}/api/v3/user`,
|
||||
{
|
||||
@@ -79,7 +78,6 @@ async function processEmailAddress (email) {
|
||||
|
||||
const socialUsers = await User.find(
|
||||
{
|
||||
'auth.local.email': { $not: emailRegex },
|
||||
$or: [
|
||||
{ 'auth.facebook.emails.value': email },
|
||||
{ 'auth.google.emails.value': email },
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable camelcase */
|
||||
import nconf from 'nconf';
|
||||
import Amplitude from 'amplitude';
|
||||
import { Visitor } from 'universal-analytics';
|
||||
import * as analyticsService from '../../../../website/server/libs/analyticsService';
|
||||
@@ -16,22 +15,6 @@ describe('analyticsService', () => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('#getServiceByEnvironment', () => {
|
||||
it('returns mock methods when not in production', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
|
||||
expect(analyticsService.getAnalyticsServiceByEnvironment())
|
||||
.to.equal(analyticsService.mockAnalyticsService);
|
||||
});
|
||||
|
||||
it('returns real methods when in production', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
expect(analyticsService.getAnalyticsServiceByEnvironment().track)
|
||||
.to.equal(analyticsService.track);
|
||||
expect(analyticsService.getAnalyticsServiceByEnvironment().trackPurchase)
|
||||
.to.equal(analyticsService.trackPurchase);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#track', () => {
|
||||
let eventType; let
|
||||
data;
|
||||
|
||||
@@ -56,6 +56,7 @@ describe('Base model plugin', () => {
|
||||
expect(sanitized).not.to.have.property('usuallySettable');
|
||||
});
|
||||
|
||||
|
||||
it('can make fields private', () => {
|
||||
schema.plugin(baseModel, {
|
||||
private: ['amPrivate'],
|
||||
|
||||
@@ -42,13 +42,13 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('updates user.preferences.timezoneOffsetAtLastCron', () => {
|
||||
const timezoneUtcOffsetFromUserPrefs = -1;
|
||||
const timezoneOffsetFromUserPrefs = 1;
|
||||
|
||||
cron({
|
||||
user, tasksByType, daysMissed, analytics, timezoneUtcOffsetFromUserPrefs,
|
||||
user, tasksByType, daysMissed, analytics, timezoneOffsetFromUserPrefs,
|
||||
});
|
||||
|
||||
expect(user.preferences.timezoneOffsetAtLastCron).to.equal(1);
|
||||
expect(user.preferences.timezoneOffsetAtLastCron).to.equal(timezoneOffsetFromUserPrefs);
|
||||
});
|
||||
|
||||
it('resets user.items.lastDrop.count', () => {
|
||||
@@ -240,7 +240,7 @@ describe('cron', () => {
|
||||
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||
|
||||
it('does not increment consecutive benefits after the first month', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
@@ -256,7 +256,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits after the second month', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
@@ -272,7 +272,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits after the third month', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
@@ -288,7 +288,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits after the fourth month', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||
@@ -304,7 +304,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(10, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(10, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -339,7 +339,7 @@ describe('cron', () => {
|
||||
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', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -352,7 +352,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the middle of the period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -365,7 +365,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -378,7 +378,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -391,7 +391,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(5, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -404,7 +404,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(6, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -417,7 +417,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -430,7 +430,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(10, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(10, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -465,7 +465,7 @@ describe('cron', () => {
|
||||
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', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -478,7 +478,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(6, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -491,7 +491,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -504,7 +504,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(13, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -517,7 +517,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(19, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(19, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -552,7 +552,7 @@ describe('cron', () => {
|
||||
user12.purchased.plan.consecutive.gemCapExtra = 20;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -565,7 +565,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(12, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(12, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -578,7 +578,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(13, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -591,7 +591,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(25, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(25, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -604,7 +604,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(37, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(37, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -641,7 +641,7 @@ describe('cron', () => {
|
||||
user3g.purchased.plan.consecutive.gemCapExtra = 5;
|
||||
|
||||
it('does not increment consecutive benefits in the first month of the gift subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -654,7 +654,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month of the gift subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -667,7 +667,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the third month of the gift subscription', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -680,7 +680,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the month after the gift subscription has ended', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -717,7 +717,7 @@ describe('cron', () => {
|
||||
user6x.purchased.plan.consecutive.gemCapExtra = 15;
|
||||
|
||||
it('increments consecutive benefits in the first month since the fix for #4819 goes live', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -730,7 +730,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the second month after the fix goes live', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -743,7 +743,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('does not increment consecutive benefits in the third month after the fix goes live', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
@@ -756,7 +756,7 @@ describe('cron', () => {
|
||||
});
|
||||
|
||||
it('increments consecutive benefits in the seventh month after the fix goes live', () => {
|
||||
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
|
||||
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months')
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
cron({
|
||||
|
||||
@@ -11,14 +11,14 @@ describe('highlightMentions', () => {
|
||||
lean () {
|
||||
return this;
|
||||
},
|
||||
async exec () {
|
||||
return [
|
||||
exec () {
|
||||
return Promise.resolve([
|
||||
{ auth: { local: { username: 'user' } }, _id: '111' },
|
||||
{ auth: { local: { username: 'user2' } }, _id: '222' },
|
||||
{ auth: { local: { username: 'user3' } }, _id: '333' },
|
||||
{ auth: { local: { username: 'user-dash' } }, _id: '444' },
|
||||
{ auth: { local: { username: 'user_underscore' } }, _id: '555' },
|
||||
];
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -168,50 +168,31 @@ describe('highlightMentions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('issues', () => {
|
||||
it('github issue 12118, method crashes when square brackets are used', async () => {
|
||||
const text = '[test]';
|
||||
it('github issue 12118, method crashes when square brackets are used', async () => {
|
||||
const text = '[test]';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
let err;
|
||||
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
try {
|
||||
await highlightMentions(text);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
it('github issue 12138, method crashes when regex chars are used in code block', async () => {
|
||||
const text = '`[test]`';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
|
||||
it('github issue 12586, method crashes when empty link is used', async () => {
|
||||
const text = '[]()';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
|
||||
it('github issue 12586, method crashes when link without title is used', async () => {
|
||||
const text = '[](www.google.com)';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
expect(err).to.be.undefined;
|
||||
});
|
||||
|
||||
describe('base url', () => {
|
||||
it('should prefix habitica.com if it is production', async () => {
|
||||
const OLD_NODE_ENV = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = 'production';
|
||||
const text = '@user';
|
||||
it('github issue 12138, method crashes when regex chars are used in code block', async () => {
|
||||
const text = '`[test]`';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
let err;
|
||||
|
||||
expect(result[0]).to.equal('[@user](https://habitica.com/profile/111)');
|
||||
process.env.NODE_ENV = OLD_NODE_ENV;
|
||||
});
|
||||
try {
|
||||
await highlightMentions(text);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
expect(err).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import os from 'os';
|
||||
import nconf from 'nconf';
|
||||
import requireAgain from 'require-again';
|
||||
|
||||
const pathToMongoLib = '../../../../website/server/libs/mongodb';
|
||||
|
||||
describe('mongodb', () => {
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('getDevelopmentConnectionUrl', () => {
|
||||
it('returns the original connection url if not on windows', () => {
|
||||
sandbox.stub(os, 'platform').returns('linux');
|
||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
||||
|
||||
const originalString = 'mongodb://localhost:3030';
|
||||
const string = mongoLibOverride.getDevelopmentConnectionUrl(originalString);
|
||||
expect(string).to.equal(originalString);
|
||||
});
|
||||
|
||||
it('replaces localhost with hostname on windows', () => {
|
||||
sandbox.stub(os, 'platform').returns('win32');
|
||||
sandbox.stub(os, 'hostname').returns('hostname');
|
||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
||||
|
||||
const originalString = 'mongodb://localhost:3030';
|
||||
const string = mongoLibOverride.getDevelopmentConnectionUrl(originalString);
|
||||
expect(string).to.equal('mongodb://hostname:3030');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultConnectionOptions', () => {
|
||||
it('returns development config when IS_PROD is false', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
|
||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
||||
|
||||
const options = mongoLibOverride.getDefaultConnectionOptions();
|
||||
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology']);
|
||||
});
|
||||
|
||||
it('returns production config when IS_PROD is true', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
||||
|
||||
const options = mongoLibOverride.getDefaultConnectionOptions();
|
||||
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology', 'keepAlive', 'keepAliveInitialDelay']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,15 +2,13 @@ import { model as User } from '../../../../../../website/server/models/user';
|
||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('Amazon Payments - Checkout', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
let user; let orderReferenceId; let
|
||||
headers; const gemsBlockKey = '21gems'; const gemsBlock = common.content.gems[gemsBlockKey];
|
||||
headers;
|
||||
let setOrderReferenceDetailsSpy;
|
||||
let confirmOrderReferenceSpy;
|
||||
let authorizeSpy;
|
||||
@@ -18,7 +16,7 @@ describe('Amazon Payments - Checkout', () => {
|
||||
|
||||
let paymentBuyGemsStub;
|
||||
let paymentCreateSubscritionStub;
|
||||
let amount = gemsBlock.price / 100;
|
||||
let amount = 5;
|
||||
|
||||
function expectOrderReferenceSpy () {
|
||||
expect(setOrderReferenceDetailsSpy).to.be.calledOnce;
|
||||
@@ -89,7 +87,6 @@ describe('Amazon Payments - Checkout', () => {
|
||||
paymentCreateSubscritionStub.resolves({});
|
||||
|
||||
sinon.stub(common, 'uuid').returns('uuid-generated');
|
||||
sandbox.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -110,23 +107,13 @@ describe('Amazon Payments - Checkout', () => {
|
||||
paymentMethod,
|
||||
headers,
|
||||
};
|
||||
if (gift) {
|
||||
expectedArgs.gift = gift;
|
||||
expectedArgs.gemsBlock = undefined;
|
||||
expect(gems.validateGiftMessage).to.be.calledOnce;
|
||||
expect(gems.validateGiftMessage).to.be.calledWith(gift, user);
|
||||
} else {
|
||||
expect(gems.validateGiftMessage).to.not.be.called;
|
||||
expectedArgs.gemsBlock = gemsBlock;
|
||||
}
|
||||
if (gift) expectedArgs.gift = gift;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith(expectedArgs);
|
||||
}
|
||||
|
||||
it('should purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await amzLib.checkout({
|
||||
user, orderReferenceId, headers, gemsBlock: gemsBlockKey,
|
||||
});
|
||||
await amzLib.checkout({ user, orderReferenceId, headers });
|
||||
|
||||
expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD);
|
||||
expectAmazonStubs();
|
||||
@@ -157,9 +144,7 @@ describe('Amazon Payments - Checkout', () => {
|
||||
|
||||
it('should error if user cannot get gems gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(amzLib.checkout({
|
||||
user, orderReferenceId, headers, gemsBlock: gemsBlockKey,
|
||||
}))
|
||||
await expect(amzLib.checkout({ user, orderReferenceId, headers }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
@@ -168,17 +153,6 @@ describe('Amazon Payments - Checkout', () => {
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('should error if gems block is not valid', async () => {
|
||||
await expect(amzLib.checkout({
|
||||
user, orderReferenceId, headers, gemsBlock: 'invalid',
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: apiError('invalidGemsBlock'),
|
||||
name: 'BadRequest',
|
||||
});
|
||||
});
|
||||
|
||||
it('should gift gems', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
@@ -221,7 +195,6 @@ describe('Amazon Payments - Checkout', () => {
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT,
|
||||
headers,
|
||||
gift,
|
||||
gemsBlock: undefined,
|
||||
});
|
||||
expectAmazonStubs();
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
@@ -5,7 +7,6 @@ import { model as User } from '../../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../../website/server/models/group';
|
||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
describe('#upgradeGroupPlan', () => {
|
||||
let spy; let data; let user; let group; let
|
||||
@@ -31,19 +32,16 @@ describe('#upgradeGroupPlan', () => {
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
await group.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
|
||||
spy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
|
||||
spy.resolves([]);
|
||||
|
||||
uuidString = 'uuid-v4';
|
||||
sinon.stub(common, 'uuid').returns(uuidString);
|
||||
sinon.stub(uuid, 'v4').returns(uuidString);
|
||||
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
@@ -51,7 +49,7 @@ describe('#upgradeGroupPlan', () => {
|
||||
|
||||
afterEach(() => {
|
||||
amzLib.authorizeOnBillingAgreement.restore();
|
||||
common.uuid.restore();
|
||||
uuid.v4.restore();
|
||||
});
|
||||
|
||||
it('charges for a new member', async () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import applePayments from '../../../../../website/server/libs/payments/apple';
|
||||
import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import * as gems from '../../../../../website/server/libs/payments/gems';
|
||||
import { mockFindById, restoreFindById } from '../../../../helpers/mongoose.helper';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('Apple Payments', () => {
|
||||
let sku; let user; let token; let receipt; let
|
||||
headers;
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuyGemsStub; let
|
||||
iapGetPurchaseDataStub; let validateGiftMessageStub;
|
||||
iapGetPurchaseDataStub;
|
||||
|
||||
beforeEach(() => {
|
||||
token = 'testToken';
|
||||
@@ -37,7 +37,6 @@ describe('Apple Payments', () => {
|
||||
transactionId: token,
|
||||
}]);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -46,7 +45,6 @@ describe('Apple Payments', () => {
|
||||
iap.isValidated.restore();
|
||||
iap.getPurchaseData.restore();
|
||||
payments.buyGems.restore();
|
||||
gems.validateGiftMessage.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if receipt is invalid', async () => {
|
||||
@@ -86,7 +84,7 @@ describe('Apple Payments', () => {
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('errors if gemsBlock does not exist', async () => {
|
||||
it('errors if amount does not exist', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
@@ -108,23 +106,23 @@ describe('Apple Payments', () => {
|
||||
const gemsCanPurchase = [
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.4gems',
|
||||
gemsBlock: '4gems',
|
||||
amount: 1,
|
||||
},
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.20gems',
|
||||
gemsBlock: '21gems',
|
||||
amount: 5.25,
|
||||
},
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||
gemsBlock: '21gems',
|
||||
amount: 5.25,
|
||||
},
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.42gems',
|
||||
gemsBlock: '42gems',
|
||||
amount: 10.5,
|
||||
},
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.84gems',
|
||||
gemsBlock: '84gems',
|
||||
amount: 21,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -146,15 +144,13 @@ describe('Apple Payments', () => {
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.not.be.called;
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
gemsBlock: common.content.gems[gemTest.gemsBlock],
|
||||
amount: gemTest.amount,
|
||||
headers,
|
||||
gift: undefined,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
@@ -165,6 +161,8 @@ describe('Apple Payments', () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
|
||||
mockFindById(receivingUser);
|
||||
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||
.returns([{
|
||||
@@ -184,22 +182,14 @@ describe('Apple Payments', () => {
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(validateGiftMessageStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.be.calledWith(gift, user);
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
user: receivingUser,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
amount: gemsCanPurchase[0].amount,
|
||||
headers,
|
||||
gift: {
|
||||
type: 'gems',
|
||||
gems: { amount: 4 },
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
gemsBlock: common.content.gems['4gems'],
|
||||
});
|
||||
restoreFindById();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import common from '../../../../../website/common';
|
||||
import {
|
||||
getGemsBlock,
|
||||
validateGiftMessage,
|
||||
} from '../../../../../website/server/libs/payments/gems';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('payments/gems', () => {
|
||||
describe('#getGemsBlock', () => {
|
||||
it('throws an error if the gem block key is invalid', () => {
|
||||
expect(() => getGemsBlock('invalid')).to.throw;
|
||||
});
|
||||
|
||||
it('returns the gem block for the given key', () => {
|
||||
expect(getGemsBlock('21gems')).to.equal(common.content.gems['21gems']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#validateGiftMessage', () => {
|
||||
let user;
|
||||
let gift;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
|
||||
gift = {
|
||||
message: (` // exactly 201 chars
|
||||
A gift message that is over the 200 chars limit.
|
||||
A gift message that is over the 200 chars limit.
|
||||
A gift message that is over the 200 chars limit.
|
||||
A gift message that is over the 200 chars limit. 1
|
||||
`).trim().substring(0, 201),
|
||||
};
|
||||
|
||||
expect(gift.message.length).to.equal(201);
|
||||
});
|
||||
|
||||
it('throws if the gift message is too long', () => {
|
||||
let expectedErr;
|
||||
|
||||
try {
|
||||
validateGiftMessage(gift, user);
|
||||
} catch (err) {
|
||||
expectedErr = err;
|
||||
}
|
||||
|
||||
expect(expectedErr).to.exist;
|
||||
expect(expectedErr).to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('giftMessageTooLong', { maxGiftMessageLength: 200 }),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not throw if the gift message is not too long', () => {
|
||||
gift.message = gift.message.substring(0, 200);
|
||||
expect(() => validateGiftMessage(gift, user)).to.not.throw;
|
||||
});
|
||||
|
||||
it('does not throw if it is not a gift', () => {
|
||||
expect(() => validateGiftMessage(null, user)).to.not.throw;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ import googlePayments from '../../../../../website/server/libs/payments/google';
|
||||
import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import * as gems from '../../../../../website/server/libs/payments/gems';
|
||||
import { mockFindById, restoreFindById } from '../../../../helpers/mongoose.helper';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
@@ -14,9 +14,9 @@ describe('Google Payments', () => {
|
||||
|
||||
describe('verifyGemPurchase', () => {
|
||||
let sku; let user; let token; let receipt; let signature; let
|
||||
headers; const gemsBlock = common.content.gems['21gems'];
|
||||
headers;
|
||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
|
||||
paymentBuyGemsStub; let validateGiftMessageStub;
|
||||
paymentBuyGemsStub;
|
||||
|
||||
beforeEach(() => {
|
||||
sku = 'com.habitrpg.android.habitica.iap.21gems';
|
||||
@@ -32,7 +32,6 @@ describe('Google Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||
.returns(true);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -40,7 +39,6 @@ describe('Google Payments', () => {
|
||||
iap.validate.restore();
|
||||
iap.isValidated.restore();
|
||||
payments.buyGems.restore();
|
||||
gems.validateGiftMessage.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if receipt is invalid', async () => {
|
||||
@@ -92,8 +90,6 @@ describe('Google Payments', () => {
|
||||
user, receipt, signature, headers,
|
||||
});
|
||||
|
||||
expect(validateGiftMessageStub).to.not.be.called;
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
|
||||
@@ -107,9 +103,8 @@ describe('Google Payments', () => {
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||
gemsBlock,
|
||||
amount: 5.25,
|
||||
headers,
|
||||
gift: undefined,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
@@ -119,14 +114,13 @@ describe('Google Payments', () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
|
||||
mockFindById(receivingUser);
|
||||
|
||||
const gift = { uuid: receivingUser._id };
|
||||
await googlePayments.verifyGemPurchase({
|
||||
user, gift, receipt, signature, headers,
|
||||
});
|
||||
|
||||
expect(validateGiftMessageStub).to.be.calledOnce;
|
||||
expect(validateGiftMessageStub).to.be.calledWith(gift, user);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
|
||||
@@ -138,17 +132,12 @@ describe('Google Payments', () => {
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
user: receivingUser,
|
||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||
gemsBlock,
|
||||
amount: 5.25,
|
||||
headers,
|
||||
gift: {
|
||||
type: 'gems',
|
||||
gems: { amount: 21 },
|
||||
member: sinon.match({ _id: receivingUser._id }),
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
});
|
||||
restoreFindById();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -21,14 +21,11 @@ describe('Canceling a subscription for group', () => {
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
await group.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
|
||||
data = {
|
||||
user,
|
||||
sub: {
|
||||
@@ -144,8 +141,6 @@ describe('Canceling a subscription for group', () => {
|
||||
|
||||
it('prevents non group leader from managing subscription', async () => {
|
||||
const groupMember = new User();
|
||||
groupMember.guilds.push(group._id);
|
||||
await groupMember.save();
|
||||
data.user = groupMember;
|
||||
data.groupId = group._id;
|
||||
|
||||
@@ -167,9 +162,7 @@ describe('Canceling a subscription for group', () => {
|
||||
|
||||
let updatedGroup = await Group.findById(group._id).exec();
|
||||
const newLeader = new User();
|
||||
newLeader.profile.name = 'newLeader';
|
||||
updatedGroup.leader = newLeader._id;
|
||||
await newLeader.save();
|
||||
await updatedGroup.save();
|
||||
|
||||
await api.cancelSubscription(data);
|
||||
@@ -192,6 +185,8 @@ describe('Canceling a subscription for group', () => {
|
||||
'user-agent': '',
|
||||
},
|
||||
};
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
expect(group.purchased.plan.planId).to.not.exist;
|
||||
data.groupId = group._id;
|
||||
await api.createSubscription(data);
|
||||
@@ -216,15 +211,10 @@ describe('Canceling a subscription for group', () => {
|
||||
await api.createSubscription(data);
|
||||
await api.cancelSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.have.callCount(6);
|
||||
const recipientCall = sender.sendTxn.getCalls().find(call => {
|
||||
const isRecipient = call.args[0]._id === recipient._id;
|
||||
const isGroupMemberCancel = call.args[1] === 'group-member-cancel';
|
||||
return isRecipient && isGroupMemberCancel;
|
||||
});
|
||||
expect(recipientCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(recipientCall.args[1]).to.equal('group-member-cancel');
|
||||
expect(recipientCall.args[2]).to.eql([
|
||||
expect(sender.sendTxn).to.be.have.callCount(4);
|
||||
expect(sender.sendTxn.thirdCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.thirdCall.args[1]).to.equal('group-member-cancel');
|
||||
expect(sender.sendTxn.thirdCall.args[2]).to.eql([
|
||||
{ name: 'LEADER', content: user.profile.name },
|
||||
{ name: 'GROUP_NAME', content: group.name },
|
||||
]);
|
||||
@@ -256,6 +246,8 @@ describe('Canceling a subscription for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -267,13 +259,11 @@ describe('Canceling a subscription for group', () => {
|
||||
const group2 = generateGroup({
|
||||
name: 'test group2',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
data.groupId = group2._id;
|
||||
await group2.save();
|
||||
user.guilds.push(group2._id);
|
||||
await user.save();
|
||||
recipient.guilds.push(group2._id);
|
||||
await recipient.save();
|
||||
|
||||
@@ -295,6 +285,8 @@ describe('Canceling a subscription for group', () => {
|
||||
});
|
||||
|
||||
it('does cancel a leader subscription with two cancelled group plans', async () => {
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -306,7 +298,7 @@ describe('Canceling a subscription for group', () => {
|
||||
const group2 = generateGroup({
|
||||
name: 'test group2',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
user.guilds.push(group2._id);
|
||||
|
||||
@@ -12,7 +12,6 @@ import { model as Group } from '../../../../../../website/server/models/group';
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import i18n from '../../../../../../website/common/script/i18n';
|
||||
|
||||
describe('Purchasing a group plan for group', () => {
|
||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription';
|
||||
@@ -34,14 +33,11 @@ describe('Purchasing a group plan for group', () => {
|
||||
group = generateGroup({
|
||||
name: groupName,
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
await group.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
|
||||
data = {
|
||||
user,
|
||||
sub: {
|
||||
@@ -116,30 +112,6 @@ describe('Purchasing a group plan for group', () => {
|
||||
expect(updatedGroup.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('does not create a group plan for a public guild', async () => {
|
||||
const publicGroup = generateGroup({
|
||||
name: groupName,
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
await publicGroup.save();
|
||||
|
||||
expect(publicGroup.purchased.plan.planId).to.not.exist;
|
||||
data.groupId = publicGroup._id;
|
||||
|
||||
await expect(api.createSubscription(data))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('onlyPrivateGuildsCanUpgrade'),
|
||||
});
|
||||
|
||||
const updatedGroup = await Group.findById(publicGroup._id).exec();
|
||||
|
||||
expect(updatedGroup.purchased.plan.planId).to.not.exist;
|
||||
});
|
||||
|
||||
it('sends an email', async () => {
|
||||
expect(group.purchased.plan.planId).to.not.exist;
|
||||
data.groupId = group._id;
|
||||
@@ -176,6 +148,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
});
|
||||
|
||||
it('grants all members of a group a subscription', async () => {
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
expect(group.purchased.plan.planId).to.not.exist;
|
||||
data.groupId = group._id;
|
||||
|
||||
@@ -205,28 +179,17 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledThrice;
|
||||
const recipientCall = sender.sendTxn.getCalls().find(call => {
|
||||
const isRecipient = call.args[0]._id === recipient._id;
|
||||
const isJoin = call.args[1] === 'group-member-join';
|
||||
return isRecipient && isJoin;
|
||||
});
|
||||
expect(recipientCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(recipientCall.args[1]).to.equal('group-member-join');
|
||||
expect(recipientCall.args[2]).to.eql([
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{ name: 'LEADER', content: user.profile.name },
|
||||
{ name: 'GROUP_NAME', content: group.name },
|
||||
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE },
|
||||
]);
|
||||
|
||||
// confirm that the other email sent is appropriate:
|
||||
const leaderCall = sender.sendTxn.getCalls().find(call => {
|
||||
const isLeader = call.args[0]._id === group.leader;
|
||||
const isSubscriptionBegin = call.args[1] === 'group-subscription-begins';
|
||||
return isLeader && isSubscriptionBegin;
|
||||
});
|
||||
expect(leaderCall.args[0]._id).to.equal(group.leader);
|
||||
expect(leaderCall.args[1]).to.equal('group-subscription-begins');
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (Stripe)', async () => {
|
||||
@@ -242,28 +205,17 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledThrice;
|
||||
const recipientCall = sender.sendTxn.getCalls().find(call => {
|
||||
const isRecipient = call.args[0]._id === recipient._id;
|
||||
const isJoin = call.args[1] === 'group-member-join';
|
||||
return isRecipient && isJoin;
|
||||
});
|
||||
expect(recipientCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(recipientCall.args[1]).to.equal('group-member-join');
|
||||
expect(recipientCall.args[2]).to.eql([
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{ name: 'LEADER', content: user.profile.name },
|
||||
{ name: 'GROUP_NAME', content: group.name },
|
||||
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL },
|
||||
]);
|
||||
|
||||
// confirm that the other email sent is not a cancel-subscription email:
|
||||
const leaderCall = sender.sendTxn.getCalls().find(call => {
|
||||
const isLeader = call.args[0]._id === group.leader;
|
||||
const isSubscriptionBegin = call.args[1] === 'group-subscription-begins';
|
||||
return isLeader && isSubscriptionBegin;
|
||||
});
|
||||
expect(leaderCall.args[0]._id).to.equal(group.leader);
|
||||
expect(leaderCall.args[1]).to.equal('group-subscription-begins');
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (Amazon)', async () => {
|
||||
@@ -286,28 +238,17 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledThrice;
|
||||
const recipientCall = sender.sendTxn.getCalls().find(call => {
|
||||
const isRecipient = call.args[0]._id === recipient._id;
|
||||
const isJoin = call.args[1] === 'group-member-join';
|
||||
return isRecipient && isJoin;
|
||||
});
|
||||
expect(recipientCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(recipientCall.args[1]).to.equal('group-member-join');
|
||||
expect(recipientCall.args[2]).to.eql([
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{ name: 'LEADER', content: user.profile.name },
|
||||
{ name: 'GROUP_NAME', content: group.name },
|
||||
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL },
|
||||
]);
|
||||
|
||||
// confirm that the other email sent is not a cancel-subscription email:
|
||||
const leaderCall = sender.sendTxn.getCalls().find(call => {
|
||||
const isLeader = call.args[0]._id === group.leader;
|
||||
const isSubscriptionBegin = call.args[1] === 'group-subscription-begins';
|
||||
return isLeader && isSubscriptionBegin;
|
||||
});
|
||||
expect(leaderCall.args[0]._id).to.equal(group.leader);
|
||||
expect(leaderCall.args[1]).to.equal('group-subscription-begins');
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
|
||||
amzLib.getBillingAgreementDetails.restore();
|
||||
});
|
||||
@@ -334,28 +275,17 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledThrice;
|
||||
const recipientCall = sender.sendTxn.getCalls().find(call => {
|
||||
const isRecipient = call.args[0]._id === recipient._id;
|
||||
const isJoin = call.args[1] === 'group-member-join';
|
||||
return isRecipient && isJoin;
|
||||
});
|
||||
expect(recipientCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(recipientCall.args[1]).to.equal('group-member-join');
|
||||
expect(recipientCall.args[2]).to.eql([
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{ name: 'LEADER', content: user.profile.name },
|
||||
{ name: 'GROUP_NAME', content: group.name },
|
||||
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL },
|
||||
]);
|
||||
|
||||
// confirm that the other email sent is not a cancel-subscription email:
|
||||
const leaderCall = sender.sendTxn.getCalls().find(call => {
|
||||
const isLeader = call.args[0]._id === group.leader;
|
||||
const isSubscriptionBegin = call.args[1] === 'group-subscription-begins';
|
||||
return isLeader && isSubscriptionBegin;
|
||||
});
|
||||
expect(leaderCall.args[0]._id).to.equal(group.leader);
|
||||
expect(leaderCall.args[1]).to.equal('group-subscription-begins');
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
|
||||
paypalPayments.paypalBillingAgreementGet.restore();
|
||||
paypalPayments.paypalBillingAgreementCancel.restore();
|
||||
@@ -372,6 +302,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -424,6 +356,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -485,6 +419,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
data.gift = undefined;
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -519,6 +455,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
data.gift = undefined;
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -545,6 +483,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -571,6 +511,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -599,6 +541,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -622,6 +566,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await recipient.cancelSubscription();
|
||||
@@ -643,6 +589,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await recipient.cancelSubscription();
|
||||
@@ -663,6 +611,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -682,6 +632,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -701,6 +653,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -734,6 +688,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -757,6 +713,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -768,15 +726,13 @@ describe('Purchasing a group plan for group', () => {
|
||||
const group2 = generateGroup({
|
||||
name: 'test group2',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
data.groupId = group2._id;
|
||||
await group2.save();
|
||||
recipient.guilds.push(group2._id);
|
||||
await recipient.save();
|
||||
user.guilds.push(group2._id);
|
||||
await user.save();
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -801,6 +757,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -812,22 +770,17 @@ describe('Purchasing a group plan for group', () => {
|
||||
const group2 = generateGroup({
|
||||
name: 'test group2',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
data.groupId = group2._id;
|
||||
await group2.save();
|
||||
recipient.guilds.push(group2._id);
|
||||
await recipient.save();
|
||||
user.guilds.push(group2._id);
|
||||
await user.save();
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
const updatedGroup = await Group.findById(group._id).exec();
|
||||
updatedGroup.memberCount = 2;
|
||||
await updatedGroup.save();
|
||||
|
||||
await updatedGroup.leave(recipient);
|
||||
|
||||
updatedUser = await User.findById(recipient._id).exec();
|
||||
@@ -853,6 +806,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -880,6 +835,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -907,6 +864,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
@@ -935,6 +894,8 @@ describe('Purchasing a group plan for group', () => {
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import moment from 'moment';
|
||||
|
||||
import * as sender from '../../../../../website/server/libs/email';
|
||||
import common from '../../../../../website/common';
|
||||
import api from '../../../../../website/server/libs/payments/payments';
|
||||
import * as analytics from '../../../../../website/server/libs/analyticsService';
|
||||
import * as notifications from '../../../../../website/server/libs/pushNotifications';
|
||||
@@ -10,7 +9,6 @@ import { translate as t } from '../../../../helpers/api-integration/v3';
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
import * as worldState from '../../../../../website/server/libs/worldState';
|
||||
|
||||
describe('payments/index', () => {
|
||||
let user; let group; let data; let
|
||||
@@ -32,8 +30,8 @@ describe('payments/index', () => {
|
||||
|
||||
sandbox.stub(sender, 'sendTxn');
|
||||
sandbox.stub(user, 'sendMessage');
|
||||
sandbox.stub(analytics.mockAnalyticsService, 'trackPurchase');
|
||||
sandbox.stub(analytics.mockAnalyticsService, 'track');
|
||||
sandbox.stub(analytics, 'trackPurchase');
|
||||
sandbox.stub(analytics, 'track');
|
||||
sandbox.stub(notifications, 'sendNotification');
|
||||
|
||||
data = {
|
||||
@@ -209,6 +207,17 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.txnCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('sends a private message about the gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
const msg = '`Hello recipient, sender has sent you 3 months of subscription!`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledOnce;
|
||||
expect(user.sendMessage).to.be.calledWith(
|
||||
recipient,
|
||||
{ receiverMsg: msg, senderMsg: msg, save: false },
|
||||
);
|
||||
});
|
||||
|
||||
it('sends an email about the gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -226,8 +235,8 @@ describe('payments/index', () => {
|
||||
it('tracks subscription purchase as gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledOnce;
|
||||
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledWith({
|
||||
expect(analytics.trackPurchase).to.be.calledOnce;
|
||||
expect(analytics.trackPurchase).to.be.calledWith({
|
||||
uuid: user._id,
|
||||
groupId: undefined,
|
||||
itemPurchased: 'Subscription',
|
||||
@@ -237,116 +246,12 @@ describe('payments/index', () => {
|
||||
quantity: 1,
|
||||
gift: true,
|
||||
purchaseValue: 15,
|
||||
firstPurchase: true,
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('No Active Promotion', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(worldState, 'getCurrentEvent').returns(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
worldState.getCurrentEvent.restore();
|
||||
});
|
||||
|
||||
it('sends a private message about the gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
const msg = '`Hello recipient, sender has sent you 3 months of subscription!`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledOnce;
|
||||
expect(user.sendMessage).to.be.calledWith(
|
||||
recipient,
|
||||
{ receiverMsg: msg, senderMsg: msg, save: false },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('Active Promotion', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(worldState, 'getCurrentEvent').returns({
|
||||
...common.content.events.winter2021Promo,
|
||||
event: 'winter2021',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
worldState.getCurrentEvent.restore();
|
||||
});
|
||||
|
||||
it('creates a gift subscription for purchaser and recipient if none exist', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(user.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
|
||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => {
|
||||
user.purchased.plan = plan;
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||
|
||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('adds extraMonths to existing subscription for recipient and creates a gift subscription for purchaser without sub', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||
|
||||
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(user.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('adds extraMonths to existing subscriptions for purchaser and recipient', async () => {
|
||||
user.purchased.plan = plan;
|
||||
recipient.purchased.plan = plan;
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||
});
|
||||
|
||||
it('sends a private message about the promotion', async () => {
|
||||
await api.createSubscription(data);
|
||||
const msg = '`Hello sender, you received 3 months of subscription as part of our holiday gift-giving promotion!`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledTwice;
|
||||
expect(user.sendMessage).to.be.calledWith(user, { receiverMsg: msg, save: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Purchasing a subscription for self', () => {
|
||||
@@ -427,8 +332,8 @@ describe('payments/index', () => {
|
||||
it('tracks subscription purchase', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledOnce;
|
||||
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledWith({
|
||||
expect(analytics.trackPurchase).to.be.calledOnce;
|
||||
expect(analytics.trackPurchase).to.be.calledWith({
|
||||
uuid: user._id,
|
||||
groupId: undefined,
|
||||
itemPurchased: 'Subscription',
|
||||
@@ -438,7 +343,6 @@ describe('payments/index', () => {
|
||||
quantity: 1,
|
||||
gift: false,
|
||||
purchaseValue: 15,
|
||||
firstPurchase: true,
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
@@ -515,22 +419,10 @@ describe('payments/index', () => {
|
||||
});
|
||||
|
||||
context('Mystery Items', () => {
|
||||
let clock;
|
||||
const mayMysteryItem = 'armor_mystery_201605';
|
||||
|
||||
beforeEach(() => {
|
||||
const mayMysteryItemTimeframe = new Date(2016, 4, 31); // May 31st 2016
|
||||
clock = sinon.useFakeTimers({
|
||||
now: mayMysteryItemTimeframe,
|
||||
toFake: ['Date'],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (clock) clock.restore();
|
||||
});
|
||||
|
||||
it('awards mystery items when within the timeframe for a mystery item', async () => {
|
||||
const mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
|
||||
const fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
|
||||
|
||||
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
||||
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
@@ -543,9 +435,14 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount + 1);
|
||||
expect(user.notifications[0].type).to.equal('NEW_MYSTERY_ITEMS');
|
||||
|
||||
fakeClock.restore();
|
||||
});
|
||||
|
||||
it('does not award mystery item when user already owns the item', async () => {
|
||||
const mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
|
||||
const fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
|
||||
const mayMysteryItem = 'armor_mystery_201605';
|
||||
user.items.gear.owned[mayMysteryItem] = true;
|
||||
|
||||
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
||||
@@ -554,9 +451,14 @@ describe('payments/index', () => {
|
||||
|
||||
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(1);
|
||||
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
|
||||
|
||||
fakeClock.restore();
|
||||
});
|
||||
|
||||
it('does not award mystery item when user already has the item in the mystery box', async () => {
|
||||
const mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
|
||||
const fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
|
||||
const mayMysteryItem = 'armor_mystery_201605';
|
||||
user.purchased.plan.mysteryItems = [mayMysteryItem];
|
||||
|
||||
sandbox.spy(user.purchased.plan.mysteryItems, 'push');
|
||||
@@ -566,6 +468,8 @@ describe('payments/index', () => {
|
||||
|
||||
expect(user.purchased.plan.mysteryItems.push).to.be.calledOnce;
|
||||
expect(user.purchased.plan.mysteryItems.push).to.be.calledWith('head_mystery_201605');
|
||||
|
||||
fakeClock.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -651,7 +555,6 @@ describe('payments/index', () => {
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
user,
|
||||
gemsBlock: common.content.gems['21gems'],
|
||||
paymentMethod: 'payment',
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
@@ -661,6 +564,22 @@ describe('payments/index', () => {
|
||||
});
|
||||
|
||||
context('Self Purchase', () => {
|
||||
it('amount property defaults to 5', async () => {
|
||||
expect(user.balance).to.eql(0);
|
||||
|
||||
await api.buyGems(data);
|
||||
|
||||
expect(user.balance).to.eql(5);
|
||||
});
|
||||
|
||||
it('can set amount that is purchased', async () => {
|
||||
data.amount = 13;
|
||||
|
||||
await api.buyGems(data);
|
||||
|
||||
expect(user.balance).to.eql(13);
|
||||
});
|
||||
|
||||
it('sends a donation email', async () => {
|
||||
await api.buyGems(data);
|
||||
|
||||
@@ -669,51 +588,6 @@ describe('payments/index', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('No Active Promotion', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(worldState, 'getCurrentEvent').returns(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
worldState.getCurrentEvent.restore();
|
||||
});
|
||||
|
||||
it('does not apply a discount', async () => {
|
||||
const balanceBefore = user.balance;
|
||||
|
||||
await api.buyGems(data);
|
||||
|
||||
const balanceAfter = user.balance;
|
||||
const balanceDiff = balanceAfter - balanceBefore;
|
||||
|
||||
expect(balanceDiff * 4).to.eql(21);
|
||||
});
|
||||
});
|
||||
|
||||
context('Active Promotion', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(worldState, 'getCurrentEvent').returns({
|
||||
...common.content.events.fall2020,
|
||||
event: 'fall2020',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
worldState.getCurrentEvent.restore();
|
||||
});
|
||||
|
||||
it('applies a discount', async () => {
|
||||
const balanceBefore = user.balance;
|
||||
|
||||
await api.buyGems(data);
|
||||
|
||||
const balanceAfter = user.balance;
|
||||
const balanceDiff = balanceAfter - balanceBefore;
|
||||
|
||||
expect(balanceDiff * 4).to.eql(30);
|
||||
});
|
||||
});
|
||||
|
||||
context('Gift', () => {
|
||||
let recipient;
|
||||
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
describe('paypal - checkout success', () => {
|
||||
describe('checkout success', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
let user; let gift; let customerId; let
|
||||
paymentId;
|
||||
const gemsBlockKey = '21gems'; const gemsBlock = common.content.gems[gemsBlockKey];
|
||||
let paypalPaymentExecuteStub; let paymentBuyGemsStub; let
|
||||
paymentsCreateSubscritionStub;
|
||||
|
||||
@@ -30,7 +28,7 @@ describe('paypal - checkout success', () => {
|
||||
|
||||
it('purchases gems', async () => {
|
||||
await paypalPayments.checkoutSuccess({
|
||||
user, gift, paymentId, customerId, gemsBlock: gemsBlockKey,
|
||||
user, gift, paymentId, customerId,
|
||||
});
|
||||
|
||||
expect(paypalPaymentExecuteStub).to.be.calledOnce;
|
||||
@@ -40,7 +38,6 @@ describe('paypal - checkout success', () => {
|
||||
user,
|
||||
customerId,
|
||||
paymentMethod: 'Paypal',
|
||||
gemsBlock,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,15 +4,12 @@ import nconf from 'nconf';
|
||||
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import common from '../../../../../../website/common';
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
const { i18n } = common;
|
||||
|
||||
describe('paypal - checkout', () => {
|
||||
describe('checkout', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const gemsBlockKey = '21gems';
|
||||
let paypalPaymentCreateStub;
|
||||
let approvalHerf;
|
||||
|
||||
@@ -49,7 +46,6 @@ describe('paypal - checkout', () => {
|
||||
.resolves({
|
||||
links: [{ rel: 'approval_url', href: approvalHerf }],
|
||||
});
|
||||
sandbox.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -57,11 +53,10 @@ describe('paypal - checkout', () => {
|
||||
});
|
||||
|
||||
it('creates a link for gem purchases', async () => {
|
||||
const link = await paypalPayments.checkout({ user: new User(), gemsBlock: gemsBlockKey });
|
||||
const link = await paypalPayments.checkout({ user: new User() });
|
||||
|
||||
expect(gems.validateGiftMessage).to.not.be.called;
|
||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 4.99));
|
||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 5.00));
|
||||
expect(link).to.eql(approvalHerf);
|
||||
});
|
||||
|
||||
@@ -88,27 +83,14 @@ describe('paypal - checkout', () => {
|
||||
const user = new User();
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(paypalPayments.checkout({ user, gemsBlock: gemsBlockKey }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if the gems block is not valid', async () => {
|
||||
const user = new User();
|
||||
|
||||
await expect(paypalPayments.checkout({ user, gemsBlock: 'invalid' }))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: apiError('invalidGemsBlock'),
|
||||
name: 'BadRequest',
|
||||
});
|
||||
await expect(paypalPayments.checkout({ user })).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a link for gifting gems', async () => {
|
||||
const user = new User();
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
@@ -119,17 +101,14 @@ describe('paypal - checkout', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const link = await paypalPayments.checkout({ user, gift });
|
||||
const link = await paypalPayments.checkout({ gift });
|
||||
|
||||
expect(gems.validateGiftMessage).to.be.calledOnce;
|
||||
expect(gems.validateGiftMessage).to.be.calledWith(gift, user);
|
||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems (Gift)', '4.00'));
|
||||
expect(link).to.eql(approvalHerf);
|
||||
});
|
||||
|
||||
it('creates a link for gifting a subscription', async () => {
|
||||
const user = new User();
|
||||
const receivingUser = new User();
|
||||
receivingUser.save();
|
||||
const gift = {
|
||||
@@ -140,10 +119,7 @@ describe('paypal - checkout', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const link = await paypalPayments.checkout({ user, gift });
|
||||
|
||||
expect(gems.validateGiftMessage).to.be.calledOnce;
|
||||
expect(gems.validateGiftMessage).to.be.calledWith(gift, user);
|
||||
const link = await paypalPayments.checkout({ gift });
|
||||
|
||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('mo. Habitica Subscription (Gift)', '15.00'));
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
|
||||
describe('paypal - ipn', () => {
|
||||
describe('ipn', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
let user; let group; let txn_type; let userPaymentId; let
|
||||
groupPaymentId;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { createNonLeaderGroupMember } from '../paymentHelpers';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('paypal - subscribeCancel', () => {
|
||||
describe('subscribeCancel', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
let user; let group; let groupId; let customerId; let groupCustomerId; let
|
||||
nextBillingDate;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
describe('paypal - subscribeSuccess', () => {
|
||||
describe('subscribeSuccess', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
let user; let group; let block; let groupId; let token; let headers; let
|
||||
customerId;
|
||||
|
||||
@@ -8,7 +8,7 @@ import common from '../../../../../../website/common';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('paypal - subscribe', () => {
|
||||
describe('subscribe', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
let coupon; let sub; let
|
||||
approvalHerf;
|
||||
|
||||
@@ -4,7 +4,7 @@ import api from '../../../../../../website/server/libs/payments/payments';
|
||||
|
||||
const groupPlanId = api.constants.GROUP_PLAN_CUSTOMER_ID;
|
||||
|
||||
describe('stripe - #calculateSubscriptionTerminationDate', () => {
|
||||
describe('#calculateSubscriptionTerminationDate', () => {
|
||||
let plan;
|
||||
let nextBill;
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('cancel subscription', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const stripe = stripeModule('test');
|
||||
let user; let groupId; let
|
||||
group;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = subKey;
|
||||
await group.save();
|
||||
|
||||
groupId = group._id;
|
||||
});
|
||||
|
||||
it('throws an error if there is no customer id', async () => {
|
||||
user.purchased.plan.customerId = undefined;
|
||||
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if the group is not found', async () => {
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: 'fake-group',
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if user is not the group leader', async () => {
|
||||
const nonLeader = new User();
|
||||
nonLeader.guilds.push(groupId);
|
||||
await nonLeader.save();
|
||||
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user: nonLeader,
|
||||
groupId,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let stripeDeleteCustomerStub; let paymentsCancelSubStub;
|
||||
let stripeRetrieveStub; let subscriptionId; let
|
||||
currentPeriodEndTimeStamp;
|
||||
|
||||
beforeEach(() => {
|
||||
subscriptionId = 'subId';
|
||||
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').resolves({});
|
||||
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
|
||||
currentPeriodEndTimeStamp = (new Date()).getTime();
|
||||
stripeRetrieveStub = sinon.stub(stripe.customers, 'retrieve')
|
||||
.resolves({
|
||||
subscriptions: {
|
||||
data: [{
|
||||
id: subscriptionId,
|
||||
current_period_end: currentPeriodEndTimeStamp,
|
||||
}], // eslint-disable-line camelcase
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.customers.del.restore();
|
||||
stripe.customers.retrieve.restore();
|
||||
payments.cancelSubscription.restore();
|
||||
});
|
||||
|
||||
it('cancels a user subscription', async () => {
|
||||
await stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeDeleteCustomerStub).to.be.calledOnce;
|
||||
expect(stripeDeleteCustomerStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(stripeRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(paymentsCancelSubStub).to.be.calledOnce;
|
||||
expect(paymentsCancelSubStub).to.be.calledWith({
|
||||
user,
|
||||
groupId: undefined,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
await stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeDeleteCustomerStub).to.be.calledOnce;
|
||||
expect(stripeDeleteCustomerStub).to.be.calledWith(group.purchased.plan.customerId);
|
||||
expect(stripeRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(paymentsCancelSubStub).to.be.calledOnce;
|
||||
expect(paymentsCancelSubStub).to.be.calledWith({
|
||||
user,
|
||||
groupId,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,321 @@
|
||||
import stripeModule from 'stripe';
|
||||
import cc from 'coupon-code';
|
||||
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import { model as Coupon } from '../../../../../../website/server/models/coupon';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('checkout with subscription', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const stripe = stripeModule('test');
|
||||
let user; let group; let data; let gift; let sub;
|
||||
let groupId; let email; let headers; let coupon;
|
||||
let customerIdResponse; let subscriptionId; let
|
||||
token;
|
||||
let spy;
|
||||
let stripeCreateCustomerSpy;
|
||||
let stripePaymentsCreateSubSpy;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = subKey;
|
||||
await group.save();
|
||||
|
||||
sub = {
|
||||
key: 'basic_3mo',
|
||||
};
|
||||
|
||||
data = {
|
||||
user,
|
||||
sub,
|
||||
customerId: 'customer-id',
|
||||
paymentMethod: 'Payment Method',
|
||||
};
|
||||
|
||||
email = 'example@example.com';
|
||||
customerIdResponse = 'test-id';
|
||||
subscriptionId = 'test-sub-id';
|
||||
token = 'test-token';
|
||||
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.resolves;
|
||||
|
||||
stripeCreateCustomerSpy = sinon.stub(stripe.customers, 'create');
|
||||
const stripCustomerResponse = {
|
||||
id: customerIdResponse,
|
||||
subscriptions: {
|
||||
data: [{ id: subscriptionId }],
|
||||
},
|
||||
};
|
||||
stripeCreateCustomerSpy.resolves(stripCustomerResponse);
|
||||
|
||||
stripePaymentsCreateSubSpy = sinon.stub(payments, 'createSubscription');
|
||||
stripePaymentsCreateSubSpy.resolves({});
|
||||
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.subscriptions.update.restore();
|
||||
stripe.customers.create.restore();
|
||||
payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should throw an error if we are missing a token', async () => {
|
||||
await expect(stripePayments.checkout({
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: 'Missing req.body.id',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when coupon code is missing', async () => {
|
||||
sub.discount = 40;
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('couponCodeRequired'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when coupon code is invalid', async () => {
|
||||
sub.discount = 40;
|
||||
sub.key = 'google_6mo';
|
||||
coupon = 'example-coupon';
|
||||
|
||||
const couponModel = new Coupon();
|
||||
couponModel.event = 'google_6mo';
|
||||
await couponModel.save();
|
||||
|
||||
sinon.stub(cc, 'validate').returns('invalid');
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('invalidCoupon'),
|
||||
});
|
||||
cc.validate.restore();
|
||||
});
|
||||
|
||||
it('subscribes with stripe with a coupon', async () => {
|
||||
sub.discount = 40;
|
||||
sub.key = 'google_6mo';
|
||||
coupon = 'example-coupon';
|
||||
|
||||
const couponModel = new Coupon();
|
||||
couponModel.event = 'google_6mo';
|
||||
const updatedCouponModel = await couponModel.save();
|
||||
|
||||
sinon.stub(cc, 'validate').returns(updatedCouponModel._id);
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy).to.be.calledOnce;
|
||||
expect(stripeCreateCustomerSpy).to.be.calledWith({
|
||||
email,
|
||||
metadata: { uuid: user._id },
|
||||
card: token,
|
||||
plan: sub.key,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
sub,
|
||||
headers,
|
||||
groupId: undefined,
|
||||
subscriptionId: undefined,
|
||||
});
|
||||
|
||||
cc.validate.restore();
|
||||
});
|
||||
|
||||
it('subscribes a user', async () => {
|
||||
sub = data.sub;
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy).to.be.calledOnce;
|
||||
expect(stripeCreateCustomerSpy).to.be.calledWith({
|
||||
email,
|
||||
metadata: { uuid: user._id },
|
||||
card: token,
|
||||
plan: sub.key,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
sub,
|
||||
headers,
|
||||
groupId: undefined,
|
||||
subscriptionId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes a group', async () => {
|
||||
token = 'test-token';
|
||||
sub = data.sub;
|
||||
groupId = group._id;
|
||||
email = 'test@test.com';
|
||||
|
||||
// Add user to group
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
headers = {};
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy).to.be.calledOnce;
|
||||
expect(stripeCreateCustomerSpy).to.be.calledWith({
|
||||
email,
|
||||
metadata: { uuid: user._id },
|
||||
card: token,
|
||||
plan: sub.key,
|
||||
quantity: 3,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
sub,
|
||||
headers,
|
||||
groupId,
|
||||
subscriptionId,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes a group with the correct number of group members', async () => {
|
||||
token = 'test-token';
|
||||
sub = data.sub;
|
||||
groupId = group._id;
|
||||
email = 'test@test.com';
|
||||
headers = {};
|
||||
|
||||
// Add user to group
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
user = new User();
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
group.memberCount = 2;
|
||||
await group.save();
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy).to.be.calledOnce;
|
||||
expect(stripeCreateCustomerSpy).to.be.calledWith({
|
||||
email,
|
||||
metadata: { uuid: user._id },
|
||||
card: token,
|
||||
plan: sub.key,
|
||||
quantity: 4,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
sub,
|
||||
headers,
|
||||
groupId,
|
||||
subscriptionId,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,482 +1,210 @@
|
||||
import stripeModule from 'stripe';
|
||||
import nconf from 'nconf';
|
||||
import common from '../../../../../../website/common';
|
||||
import * as subscriptions from '../../../../../../website/server/libs/payments/stripe/subscriptions';
|
||||
import * as oneTimePayments from '../../../../../../website/server/libs/payments/stripe/oneTimePayments';
|
||||
import {
|
||||
createCheckoutSession,
|
||||
createEditCardCheckoutSession,
|
||||
} from '../../../../../../website/server/libs/payments/stripe/checkout';
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../../website/server/models/group';
|
||||
import * as gems from '../../../../../../website/server/libs/payments/gems';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('Stripe - Checkout', () => {
|
||||
describe('checkout', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const stripe = stripeModule('test');
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
const redirectUrls = {
|
||||
success_url: `${BASE_URL}/redirect/stripe-success-checkout`,
|
||||
cancel_url: `${BASE_URL}/redirect/stripe-error-checkout`,
|
||||
};
|
||||
let stripeChargeStub; let paymentBuyGemsStub; let
|
||||
paymentCreateSubscritionStub;
|
||||
let user; let gift; let groupId; let email; let headers; let coupon; let customerIdResponse; let
|
||||
token;
|
||||
|
||||
describe('createCheckoutSession', () => {
|
||||
let user;
|
||||
const sessionId = 'session-id';
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
sandbox.stub(stripe.checkout.sessions, 'create').returns(sessionId);
|
||||
sandbox.stub(gems, 'validateGiftMessage');
|
||||
});
|
||||
token = 'test-token';
|
||||
|
||||
it('gems', async () => {
|
||||
const amount = 999;
|
||||
const gemsBlockKey = '21gems';
|
||||
sandbox.stub(oneTimePayments, 'getOneTimePaymentInfo').returns({
|
||||
amount,
|
||||
gemsBlock: common.content.gems[gemsBlockKey],
|
||||
customerIdResponse = 'example-customerIdResponse';
|
||||
const stripCustomerResponse = {
|
||||
id: customerIdResponse,
|
||||
};
|
||||
stripeChargeStub = sinon.stub(stripe.charges, 'create').resolves(stripCustomerResponse);
|
||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.charges.create.restore();
|
||||
payments.buyGems.restore();
|
||||
payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should error if there is no token', async () => {
|
||||
await expect(stripePayments.checkout({
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: 'Missing req.body.id',
|
||||
name: 'BadRequest',
|
||||
});
|
||||
});
|
||||
|
||||
const res = await createCheckoutSession({ user, gemsBlock: gemsBlockKey }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
|
||||
const metadata = {
|
||||
type: 'gems',
|
||||
userId: user._id,
|
||||
gift: undefined,
|
||||
sub: undefined,
|
||||
gemsBlock: gemsBlockKey,
|
||||
};
|
||||
|
||||
expect(gems.validateGiftMessage).to.not.be.called;
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce;
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledWith(gemsBlockKey, undefined, user);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price_data: {
|
||||
product_data: {
|
||||
name: common.i18n.t('nGems', { nGems: 21 }),
|
||||
},
|
||||
unit_amount: amount,
|
||||
currency: 'usd',
|
||||
},
|
||||
quantity: 1,
|
||||
}],
|
||||
mode: 'payment',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('gems gift', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
it('should error if gem amount is too low', async () => {
|
||||
const receivingUser = new User();
|
||||
receivingUser.save();
|
||||
gift = {
|
||||
type: 'gems',
|
||||
gems: {
|
||||
amount: 0,
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 4,
|
||||
},
|
||||
};
|
||||
const amount = 100;
|
||||
sandbox.stub(oneTimePayments, 'getOneTimePaymentInfo').returns({
|
||||
amount,
|
||||
gemsBlock: null,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: 'Amount must be at least 1.',
|
||||
name: 'BadRequest',
|
||||
});
|
||||
});
|
||||
|
||||
const res = await createCheckoutSession({ user, gift }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
it('should error if user cannot get gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
const metadata = {
|
||||
type: 'gift-gems',
|
||||
userId: user._id,
|
||||
gift: JSON.stringify(gift),
|
||||
sub: undefined,
|
||||
gemsBlock: undefined,
|
||||
};
|
||||
|
||||
expect(gems.validateGiftMessage).to.be.calledOnce;
|
||||
expect(gems.validateGiftMessage).to.be.calledWith(gift, user);
|
||||
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce;
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledWith(undefined, gift, user);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price_data: {
|
||||
product_data: {
|
||||
name: common.i18n.t('nGemsGift', { nGems: 4 }),
|
||||
},
|
||||
unit_amount: amount,
|
||||
currency: 'usd',
|
||||
},
|
||||
quantity: 1,
|
||||
}],
|
||||
mode: 'payment',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscription gift', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const subKey = 'basic_3mo';
|
||||
|
||||
const gift = {
|
||||
type: 'subscription',
|
||||
uuid: receivingUser._id,
|
||||
subscription: {
|
||||
key: subKey,
|
||||
},
|
||||
};
|
||||
const amount = 1500;
|
||||
sandbox.stub(oneTimePayments, 'getOneTimePaymentInfo').returns({
|
||||
amount,
|
||||
gemsBlock: null,
|
||||
subscription: common.content.subscriptionBlocks[subKey],
|
||||
});
|
||||
|
||||
const res = await createCheckoutSession({ user, gift }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
|
||||
const metadata = {
|
||||
type: 'gift-sub',
|
||||
userId: user._id,
|
||||
gift: JSON.stringify(gift),
|
||||
sub: undefined,
|
||||
gemsBlock: undefined,
|
||||
};
|
||||
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce;
|
||||
expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledWith(undefined, gift, user);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price_data: {
|
||||
product_data: {
|
||||
name: common.i18n.t('nMonthsSubscriptionGift', { nMonths: 3 }),
|
||||
},
|
||||
unit_amount: amount,
|
||||
currency: 'usd',
|
||||
},
|
||||
quantity: 1,
|
||||
}],
|
||||
mode: 'payment',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscription', async () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const coupon = null;
|
||||
sandbox.stub(subscriptions, 'checkSubData').returns(undefined);
|
||||
const sub = common.content.subscriptionBlocks[subKey];
|
||||
|
||||
const res = await createCheckoutSession({ user, sub, coupon }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
|
||||
const metadata = {
|
||||
type: 'subscription',
|
||||
userId: user._id,
|
||||
gift: undefined,
|
||||
sub: JSON.stringify(sub),
|
||||
};
|
||||
|
||||
expect(subscriptions.checkSubData).to.be.calledOnce;
|
||||
expect(subscriptions.checkSubData).to.be.calledWith(sub, false, coupon);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price: sub.key,
|
||||
quantity: 1,
|
||||
// @TODO proper copy
|
||||
}],
|
||||
mode: 'subscription',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if group does not exists', async () => {
|
||||
const groupId = 'invalid';
|
||||
sandbox.stub(Group.prototype, 'getMemberCount').resolves(4);
|
||||
|
||||
const subKey = 'group_monthly';
|
||||
const coupon = null;
|
||||
const sub = common.content.subscriptionBlocks[subKey];
|
||||
|
||||
await expect(createCheckoutSession({
|
||||
user, sub, coupon, groupId,
|
||||
}, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('group plan', async () => {
|
||||
const group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
const groupId = group._id;
|
||||
await group.save();
|
||||
sandbox.stub(Group.prototype, 'getMemberCount').resolves(4);
|
||||
|
||||
// Add user to group
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
const subKey = 'group_monthly';
|
||||
const coupon = null;
|
||||
sandbox.stub(subscriptions, 'checkSubData').returns(undefined);
|
||||
const sub = common.content.subscriptionBlocks[subKey];
|
||||
|
||||
const res = await createCheckoutSession({
|
||||
user, sub, coupon, groupId,
|
||||
}, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
|
||||
const metadata = {
|
||||
type: 'subscription',
|
||||
userId: user._id,
|
||||
gift: undefined,
|
||||
sub: JSON.stringify(sub),
|
||||
groupId,
|
||||
};
|
||||
|
||||
expect(Group.prototype.getMemberCount).to.be.calledOnce;
|
||||
expect(subscriptions.checkSubData).to.be.calledOnce;
|
||||
expect(subscriptions.checkSubData).to.be.calledWith(sub, true, coupon);
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
line_items: [{
|
||||
price: sub.key,
|
||||
quantity: 6,
|
||||
// @TODO proper copy
|
||||
}],
|
||||
mode: 'subscription',
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
// no gift, sub or gem payment
|
||||
it('throws if type is invalid', async () => {
|
||||
await expect(createCheckoutSession({ user }, stripe))
|
||||
.to.eventually.be.rejected;
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe)).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
describe('createEditCardCheckoutSession', () => {
|
||||
let user;
|
||||
const sessionId = 'session-id';
|
||||
const customerId = 'customerId';
|
||||
const subscriptionId = 'subscription-id';
|
||||
let subscriptionsListStub;
|
||||
it('should purchase gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
sandbox.stub(stripe.checkout.sessions, 'create').returns(sessionId);
|
||||
subscriptionsListStub = sandbox.stub(stripe.subscriptions, 'list');
|
||||
subscriptionsListStub.resolves({ data: [{ id: subscriptionId }] });
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeChargeStub).to.be.calledOnce;
|
||||
expect(stripeChargeStub).to.be.calledWith({
|
||||
amount: 500,
|
||||
currency: 'usd',
|
||||
card: token,
|
||||
});
|
||||
|
||||
it('throws if no valid data is supplied', async () => {
|
||||
await expect(createEditCardCheckoutSession({}, stripe))
|
||||
.to.eventually.be.rejected;
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Stripe',
|
||||
gift,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('should gift gems', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
},
|
||||
};
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeChargeStub).to.be.calledOnce;
|
||||
expect(stripeChargeStub).to.be.calledWith({
|
||||
amount: '400',
|
||||
currency: 'usd',
|
||||
card: token,
|
||||
});
|
||||
|
||||
it('throws if customer does not exists', async () => {
|
||||
await expect(createEditCardCheckoutSession({ user }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Gift',
|
||||
gift,
|
||||
});
|
||||
});
|
||||
|
||||
it('should gift a subscription', async () => {
|
||||
const receivingUser = new User();
|
||||
receivingUser.save();
|
||||
gift = {
|
||||
type: 'subscription',
|
||||
subscription: {
|
||||
key: subKey,
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
gift.member = receivingUser;
|
||||
expect(stripeChargeStub).to.be.calledOnce;
|
||||
expect(stripeChargeStub).to.be.calledWith({
|
||||
amount: '1500',
|
||||
currency: 'usd',
|
||||
card: token,
|
||||
});
|
||||
|
||||
it('throws if subscription does not exists', async () => {
|
||||
user.purchased.plan.customerId = customerId;
|
||||
subscriptionsListStub.resolves({ data: [] });
|
||||
|
||||
await expect(createEditCardCheckoutSession({ user }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
it('change card for user subscription', async () => {
|
||||
user.purchased.plan.customerId = customerId;
|
||||
|
||||
const metadata = {
|
||||
userId: user._id,
|
||||
type: 'edit-card-user',
|
||||
};
|
||||
|
||||
const res = await createEditCardCheckoutSession({ user }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
expect(subscriptionsListStub).to.be.calledOnce;
|
||||
expect(subscriptionsListStub).to.be.calledWith({ customer: customerId });
|
||||
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
mode: 'setup',
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
customer: customerId,
|
||||
setup_intent_data: {
|
||||
metadata: {
|
||||
customer_id: customerId,
|
||||
subscription_id: subscriptionId,
|
||||
},
|
||||
},
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if group does not exists', async () => {
|
||||
const groupId = 'invalid';
|
||||
|
||||
await expect(createEditCardCheckoutSession({ user, groupId }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('with group', () => {
|
||||
let group; let groupId;
|
||||
beforeEach(async () => {
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
groupId = group._id;
|
||||
await group.save();
|
||||
});
|
||||
|
||||
it('throws if user is not allowed to change group plan', async () => {
|
||||
const anotherUser = new User();
|
||||
anotherUser.guilds.push(groupId);
|
||||
await anotherUser.save();
|
||||
|
||||
await expect(createEditCardCheckoutSession({ user: anotherUser, groupId }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if customer does not exists (group)', async () => {
|
||||
await expect(createEditCardCheckoutSession({ user, groupId }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if subscription does not exists (group)', async () => {
|
||||
group.purchased.plan.customerId = customerId;
|
||||
subscriptionsListStub.resolves({ data: [] });
|
||||
|
||||
await expect(createEditCardCheckoutSession({ user, groupId }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('change card for group plans - leader', async () => {
|
||||
group.purchased.plan.customerId = customerId;
|
||||
await group.save();
|
||||
|
||||
const metadata = {
|
||||
userId: user._id,
|
||||
type: 'edit-card-group',
|
||||
groupId,
|
||||
};
|
||||
|
||||
const res = await createEditCardCheckoutSession({ user, groupId }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
expect(subscriptionsListStub).to.be.calledOnce;
|
||||
expect(subscriptionsListStub).to.be.calledWith({ customer: customerId });
|
||||
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
mode: 'setup',
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
customer: customerId,
|
||||
setup_intent_data: {
|
||||
metadata: {
|
||||
customer_id: customerId,
|
||||
subscription_id: subscriptionId,
|
||||
},
|
||||
},
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
|
||||
it('change card for group plans - plan owner', async () => {
|
||||
const anotherUser = new User();
|
||||
anotherUser.guilds.push(groupId);
|
||||
await anotherUser.save();
|
||||
|
||||
group.purchased.plan.customerId = customerId;
|
||||
group.purchased.plan.owner = anotherUser._id;
|
||||
await group.save();
|
||||
|
||||
const metadata = {
|
||||
userId: anotherUser._id,
|
||||
type: 'edit-card-group',
|
||||
groupId,
|
||||
};
|
||||
|
||||
const res = await createEditCardCheckoutSession({ user: anotherUser, groupId }, stripe);
|
||||
expect(res).to.equal(sessionId);
|
||||
expect(subscriptionsListStub).to.be.calledOnce;
|
||||
expect(subscriptionsListStub).to.be.calledWith({ customer: customerId });
|
||||
|
||||
expect(stripe.checkout.sessions.create).to.be.calledOnce;
|
||||
expect(stripe.checkout.sessions.create).to.be.calledWith({
|
||||
mode: 'setup',
|
||||
payment_method_types: ['card'],
|
||||
metadata,
|
||||
customer: customerId,
|
||||
setup_intent_data: {
|
||||
metadata: {
|
||||
customer_id: customerId,
|
||||
subscription_id: subscriptionId,
|
||||
},
|
||||
},
|
||||
...redirectUrls,
|
||||
});
|
||||
});
|
||||
expect(paymentCreateSubscritionStub).to.be.calledOnce;
|
||||
expect(paymentCreateSubscritionStub).to.be.calledWith({
|
||||
user,
|
||||
customerId: customerIdResponse,
|
||||
paymentMethod: 'Gift',
|
||||
gift,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import common from '../../../../../../website/common';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('edit subscription', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const stripe = stripeModule('test');
|
||||
let user; let groupId; let group; let
|
||||
token;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = subKey;
|
||||
await group.save();
|
||||
|
||||
groupId = group._id;
|
||||
|
||||
token = 'test-token';
|
||||
});
|
||||
|
||||
it('throws an error if there is no customer id', async () => {
|
||||
user.purchased.plan.customerId = undefined;
|
||||
|
||||
await expect(stripePayments.editSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if a token is not provided', async () => {
|
||||
await expect(stripePayments.editSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: 'Missing req.body.id',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if the group is not found', async () => {
|
||||
await expect(stripePayments.editSubscription({
|
||||
token,
|
||||
user,
|
||||
groupId: 'fake-group',
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if user is not the group leader', async () => {
|
||||
const nonLeader = new User();
|
||||
nonLeader.guilds.push(groupId);
|
||||
await nonLeader.save();
|
||||
|
||||
await expect(stripePayments.editSubscription({
|
||||
token,
|
||||
user: nonLeader,
|
||||
groupId,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let stripeListSubscriptionStub; let stripeUpdateSubscriptionStub; let
|
||||
subscriptionId;
|
||||
|
||||
beforeEach(() => {
|
||||
subscriptionId = 'subId';
|
||||
stripeListSubscriptionStub = sinon.stub(stripe.subscriptions, 'list')
|
||||
.resolves({
|
||||
data: [{ id: subscriptionId }],
|
||||
});
|
||||
|
||||
stripeUpdateSubscriptionStub = sinon.stub(stripe.subscriptions, 'update').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.subscriptions.list.restore();
|
||||
stripe.subscriptions.update.restore();
|
||||
});
|
||||
|
||||
it('edits a user subscription', async () => {
|
||||
await stripePayments.editSubscription({
|
||||
token,
|
||||
user,
|
||||
groupId: undefined,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeListSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeListSubscriptionStub).to.be.calledWith({
|
||||
customer: user.purchased.plan.customerId,
|
||||
});
|
||||
expect(stripeUpdateSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeUpdateSubscriptionStub).to.be.calledWith(
|
||||
subscriptionId,
|
||||
{ card: token },
|
||||
);
|
||||
});
|
||||
|
||||
it('edits a group subscription', async () => {
|
||||
await stripePayments.editSubscription({
|
||||
token,
|
||||
user,
|
||||
groupId,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeListSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeListSubscriptionStub).to.be.calledWith({
|
||||
customer: group.purchased.plan.customerId,
|
||||
});
|
||||
expect(stripeUpdateSubscriptionStub).to.be.calledOnce;
|
||||
expect(stripeUpdateSubscriptionStub).to.be.calledWith(
|
||||
subscriptionId,
|
||||
{ card: token },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+53
-127
@@ -1,5 +1,5 @@
|
||||
import stripeModule from 'stripe';
|
||||
import nconf from 'nconf';
|
||||
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
@@ -10,102 +10,76 @@ import stripePayments from '../../../../../../website/server/libs/payments/strip
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import common from '../../../../../../website/common';
|
||||
import logger from '../../../../../../website/server/libs/logger';
|
||||
import * as oneTimePayments from '../../../../../../website/server/libs/payments/stripe/oneTimePayments';
|
||||
import * as subscriptions from '../../../../../../website/server/libs/payments/stripe/subscriptions';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('Stripe - Webhooks', () => {
|
||||
const stripe = stripeModule('test');
|
||||
const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET');
|
||||
const headers = {};
|
||||
const body = {};
|
||||
|
||||
describe('all events', () => {
|
||||
let event;
|
||||
let constructEventStub;
|
||||
const eventType = 'account.updated';
|
||||
const event = { id: 123 };
|
||||
const eventRetrieved = { type: eventType };
|
||||
|
||||
beforeEach(() => {
|
||||
event = { type: 'payment_intent.created' };
|
||||
constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent');
|
||||
constructEventStub.returns(event);
|
||||
sandbox.stub(logger, 'error');
|
||||
sinon.stub(stripe.events, 'retrieve').resolves(eventRetrieved);
|
||||
sinon.stub(logger, 'error');
|
||||
});
|
||||
|
||||
it('throws if the event can\'t be validated', async () => {
|
||||
const err = new Error('fail');
|
||||
constructEventStub.throws(err);
|
||||
await expect(stripePayments.handleWebhooks({ body: event, headers }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: `Webhook Error: ${err.message}`,
|
||||
});
|
||||
|
||||
expect(logger.error).to.have.been.calledOnce;
|
||||
const calledWith = logger.error.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal('Error verifying Stripe webhook');
|
||||
expect(calledWith[1]).to.eql({ err });
|
||||
afterEach(() => {
|
||||
stripe.events.retrieve.restore();
|
||||
logger.error.restore();
|
||||
});
|
||||
|
||||
it('logs an error if an unsupported webhook event is passed', async () => {
|
||||
event.type = 'account.updated';
|
||||
await expect(stripePayments.handleWebhooks({ body, headers }, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: `Missing handler for Stripe webhook ${event.type}`,
|
||||
});
|
||||
|
||||
const error = new Error(`Missing handler for Stripe webhook ${eventType}`);
|
||||
await stripePayments.handleWebhooks({ requestBody: event }, stripe);
|
||||
expect(logger.error).to.have.been.calledOnce;
|
||||
|
||||
const calledWith = logger.error.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal('Error handling Stripe webhook');
|
||||
expect(calledWith[1].event).to.eql(event);
|
||||
expect(calledWith[1].err.message).to.eql(`Missing handler for Stripe webhook ${event.type}`);
|
||||
expect(calledWith[0].message).to.equal(error.message);
|
||||
expect(calledWith[1].event).to.equal(eventRetrieved);
|
||||
});
|
||||
|
||||
it('retrieves and validates the event from Stripe', async () => {
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(stripe.webhooks.constructEvent)
|
||||
.to.have.been.calledWith(body, undefined, endpointSecret);
|
||||
await stripePayments.handleWebhooks({ requestBody: event }, stripe);
|
||||
expect(stripe.events.retrieve).to.have.been.calledOnce;
|
||||
expect(stripe.events.retrieve).to.have.been.calledWith(event.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('customer.subscription.deleted', () => {
|
||||
const eventType = 'customer.subscription.deleted';
|
||||
let event;
|
||||
let constructEventStub;
|
||||
|
||||
beforeEach(() => {
|
||||
event = { type: eventType };
|
||||
constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent');
|
||||
constructEventStub.returns(event);
|
||||
sinon.stub(stripe.customers, 'del').resolves({});
|
||||
sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox.stub(stripe.customers, 'del').resolves({});
|
||||
sandbox.stub(payments, 'cancelSubscription').resolves({});
|
||||
afterEach(() => {
|
||||
stripe.customers.del.restore();
|
||||
payments.cancelSubscription.restore();
|
||||
});
|
||||
|
||||
it('does not do anything if event.request is not null (subscription cancelled manually)', async () => {
|
||||
constructEventStub.returns({
|
||||
it('does not do anything if event.request is null (subscription cancelled manually)', async () => {
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
request: { id: 123 },
|
||||
request: 123,
|
||||
});
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
await stripePayments.handleWebhooks({ requestBody: {} }, stripe);
|
||||
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(stripe.events.retrieve).to.have.been.calledOnce;
|
||||
expect(stripe.customers.del).to.not.have.been.called;
|
||||
expect(payments.cancelSubscription).to.not.have.been.called;
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
|
||||
describe('user subscription', () => {
|
||||
it('throws an error if the user is not found', async () => {
|
||||
const customerId = 456;
|
||||
constructEventStub.returns({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -116,10 +90,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: { id: null },
|
||||
request: null,
|
||||
});
|
||||
|
||||
await expect(stripePayments.handleWebhooks({ body, headers }, stripe))
|
||||
await expect(stripePayments.handleWebhooks({ requestBody: {} }, stripe))
|
||||
.to.eventually.be.rejectedWith({
|
||||
message: i18n.t('userNotFound'),
|
||||
httpCode: 404,
|
||||
@@ -128,6 +102,8 @@ describe('Stripe - Webhooks', () => {
|
||||
|
||||
expect(stripe.customers.del).to.not.have.been.called;
|
||||
expect(payments.cancelSubscription).to.not.have.been.called;
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
|
||||
it('deletes the customer on Stripe and calls payments.cancelSubscription', async () => {
|
||||
@@ -138,7 +114,7 @@ describe('Stripe - Webhooks', () => {
|
||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||
await subscriber.save();
|
||||
|
||||
constructEventStub.returns({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -149,10 +125,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: { id: null },
|
||||
request: null,
|
||||
});
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
await stripePayments.handleWebhooks({ requestBody: {} }, stripe);
|
||||
|
||||
expect(stripe.customers.del).to.have.been.calledOnce;
|
||||
expect(stripe.customers.del).to.have.been.calledWith(customerId);
|
||||
@@ -163,13 +139,15 @@ describe('Stripe - Webhooks', () => {
|
||||
expect(cancelSubscriptionOpts.paymentMethod).to.equal('Stripe');
|
||||
expect(Math.round(moment(cancelSubscriptionOpts.nextBill).diff(new Date(), 'days', true))).to.equal(3);
|
||||
expect(cancelSubscriptionOpts.groupId).to.be.undefined;
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('group plan subscription', () => {
|
||||
it('throws an error if the group is not found', async () => {
|
||||
const customerId = 456;
|
||||
constructEventStub.returns({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -180,10 +158,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: { id: null },
|
||||
request: null,
|
||||
});
|
||||
|
||||
await expect(stripePayments.handleWebhooks({ body, headers }, stripe))
|
||||
await expect(stripePayments.handleWebhooks({ requestBody: {} }, stripe))
|
||||
.to.eventually.be.rejectedWith({
|
||||
message: i18n.t('groupNotFound'),
|
||||
httpCode: 404,
|
||||
@@ -192,6 +170,8 @@ describe('Stripe - Webhooks', () => {
|
||||
|
||||
expect(stripe.customers.del).to.not.have.been.called;
|
||||
expect(payments.cancelSubscription).to.not.have.been.called;
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
|
||||
it('throws an error if the group leader is not found', async () => {
|
||||
@@ -207,7 +187,7 @@ describe('Stripe - Webhooks', () => {
|
||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||
await subscriber.save();
|
||||
|
||||
constructEventStub.returns({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -218,10 +198,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: { id: null },
|
||||
request: null,
|
||||
});
|
||||
|
||||
await expect(stripePayments.handleWebhooks({ body, headers }, stripe))
|
||||
await expect(stripePayments.handleWebhooks({ requestBody: {} }, stripe))
|
||||
.to.eventually.be.rejectedWith({
|
||||
message: i18n.t('userNotFound'),
|
||||
httpCode: 404,
|
||||
@@ -230,6 +210,8 @@ describe('Stripe - Webhooks', () => {
|
||||
|
||||
expect(stripe.customers.del).to.not.have.been.called;
|
||||
expect(payments.cancelSubscription).to.not.have.been.called;
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
|
||||
it('deletes the customer on Stripe and calls payments.cancelSubscription', async () => {
|
||||
@@ -248,7 +230,7 @@ describe('Stripe - Webhooks', () => {
|
||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||
await subscriber.save();
|
||||
|
||||
constructEventStub.returns({
|
||||
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||
id: 123,
|
||||
type: eventType,
|
||||
data: {
|
||||
@@ -259,10 +241,10 @@ describe('Stripe - Webhooks', () => {
|
||||
customer: customerId,
|
||||
},
|
||||
},
|
||||
request: { id: null },
|
||||
request: null,
|
||||
});
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
await stripePayments.handleWebhooks({ requestBody: {} }, stripe);
|
||||
|
||||
expect(stripe.customers.del).to.have.been.calledOnce;
|
||||
expect(stripe.customers.del).to.have.been.calledWith(customerId);
|
||||
@@ -273,65 +255,9 @@ describe('Stripe - Webhooks', () => {
|
||||
expect(cancelSubscriptionOpts.paymentMethod).to.equal('Stripe');
|
||||
expect(Math.round(moment(cancelSubscriptionOpts.nextBill).diff(new Date(), 'days', true))).to.equal(3);
|
||||
expect(cancelSubscriptionOpts.groupId).to.equal(subscriber._id);
|
||||
|
||||
stripe.events.retrieve.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkout.session.completed', () => {
|
||||
const eventType = 'checkout.session.completed';
|
||||
let event;
|
||||
let constructEventStub;
|
||||
const session = {};
|
||||
|
||||
beforeEach(() => {
|
||||
session.metadata = {};
|
||||
event = { type: eventType, data: { object: session } };
|
||||
constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent');
|
||||
constructEventStub.returns(event);
|
||||
|
||||
sandbox.stub(oneTimePayments, 'applyGemPayment').resolves({});
|
||||
sandbox.stub(subscriptions, 'applySubscription').resolves({});
|
||||
sandbox.stub(subscriptions, 'handlePaymentMethodChange').resolves({});
|
||||
});
|
||||
|
||||
it('handles changing an user sub', async () => {
|
||||
session.metadata.type = 'edit-card-user';
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(subscriptions.handlePaymentMethodChange).to.have.been.calledOnce;
|
||||
expect(subscriptions.handlePaymentMethodChange).to.have.been.calledWith(session);
|
||||
});
|
||||
|
||||
it('handles changing a group sub', async () => {
|
||||
session.metadata.type = 'edit-card-group';
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(subscriptions.handlePaymentMethodChange).to.have.been.calledOnce;
|
||||
expect(subscriptions.handlePaymentMethodChange).to.have.been.calledWith(session);
|
||||
});
|
||||
|
||||
it('applies a subscription', async () => {
|
||||
session.metadata.type = 'subscription';
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(subscriptions.applySubscription).to.have.been.calledOnce;
|
||||
expect(subscriptions.applySubscription).to.have.been.calledWith(session);
|
||||
});
|
||||
|
||||
it('handles a one time payment', async () => {
|
||||
session.metadata.type = 'something else';
|
||||
|
||||
await stripePayments.handleWebhooks({ body, headers }, stripe);
|
||||
|
||||
expect(stripe.webhooks.constructEvent).to.have.been.calledOnce;
|
||||
expect(oneTimePayments.applyGemPayment).to.have.been.calledOnce;
|
||||
expect(oneTimePayments.applyGemPayment).to.have.been.calledWith(session);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,316 +0,0 @@
|
||||
import apiError from '../../../../../../website/server/libs/apiError';
|
||||
import common from '../../../../../../website/common';
|
||||
import {
|
||||
getOneTimePaymentInfo,
|
||||
applyGemPayment,
|
||||
} from '../../../../../../website/server/libs/payments/stripe/oneTimePayments';
|
||||
import * as subscriptions from '../../../../../../website/server/libs/payments/stripe/subscriptions';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('Stripe - One Time Payments', () => {
|
||||
describe('getOneTimePaymentInfo', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
sandbox.stub(subscriptions, 'checkSubData');
|
||||
});
|
||||
|
||||
describe('gemsBlock', () => {
|
||||
it('returns the gemsBlock and amount', async () => {
|
||||
const { gemsBlock, amount, subscription } = await getOneTimePaymentInfo('21gems', null, user);
|
||||
expect(gemsBlock).to.equal(common.content.gems['21gems']);
|
||||
expect(amount).to.equal(gemsBlock.price);
|
||||
expect(amount).to.equal(499);
|
||||
expect(subscription).to.be.null;
|
||||
expect(subscriptions.checkSubData).to.not.be.called;
|
||||
});
|
||||
|
||||
it('throws if the gemsBlock does not exist', async () => {
|
||||
await expect(getOneTimePaymentInfo('not existant', null, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: apiError('invalidGemsBlock'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the user cannot receive gems', async () => {
|
||||
sandbox.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(getOneTimePaymentInfo('21gems', null, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('gift', () => {
|
||||
it('throws if the receiver does not exist', async () => {
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: 'invalid',
|
||||
gems: {
|
||||
amount: 3,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(getOneTimePaymentInfo(null, gift, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('userWithIDNotFound', { userId: 'invalid' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the user cannot receive gems', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
sandbox.stub(User.prototype, 'canGetGems').resolves(false);
|
||||
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 2,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(getOneTimePaymentInfo(null, gift, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the amount of gems is <= 0', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 0,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(getOneTimePaymentInfo(null, gift, user))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('badAmountOfGemsToPurchase'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the subscription block does not exist', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
type: 'subscription',
|
||||
uuid: receivingUser._id,
|
||||
subscription: {
|
||||
key: 'invalid',
|
||||
},
|
||||
};
|
||||
|
||||
await expect(getOneTimePaymentInfo(null, gift, user))
|
||||
.to.eventually.throw;
|
||||
});
|
||||
|
||||
it('returns the amount (gems)', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 4,
|
||||
},
|
||||
};
|
||||
|
||||
expect(subscriptions.checkSubData).to.not.be.called;
|
||||
|
||||
const { gemsBlock, amount, subscription } = await getOneTimePaymentInfo(null, gift, user);
|
||||
expect(gemsBlock).to.equal(null);
|
||||
expect(amount).to.equal('100');
|
||||
expect(subscription).to.be.null;
|
||||
});
|
||||
|
||||
it('returns the amount (subscription)', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
const gift = {
|
||||
type: 'subscription',
|
||||
uuid: receivingUser._id,
|
||||
subscription: {
|
||||
key: 'basic_3mo',
|
||||
},
|
||||
};
|
||||
const sub = common.content.subscriptionBlocks['basic_3mo']; // eslint-disable-line dot-notation
|
||||
|
||||
const { gemsBlock, amount, subscription } = await getOneTimePaymentInfo(null, gift, user);
|
||||
|
||||
expect(subscriptions.checkSubData).to.be.calledOnce;
|
||||
expect(subscriptions.checkSubData).to.be.calledWith(sub, false, null);
|
||||
|
||||
expect(gemsBlock).to.equal(null);
|
||||
expect(amount).to.equal('1500');
|
||||
expect(Number(amount)).to.equal(sub.price * 100);
|
||||
expect(subscription).to.equal(sub);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyGemPayment', () => {
|
||||
let user;
|
||||
let customerId;
|
||||
let subKey;
|
||||
let userFindByIdStub;
|
||||
let paymentsCreateSubSpy;
|
||||
let paymentBuyGemsStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subKey = 'basic_3mo';
|
||||
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
customerId = 'test-id';
|
||||
|
||||
paymentsCreateSubSpy = sandbox.stub(payments, 'createSubscription');
|
||||
paymentsCreateSubSpy.resolves({});
|
||||
|
||||
paymentBuyGemsStub = sandbox.stub(payments, 'buyGems');
|
||||
paymentBuyGemsStub.resolves({});
|
||||
});
|
||||
|
||||
it('throws if the user does not exist', async () => {
|
||||
const metadata = { userId: 'invalid' };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await expect(applyGemPayment(session))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('userWithIDNotFound', { userId: metadata.userId }),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the receiving user does not exist', async () => {
|
||||
const metadata = { userId: 'invalid' };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await expect(applyGemPayment(session))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('userWithIDNotFound', { userId: metadata.userId }),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the gems block does not exist', async () => {
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: 'invalid',
|
||||
gems: {
|
||||
amount: 16,
|
||||
},
|
||||
};
|
||||
|
||||
const metadata = { userId: user._id, gift: JSON.stringify(gift) };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await expect(applyGemPayment(session))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('userWithIDNotFound', { userId: 'invalid' }),
|
||||
});
|
||||
});
|
||||
|
||||
describe('with existing user', () => {
|
||||
beforeEach(() => {
|
||||
const execStub = sandbox.stub().resolves(user);
|
||||
userFindByIdStub = sandbox.stub(User, 'findById');
|
||||
userFindByIdStub.withArgs(user._id).returns({ exec: execStub });
|
||||
});
|
||||
|
||||
it('buys gems', async () => {
|
||||
const metadata = { userId: user._id, gemsBlock: '21gems' };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await applyGemPayment(session);
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
paymentMethod: 'Stripe',
|
||||
gift: undefined,
|
||||
gemsBlock: common.content.gems['21gems'],
|
||||
});
|
||||
});
|
||||
|
||||
it('gift gems', async () => {
|
||||
const receivingUser = new User();
|
||||
const execStub = sandbox.stub().resolves(receivingUser);
|
||||
userFindByIdStub.withArgs(receivingUser._id).returns({ exec: execStub });
|
||||
const gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
},
|
||||
};
|
||||
|
||||
sandbox.stub(JSON, 'parse').returns(gift);
|
||||
const metadata = { userId: user._id, gift: JSON.stringify(gift) };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await applyGemPayment(session);
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
paymentMethod: 'Gift',
|
||||
gift,
|
||||
gemsBlock: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('gift sub', async () => {
|
||||
const receivingUser = new User();
|
||||
const execStub = sandbox.stub().resolves(receivingUser);
|
||||
userFindByIdStub.withArgs(receivingUser._id).returns({ exec: execStub });
|
||||
const gift = {
|
||||
type: 'subscription',
|
||||
uuid: receivingUser._id,
|
||||
subscription: {
|
||||
key: subKey,
|
||||
},
|
||||
};
|
||||
|
||||
sandbox.stub(JSON, 'parse').returns(gift);
|
||||
const metadata = { userId: user._id, gift: JSON.stringify(gift) };
|
||||
const session = { metadata, customer: customerId };
|
||||
|
||||
await applyGemPayment(session);
|
||||
|
||||
expect(paymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(paymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
paymentMethod: 'Gift',
|
||||
gift,
|
||||
gemsBlock: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,436 +0,0 @@
|
||||
import cc from 'coupon-code';
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import { model as Coupon } from '../../../../../../website/server/models/coupon';
|
||||
import common from '../../../../../../website/common';
|
||||
import {
|
||||
checkSubData,
|
||||
applySubscription,
|
||||
chargeForAdditionalGroupMember,
|
||||
handlePaymentMethodChange,
|
||||
} from '../../../../../../website/server/libs/payments/stripe/subscriptions';
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
const { i18n } = common;
|
||||
|
||||
describe('Stripe - Subscriptions', () => {
|
||||
describe('checkSubData', () => {
|
||||
it('does not throw if the subscription can be used', async () => {
|
||||
const sub = common.content.subscriptionBlocks['basic_3mo']; // eslint-disable-line dot-notation
|
||||
const res = await checkSubData(sub);
|
||||
expect(res).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('throws if the subscription does not exists', async () => {
|
||||
await expect(checkSubData())
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the subscription can\'t be used', async () => {
|
||||
const sub = common.content.subscriptionBlocks['group_plan_auto']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, true))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the subscription targets a group and an user is making the request', async () => {
|
||||
const sub = common.content.subscriptionBlocks['group_monthly']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, false))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the subscription targets an user and a group is making the request', async () => {
|
||||
const sub = common.content.subscriptionBlocks['basic_3mo']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, true))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the coupon is required but not passed', async () => {
|
||||
const sub = common.content.subscriptionBlocks['google_6mo']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, false))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('couponCodeRequired'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the coupon is required but does not exist', async () => {
|
||||
const coupon = 'not-valid';
|
||||
const sub = common.content.subscriptionBlocks['google_6mo']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, false, coupon))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('invalidCoupon'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if the coupon is required but is invalid', async () => {
|
||||
const couponModel = new Coupon();
|
||||
couponModel.event = 'google_6mo';
|
||||
await couponModel.save();
|
||||
|
||||
sandbox.stub(cc, 'validate').returns('invalid');
|
||||
|
||||
const sub = common.content.subscriptionBlocks['google_6mo']; // eslint-disable-line dot-notation
|
||||
await expect(checkSubData(sub, false, couponModel._id))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
name: 'BadRequest',
|
||||
message: i18n.t('invalidCoupon'),
|
||||
});
|
||||
});
|
||||
|
||||
it('works if the coupon is required and valid', async () => {
|
||||
const couponModel = new Coupon();
|
||||
couponModel.event = 'google_6mo';
|
||||
await couponModel.save();
|
||||
|
||||
sandbox.stub(cc, 'validate').returns(couponModel._id);
|
||||
|
||||
const sub = common.content.subscriptionBlocks['google_6mo']; // eslint-disable-line dot-notation
|
||||
await checkSubData(sub, false, couponModel._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applySubscription', () => {
|
||||
let user; let group; let sub;
|
||||
let groupId;
|
||||
let customerId; let subscriptionId;
|
||||
let subKey;
|
||||
let userFindByIdStub;
|
||||
let stripePaymentsCreateSubSpy;
|
||||
|
||||
beforeEach(async () => {
|
||||
subKey = 'basic_3mo';
|
||||
sub = common.content.subscriptionBlocks[subKey];
|
||||
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
const execStub = sandbox.stub().resolves(user);
|
||||
userFindByIdStub = sandbox.stub(User, 'findById');
|
||||
userFindByIdStub.withArgs(user._id).returns({ exec: execStub });
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
groupId = group._id;
|
||||
await group.save();
|
||||
|
||||
// Add user to group
|
||||
user.guilds.push(groupId);
|
||||
await user.save();
|
||||
|
||||
customerId = 'test-id';
|
||||
subscriptionId = 'test-sub-id';
|
||||
|
||||
stripePaymentsCreateSubSpy = sandbox.stub(payments, 'createSubscription');
|
||||
stripePaymentsCreateSubSpy.resolves({});
|
||||
});
|
||||
|
||||
it('subscribes a user', async () => {
|
||||
await applySubscription({
|
||||
customer: customerId,
|
||||
subscription: subscriptionId,
|
||||
metadata: {
|
||||
sub: JSON.stringify(sub),
|
||||
userId: user._id,
|
||||
groupId: null,
|
||||
},
|
||||
user,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
subscriptionId,
|
||||
paymentMethod: 'Stripe',
|
||||
sub: sinon.match({ ...sub }),
|
||||
groupId: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes a group', async () => {
|
||||
sub = common.content.subscriptionBlocks['group_monthly']; // eslint-disable-line dot-notation
|
||||
await applySubscription({
|
||||
customer: customerId,
|
||||
subscription: subscriptionId,
|
||||
metadata: {
|
||||
sub: JSON.stringify(sub),
|
||||
userId: user._id,
|
||||
groupId,
|
||||
},
|
||||
user,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
subscriptionId,
|
||||
paymentMethod: 'Stripe',
|
||||
sub: sinon.match({ ...sub }),
|
||||
groupId,
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes a group with multiple users', async () => {
|
||||
const user2 = new User();
|
||||
user2.guilds.push(groupId);
|
||||
await user2.save();
|
||||
|
||||
const execStub2 = sandbox.stub().resolves(user);
|
||||
userFindByIdStub.withArgs(user2._id).returns({ exec: execStub2 });
|
||||
|
||||
group.memberCount = 2;
|
||||
await group.save();
|
||||
|
||||
sub = common.content.subscriptionBlocks['group_monthly']; // eslint-disable-line dot-notation
|
||||
await applySubscription({
|
||||
customer: customerId,
|
||||
subscription: subscriptionId,
|
||||
metadata: {
|
||||
sub: JSON.stringify(sub),
|
||||
userId: user._id,
|
||||
groupId,
|
||||
},
|
||||
user,
|
||||
});
|
||||
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledOnce;
|
||||
expect(stripePaymentsCreateSubSpy).to.be.calledWith({
|
||||
user,
|
||||
customerId,
|
||||
subscriptionId,
|
||||
paymentMethod: 'Stripe',
|
||||
sub: sinon.match({ ...sub }),
|
||||
groupId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlePaymentMethodChange', () => {
|
||||
const stripe = stripeModule('test');
|
||||
|
||||
it('updates the plan quantity based on the number of group members', async () => {
|
||||
const stripeIntentRetrieveStub = sandbox.stub(stripe.setupIntents, 'retrieve').resolves({
|
||||
payment_method: 1,
|
||||
metadata: {
|
||||
subscription_id: 2,
|
||||
},
|
||||
});
|
||||
const stripeSubUpdateStub = sandbox.stub(stripe.subscriptions, 'update');
|
||||
|
||||
await handlePaymentMethodChange({}, stripe);
|
||||
expect(stripeIntentRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeSubUpdateStub).to.be.calledOnce;
|
||||
expect(stripeSubUpdateStub).to.be.calledWith(2, {
|
||||
default_payment_method: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('chargeForAdditionalGroupMember', () => {
|
||||
const stripe = stripeModule('test');
|
||||
let stripeUpdateSubStub;
|
||||
const plan = common.content.subscriptionBlocks['group_monthly']; // eslint-disable-line dot-notation
|
||||
|
||||
let user; let group;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = plan.key;
|
||||
group.purchased.plan.subscriptionId = 'sub-id';
|
||||
await group.save();
|
||||
|
||||
stripeUpdateSubStub = sandbox.stub(stripe.subscriptions, 'update').resolves({});
|
||||
});
|
||||
|
||||
it('updates the plan quantity based on the number of group members', async () => {
|
||||
group.memberCount = 4;
|
||||
const newQuantity = group.memberCount + plan.quantity - 1;
|
||||
|
||||
await chargeForAdditionalGroupMember(group, stripe);
|
||||
expect(stripeUpdateSubStub).to.be.calledWithMatch(
|
||||
group.purchased.plan.subscriptionId,
|
||||
sinon.match({
|
||||
plan: group.purchased.plan.planId,
|
||||
quantity: newQuantity,
|
||||
}),
|
||||
);
|
||||
expect(group.purchased.plan.quantity).to.equal(newQuantity);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancelSubscription', () => {
|
||||
const subKey = 'basic_3mo';
|
||||
const stripe = stripeModule('test');
|
||||
let user; let groupId; let
|
||||
group;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
user.purchased.plan.planId = subKey;
|
||||
user.purchased.plan.lastBillingDate = new Date();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
group.purchased.plan.customerId = 'customer-id';
|
||||
group.purchased.plan.planId = subKey;
|
||||
await group.save();
|
||||
|
||||
groupId = group._id;
|
||||
});
|
||||
|
||||
it('throws an error if there is no customer id', async () => {
|
||||
user.purchased.plan.customerId = undefined;
|
||||
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('missingSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if the group is not found', async () => {
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: 'fake-group',
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 404,
|
||||
name: 'NotFound',
|
||||
message: i18n.t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if user is not the group leader', async () => {
|
||||
const nonLeader = new User();
|
||||
nonLeader.guilds.push(groupId);
|
||||
await nonLeader.save();
|
||||
|
||||
await expect(stripePayments.cancelSubscription({
|
||||
user: nonLeader,
|
||||
groupId,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let stripeDeleteCustomerStub; let paymentsCancelSubStub;
|
||||
let stripeRetrieveStub; let subscriptionId; let
|
||||
currentPeriodEndTimeStamp;
|
||||
|
||||
beforeEach(() => {
|
||||
subscriptionId = 'subId';
|
||||
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').resolves({});
|
||||
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||
|
||||
currentPeriodEndTimeStamp = (new Date()).getTime();
|
||||
stripeRetrieveStub = sinon.stub(stripe.customers, 'retrieve')
|
||||
.resolves({
|
||||
subscriptions: {
|
||||
data: [{
|
||||
id: subscriptionId,
|
||||
current_period_end: currentPeriodEndTimeStamp,
|
||||
}], // eslint-disable-line camelcase
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.customers.del.restore();
|
||||
stripe.customers.retrieve.restore();
|
||||
payments.cancelSubscription.restore();
|
||||
});
|
||||
|
||||
it('cancels a user subscription', async () => {
|
||||
await stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId: undefined,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeDeleteCustomerStub).to.be.calledOnce;
|
||||
expect(stripeDeleteCustomerStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(stripeRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(paymentsCancelSubStub).to.be.calledOnce;
|
||||
expect(paymentsCancelSubStub).to.be.calledWith({
|
||||
user,
|
||||
groupId: undefined,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('cancels a group subscription', async () => {
|
||||
await stripePayments.cancelSubscription({
|
||||
user,
|
||||
groupId,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeDeleteCustomerStub).to.be.calledOnce;
|
||||
expect(stripeDeleteCustomerStub).to.be.calledWith(group.purchased.plan.customerId);
|
||||
expect(stripeRetrieveStub).to.be.calledOnce;
|
||||
expect(stripeRetrieveStub).to.be.calledWith(user.purchased.plan.customerId);
|
||||
expect(paymentsCancelSubStub).to.be.calledOnce;
|
||||
expect(paymentsCancelSubStub).to.be.calledWith({
|
||||
user,
|
||||
groupId,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import stripeModule from 'stripe';
|
||||
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
import { model as User } from '../../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../../website/server/models/group';
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||
|
||||
describe('Stripe - Upgrade Group Plan', () => {
|
||||
const stripe = stripeModule('test');
|
||||
let spy; let data; let user; let
|
||||
group;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
|
||||
data = {
|
||||
user,
|
||||
sub: {
|
||||
key: 'basic_3mo', // @TODO: Validate that this is group
|
||||
},
|
||||
customerId: 'customer-id',
|
||||
paymentMethod: 'Payment Method',
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
};
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
});
|
||||
await group.save();
|
||||
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.resolves([]);
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
stripePayments.setStripeApi(stripe);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stripe.subscriptions.update.restore();
|
||||
});
|
||||
|
||||
it('updates a group plan quantity', async () => {
|
||||
data.paymentMethod = 'Stripe';
|
||||
await payments.createSubscription(data);
|
||||
|
||||
const updatedGroup = await Group.findById(group._id).exec();
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(3);
|
||||
|
||||
updatedGroup.memberCount += 1;
|
||||
await updatedGroup.save();
|
||||
|
||||
await stripePayments.chargeForAdditionalGroupMember(updatedGroup);
|
||||
|
||||
expect(spy.calledOnce).to.be.true;
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(4);
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,7 @@ describe('preenHistory', () => {
|
||||
beforeEach(() => {
|
||||
// Replace system clocks so we can get predictable results
|
||||
clock = sinon.useFakeTimers({
|
||||
now: Number(moment('2013-10-20').utcOffset(0).startOf('day').toDate()),
|
||||
now: Number(moment('2013-10-20').zone(0).startOf('day').toDate()),
|
||||
toFake: ['Date'],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import apn from '@parse/node-apn/mock';
|
||||
import apn from 'apn/mock';
|
||||
import _ from 'lodash';
|
||||
import nconf from 'nconf';
|
||||
import gcmLib from 'node-gcm'; // works with FCM notifications too
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
import path from 'path';
|
||||
import nconf from 'nconf';
|
||||
import setupNconf from '../../../../website/server/libs/setupNconf';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import { IncomingWebhook } from '@slack/client';
|
||||
import requireAgain from 'require-again';
|
||||
import nconf from 'nconf';
|
||||
import moment from 'moment';
|
||||
@@ -12,7 +12,7 @@ describe('slack', () => {
|
||||
let data;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send');
|
||||
data = {
|
||||
authorEmail: 'author@example.com',
|
||||
flagger: {
|
||||
@@ -112,7 +112,6 @@ describe('slack', () => {
|
||||
|
||||
it('noops if no flagging url is provided', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('SLACK_FLAGGING_URL').returns('');
|
||||
nconf.get.withArgs('IS_TEST').returns(true);
|
||||
sandbox.stub(logger, 'error');
|
||||
const reRequiredSlack = requireAgain('../../../../website/server/libs/slack');
|
||||
|
||||
|
||||
@@ -8,10 +8,5 @@ describe('stringUtils', () => {
|
||||
const matches = getMatchesByWordArray(message, bannedWords);
|
||||
expect(matches.length).to.equal(bannedWords.length);
|
||||
});
|
||||
it('doesn\'t flag names with accented characters', () => {
|
||||
const name = 'TESTPLACEHOLDERSWEARWORDHEREé';
|
||||
const matches = getMatchesByWordArray(name, bannedWords);
|
||||
expect(matches.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
createTasks,
|
||||
getTasks,
|
||||
} from '../../../../website/server/libs/tasks';
|
||||
import {
|
||||
syncableAttrs,
|
||||
moveTask,
|
||||
} from '../../../../website/server/libs/tasks/utils';
|
||||
} from '../../../../website/server/libs/taskManager';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import shared from '../../../../website/common/script';
|
||||
import {
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import * as xmlMarshaller from '../../../../website/server/libs/xmlMarshaller';
|
||||
|
||||
describe('xml marshaller marshalls user data', () => {
|
||||
const minimumUser = {
|
||||
pinnedItems: [],
|
||||
unpinnedItems: [],
|
||||
inbox: {},
|
||||
};
|
||||
|
||||
function userDataWith (fields) {
|
||||
return { ...minimumUser, ...fields };
|
||||
}
|
||||
|
||||
it('maps the newMessages field to have id as a value in a list.', () => {
|
||||
const userData = userDataWith({
|
||||
newMessages: {
|
||||
'283171a5-422c-4991-bc78-95b1b5b51629': {
|
||||
name: 'The Language Hackers',
|
||||
value: true,
|
||||
},
|
||||
'283171a6-422c-4991-bc78-95b1b5b51629': {
|
||||
name: 'The Bug Hackers',
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const xml = xmlMarshaller.marshallUserData(userData);
|
||||
|
||||
expect(xml).to.equal(`<user>
|
||||
<inbox/>
|
||||
<newMessages>
|
||||
<id>283171a5-422c-4991-bc78-95b1b5b51629</id>
|
||||
<name>The Language Hackers</name>
|
||||
<value>true</value>
|
||||
</newMessages>
|
||||
<newMessages>
|
||||
<id>283171a6-422c-4991-bc78-95b1b5b51629</id>
|
||||
<name>The Bug Hackers</name>
|
||||
<value>false</value>
|
||||
</newMessages>
|
||||
</user>`);
|
||||
});
|
||||
});
|
||||
@@ -21,8 +21,7 @@ describe('cors middleware', () => {
|
||||
expect(res.set).to.have.been.calledWith({
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
|
||||
'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
|
||||
'Access-Control-Expose-Headers': 'X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After',
|
||||
'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
|
||||
});
|
||||
expect(res.sendStatus).to.not.have.been.called;
|
||||
expect(next).to.have.been.calledOnce;
|
||||
@@ -34,8 +33,7 @@ describe('cors middleware', () => {
|
||||
expect(res.set).to.have.been.calledWith({
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
|
||||
'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
|
||||
'Access-Control-Expose-Headers': 'X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After',
|
||||
'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
|
||||
});
|
||||
expect(res.sendStatus).to.have.been.calledWith(200);
|
||||
expect(next).to.not.have.been.called;
|
||||
|
||||
@@ -293,90 +293,4 @@ describe('cron middleware', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Drop Cap A/B Test', async () => {
|
||||
it('enrolls web users', async () => {
|
||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||
await user.save();
|
||||
req.headers['x-client'] = 'habitica-web';
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, async err => {
|
||||
if (err) return reject(err);
|
||||
user = await User.findById(user._id).exec();
|
||||
expect(user._ABtests.dropCapNotif).to.be.a.string;
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('enables the new notification for 50% of users', async () => {
|
||||
sandbox.stub(Math, 'random').returns(0.5);
|
||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||
await user.save();
|
||||
req.headers['x-client'] = 'habitica-web';
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, async err => {
|
||||
if (err) return reject(err);
|
||||
user = await User.findById(user._id).exec();
|
||||
expect(user._ABtests.dropCapNotif).to.be.equal('drop-cap-notif-enabled');
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('disables the new notification for 50% of users', async () => {
|
||||
sandbox.stub(Math, 'random').returns(0.51);
|
||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||
await user.save();
|
||||
req.headers['x-client'] = 'habitica-web';
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, async err => {
|
||||
if (err) return reject(err);
|
||||
user = await User.findById(user._id).exec();
|
||||
expect(user._ABtests.dropCapNotif).to.be.equal('drop-cap-notif-disabled');
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not affect subscribers', async () => {
|
||||
sandbox.stub(Math, 'random').returns(0.2);
|
||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||
await user.save();
|
||||
req.headers['x-client'] = 'habitica-web';
|
||||
sandbox.stub(User.prototype, 'isSubscribed').returns(true);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, async err => {
|
||||
if (err) return reject(err);
|
||||
user = await User.findById(user._id).exec();
|
||||
expect(user._ABtests.dropCapNotif).to.not.exist;
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not affect mobile users', async () => {
|
||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||
await user.save();
|
||||
req.headers['x-client'] = 'habitica-ios';
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, async err => {
|
||||
if (err) return reject(err);
|
||||
user = await User.findById(user._id).exec();
|
||||
expect(user._ABtests.dropCapNotif).to.not.exist;
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
generateNext,
|
||||
} from '../../../helpers/api-unit.helper';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import { ensureAdmin, ensureSudo, ensureNewsPoster } from '../../../../website/server/middlewares/ensureAccessRight';
|
||||
import { ensureAdmin, ensureSudo } from '../../../../website/server/middlewares/ensureAccessRight';
|
||||
import { NotAuthorized } from '../../../../website/server/libs/errors';
|
||||
import apiError from '../../../../website/server/libs/apiError';
|
||||
|
||||
@@ -40,27 +40,6 @@ describe('ensure access middlewares', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('ensure newsPoster', () => {
|
||||
it('returns not authorized when user is not a newsPoster', () => {
|
||||
res.locals = { user: { contributor: { newsPoster: false } } };
|
||||
|
||||
ensureNewsPoster(req, res, next);
|
||||
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal(apiError('noNewsPosterAccess'));
|
||||
expect(calledWith[0] instanceof NotAuthorized).to.equal(true);
|
||||
});
|
||||
|
||||
it('passes when user is a newsPoster', () => {
|
||||
res.locals = { user: { contributor: { newsPoster: true } } };
|
||||
|
||||
ensureNewsPoster(req, res, next);
|
||||
|
||||
expect(next).to.be.calledOnce;
|
||||
expect(next.args[0]).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
context('ensure sudo', () => {
|
||||
it('returns not authorized when user is not a sudo user', () => {
|
||||
res.locals = { user: { contributor: { sudo: false } } };
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('ipBlocker middleware', () => {
|
||||
});
|
||||
|
||||
it('does not throw when the ip does not match', () => {
|
||||
req.ip = '192.168.1.1';
|
||||
req.headers['x-forwarded-for'] = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
@@ -65,12 +65,30 @@ describe('ipBlocker middleware', () => {
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('throws when the ip is blocked', () => {
|
||||
req.ip = '192.168.1.1';
|
||||
it('throws when a matching ip exist in x-forwarded-for', () => {
|
||||
req.headers['x-forwarded-for'] = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorThrown(next);
|
||||
});
|
||||
|
||||
it('trims ips in x-forwarded-for', () => {
|
||||
req.headers['x-forwarded-for'] = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(', 192.168.1.1 , 192.168.1.4, ');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorThrown(next);
|
||||
});
|
||||
|
||||
it('works when multiple ips are passed in x-forwarded-for', () => {
|
||||
req.headers['x-forwarded-for'] = '192.168.1.4';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1, 192.168.1.4, 192.168.1.3');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorThrown(next);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
import nconf from 'nconf';
|
||||
import { RateLimiterMemory } from 'rate-limiter-flexible';
|
||||
import requireAgain from 'require-again';
|
||||
import {
|
||||
generateRes,
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../helpers/api-unit.helper';
|
||||
import { TooManyRequests } from '../../../../website/server/libs/errors';
|
||||
import apiError from '../../../../website/server/libs/apiError';
|
||||
import logger from '../../../../website/server/libs/logger';
|
||||
|
||||
describe('rateLimiter middleware', () => {
|
||||
const pathToRateLimiter = '../../../../website/server/middlewares/rateLimiter';
|
||||
|
||||
let res; let req; let next; let nconfGetStub;
|
||||
|
||||
beforeEach(() => {
|
||||
nconfGetStub = sandbox.stub(nconf, 'get');
|
||||
|
||||
nconfGetStub.withArgs('NODE_ENV').returns('test');
|
||||
nconfGetStub.withArgs('IS_TEST').returns(true);
|
||||
|
||||
res = generateRes();
|
||||
req = generateReq();
|
||||
next = generateNext();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('is disabled when the env var is not defined', () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns(undefined);
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
attachRateLimiter(req, res, next);
|
||||
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
expect(res.set).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('is disabled when the env var is an not "true"', () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('false');
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
attachRateLimiter(req, res, next);
|
||||
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
expect(res.set).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('does not throw when there are available points', async () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
|
||||
expect(res.set).to.have.been.calledOnce;
|
||||
expect(res.set).to.have.been.calledWithMatch({
|
||||
'X-RateLimit-Limit': 30,
|
||||
'X-RateLimit-Remaining': 29,
|
||||
'X-RateLimit-Reset': sinon.match(Date),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not throw when an unknown error is thrown by the rate limiter', async () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||
sandbox.stub(logger, 'error');
|
||||
sandbox.stub(RateLimiterMemory.prototype, 'consume')
|
||||
.returns(Promise.reject(new Error('Unknown error.')));
|
||||
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
expect(res.set).to.not.have.been.called;
|
||||
|
||||
expect(logger.error).to.be.calledOnce;
|
||||
expect(logger.error).to.have.been.calledWithMatch(Error, 'Rate Limiter Error');
|
||||
});
|
||||
|
||||
it('throws when there are no available points remaining', async () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
|
||||
// call for 31 times
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
await attachRateLimiter(req, res, next); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
|
||||
expect(next).to.have.been.callCount(31);
|
||||
const calledWith = next.getCall(30).args;
|
||||
expect(calledWith[0].message).to.equal(apiError('clientRateLimited'));
|
||||
expect(calledWith[0] instanceof TooManyRequests).to.equal(true);
|
||||
|
||||
expect(res.set).to.have.been.callCount(31);
|
||||
expect(res.set).to.have.been.calledWithMatch({
|
||||
'Retry-After': sinon.match(Number),
|
||||
'X-RateLimit-Limit': 30,
|
||||
'X-RateLimit-Remaining': 0,
|
||||
'X-RateLimit-Reset': sinon.match(Date),
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the user id if supplied or the ip address', async () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
|
||||
req.ip = 1;
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
req.headers['x-api-user'] = 'user-1';
|
||||
await attachRateLimiter(req, res, next);
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
// user id an ip are counted as separate sources
|
||||
expect(res.set).to.have.been.calledWithMatch({
|
||||
'X-RateLimit-Limit': 30,
|
||||
'X-RateLimit-Remaining': 28, // 2 calls with user id
|
||||
'X-RateLimit-Reset': sinon.match(Date),
|
||||
});
|
||||
|
||||
req.headers['x-api-user'] = undefined;
|
||||
await attachRateLimiter(req, res, next);
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
expect(res.set).to.have.been.calledWithMatch({
|
||||
'X-RateLimit-Limit': 30,
|
||||
'X-RateLimit-Remaining': 27, // 3 calls with only ip
|
||||
'X-RateLimit-Reset': sinon.match(Date),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,7 @@ describe('redirects middleware', () => {
|
||||
const nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
req.protocol = 'http';
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.originalUrl = '/static/front';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
@@ -37,7 +37,7 @@ describe('redirects middleware', () => {
|
||||
const nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
req.protocol = 'https';
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('https');
|
||||
req.originalUrl = '/static/front';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
@@ -51,7 +51,7 @@ describe('redirects middleware', () => {
|
||||
const nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(false);
|
||||
req.protocol = 'http';
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.originalUrl = '/static/front';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
@@ -65,7 +65,7 @@ describe('redirects middleware', () => {
|
||||
const nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('http://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
req.protocol = 'http';
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.originalUrl = '/static/front';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
@@ -81,7 +81,7 @@ describe('redirects middleware', () => {
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
|
||||
|
||||
req.protocol = 'http';
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.originalUrl = '/static/front';
|
||||
req.query.skipSSLCheck = 'test-key';
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('redirects middleware', () => {
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
|
||||
|
||||
req.protocol = 'http';
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.originalUrl = '/static/front?skipSSLCheck=INVALID';
|
||||
req.query.skipSSLCheck = 'INVALID';
|
||||
|
||||
@@ -114,7 +114,7 @@ describe('redirects middleware', () => {
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns(null);
|
||||
|
||||
req.protocol = 'http';
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.originalUrl = '/static/front';
|
||||
req.query.skipSSLCheck = 'INVALID';
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('response middleware', () => {
|
||||
next = generateNext();
|
||||
});
|
||||
|
||||
|
||||
it('attaches respond method to res', () => {
|
||||
responseMiddleware(req, res, next);
|
||||
|
||||
|
||||
@@ -35,33 +35,6 @@ describe('Challenge Model', () => {
|
||||
notes: 'test notes',
|
||||
},
|
||||
};
|
||||
const tasks2ToTest = {
|
||||
habit: {
|
||||
text: 'test habit 2',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
notes: 'test notes',
|
||||
},
|
||||
todo: {
|
||||
text: 'test todo 2',
|
||||
type: 'todo',
|
||||
notes: 'test notes',
|
||||
},
|
||||
daily: {
|
||||
text: 'test daily 2',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
notes: 'test notes',
|
||||
},
|
||||
reward: {
|
||||
text: 'test reward 2',
|
||||
type: 'reward',
|
||||
notes: 'test notes',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
guild = new Group({
|
||||
@@ -173,60 +146,6 @@ describe('Challenge Model', () => {
|
||||
expect(syncedTask.attribute).to.eql('str');
|
||||
});
|
||||
|
||||
it('should add challenge tag back to user upon syncing challenge tasks to a user with challenge tag removed', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
const newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
await challenge.syncTasksToUser(newMember);
|
||||
|
||||
let updatedNewMember = await User.findById(newMember._id).exec();
|
||||
const updatedNewMemberId = updatedNewMember._id;
|
||||
|
||||
updatedNewMember.tags = [];
|
||||
await updatedNewMember.save();
|
||||
|
||||
const taskValue2 = tasks2ToTest[taskType];
|
||||
const task2 = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue2));
|
||||
task2.challenge.id = challenge._id;
|
||||
await challenge.addTasks([task2]);
|
||||
await challenge.syncTasksToUser(updatedNewMember);
|
||||
|
||||
updatedNewMember = await User.findById(updatedNewMemberId).exec();
|
||||
|
||||
expect(updatedNewMember.tags.length).to.equal(1);
|
||||
expect(updatedNewMember.tags[0].id).to.equal(challenge._id);
|
||||
expect(updatedNewMember.tags[0].name).to.equal(challenge.shortName);
|
||||
});
|
||||
|
||||
it('should not add a duplicate challenge tag to user upon syncing challenge tasks to a user with existing challenge tag', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
const newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
await challenge.syncTasksToUser(newMember);
|
||||
|
||||
let updatedNewMember = await User.findById(newMember._id).exec();
|
||||
const updatedNewMemberId = updatedNewMember._id;
|
||||
|
||||
const taskValue2 = tasks2ToTest[taskType];
|
||||
const task2 = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue2));
|
||||
task2.challenge.id = challenge._id;
|
||||
await challenge.addTasks([task2]);
|
||||
await challenge.syncTasksToUser(updatedNewMember);
|
||||
|
||||
updatedNewMember = await User.findById(updatedNewMemberId);
|
||||
|
||||
expect(updatedNewMember.tags.length).to.equal(8);
|
||||
expect(updatedNewMember.tags[7].id).to.equal(challenge._id);
|
||||
expect(updatedNewMember.tags[7].name).to.equal(challenge.shortName);
|
||||
expect(updatedNewMember.tags.filter(tag => tag.id === challenge._id).length).to.equal(1);
|
||||
});
|
||||
|
||||
it('syncs challenge tasks to a user with the existing task', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
|
||||
@@ -769,6 +769,7 @@ describe('Group Model', () => {
|
||||
expect(res.t).to.not.be.called;
|
||||
});
|
||||
|
||||
|
||||
it('does not throw an error if only user ids are passed in', async () => {
|
||||
await Group.validateInvitations({ uuids: ['user-id', 'user-id2'] }, res);
|
||||
expect(res.t).to.not.be.called;
|
||||
|
||||
@@ -235,16 +235,15 @@ describe('Group Task Methods', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('removes assigned tasks when master task is deleted', async () => {
|
||||
it('removes an assigned task and unlinks assignees', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
await guild.removeTask(task);
|
||||
|
||||
const updatedLeader = await User.findOne({ _id: leader._id });
|
||||
const updatedLeadersTasks = await Tasks.Task.find({ userId: leader._id, type: taskType });
|
||||
const updatedLeadersTasks = await Tasks.Task.find({ _id: { $in: updatedLeader.tasksOrder[`${taskType}s`] } });
|
||||
const syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
expect(updatedLeader.tasksOrder[`${taskType}s`]).to.not.include(task._id);
|
||||
expect(syncedTask).to.not.exist;
|
||||
expect(syncedTask.group.broken).to.equal('TASK_DELETED');
|
||||
});
|
||||
|
||||
it('unlinks and deletes group tasks for a user when remove-all is specified', async () => {
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { model as NewsPost, refreshNewsPost } from '../../../../website/server/models/newsPost';
|
||||
import { sleep } from '../../../helpers/api-unit.helper';
|
||||
|
||||
describe('NewsPost Model', () => {
|
||||
const publishDate = Number(new Date());
|
||||
|
||||
// NOTE publishDate is manually increased by +500 for each test
|
||||
// to make sure it's always in the future from the previous one
|
||||
// bevause NewsPost.lastNewsPost() is not reset between tests.
|
||||
// And without a more recent publishDate it wouldn't update
|
||||
|
||||
it('#lastNewsPost', () => {
|
||||
const lastPost = { _id: v4(), publishDate, published: true };
|
||||
NewsPost.updateLastNewsPost(lastPost);
|
||||
expect(NewsPost.lastNewsPost()).to.equal(lastPost);
|
||||
});
|
||||
|
||||
it('#getLastPostFromDatabase', async () => {
|
||||
const expectedId = v4();
|
||||
|
||||
await NewsPost.create([
|
||||
// more recent but not published
|
||||
{
|
||||
_id: v4(),
|
||||
publishDate: new Date(publishDate + 50),
|
||||
author: v4(),
|
||||
published: false,
|
||||
title: 'Title',
|
||||
credits: 'credits',
|
||||
text: 'text',
|
||||
},
|
||||
// expected
|
||||
{
|
||||
_id: expectedId,
|
||||
publishDate,
|
||||
author: v4(),
|
||||
published: true,
|
||||
title: 'Title',
|
||||
credits: 'credits',
|
||||
text: 'text',
|
||||
},
|
||||
// published but less recent
|
||||
{
|
||||
_id: v4(),
|
||||
publishDate: new Date(Number(publishDate) - 50),
|
||||
author: v4(),
|
||||
published: true,
|
||||
title: 'Title',
|
||||
credits: 'credits',
|
||||
text: 'text',
|
||||
},
|
||||
]);
|
||||
|
||||
const fetched = await NewsPost.getLastPostFromDatabase();
|
||||
expect(fetched._id).to.equal(expectedId);
|
||||
});
|
||||
|
||||
context('#updateLastNewsPost', () => {
|
||||
it('updates the post if new one is more recent and published', () => {
|
||||
const previousPost = {
|
||||
_id: v4(),
|
||||
publishDate: new Date(publishDate + 100),
|
||||
published: true,
|
||||
};
|
||||
NewsPost.updateLastNewsPost(previousPost);
|
||||
const newPost = {
|
||||
_id: v4(),
|
||||
publishDate: new Date(publishDate + 150),
|
||||
published: true,
|
||||
};
|
||||
NewsPost.updateLastNewsPost(newPost);
|
||||
expect(NewsPost.lastNewsPost()._id).to.equal(newPost._id);
|
||||
});
|
||||
|
||||
it('does not update the post if new one is from the past', () => {
|
||||
const previousPost = new NewsPost({
|
||||
_id: v4(), publishDate: new Date(publishDate + 200), published: true,
|
||||
});
|
||||
NewsPost.updateLastNewsPost(previousPost);
|
||||
const newPost = new NewsPost({
|
||||
_id: v4(), publishDate: new Date(publishDate + 175), published: true,
|
||||
});
|
||||
NewsPost.updateLastNewsPost(newPost);
|
||||
expect(NewsPost.lastNewsPost()._id).to.equal(previousPost._id);
|
||||
});
|
||||
|
||||
it('does not update the post if new one is not published', () => {
|
||||
const previousPost = new NewsPost({
|
||||
_id: v4(), publishDate: new Date(publishDate + 250), published: true,
|
||||
});
|
||||
NewsPost.updateLastNewsPost(previousPost);
|
||||
const newPost = new NewsPost({
|
||||
_id: v4(), publishDate: new Date(publishDate + 300), published: false,
|
||||
});
|
||||
NewsPost.updateLastNewsPost(newPost);
|
||||
expect(NewsPost.lastNewsPost()._id).to.equal(previousPost._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('refreshes NewsPost', () => {
|
||||
let intervalId;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Delete all existing posts from the database
|
||||
await NewsPost.remove();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
});
|
||||
|
||||
it('refreshes the last post at a specific interval', async () => {
|
||||
await sleep(0.1); // wait 100ms to make sure all previous posts are in the past
|
||||
const previousPost = {
|
||||
_id: v4(), publishDate: new Date(), published: true,
|
||||
};
|
||||
NewsPost.updateLastNewsPost(previousPost);
|
||||
intervalId = refreshNewsPost(50); // refreshes every 50ms
|
||||
|
||||
await sleep(0.1); // wait 100ms to make sure the new post has a more recent publishDate
|
||||
const newPost = await NewsPost.create({
|
||||
_id: v4(),
|
||||
publishDate: new Date(),
|
||||
author: v4(),
|
||||
published: true,
|
||||
title: 'Title',
|
||||
credits: 'credits',
|
||||
text: 'text',
|
||||
});
|
||||
|
||||
expect(NewsPost.lastNewsPost()._id).to.equal(previousPost._id);
|
||||
await sleep(0.15); // wait 150ms
|
||||
|
||||
expect(NewsPost.lastNewsPost()._id).to.equal(newPost._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import { model as Challenge } from '../../../../website/server/models/challenge'
|
||||
import { model as Group } from '../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../website/server/models/task';
|
||||
import { InternalServerError } from '../../../../website/server/libs/errors';
|
||||
import { generateHistory } from '../../../helpers/api-unit.helper';
|
||||
|
||||
describe('Task Model', () => {
|
||||
@@ -98,8 +99,7 @@ describe('Task Model', () => {
|
||||
throw new Error('No exception when Id is None');
|
||||
} catch (err) {
|
||||
expect(err).to.exist;
|
||||
expect(err).to.be.an.instanceOf(Error);
|
||||
expect(err.message).to.eql('Task identifier is a required argument');
|
||||
expect(err).to.eql(new InternalServerError('Task identifier is a required argument'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -109,8 +109,7 @@ describe('Task Model', () => {
|
||||
throw new Error('No exception when user_id is undefined');
|
||||
} catch (err) {
|
||||
expect(err).to.exist;
|
||||
expect(err).to.be.an.instanceOf(Error);
|
||||
expect(err.message).to.eql('User identifier is a required argument');
|
||||
expect(err).to.eql(new InternalServerError('User identifier is a required argument'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -154,132 +153,6 @@ describe('Task Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('findMultipleByIdOrAlias', () => {
|
||||
let taskWithAlias;
|
||||
let secondTask;
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
taskWithAlias = new Tasks.todo({ // eslint-disable-line new-cap
|
||||
text: 'some text',
|
||||
alias: 'short-name',
|
||||
userId: user.id,
|
||||
});
|
||||
await taskWithAlias.save();
|
||||
|
||||
secondTask = new Tasks.habit({ // eslint-disable-line new-cap
|
||||
text: 'second task',
|
||||
alias: 'second-short-name',
|
||||
userId: user.id,
|
||||
});
|
||||
await secondTask.save();
|
||||
|
||||
sandbox.spy(Tasks.Task, 'find');
|
||||
});
|
||||
|
||||
it('throws an error if task identifiers is not passed in', async () => {
|
||||
try {
|
||||
await Tasks.Task.findMultipleByIdOrAlias(null, user._id);
|
||||
throw new Error('No exception when Id is None');
|
||||
} catch (err) {
|
||||
expect(err).to.exist;
|
||||
expect(err).to.be.an.instanceOf(Error);
|
||||
expect(err.message).to.eql('Task identifiers is a required array argument');
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if task identifiers is not an array', async () => {
|
||||
try {
|
||||
await Tasks.Task.findMultipleByIdOrAlias('string', user._id);
|
||||
throw new Error('No exception when Id is None');
|
||||
} catch (err) {
|
||||
expect(err).to.exist;
|
||||
expect(err).to.be.an.instanceOf(Error);
|
||||
expect(err.message).to.eql('Task identifiers is a required array argument');
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if user identifier is not passed in', async () => {
|
||||
try {
|
||||
await Tasks.Task.findMultipleByIdOrAlias([taskWithAlias._id]);
|
||||
throw new Error('No exception when user_id is undefined');
|
||||
} catch (err) {
|
||||
expect(err).to.exist;
|
||||
expect(err).to.be.an.instanceOf(Error);
|
||||
expect(err.message).to.eql('User identifier is a required argument');
|
||||
}
|
||||
});
|
||||
|
||||
it('returns task by id', async () => {
|
||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias([taskWithAlias._id], user._id);
|
||||
|
||||
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
|
||||
});
|
||||
|
||||
it('returns task by alias', async () => {
|
||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||
[taskWithAlias.alias], user._id,
|
||||
);
|
||||
|
||||
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
|
||||
});
|
||||
|
||||
it('returns multiple tasks', async () => {
|
||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||
[taskWithAlias.alias, secondTask._id], user._id,
|
||||
);
|
||||
|
||||
expect(foundTasks.length).to.eql(2);
|
||||
expect(foundTasks[0]._id).to.eql(taskWithAlias._id);
|
||||
expect(foundTasks[1]._id).to.eql(secondTask._id);
|
||||
});
|
||||
|
||||
it('returns a task only once if searched by both id and alias', async () => {
|
||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
|
||||
[taskWithAlias.alias, taskWithAlias._id], user._id,
|
||||
);
|
||||
|
||||
expect(foundTasks.length).to.eql(1);
|
||||
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
|
||||
});
|
||||
|
||||
it('scopes alias lookup to user', async () => {
|
||||
await Tasks.Task.findMultipleByIdOrAlias([taskWithAlias.alias], user._id);
|
||||
|
||||
expect(Tasks.Task.find).to.be.calledOnce;
|
||||
expect(Tasks.Task.find).to.be.calledWithMatch({
|
||||
$or: [
|
||||
{ _id: { $in: [] } },
|
||||
{ alias: { $in: [taskWithAlias.alias] } },
|
||||
],
|
||||
userId: user._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty array if tasks cannot be found', async () => {
|
||||
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(['not-found'], user._id);
|
||||
|
||||
expect(foundTasks).to.eql([]);
|
||||
});
|
||||
|
||||
it('accepts additional query parameters', async () => {
|
||||
await Tasks.Task.findMultipleByIdOrAlias([taskWithAlias.alias], user._id, { foo: 'bar' });
|
||||
|
||||
expect(Tasks.Task.find).to.be.calledOnce;
|
||||
expect(Tasks.Task.find).to.be.calledWithMatch({
|
||||
$or: [
|
||||
{ _id: { $in: [] } },
|
||||
{ alias: { $in: [taskWithAlias.alias] } },
|
||||
],
|
||||
userId: user._id,
|
||||
foo: 'bar',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitizeUserChallengeTask ', () => {
|
||||
});
|
||||
|
||||
|
||||
@@ -1,90 +1,87 @@
|
||||
import moment from 'moment';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import { model as NewsPost } from '../../../../website/server/models/newsPost';
|
||||
import { model as Group } from '../../../../website/server/models/group';
|
||||
import common from '../../../../website/common';
|
||||
|
||||
describe('User Model', () => {
|
||||
describe('.toJSON()', () => {
|
||||
it('keeps user._tmp when calling .toJSON', () => {
|
||||
const user = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username',
|
||||
lowerCaseUsername: 'username',
|
||||
email: 'email@email.email',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
it('keeps user._tmp when calling .toJSON', () => {
|
||||
const user = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username',
|
||||
lowerCaseUsername: 'username',
|
||||
email: 'email@email.email',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
});
|
||||
|
||||
user._tmp = { ok: true };
|
||||
user._nonTmp = { ok: true };
|
||||
|
||||
expect(user._tmp).to.eql({ ok: true });
|
||||
expect(user._nonTmp).to.eql({ ok: true });
|
||||
|
||||
const toObject = user.toObject();
|
||||
const toJSON = user.toJSON();
|
||||
|
||||
expect(toObject).to.not.have.keys('_tmp');
|
||||
expect(toObject).to.not.have.keys('_nonTmp');
|
||||
|
||||
expect(toJSON).to.have.any.key('_tmp');
|
||||
expect(toJSON._tmp).to.eql({ ok: true });
|
||||
expect(toJSON).to.not.have.keys('_nonTmp');
|
||||
},
|
||||
});
|
||||
|
||||
it('can add computed stats to a JSONified user object', () => {
|
||||
const user = new User();
|
||||
const userToJSON = user.toJSON();
|
||||
user._tmp = { ok: true };
|
||||
user._nonTmp = { ok: true };
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
expect(user._tmp).to.eql({ ok: true });
|
||||
expect(user._nonTmp).to.eql({ ok: true });
|
||||
|
||||
User.addComputedStatsToJSONObj(userToJSON.stats, userToJSON);
|
||||
const toObject = user.toObject();
|
||||
const toJSON = user.toJSON();
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
|
||||
});
|
||||
expect(toObject).to.not.have.keys('_tmp');
|
||||
expect(toObject).to.not.have.keys('_nonTmp');
|
||||
|
||||
it('can transform user object without mongoose helpers', async () => {
|
||||
const user = new User();
|
||||
await user.save();
|
||||
const userToJSON = await User.findById(user._id).lean().exec();
|
||||
expect(toJSON).to.have.any.key('_tmp');
|
||||
expect(toJSON._tmp).to.eql({ ok: true });
|
||||
expect(toJSON).to.not.have.keys('_nonTmp');
|
||||
});
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
expect(userToJSON.id).to.not.exist;
|
||||
it('can add computed stats to a JSONified user object', () => {
|
||||
const user = new User();
|
||||
const userToJSON = user.toJSON();
|
||||
|
||||
User.transformJSONUser(userToJSON);
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
|
||||
expect(userToJSON.id).to.equal(userToJSON._id);
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
});
|
||||
User.addComputedStatsToJSONObj(userToJSON.stats, userToJSON);
|
||||
|
||||
it('can transform user object without mongoose helpers (including computed stats)', async () => {
|
||||
const user = new User();
|
||||
await user.save();
|
||||
const userToJSON = await User.findById(user._id).lean().exec();
|
||||
expect(userToJSON.stats.maxMP).to.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
|
||||
});
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
it('can transform user object without mongoose helpers', async () => {
|
||||
const user = new User();
|
||||
await user.save();
|
||||
const userToJSON = await User.findById(user._id).lean().exec();
|
||||
|
||||
User.transformJSONUser(userToJSON, true);
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
expect(userToJSON.id).to.not.exist;
|
||||
|
||||
expect(userToJSON.id).to.equal(userToJSON._id);
|
||||
expect(userToJSON.stats.maxMP).to.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
|
||||
});
|
||||
User.transformJSONUser(userToJSON);
|
||||
|
||||
expect(userToJSON.id).to.equal(userToJSON._id);
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
});
|
||||
|
||||
it('can transform user object without mongoose helpers (including computed stats)', async () => {
|
||||
const user = new User();
|
||||
await user.save();
|
||||
const userToJSON = await User.findById(user._id).lean().exec();
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
|
||||
User.transformJSONUser(userToJSON, true);
|
||||
|
||||
expect(userToJSON.id).to.equal(userToJSON._id);
|
||||
expect(userToJSON.stats.maxMP).to.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
|
||||
});
|
||||
|
||||
context('achievements', () => {
|
||||
@@ -435,6 +432,7 @@ describe('User Model', () => {
|
||||
user = new User();
|
||||
});
|
||||
|
||||
|
||||
it('returns false if user does not have customer id', () => {
|
||||
expect(user.isSubscribed()).to.be.undefined;
|
||||
});
|
||||
@@ -560,6 +558,7 @@ describe('User Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
context('hasCancelled', () => {
|
||||
let user;
|
||||
beforeEach(() => {
|
||||
@@ -592,50 +591,6 @@ describe('User Model', () => {
|
||||
});
|
||||
|
||||
context('pre-save hook', () => {
|
||||
it('enrolls users that signup through web in the Drop Cap AB test', async () => {
|
||||
let user = new User();
|
||||
user.registeredThrough = 'habitica-web';
|
||||
user = await user.save();
|
||||
expect(user._ABtests.dropCapNotif).to.exist;
|
||||
});
|
||||
|
||||
it('does not enroll users that signup through modal in the Drop Cap AB test', async () => {
|
||||
let user = new User();
|
||||
user.registeredThrough = 'habitica-ios';
|
||||
user = await user.save();
|
||||
expect(user._ABtests.dropCapNotif).to.not.exist;
|
||||
});
|
||||
|
||||
it('marks the last news post as read for new users', async () => {
|
||||
const lastNewsPost = { _id: '1' };
|
||||
sandbox.stub(NewsPost, 'lastNewsPost').returns(lastNewsPost);
|
||||
|
||||
let user = new User();
|
||||
expect(user.isNew).to.equal(true);
|
||||
user = await user.save();
|
||||
|
||||
expect(user.checkNewStuff()).to.equal(false);
|
||||
expect(user.toJSON().flags.newStuff).to.equal(false);
|
||||
expect(user.flags.lastNewStuffRead).to.equal(lastNewsPost._id);
|
||||
});
|
||||
|
||||
it('does not mark the last news post as read for existing users', async () => {
|
||||
const lastNewsPost = { _id: '1' };
|
||||
const lastNewsPostStub = sandbox.stub(NewsPost, 'lastNewsPost');
|
||||
lastNewsPostStub.returns(lastNewsPost);
|
||||
|
||||
let user = new User();
|
||||
user = await user.save();
|
||||
|
||||
expect(user.isNew).to.equal(false);
|
||||
user.profile.name = 'new name';
|
||||
|
||||
lastNewsPostStub.returns({ _id: '2' });
|
||||
user = await user.save();
|
||||
|
||||
expect(user.flags.lastNewStuffRead).to.equal(lastNewsPost._id); // not _id: 2
|
||||
});
|
||||
|
||||
it('does not try to award achievements when achievements or items not selected in query', async () => {
|
||||
let user = new User();
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
@@ -808,7 +763,7 @@ describe('User Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('daysUserHasMissed', () => {
|
||||
context('days missed', () => {
|
||||
// http://forbrains.co.uk/international_tools/earth_timezones
|
||||
let user;
|
||||
|
||||
@@ -816,51 +771,24 @@ describe('User Model', () => {
|
||||
user = new User();
|
||||
});
|
||||
|
||||
it('correctly calculates days missed since lastCron', () => {
|
||||
const now = moment();
|
||||
user.lastCron = moment(now).subtract(5, 'days');
|
||||
it('should not cron early when going back a timezone', () => {
|
||||
const yesterday = moment('2017-12-05T00:00:00.000-06:00'); // 11 pm on 4 Texas
|
||||
const timezoneOffset = moment().zone('-06:00').zone();
|
||||
user.lastCron = yesterday;
|
||||
user.preferences.timezoneOffset = timezoneOffset;
|
||||
|
||||
const { daysMissed } = user.daysUserHasMissed(now);
|
||||
const today = moment('2017-12-06T00:00:00.000-06:00'); // 11 pm on 4 Texas
|
||||
const req = {};
|
||||
req.header = () => timezoneOffset + 60;
|
||||
|
||||
expect(daysMissed).to.eql(5);
|
||||
});
|
||||
const { daysMissed } = user.daysUserHasMissed(today, req);
|
||||
|
||||
it('uses timezone from preferences to calculate days missed', () => {
|
||||
const now = moment('2017-07-08 01:00:00Z');
|
||||
user.lastCron = moment('2017-07-04 13:00:00Z');
|
||||
user.preferences.timezoneOffset = 120;
|
||||
|
||||
const { daysMissed } = user.daysUserHasMissed(now);
|
||||
|
||||
expect(daysMissed).to.eql(3);
|
||||
});
|
||||
|
||||
it('uses timezone at last cron to calculate days missed', () => {
|
||||
const now = moment('2017-09-08 13:00:00Z');
|
||||
user.lastCron = moment('2017-09-06 01:00:00+02:00');
|
||||
user.preferences.timezoneOffset = 0;
|
||||
user.preferences.timezoneOffsetAtLastCron = -120;
|
||||
|
||||
const { daysMissed } = user.daysUserHasMissed(now);
|
||||
|
||||
expect(daysMissed).to.eql(2);
|
||||
});
|
||||
|
||||
it('respects new timezone that drags time into same day', () => {
|
||||
user.lastCron = moment('2017-12-05T00:00:00.000-06:00');
|
||||
user.preferences.timezoneOffset = 360;
|
||||
const today = moment('2017-12-06T00:00:00.000-06:00');
|
||||
const requestWithMinus7Timezone = { header: () => 420 };
|
||||
|
||||
const { daysMissed } = user.daysUserHasMissed(today, requestWithMinus7Timezone);
|
||||
|
||||
expect(user.preferences.timezoneOffset).to.eql(420);
|
||||
expect(daysMissed).to.eql(0);
|
||||
});
|
||||
|
||||
it('should not cron early when going back a timezone with a custom day start', () => {
|
||||
const yesterday = moment('2017-12-05T02:00:00.000-08:00');
|
||||
const timezoneOffset = 480;
|
||||
const timezoneOffset = moment().zone('-08:00').zone();
|
||||
user.lastCron = yesterday;
|
||||
user.preferences.timezoneOffset = timezoneOffset;
|
||||
user.preferences.dayStart = 2;
|
||||
@@ -874,46 +802,4 @@ describe('User Model', () => {
|
||||
expect(daysMissed).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('isNewsPoster', async () => {
|
||||
const user = new User();
|
||||
await user.save();
|
||||
|
||||
expect(user.isNewsPoster()).to.equal(false);
|
||||
|
||||
user.contributor.newsPoster = true;
|
||||
expect(user.isNewsPoster()).to.equal(true);
|
||||
});
|
||||
|
||||
describe('checkNewStuff', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('no last news post', () => {
|
||||
sandbox.stub(NewsPost, 'lastNewsPost').returns(null);
|
||||
expect(user.checkNewStuff()).to.equal(false);
|
||||
expect(user.toJSON().flags.newStuff).to.equal(false);
|
||||
});
|
||||
|
||||
it('last news post read', () => {
|
||||
sandbox.stub(NewsPost, 'lastNewsPost').returns({ _id: '123' });
|
||||
user.flags.lastNewStuffRead = '123';
|
||||
expect(user.checkNewStuff()).to.equal(false);
|
||||
expect(user.toJSON().flags.newStuff).to.equal(false);
|
||||
});
|
||||
|
||||
it('last news post not read', () => {
|
||||
sandbox.stub(NewsPost, 'lastNewsPost').returns({ _id: '123' });
|
||||
user.flags.lastNewStuffRead = '124';
|
||||
expect(user.checkNewStuff()).to.equal(true);
|
||||
expect(user.toJSON().flags.newStuff).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -299,6 +299,7 @@ describe('Webhook Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
context('type is globalActivity', () => {
|
||||
let config;
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import {
|
||||
generateUser,
|
||||
requester,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService';
|
||||
|
||||
describe('POST /analytics/track/:eventName', () => {
|
||||
it('calls res.analytics', async () => {
|
||||
const user = await generateUser();
|
||||
sandbox.spy(analytics, 'track');
|
||||
|
||||
const requestWithHeaders = requester(user, { 'x-client': 'habitica-web' });
|
||||
await requestWithHeaders.post('/analytics/track/eventName', { data: 'example' }, { 'x-client': 'habitica-web' });
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
expect(analytics.track).to.be.calledWith('eventName', sandbox.match({ data: 'example' }));
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
});
|
||||
@@ -117,7 +117,26 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('returns only first 30 members if req.query.limit is undefined', async () => {
|
||||
it('returns only first 30 members if req.query.includeAllMembers is not true', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
const usersToGenerate = [];
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
usersToGenerate.push(generateUser({ challenges: [challenge._id] }));
|
||||
}
|
||||
await Promise.all(usersToGenerate);
|
||||
|
||||
const res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=not-true`);
|
||||
expect(res.length).to.equal(30);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only first 30 members if req.query.includeAllMembers is not defined', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
@@ -136,67 +155,24 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', privacy: 'private' });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
const anotherUser = await generateUser();
|
||||
|
||||
await expect(anotherUser.get(`/challenges/${challenge._id}/members?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', privacy: 'private' });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
const anotherUser = await generateUser();
|
||||
|
||||
await expect(anotherUser.get(`/challenges/${challenge._id}/members?limit=-13`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', privacy: 'private' });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
const anotherUser = await generateUser();
|
||||
|
||||
await expect(anotherUser.get(`/challenges/${challenge._id}/members?limit=true`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns up to 60 members when req.query.limit is specified', async () => {
|
||||
it('returns all members if req.query.includeAllMembers is true', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
const usersToGenerate = [];
|
||||
for (let i = 0; i < 62; i += 1) {
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
usersToGenerate.push(generateUser({ challenges: [challenge._id] }));
|
||||
}
|
||||
await Promise.all(usersToGenerate);
|
||||
|
||||
let res = await user.get(`/challenges/${challenge._id}/members?limit=57`);
|
||||
expect(res.length).to.equal(57);
|
||||
const res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=true`);
|
||||
expect(res.length).to.equal(32);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
res = await user.get(`/challenges/${challenge._id}/members?limit=60&lastId=${res[res.length - 1]._id}`);
|
||||
expect(res.length).to.equal(6);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
}).timeout(30000);
|
||||
});
|
||||
|
||||
it('supports using req.query.lastId to get more members', async function test () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
@@ -221,34 +197,6 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
expect(resIds).to.eql(expectedIds.sort());
|
||||
});
|
||||
|
||||
it('supports using req.query.includeTasks in order to add challenge-related tasks of all members', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
const usersToGenerate = [];
|
||||
for (let i = 0; i < 8; i += 1) {
|
||||
usersToGenerate.push(generateUser({ challenges: [challenge._id] }));
|
||||
}
|
||||
await Promise.all(usersToGenerate);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, [{ type: 'habit', text: 'Some task' }]);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, [{ type: 'daily', text: 'Some different task' }]);
|
||||
|
||||
const res = await user.get(`/challenges/${challenge._id}/members?includeTasks=true`);
|
||||
expect(res.length).to.equal(9);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.property('tasks');
|
||||
expect(member.tasks).to.be.an('array');
|
||||
expect(member.tasks).to.have.lengthOf(2);
|
||||
member.tasks.forEach(task => {
|
||||
expect(task).to.include.all.keys(['type', 'value', 'priority', 'text', '_id', 'userId']);
|
||||
expect(task).to.not.have.any.keys(['tags', 'checklist']);
|
||||
expect(task.challenge.id).to.be.equal(challenge._id);
|
||||
expect(task.userId).to.be.equal(member._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('supports using req.query.search to get search members', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
|
||||
+2
-1
@@ -116,6 +116,7 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
}]);
|
||||
|
||||
const memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`);
|
||||
expect(memberProgress.tasks[0]).to.not.have.any.keys(['tags', 'checklist']);
|
||||
expect(memberProgress.tasks[0]).not.to.have.key('tags');
|
||||
expect(memberProgress.tasks[0].checklist).to.eql([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
|
||||
describe('GET challenges/user', () => {
|
||||
context('no official challenges', () => {
|
||||
let user; let member; let nonMember; let challenge; let challenge2;
|
||||
let publicGuild; let userData; let groupData;
|
||||
let user; let member; let nonMember; let challenge; let challenge2; let
|
||||
publicGuild;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
@@ -19,20 +19,25 @@ describe('GET challenges/user', () => {
|
||||
members: 1,
|
||||
});
|
||||
|
||||
publicGuild = group;
|
||||
groupData = {
|
||||
_id: publicGuild._id,
|
||||
categories: [],
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
};
|
||||
|
||||
user = groupLeader;
|
||||
userData = {
|
||||
publicGuild = group;
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
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 challenges user has joined', async () => {
|
||||
await nonMember.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
const challenges = await nonMember.get('/challenges/user');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
expect(foundChallenge.leader).to.eql({
|
||||
_id: publicGuild.leader._id,
|
||||
id: publicGuild.leader._id,
|
||||
profile: { name: user.profile.name },
|
||||
@@ -44,172 +49,195 @@ describe('GET challenges/user', () => {
|
||||
flags: {
|
||||
verifiedUsername: true,
|
||||
},
|
||||
};
|
||||
|
||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
|
||||
await nonMember.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
context('all challenges', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
expect(foundChallenge.leader).to.eql(userData);
|
||||
expect(foundChallenge.group).to.eql(groupData);
|
||||
});
|
||||
|
||||
it('should not return challenges a non-member has not joined', async () => {
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
|
||||
it('should return challenges user has created', async () => {
|
||||
const challenges = await user.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql(userData);
|
||||
expect(foundChallenge1.group).to.eql(groupData);
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
});
|
||||
|
||||
it('should return challenges in user\'s group', async () => {
|
||||
const challenges = await member.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql(userData);
|
||||
expect(foundChallenge1.group).to.eql(groupData);
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
});
|
||||
|
||||
it('should return newest challenges first', async () => {
|
||||
let challenges = await user.get('/challenges/user?page=0');
|
||||
|
||||
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/user?page=0');
|
||||
|
||||
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
const privateChallenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
const challenges = await nonMember.get('/challenges/user?page=0');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
const privateChallenge = await generateChallenge(groupLeader, group, {
|
||||
categories: [{
|
||||
name: 'academics',
|
||||
slug: 'academics',
|
||||
}],
|
||||
});
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
const challenges = await nonMember.get('/challenges/user?page=0&categories=academics&owned=not_owned');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
expect(foundChallenge.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
categories: [],
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
});
|
||||
});
|
||||
|
||||
context('my challenges', () => {
|
||||
it('should return challenges user has joined', async () => {
|
||||
const challenges = await nonMember.get(`/challenges/user?page=0&member=${true}`);
|
||||
it('should return challenges user has created', async () => {
|
||||
const challenges = await user.get('/challenges/user');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge).to.exist;
|
||||
expect(foundChallenge.leader).to.eql(userData);
|
||||
expect(foundChallenge.group).to.eql(groupData);
|
||||
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,
|
||||
},
|
||||
});
|
||||
expect(foundChallenge1.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
categories: [],
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
});
|
||||
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,
|
||||
},
|
||||
});
|
||||
expect(foundChallenge2.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
categories: [],
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return challenges in user\'s group', async () => {
|
||||
const challenges = await member.get('/challenges/user');
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
expect(foundChallenge1.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
categories: [],
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
});
|
||||
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,
|
||||
},
|
||||
});
|
||||
expect(foundChallenge2.group).to.eql({
|
||||
_id: publicGuild._id,
|
||||
categories: [],
|
||||
id: publicGuild._id,
|
||||
type: publicGuild.type,
|
||||
privacy: publicGuild.privacy,
|
||||
name: publicGuild.name,
|
||||
summary: publicGuild.name,
|
||||
leader: publicGuild.leader._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return challenges in user groups if we send member true param', async () => {
|
||||
const challenges = await member.get(`/challenges/user?member=${true}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.not.exist;
|
||||
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
|
||||
it('should return newest challenges first', async () => {
|
||||
let challenges = await user.get('/challenges/user');
|
||||
|
||||
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/user');
|
||||
|
||||
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
});
|
||||
|
||||
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',
|
||||
},
|
||||
});
|
||||
|
||||
it('should return challenges user has created', async () => {
|
||||
const challenges = await user.get(`/challenges/user?page=0&member=${true}`);
|
||||
const privateChallenge = await generateChallenge(groupLeader, group);
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql(userData);
|
||||
expect(foundChallenge1.group).to.eql(groupData);
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
const challenges = await nonMember.get('/challenges/user');
|
||||
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
it('should return challenges user has created if filter by owned', async () => {
|
||||
const challenges = await user.get(`/challenges/user?member=${true}&owned=owned&page=0`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql(userData);
|
||||
expect(foundChallenge1.group).to.eql(groupData);
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql(userData);
|
||||
expect(foundChallenge2.group).to.eql(groupData);
|
||||
const privateChallenge = await generateChallenge(groupLeader, group, {
|
||||
categories: [{
|
||||
name: 'academics',
|
||||
slug: 'academics',
|
||||
}],
|
||||
});
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
it('should not return challenges user has created if filter by not owned', async () => {
|
||||
const challenges = await user.get(`/challenges/user?page=0&owned=not_owned&member=${true}`);
|
||||
const challenges = await nonMember.get('/challenges/user?categories=academics&owned=not_owned');
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.not.exist;
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges in user groups', async () => {
|
||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
||||
|
||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.not.exist;
|
||||
|
||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.not.exist;
|
||||
});
|
||||
const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -253,7 +281,7 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
const challenges = await user.get('/challenges/user?page=0');
|
||||
const challenges = await user.get('/challenges/user');
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(0);
|
||||
@@ -274,7 +302,7 @@ describe('GET challenges/user', () => {
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user?page=0');
|
||||
challenges = await user.get('/challenges/user');
|
||||
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
@@ -314,12 +342,18 @@ describe('GET challenges/user', () => {
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
const categoryChallenge = await generateChallenge(user, guild, { categories });
|
||||
await user.post(`/challenges/${categoryChallenge._id}/join`);
|
||||
const challenges = await user.get(`/challenges/user?page=0&categories=${categories[0].slug}`);
|
||||
const challenges = await user.get(`/challenges/user?categories=${categories[0].slug}`);
|
||||
|
||||
expect(challenges[0]._id).to.eql(categoryChallenge._id);
|
||||
expect(challenges.length).to.eql(1);
|
||||
});
|
||||
|
||||
it('does not page challenges if page parameter is absent', async () => {
|
||||
const challenges = await user.get('/challenges/user');
|
||||
|
||||
expect(challenges.length).to.be.above(11);
|
||||
});
|
||||
|
||||
it('paginates challenges', async () => {
|
||||
const challenges = await user.get('/challenges/user?page=0');
|
||||
const challengesPaged = await user.get('/challenges/user?page=1&owned=owned');
|
||||
@@ -329,7 +363,7 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
|
||||
it('filters by owned', async () => {
|
||||
const challenges = await member.get('/challenges/user?page=0&owned=owned');
|
||||
const challenges = await member.get('/challenges/user?owned=owned');
|
||||
|
||||
expect(challenges.length).to.eql(0);
|
||||
});
|
||||
|
||||
+2
-10
@@ -103,15 +103,7 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
||||
await expect(winningUser.sync()).to.eventually.have.nested.property('achievements.challenges').to.include(challenge.name);
|
||||
// 2 because winningUser just joined the challenge, which now awards an achievement
|
||||
expect(winningUser.notifications.length).to.equal(2);
|
||||
|
||||
const notif = winningUser.notifications[1];
|
||||
expect(notif.type).to.equal('WON_CHALLENGE');
|
||||
expect(notif.data).to.eql({
|
||||
id: challenge._id,
|
||||
name: challenge.name,
|
||||
prize: challenge.prize,
|
||||
leader: challenge.leader,
|
||||
});
|
||||
expect(winningUser.notifications[1].type).to.equal('WON_CHALLENGE');
|
||||
});
|
||||
|
||||
it('gives winner gems as reward', async () => {
|
||||
@@ -167,7 +159,7 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
||||
|
||||
expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED');
|
||||
expect(testTask.challenge.winner).to.eql(winningUser.profile.name);
|
||||
expect(challengeTag.challenge).to.eql(false);
|
||||
expect(challengeTag.challenge).to.eql('false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { find } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import { IncomingWebhook } from '@slack/client';
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
@@ -20,7 +20,7 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
admin = await generateUser({ balance: 1, 'contributor.admin': true });
|
||||
anotherUser = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
||||
newUser = await generateUser({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send');
|
||||
|
||||
group = await user.post('/groups', {
|
||||
name: 'Test Guild',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IncomingWebhook } from '@slack/webhook';
|
||||
import { IncomingWebhook } from '@slack/client';
|
||||
import nconf from 'nconf';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { CHAT_FLAG_FROM_SHADOW_MUTE, MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
@@ -133,7 +134,7 @@ describe('POST /chat', () => {
|
||||
describe('shadow-mute user', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -328,8 +329,7 @@ describe('POST /chat', () => {
|
||||
members: 1,
|
||||
});
|
||||
|
||||
// Update the bannedWordsAllowed property for the group
|
||||
group.update({ bannedWordsAllowed: true });
|
||||
guildsAllowingBannedWords[group._id] = true;
|
||||
|
||||
const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage });
|
||||
|
||||
@@ -355,7 +355,7 @@ describe('POST /chat', () => {
|
||||
context('banned slur', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -503,8 +503,8 @@ describe('POST /chat', () => {
|
||||
const memberUsername = 'memberUsername';
|
||||
await member.update({ 'auth.local.username': memberUsername });
|
||||
|
||||
const messageWithMentions = `hi @${memberUsername} 123456789
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 89 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 12345678 END.`;
|
||||
const messageWithMentions = `hi @${memberUsername} 123456789
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 89 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 12345678 END.`;
|
||||
expect(messageWithMentions.length).to.equal(MAX_MESSAGE_LENGTH);
|
||||
const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: messageWithMentions });
|
||||
const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`);
|
||||
|
||||
@@ -12,7 +12,7 @@ import apiError from '../../../../../website/server/libs/apiError';
|
||||
describe('GET /groups', () => {
|
||||
let user;
|
||||
let userInGuild;
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 2;
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 3; // 2 + the tavern
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2;
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
||||
@@ -236,22 +236,11 @@ describe('GET /groups', () => {
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
const page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
|
||||
// 1 created now, 4 by other tests, -1 for no more tavern.
|
||||
.to.eventually.have.a.lengthOf(1 + 4 - 1);
|
||||
expect(page2[3].name).to.equal('guild with less members');
|
||||
.to.eventually.have.a.lengthOf(1 + 4); // 1 created now, 4 by other tests
|
||||
expect(page2[4].name).to.equal('guild with less members');
|
||||
}).timeout(10000);
|
||||
});
|
||||
|
||||
it('makes sure that the tavern doesn\'t show up when guilds is passed as a query', async () => {
|
||||
const guilds = await user.get('/groups?type=guilds');
|
||||
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('makes sure that the tavern doesn\'t show up when publicGuilds is passed as a query', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds');
|
||||
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=guilds'))
|
||||
.to.eventually.have.a
|
||||
@@ -265,7 +254,7 @@ describe('GET /groups', () => {
|
||||
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW - 1); // -1 for no Tavern.
|
||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
|
||||
});
|
||||
|
||||
it('returns a list of groups user has access to', async () => {
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('returns only first 30 invites by default (req.query.limit not specified)', async () => {
|
||||
it('returns only first 30 invites', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
@@ -89,65 +89,6 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
}).timeout(10000);
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||
|
||||
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
||||
expect(res.length).to.equal(14);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
res = await leader.get(`/groups/${group._id}/invites?limit=31`);
|
||||
expect(res.length).to.equal(31);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
}).timeout(30000);
|
||||
|
||||
it('supports using req.query.lastId to get more invites', async function test () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
expect(memberRes.inbox.messages).to.not.exist;
|
||||
});
|
||||
|
||||
it('returns only first 30 members by default (req.query.limit not specified)', async () => {
|
||||
it('returns only first 30 members', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
const usersToGenerate = [];
|
||||
@@ -133,60 +133,6 @@ describe('GET /groups/:groupId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
await expect(user.get('/groups/party/members?limit=61')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
await expect(user.get('/groups/party/members?limit=0')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
await expect(user.get('/groups/party/members?limit=1.1')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns up to 60 members when req.query.limit is specified', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
const usersToGenerate = [];
|
||||
for (let i = 0; i < 62; i += 1) {
|
||||
usersToGenerate.push(generateUser({ party: { _id: group._id } }));
|
||||
}
|
||||
await Promise.all(usersToGenerate);
|
||||
|
||||
let res = await user.get('/groups/party/members?limit=60');
|
||||
expect(res.length).to.equal(60);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
res = await user.get(`/groups/party/members?limit=60&lastId=${res[res.length - 1]._id}`);
|
||||
expect(res.length).to.equal(3);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
}).timeout(30000);
|
||||
|
||||
it('returns only first 30 members even when ?includeAllMembers=true', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
|
||||
@@ -75,7 +75,12 @@ describe('POST /group/:groupId/remove-manager', () => {
|
||||
await nonLeader.post(`/tasks/${task._id}/assign/${nonManager._id}`);
|
||||
const memberTasks = await nonManager.get('/tasks/user');
|
||||
const syncedTask = find(memberTasks, findAssignedTask);
|
||||
await nonManager.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
await expect(nonManager.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
const updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
|
||||
managerId: nonLeader._id,
|
||||
|
||||
@@ -203,16 +203,6 @@ describe('POST /group/:groupId/join', () => {
|
||||
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||
});
|
||||
|
||||
it('Issue #12291: accepting a redundant party invite will let the user stay in the party', async () => {
|
||||
await invitedUser.update({
|
||||
'party._id': party._id,
|
||||
});
|
||||
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||
});
|
||||
|
||||
it('notifies inviting user that their invitation was accepted', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user