after merge

This commit is contained in:
Xaz16
2019-10-28 23:15:45 +03:00
2495 changed files with 121414 additions and 108105 deletions

View File

@@ -1,6 +1,12 @@
{ {
"plugins": [ "presets": [
"transform-es2015-modules-commonjs", [
"syntax-object-rest-spread", "@babel/preset-env",
{
"targets": {
"node": true
}
}
]
] ]
} }

View File

@@ -3,6 +3,8 @@ coverage/
database_reports/ database_reports/
website/build/ website/build/
website/transpiled-babel/ website/transpiled-babel/
# Has its own linter
website/client/
website/common/transpiled-babel/ website/common/transpiled-babel/
dist/ dist/
dist-client/ dist-client/

View File

@@ -1,10 +0,0 @@
{
"root": true,
"env": {
"node": true,
},
"extends": [
"habitrpg",
"habitrpg/esnext"
],
}

6
.eslintrc.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
root: true,
extends: [
'habitrpg/lib/node'
],
}

198
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,198 @@
name: Test
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
env:
CI: true
- run: npm run lint-no-fix
apidoc:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
env:
CI: true
- run: npm run apidoc
sanity:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
env:
CI: true
- run: npm run test:sanity
common:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
env:
CI: true
- run: npm run test:common
content:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
env:
CI: true
- run: npm run test:content
api-unit:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- 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
- run: npm run test:api:unit
env:
REQUIRES_SERVER=true: true
api-v3-integration:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- 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
- run: npm run test:api-v3:integration
env:
REQUIRES_SERVER=true: true
api-v4-integration:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- 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
- run: npm run test:api-v4:integration
env:
REQUIRES_SERVER=true: true
client-unit:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: cp config.json.example config.json
- name: npm install
run: |
npm ci
env:
CI: true
- run: npm run test:unit
working-directory: ./website/client

11
.gitignore vendored
View File

@@ -1,10 +1,5 @@
.DS_Store .DS_Store
website/client-old/gen
website/client-old/common
website/client-old/apidoc
website/build website/build
website/client-old/js/habitrpg-shared.js*
website/client-old/css/habitrpg-shared.css
website/transpiled-babel/ website/transpiled-babel/
website/common/transpiled-babel/ website/common/transpiled-babel/
node_modules node_modules
@@ -15,8 +10,6 @@ apidoc_build
config.json config.json
npm-debug.log* npm-debug.log*
lib lib
website/client-old/bower_components
website/client-old/new-stuff.html
newrelic_agent.log newrelic_agent.log
.bower-tmp .bower-tmp
.bower-registry .bower-registry
@@ -27,15 +20,13 @@ TODO
*.log *.log
src/*/*.map src/*/*.map
src/*/*/*.map src/*/*/*.map
test/*.js
test/*.map
website/client-old/docs
*.sublime-workspace *.sublime-workspace
coverage coverage
coverage.html coverage.html
common/dist/scripts/* common/dist/scripts/*
dist dist
dist-client dist-client
website/client/dist
test/client/unit/coverage test/client/unit/coverage
test/client/e2e/reports test/client/e2e/reports
test/client-old/spec/mocks/translations.js test/client-old/spec/mocks/translations.js

View File

@@ -1,20 +1,9 @@
node_modules/** node_modules/**
.bower-cache/**
.bower-tmp/**
.bower-registry/**
website/client-old/**
website/client/** website/client/**
website/client/store/**
website/views/**
website/build/**
dist/**
test/** test/**
.git/** .git/**
Gruntfile.js
CHANGELOG.md
.idea* .idea*
*.log *.log
newrelic_agent.log
*.swp *.swp
*.swx *.swx
website/raw_sprites/** website/raw_sprites/**

View File

@@ -1,29 +0,0 @@
language: node_js
node_js:
- '12'
services:
- mongodb
cache:
directories:
- 'node_modules'
addons:
chrome: stable
before_script:
- npm run test:build
- cp config.json.example config.json
- sleep 5
script:
- npm run $TEST
env:
global:
- DISABLE_REQUEST_LOGGING=true
matrix:
- TEST="lint"
- TEST="test:api:unit" REQUIRES_SERVER=true COVERAGE=true
- TEST="test:api-v3:integration" REQUIRES_SERVER=true COVERAGE=true
- TEST="test:api-v4:integration" REQUIRES_SERVER=true COVERAGE=true
- TEST="test:sanity"
- TEST="test:content" COVERAGE=true
- TEST="test:common" COVERAGE=true
- TEST="client:unit" COVERAGE=true
- TEST="apidoc"

View File

@@ -22,9 +22,6 @@ RUN git clone --branch release --depth 1 https://github.com/HabitRPG/habitica.gi
RUN npm install RUN npm install
RUN gulp build:prod --force RUN gulp build:prod --force
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica # Start Habitica
EXPOSE 3000 EXPOSE 3000
CMD ["node", "./website/transpiled-babel/index.js"] CMD ["node", "./website/transpiled-babel/index.js"]

View File

@@ -1,4 +1,4 @@
Habitica [![Build Status](https://travis-ci.org/HabitRPG/habitica.svg?branch=develop)](https://travis-ci.org/HabitRPG/habitica) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE) [![Open Source Helpers](https://www.codetriage.com/habitrpg/habitica/badges/users.svg)](https://www.codetriage.com/habitrpg/habitica) Habitica ![Build Status](https://github.com/HabitRPG/habitica/workflows/Test/badge.svg) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE) [![Open Source Helpers](https://www.codetriage.com/habitrpg/habitica/badges/users.svg)](https://www.codetriage.com/habitrpg/habitica)
=============== ===============
[![Greenkeeper badge](https://badges.greenkeeper.io/HabitRPG/habitica.svg)](https://greenkeeper.io/) [![Greenkeeper badge](https://badges.greenkeeper.io/HabitRPG/habitica.svg)](https://greenkeeper.io/)

View File

@@ -1,11 +0,0 @@
# Vagrant #
Vagrant is a system to create reproducible and portable development
environments. Because of the variety of systems used for Habitica
development and the various issues developers may encounter setting up
Habitica on them, vagrant provides a single development enviroment with
minimal dependencies on the developer's local platform. It can be used
on a variety of systems including Windows, Mac OS X, and Linux.
Instructions for using the Habitica Vagrant environment are in
[Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).

View File

@@ -1,22 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.provider "virtualbox" do |v|
v.memory = 4096
v.cpus = 1
v.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/vagrant", "1"]
end
config.vm.box = "thepeopleseason/habitrpg"
config.ssh.forward_agent = true
config.vm.hostname = "habitrpg"
config.vm.network "forwarded_port", guest: 3000, host: 3000, auto_correct: true
config.vm.usable_port_range = (3000..3050)
config.vm.network "forwarded_port", guest: 8080, host: 8080, auto_correct: true
config.vm.usable_port_range = (8080..8130)
config.vm.provision :shell, :path => "vagrant_scripts/vagrant.sh"
end

View File

@@ -33,6 +33,7 @@
"LOGGLY_TOKEN": "example-token", "LOGGLY_TOKEN": "example-token",
"MAINTENANCE_MODE": "false", "MAINTENANCE_MODE": "false",
"NODE_DB_URI": "mongodb://localhost/habitrpg", "NODE_DB_URI": "mongodb://localhost/habitrpg",
"MONGODB_POOL_SIZE": "10",
"NODE_ENV": "development", "NODE_ENV": "development",
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin", "PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo", "PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",

View File

@@ -35,7 +35,7 @@ services:
- .:/code - .:/code
- /code/node_modules - /code/node_modules
mongo: mongo:
image: mongo:3.4 image: mongo:3.6
networks: networks:
- habitica - habitica
ports: ports:

View File

@@ -25,7 +25,7 @@ services:
- mongo - mongo
mongo: mongo:
image: mongo:3.4 image: mongo:3.6
ports: ports:
- "27017:27017" - "27017:27017"
networks: networks:

View File

@@ -4,12 +4,12 @@ import apidoc from 'apidoc';
const APIDOC_DEST_PATH = './apidoc_build'; const APIDOC_DEST_PATH = './apidoc_build';
const APIDOC_SRC_PATH = './website/server'; const APIDOC_SRC_PATH = './website/server';
gulp.task('apidoc:clean', (done) => { gulp.task('apidoc:clean', done => {
clean(APIDOC_DEST_PATH, done); clean(APIDOC_DEST_PATH, done);
}); });
gulp.task('apidoc', gulp.series('apidoc:clean', (done) => { gulp.task('apidoc', gulp.series('apidoc:clean', done => {
let result = apidoc.createDoc({ const result = apidoc.createDoc({
src: APIDOC_SRC_PATH, src: APIDOC_SRC_PATH,
dest: APIDOC_DEST_PATH, dest: APIDOC_DEST_PATH,
}); });
@@ -21,6 +21,4 @@ gulp.task('apidoc', gulp.series('apidoc:clean', (done) => {
} }
})); }));
gulp.task('apidoc:watch', gulp.series('apidoc', (done) => { gulp.task('apidoc:watch', gulp.series('apidoc', done => gulp.watch(`${APIDOC_SRC_PATH}/**/*.js`, gulp.series('apidoc', done))));
return gulp.watch(`${APIDOC_SRC_PATH}/**/*.js`, gulp.series('apidoc', done));
}));

View File

@@ -1,43 +1,28 @@
import gulp from 'gulp'; import gulp from 'gulp';
import babel from 'gulp-babel'; import babel from 'gulp-babel';
import webpackProductionBuild from '../webpack/build';
gulp.task('build:src', () => { gulp.task('build:src', () => gulp.src('website/server/**/*.js')
return gulp.src('website/server/**/*.js') .pipe(babel())
.pipe(babel()) .pipe(gulp.dest('website/transpiled-babel/')));
.pipe(gulp.dest('website/transpiled-babel/'));
});
gulp.task('build:common', () => { gulp.task('build:common', () => gulp.src('website/common/script/**/*.js')
return gulp.src('website/common/script/**/*.js') .pipe(babel())
.pipe(babel()) .pipe(gulp.dest('website/common/transpiled-babel/')));
.pipe(gulp.dest('website/common/transpiled-babel/'));
});
gulp.task('build:server', gulp.series('build:src', 'build:common', done => done())); gulp.task('build:server', gulp.series('build:src', 'build:common', done => done()));
// Client Production Build
gulp.task('build:client', (done) => {
webpackProductionBuild((err, output) => {
if (err) return done(err);
console.log(output); // eslint-disable-line no-console
done();
});
});
gulp.task('build:prod', gulp.series( gulp.task('build:prod', gulp.series(
'build:server', 'build:server',
'build:client',
'apidoc', 'apidoc',
done => done() done => done(),
)); ));
let buildArgs = []; const buildArgs = [];
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
buildArgs.push('build:prod'); buildArgs.push('build:prod');
} }
gulp.task('build', gulp.series(buildArgs, (done) => { gulp.task('build', gulp.series(buildArgs, done => {
done(); done();
})); }));

View File

@@ -1,26 +1,30 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import logger from '../website/server/libs/logger'; import nconf from 'nconf';
import nconf from 'nconf'; import repl from 'repl';
import repl from 'repl'; import gulp from 'gulp';
import gulp from 'gulp'; import logger from '../website/server/libs/logger';
// Add additional properties to the repl's context // Add additional properties to the repl's context
let improveRepl = (context) => { const improveRepl = context => {
// Let "exit" and "quit" terminate the console // Let "exit" and "quit" terminate the console
['exit', 'quit'].forEach((term) => { ['exit', 'quit'].forEach(term => {
Object.defineProperty(context, term, { get () { Object.defineProperty(context, term, {
process.exit(); get () { // eslint-disable-line getter-return
}}); process.exit();
},
});
}); });
// "clear" clears the screen // "clear" clears the screen
Object.defineProperty(context, 'clear', { get () { Object.defineProperty(context, 'clear', {
process.stdout.write('\u001B[2J\u001B[0;0f'); get () { // eslint-disable-line getter-return
}}); process.stdout.write('\u001B[2J\u001B[0;0f');
},
});
context.Challenge = require('../website/server/models/challenge').model; // eslint-disable-line global-require context.Challenge = require('../website/server/models/challenge').model; // eslint-disable-line global-require
context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require 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 context.User = require('../website/server/models/user').model; // eslint-disable-line global-require
const isProd = nconf.get('NODE_ENV') === 'production'; const isProd = nconf.get('NODE_ENV') === 'production';
const mongooseOptions = !isProd ? {} : { const mongooseOptions = !isProd ? {} : {
@@ -30,14 +34,14 @@ let improveRepl = (context) => {
mongoose.connect( mongoose.connect(
nconf.get('NODE_DB_URI'), nconf.get('NODE_DB_URI'),
mongooseOptions, mongooseOptions,
(err) => { err => {
if (err) throw err; if (err) throw err;
logger.info('Connected with Mongoose'); logger.info('Connected with Mongoose');
} },
); );
}; };
gulp.task('console', (done) => { gulp.task('console', done => {
improveRepl(repl.start({ improveRepl(repl.start({
prompt: 'Habitica > ', prompt: 'Habitica > ',
}).context); }).context);

View File

@@ -4,29 +4,29 @@ import spritesmith from 'gulp.spritesmith';
import clean from 'rimraf'; import clean from 'rimraf';
import sizeOf from 'image-size'; import sizeOf from 'image-size';
import mergeStream from 'merge-stream'; import mergeStream from 'merge-stream';
import {basename} from 'path'; import { basename } from 'path';
import {sync} from 'glob'; import { sync } from 'glob';
import {each} from 'lodash'; import { each } from 'lodash';
import vinylBuffer from 'vinyl-buffer'; import vinylBuffer from 'vinyl-buffer';
// https://github.com/Ensighten/grunt-spritesmith/issues/67#issuecomment-34786248 // https://github.com/Ensighten/grunt-spritesmith/issues/67#issuecomment-34786248
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3; const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
const IMG_DIST_PATH = 'website/client/assets/images/sprites/'; const IMG_DIST_PATH = 'website/client/src/assets/images/sprites/';
const CSS_DIST_PATH = 'website/client/assets/css/sprites/'; const CSS_DIST_PATH = 'website/client/src/assets/css/sprites/';
function checkForSpecialTreatment (name) { function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame|^eyewear_special_\w+HalfMoon/; const regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame|^eyewear_special_\w+HalfMoon/;
return name.match(regex) || name === 'head_0'; return name.match(regex) || name === 'head_0';
} }
function calculateImgDimensions (img, addPadding) { function calculateImgDimensions (img, addPadding) {
let dims = sizeOf(img); let dims = sizeOf(img);
let requiresSpecialTreatment = checkForSpecialTreatment(img); const requiresSpecialTreatment = checkForSpecialTreatment(img);
if (requiresSpecialTreatment) { if (requiresSpecialTreatment) {
let newWidth = dims.width < 90 ? 90 : dims.width; const newWidth = dims.width < 90 ? 90 : dims.width;
let newHeight = dims.height < 90 ? 90 : dims.height; const newHeight = dims.height < 90 ? 90 : dims.height;
dims = { dims = {
width: newWidth, width: newWidth,
height: newHeight, height: newHeight,
@@ -41,17 +41,17 @@ function calculateImgDimensions (img, addPadding) {
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims); // eslint-disable-line no-console if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims); // eslint-disable-line no-console
let totalPixelSize = dims.width * dims.height + padding; const totalPixelSize = dims.width * dims.height + padding;
return totalPixelSize; return totalPixelSize;
} }
function calculateSpritesheetsSrcIndicies (src) { function calculateSpritesheetsSrcIndicies (src) {
let totalPixels = 0; let totalPixels = 0;
let slices = [0]; const slices = [0];
each(src, (img, index) => { each(src, (img, index) => {
let imageSize = calculateImgDimensions(img, true); const imageSize = calculateImgDimensions(img, true);
totalPixels += imageSize; totalPixels += imageSize;
if (totalPixels > MAX_SPRITESHEET_SIZE) { if (totalPixels > MAX_SPRITESHEET_SIZE) {
@@ -64,37 +64,35 @@ function calculateSpritesheetsSrcIndicies (src) {
} }
function cssVarMap (sprite) { function cssVarMap (sprite) {
// For hair, skins, beards, etc. we want to output a '.customize-options.WHATEVER' class, which works as a // For hair, skins, beards, etc. we want to output a '.customize-options.WHATEVER' class,
// 60x60 image pointing at the proper part of the 90x90 sprite. // which works as a 60x60 image pointing at the proper part of the 90x90 sprite.
// We set up the custom info here, and the template makes use of it. // We set up the custom info here, and the template makes use of it.
let requiresSpecialTreatment = checkForSpecialTreatment(sprite.name); const requiresSpecialTreatment = checkForSpecialTreatment(sprite.name);
if (requiresSpecialTreatment) { if (requiresSpecialTreatment) {
sprite.custom = { sprite.custom = {
px: { px: {
offsetX: `-${ sprite.x + 25 }px`, offsetX: `-${sprite.x + 25}px`,
offsetY: `-${ sprite.y + 15 }px`, offsetY: `-${sprite.y + 15}px`,
width: '60px', width: '60px',
height: '60px', height: '60px',
}, },
}; };
} }
if (sprite.name.indexOf('shirt') !== -1) if (sprite.name.indexOf('shirt') !== -1) sprite.custom.px.offsetY = `-${sprite.y + 35}px`; // even more for shirts
sprite.custom.px.offsetY = `-${ sprite.y + 35 }px`; // even more for shirts
if (sprite.name.indexOf('hair_base') !== -1) { if (sprite.name.indexOf('hair_base') !== -1) {
let styleArray = sprite.name.split('_').slice(2, 3); const styleArray = sprite.name.split('_').slice(2, 3);
if (Number(styleArray[0]) > 14) if (Number(styleArray[0]) > 14) sprite.custom.px.offsetY = `-${sprite.y}px`; // don't crop updos
sprite.custom.px.offsetY = `-${ sprite.y }px`; // don't crop updos
} }
} }
function createSpritesStream (name, src) { function createSpritesStream (name, src) {
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src); const spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
let stream = mergeStream(); const stream = mergeStream();
each(spritesheetSliceIndicies, (start, index) => { each(spritesheetSliceIndicies, (start, index) => {
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]); const slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
let spriteData = gulp.src(slicedSrc) const spriteData = gulp.src(slicedSrc)
.pipe(spritesmith({ .pipe(spritesmith({
imgName: `spritesmith-${name}-${index}.png`, imgName: `spritesmith-${name}-${index}.png`,
cssName: `spritesmith-${name}-${index}.css`, cssName: `spritesmith-${name}-${index}.css`,
@@ -104,12 +102,12 @@ function createSpritesStream (name, src) {
cssVarMap, cssVarMap,
})); }));
let imgStream = spriteData.img const imgStream = spriteData.img
.pipe(vinylBuffer()) .pipe(vinylBuffer())
.pipe(imagemin()) .pipe(imagemin())
.pipe(gulp.dest(IMG_DIST_PATH)); .pipe(gulp.dest(IMG_DIST_PATH));
let cssStream = spriteData.css const cssStream = spriteData.css
.pipe(gulp.dest(CSS_DIST_PATH)); .pipe(gulp.dest(CSS_DIST_PATH));
stream.add(imgStream); stream.add(imgStream);
@@ -120,32 +118,32 @@ function createSpritesStream (name, src) {
} }
gulp.task('sprites:main', () => { gulp.task('sprites:main', () => {
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png'); const mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
return createSpritesStream('main', mainSrc); return createSpritesStream('main', mainSrc);
}); });
gulp.task('sprites:largeSprites', () => { gulp.task('sprites:largeSprites', () => {
let largeSrc = sync('website/raw_sprites/spritesmith_large/**/*.png'); const largeSrc = sync('website/raw_sprites/spritesmith_large/**/*.png');
return createSpritesStream('largeSprites', largeSrc); return createSpritesStream('largeSprites', largeSrc);
}); });
gulp.task('sprites:clean', (done) => { gulp.task('sprites:clean', done => {
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done); clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
}); });
gulp.task('sprites:checkCompiledDimensions', gulp.series('sprites:main', 'sprites:largeSprites', (done) => { gulp.task('sprites:checkCompiledDimensions', gulp.series('sprites:main', 'sprites:largeSprites', done => {
console.log('Verifiying that images do not exceed max dimensions'); // eslint-disable-line no-console console.log('Verifiying that images do not exceed max dimensions'); // eslint-disable-line no-console
let numberOfSheetsThatAreTooBig = 0; let numberOfSheetsThatAreTooBig = 0;
let distSpritesheets = sync(`${IMG_DIST_PATH}*.png`); const distSpritesheets = sync(`${IMG_DIST_PATH}*.png`);
each(distSpritesheets, (img) => { each(distSpritesheets, img => {
let spriteSize = calculateImgDimensions(img); const spriteSize = calculateImgDimensions(img);
if (spriteSize > MAX_SPRITESHEET_SIZE) { if (spriteSize > MAX_SPRITESHEET_SIZE) {
numberOfSheetsThatAreTooBig++; numberOfSheetsThatAreTooBig += 1;
let name = basename(img, '.png'); const name = basename(img, '.png');
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`); // eslint-disable-line no-console console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`); // eslint-disable-line no-console
} }
}); });
@@ -155,7 +153,8 @@ gulp.task('sprites:checkCompiledDimensions', gulp.series('sprites:main', 'sprite
console.error( // eslint-disable-line no-console console.error( // eslint-disable-line no-console
`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle `${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle
them, but there is a margin of error in these calculations so it is probably okay. Mention them, but there is a margin of error in these calculations so it is probably okay. Mention
this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); this to an admin so they can test a staging site on mobile Safari after your PR is merged.`,
);
} else { } else {
console.log('All images are within the correct dimensions'); // eslint-disable-line no-console console.log('All images are within the correct dimensions'); // eslint-disable-line no-console
} }

View File

@@ -1,16 +1,11 @@
import gulp from 'gulp'; import gulp from 'gulp';
import nodemon from 'gulp-nodemon'; import nodemon from 'gulp-nodemon';
let pkg = require('../package.json'); import pkg from '../package.json';
gulp.task('nodemon', (done) => { gulp.task('nodemon', done => {
nodemon({ nodemon({
script: pkg.main, script: pkg.main,
ignore: [
'website/client-old/*',
'website/views/*',
'common/dist/script/content/*',
],
}); });
done(); done();
}); });

View File

@@ -1,60 +1,59 @@
import mongoose from 'mongoose';
import { exec } from 'child_process';
import gulp from 'gulp';
import os from 'os';
import nconf from 'nconf';
import { import {
pipe, pipe,
} from './taskHelper'; } from './taskHelper';
import mongoose from 'mongoose';
import { exec } from 'child_process';
import gulp from 'gulp';
import os from 'os';
import nconf from 'nconf';
// TODO rewrite // TODO rewrite
const TEST_SERVER_PORT = 3003; const TEST_SERVER_PORT = 3003;
let server; let server;
const TEST_DB_URI = nconf.get('TEST_DB_URI'); const TEST_DB_URI = nconf.get('TEST_DB_URI');
const SANITY_TEST_COMMAND = 'npm run test:sanity'; const SANITY_TEST_COMMAND = 'npm run test:sanity';
const COMMON_TEST_COMMAND = 'npm run test:common'; const COMMON_TEST_COMMAND = 'npm run test:common';
const CONTENT_TEST_COMMAND = 'npm run test:content'; const CONTENT_TEST_COMMAND = 'npm run test:content';
const CONTENT_OPTIONS = {maxBuffer: 1024 * 500}; const CONTENT_OPTIONS = { maxBuffer: 1024 * 500 };
/* Helper methods for reporting test summary */ /* Helper methods for reporting test summary */
let testResults = []; const testResults = [];
let testCount = (stdout, regexp) => { const testCount = (stdout, regexp) => {
let match = stdout.match(regexp); const match = stdout.match(regexp);
return parseInt(match && match[1] || 0, 10); return parseInt(match && (match[1] || 0), 10);
}; };
let testBin = (string, additionalEnvVariables = '') => { const testBin = (string, additionalEnvVariables = '') => {
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
if (additionalEnvVariables !== '') { if (additionalEnvVariables !== '') {
additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set '); additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set '); // eslint-disable-line no-param-reassign
additionalEnvVariables = `set ${additionalEnvVariables}&&`; additionalEnvVariables = `set ${additionalEnvVariables}&&`; // eslint-disable-line no-param-reassign
} }
return `set NODE_ENV=test&&${additionalEnvVariables}${string}`; return `set NODE_ENV=test&&${additionalEnvVariables}${string}`;
} else {
return `NODE_ENV=test ${additionalEnvVariables} ${string}`;
} }
return `NODE_ENV=test ${additionalEnvVariables} ${string}`;
}; };
gulp.task('test:nodemon', gulp.series(function setupNodemon (done) { gulp.task('test:nodemon', gulp.series(done => {
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env 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 process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
done(); done();
}, 'nodemon')); }, 'nodemon'));
gulp.task('test:prepare:mongo', (cb) => { gulp.task('test:prepare:mongo', cb => {
mongoose.connect(TEST_DB_URI, (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}`); if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
mongoose.connection.dropDatabase((err2) => { return mongoose.connection.dropDatabase(err2 => {
if (err2) return cb(err2); if (err2) return cb(err2);
mongoose.connection.close(cb); return mongoose.connection.close(cb);
}); });
}); });
}); });
gulp.task('test:prepare:server', gulp.series('test:prepare:mongo', (done) => { gulp.task('test:prepare:server', gulp.series('test:prepare:mongo', done => {
if (!server) { if (!server) {
server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => { server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
if (error) { if (error) {
@@ -73,45 +72,43 @@ gulp.task('test:prepare:build', gulp.series('build', done => done()));
gulp.task('test:prepare', gulp.series( gulp.task('test:prepare', gulp.series(
'test:prepare:build', 'test:prepare:build',
'test:prepare:mongo', 'test:prepare:mongo',
done => done() done => done(),
)); ));
gulp.task('test:sanity', (cb) => { gulp.task('test:sanity', cb => {
let runner = exec( const runner = exec(
testBin(SANITY_TEST_COMMAND), testBin(SANITY_TEST_COMMAND),
(err) => { err => {
if (err) { if (err) {
process.exit(1); process.exit(1);
} }
cb(); cb();
} },
); );
pipe(runner); pipe(runner);
}); });
gulp.task('test:common', gulp.series('test:prepare:build', (cb) => { gulp.task('test:common', gulp.series('test:prepare:build', cb => {
let runner = exec( const runner = exec(
testBin(COMMON_TEST_COMMAND), testBin(COMMON_TEST_COMMAND),
(err) => { err => {
if (err) { if (err) {
process.exit(1); process.exit(1);
} }
cb(); cb();
} },
); );
pipe(runner); pipe(runner);
})); }));
gulp.task('test:common:clean', (cb) => { gulp.task('test:common:clean', cb => {
pipe(exec(testBin(COMMON_TEST_COMMAND), () => cb())); pipe(exec(testBin(COMMON_TEST_COMMAND), () => cb()));
}); });
gulp.task('test:common:watch', gulp.series('test:common:clean', () => { gulp.task('test:common:watch', gulp.series('test:common:clean', () => gulp.watch(['common/script/**/*', 'test/common/**/*'], gulp.series('test:common:clean', done => done()))));
return gulp.watch(['common/script/**/*', 'test/common/**/*'], gulp.series('test:common:clean', done => done()));
}));
gulp.task('test:common:safe', gulp.series('test:prepare:build', (cb) => { gulp.task('test:common:safe', gulp.series('test:prepare:build', cb => {
let runner = exec( const runner = exec(
testBin(COMMON_TEST_COMMAND), testBin(COMMON_TEST_COMMAND),
(err, stdout) => { // eslint-disable-line handle-callback-err (err, stdout) => { // eslint-disable-line handle-callback-err
testResults.push({ testResults.push({
@@ -121,38 +118,36 @@ gulp.task('test:common:safe', gulp.series('test:prepare:build', (cb) => {
pend: testCount(stdout, /(\d+) pending/), pend: testCount(stdout, /(\d+) pending/),
}); });
cb(); cb();
} },
); );
pipe(runner); pipe(runner);
})); }));
gulp.task('test:content', gulp.series('test:prepare:build', (cb) => { gulp.task('test:content', gulp.series('test:prepare:build', cb => {
let runner = exec( const runner = exec(
testBin(CONTENT_TEST_COMMAND), testBin(CONTENT_TEST_COMMAND),
CONTENT_OPTIONS, CONTENT_OPTIONS,
(err) => { err => {
if (err) { if (err) {
process.exit(1); process.exit(1);
} }
cb(); cb();
} },
); );
pipe(runner); pipe(runner);
})); }));
gulp.task('test:content:clean', (cb) => { gulp.task('test:content:clean', cb => {
pipe(exec(testBin(CONTENT_TEST_COMMAND), CONTENT_OPTIONS, () => cb())); pipe(exec(testBin(CONTENT_TEST_COMMAND), CONTENT_OPTIONS, () => cb()));
}); });
gulp.task('test:content:watch', gulp.series('test:content:clean', () => { gulp.task('test:content:watch', gulp.series('test:content:clean', () => gulp.watch(['common/script/content/**', 'test/**'], gulp.series('test:content:clean', done => done()))));
return gulp.watch(['common/script/content/**', 'test/**'], gulp.series('test:content:clean', done => done()));
}));
gulp.task('test:content:safe', gulp.series('test:prepare:build', (cb) => { gulp.task('test:content:safe', gulp.series('test:prepare:build', cb => {
let runner = exec( const runner = exec(
testBin(CONTENT_TEST_COMMAND), testBin(CONTENT_TEST_COMMAND),
CONTENT_OPTIONS, CONTENT_OPTIONS,
(err, stdout) => { // eslint-disable-line handle-callback-err (err, stdout) => { // eslint-disable-line handle-callback-err
testResults.push({ testResults.push({
suite: 'Content Specs\t', suite: 'Content Specs\t',
pass: testCount(stdout, /(\d+) passing/), pass: testCount(stdout, /(\d+) passing/),
@@ -160,81 +155,77 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', (cb) => {
pend: testCount(stdout, /(\d+) pending/), pend: testCount(stdout, /(\d+) pending/),
}); });
cb(); cb();
} },
); );
pipe(runner); pipe(runner);
})); }));
gulp.task('test:api:unit', (done) => { gulp.task('test:api:unit', done => {
let runner = exec( const runner = exec(
testBin('istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'), testBin('istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
(err) => { err => {
if (err) { if (err) {
process.exit(1); process.exit(1);
} }
done(); done();
} },
); );
pipe(runner); pipe(runner);
}); });
gulp.task('test:api:unit:watch', () => { gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done())));
return 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) => { gulp.task('test:api-v3:integration', done => {
let runner = exec( 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'), 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}, { maxBuffer: 500 * 1024 },
(err) => { err => {
if (err) { if (err) {
process.exit(1); process.exit(1);
} }
done(); done();
} },
); );
pipe(runner); pipe(runner);
}); });
gulp.task('test:api-v3:integration:watch', () => { gulp.task('test:api-v3:integration:watch', () => gulp.watch([
return gulp.watch([ 'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js', 'test/api/v3/integration/**/*',
'test/api/v3/integration/**/*', ], gulp.series('test:api-v3:integration', done => done())));
], gulp.series('test:api-v3:integration', done => done()));
});
gulp.task('test:api-v3:integration:separate-server', (done) => { gulp.task('test:api-v3:integration:separate-server', done => {
let runner = exec( const runner = exec(
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'), testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
{maxBuffer: 500 * 1024}, { maxBuffer: 500 * 1024 },
(err) => done(err) err => done(err),
); );
pipe(runner); pipe(runner);
}); });
gulp.task('test:api-v4:integration', (done) => { gulp.task('test:api-v4:integration', done => {
let runner = exec( 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'), 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}, { maxBuffer: 500 * 1024 },
(err) => { err => {
if (err) { if (err) {
process.exit(1); process.exit(1);
} }
done(); done();
} },
); );
pipe(runner); pipe(runner);
}); });
gulp.task('test:api-v4:integration:separate-server', (done) => { gulp.task('test:api-v4:integration:separate-server', done => {
let runner = exec( const runner = exec(
testBin('mocha test/api/v4 --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'), testBin('mocha test/api/v4 --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
{maxBuffer: 500 * 1024}, { maxBuffer: 500 * 1024 },
(err) => done(err) err => done(err),
); );
pipe(runner); pipe(runner);
@@ -247,11 +238,11 @@ gulp.task('test', gulp.series(
'test:api:unit', 'test:api:unit',
'test:api-v3:integration', 'test:api-v3:integration',
'test:api-v4:integration', 'test:api-v4:integration',
done => done() done => done(),
)); ));
gulp.task('test:api-v3', gulp.series( gulp.task('test:api-v3', gulp.series(
'test:api:unit', 'test:api:unit',
'test:api-v3:integration', 'test:api-v3:integration',
done => done() done => done(),
)); ));

View File

@@ -1,6 +1,6 @@
import fs from 'fs'; import fs from 'fs';
import _ from 'lodash'; import _ from 'lodash';
import gulp from 'gulp'; import gulp from 'gulp';
import { postToSlack, conf } from './taskHelper'; import { postToSlack, conf } from './taskHelper';
const SLACK_CONFIG = { const SLACK_CONFIG = {
@@ -14,7 +14,7 @@ const ENGLISH_LOCALE = `${LOCALES}en/`;
function getArrayOfLanguages () { function getArrayOfLanguages () {
let languages = fs.readdirSync(LOCALES); const languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages languages.shift(); // Remove README.md from array of languages
return languages; return languages;
@@ -23,18 +23,16 @@ function getArrayOfLanguages () {
const ALL_LANGUAGES = getArrayOfLanguages(); const ALL_LANGUAGES = getArrayOfLanguages();
function stripOutNonJsonFiles (collection) { function stripOutNonJsonFiles (collection) {
let onlyJson = _.filter(collection, (file) => { const onlyJson = _.filter(collection, file => file.match(/[a-zA-Z]*\.json/));
return file.match(/[a-zA-Z]*\.json/);
});
return onlyJson; return onlyJson;
} }
function eachTranslationFile (languages, cb) { function eachTranslationFile (languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE)); const jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_.each(languages, (lang) => { _.each(languages, lang => {
_.each(jsonFiles, (filename) => { _.each(jsonFiles, filename => {
let parsedTranslationFile; let parsedTranslationFile;
try { try {
const translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`); const translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
@@ -43,10 +41,10 @@ function eachTranslationFile (languages, cb) {
return cb(err); return cb(err);
} }
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename); const englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
let parsedEnglishFile = JSON.parse(englishFile); const parsedEnglishFile = JSON.parse(englishFile);
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile); return cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
}); });
}); });
} }
@@ -71,9 +69,9 @@ function formatMessageForPosting (msg, items) {
} }
function getStringsWith (json, interpolationRegex) { function getStringsWith (json, interpolationRegex) {
let strings = {}; const strings = {};
_.each(json, (fileName) => { _.each(json, fileName => {
const rawFile = fs.readFileSync(ENGLISH_LOCALE + fileName); const rawFile = fs.readFileSync(ENGLISH_LOCALE + fileName);
const parsedJson = JSON.parse(rawFile); const parsedJson = JSON.parse(rawFile);
@@ -93,66 +91,69 @@ const malformedStringExceptions = {
feedPet: true, feedPet: true,
}; };
gulp.task('transifex:missingFiles', (done) => { gulp.task('transifex:missingFiles', done => {
let missingStrings = []; const missingStrings = [];
eachTranslationFile(ALL_LANGUAGES, (error) => { eachTranslationFile(ALL_LANGUAGES, error => {
if (error) { if (error) {
missingStrings.push(error.path); missingStrings.push(error.path);
} }
}); });
if (!_.isEmpty(missingStrings)) { if (!_.isEmpty(missingStrings)) {
let message = 'the following files were missing from the translations folder'; const message = 'the following files were missing from the translations folder';
let formattedMessage = formatMessageForPosting(message, missingStrings); const formattedMessage = formatMessageForPosting(message, missingStrings);
postToSlack(formattedMessage, SLACK_CONFIG); postToSlack(formattedMessage, SLACK_CONFIG);
} }
done(); done();
}); });
gulp.task('transifex:missingStrings', (done) => { gulp.task('transifex:missingStrings', done => {
let missingStrings = []; const missingStrings = [];
eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => { eachTranslationString(ALL_LANGUAGES, (lang, filename, key, englishString, translationString) => {
if (!translationString) { if (!translationString) {
let errorString = `${language} - ${filename} - ${key} - ${englishString}`; const errorString = `${lang} - ${filename} - ${key} - ${englishString}`;
missingStrings.push(errorString); missingStrings.push(errorString);
} }
}); });
if (!_.isEmpty(missingStrings)) { if (!_.isEmpty(missingStrings)) {
let message = 'The following strings are not translated'; const message = 'The following strings are not translated';
let formattedMessage = formatMessageForPosting(message, missingStrings); const formattedMessage = formatMessageForPosting(message, missingStrings);
postToSlack(formattedMessage, SLACK_CONFIG); postToSlack(formattedMessage, SLACK_CONFIG);
} }
done(); done();
}); });
gulp.task('transifex:malformedStrings', (done) => { gulp.task('transifex:malformedStrings', done => {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE)); const jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
let interpolationRegex = /<%= [a-zA-Z]* %>/g; const interpolationRegex = /<%= [a-zA-Z]* %>/g;
let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex); const stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex);
let stringsWithMalformedInterpolations = []; const stringsWithMalformedInterpolations = [];
let stringsWithIncorrectNumberOfInterpolations = []; const stringsWithIncorrectNumberOfInterpolations = [];
_.each(ALL_LANGUAGES, (lang) => { _.each(ALL_LANGUAGES, lang => {
_.each(stringsToLookFor, (strings, filename) => { _.each(stringsToLookFor, (strings, filename) => {
let translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`); const translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
let parsedTranslationFile = JSON.parse(translationFile); const parsedTranslationFile = JSON.parse(translationFile);
_.each(strings, (value, key) => { // eslint-disable-line max-nested-callbacks _.each(strings, (value, key) => { // eslint-disable-line max-nested-callbacks
let translationString = parsedTranslationFile[key]; const translationString = parsedTranslationFile[key];
if (!translationString) return; if (!translationString) return;
let englishOccurences = stringsToLookFor[filename][key]; const englishOccurences = stringsToLookFor[filename][key];
let translationOccurences = translationString.match(interpolationRegex); const translationOccurences = translationString.match(interpolationRegex);
if (!translationOccurences) { if (!translationOccurences) {
let malformedString = `${lang} - ${filename} - ${key} - ${translationString}`; const malformedString = `${lang} - ${filename} - ${key} - ${translationString}`;
stringsWithMalformedInterpolations.push(malformedString); stringsWithMalformedInterpolations.push(malformedString);
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) { } else if (
let missingInterpolationString = `${lang} - ${filename} - ${key} - ${translationString}`; englishOccurences.length !== translationOccurences.length
&& !malformedStringExceptions[key]
) {
const missingInterpolationString = `${lang} - ${filename} - ${key} - ${translationString}`;
stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString); stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString);
} }
}); });
@@ -160,14 +161,17 @@ gulp.task('transifex:malformedStrings', (done) => {
}); });
if (!_.isEmpty(stringsWithMalformedInterpolations)) { if (!_.isEmpty(stringsWithMalformedInterpolations)) {
let message = 'The following strings have malformed or missing interpolations'; const message = 'The following strings have malformed or missing interpolations';
let formattedMessage = formatMessageForPosting(message, stringsWithMalformedInterpolations); const formattedMessage = formatMessageForPosting(message, stringsWithMalformedInterpolations);
postToSlack(formattedMessage, SLACK_CONFIG); postToSlack(formattedMessage, SLACK_CONFIG);
} }
if (!_.isEmpty(stringsWithIncorrectNumberOfInterpolations)) { if (!_.isEmpty(stringsWithIncorrectNumberOfInterpolations)) {
let message = 'The following strings have a different number of string interpolations'; const message = 'The following strings have a different number of string interpolations';
let formattedMessage = formatMessageForPosting(message, stringsWithIncorrectNumberOfInterpolations); const formattedMessage = formatMessageForPosting(
message,
stringsWithIncorrectNumberOfInterpolations,
);
postToSlack(formattedMessage, SLACK_CONFIG); postToSlack(formattedMessage, SLACK_CONFIG);
} }
done(); done();
@@ -176,5 +180,5 @@ gulp.task('transifex:malformedStrings', (done) => {
gulp.task( gulp.task(
'transifex', 'transifex',
gulp.series('transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings'), gulp.series('transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings'),
(done) => done() done => done(),
); );

View File

@@ -1,11 +1,11 @@
import { exec } from 'child_process'; import { exec } from 'child_process';
import psTree from 'ps-tree'; import psTree from 'ps-tree';
import nconf from 'nconf'; import nconf from 'nconf';
import net from 'net'; import net from 'net';
import { post } from 'superagent'; import { post } from 'superagent';
import { sync as glob } from 'glob'; import { sync as glob } from 'glob';
import Mocha from 'mocha'; import Mocha from 'mocha'; // eslint-disable-line import/no-extraneous-dependencies
import { resolve } from 'path'; import { resolve } from 'path';
/* /*
* Get access to configruable values * Get access to configruable values
@@ -19,15 +19,15 @@ export const conf = nconf;
* its tasks. * its tasks.
*/ */
export function kill (proc) { export function kill (proc) {
let killProcess = (pid) => { const killProcess = pid => {
psTree(pid, (_, pids) => { psTree(pid, (_, pids) => {
if (pids.length) { if (pids.length) {
pids.forEach(kill); return; pids.forEach(kill); return;
} }
try { try {
exec(/^win/.test(process.platform) ? exec(/^win/.test(process.platform)
`taskkill /PID ${pid} /T /F` : ? `taskkill /PID ${pid} /T /F`
`kill -9 ${pid}`); : `kill -9 ${pid}`);
} catch (e) { } catch (e) {
console.log(e); // eslint-disable-line no-console console.log(e); // eslint-disable-line no-console
} }
@@ -46,16 +46,15 @@ export function kill (proc) {
export function awaitPort (port, max = 60) { export function awaitPort (port, max = 60) {
return new Promise((rej, res) => { return new Promise((rej, res) => {
let socket; let socket;
let timeout;
let interval; let interval;
timeout = setTimeout(() => { const timeout = setTimeout(() => {
clearInterval(interval); clearInterval(interval);
rej(`Timed out after ${max} seconds`); rej(`Timed out after ${max} seconds`);
}, max * 1000); }, max * 1000);
interval = setInterval(() => { interval = setInterval(() => {
socket = net.connect({port}, () => { socket = net.connect({ port }, () => {
clearInterval(interval); clearInterval(interval);
clearTimeout(timeout); clearTimeout(timeout);
socket.destroy(); socket.destroy();
@@ -71,10 +70,10 @@ export function awaitPort (port, max = 60) {
* Pipe the child's stdin and stderr to the parent process. * Pipe the child's stdin and stderr to the parent process.
*/ */
export function pipe (child) { export function pipe (child) {
child.stdout.on('data', (data) => { child.stdout.on('data', data => {
process.stdout.write(data); process.stdout.write(data);
}); });
child.stderr.on('data', (data) => { child.stderr.on('data', data => {
process.stderr.write(data); process.stderr.write(data);
}); });
} }
@@ -83,7 +82,7 @@ export function pipe (child) {
* Post request to notify configured slack channel * Post request to notify configured slack channel
*/ */
export function postToSlack (msg, config = {}) { export function postToSlack (msg, config = {}) {
let slackUrl = nconf.get('SLACK_URL'); const slackUrl = nconf.get('SLACK_URL');
if (!slackUrl) { if (!slackUrl) {
console.error('No slack post url specified. Your message was:'); // eslint-disable-line no-console console.error('No slack post url specified. Your message was:'); // eslint-disable-line no-console
@@ -99,7 +98,7 @@ export function postToSlack (msg, config = {}) {
text: msg, text: msg,
icon_emoji: `:${config.emoji || 'gulp'}:`, // eslint-disable-line camelcase icon_emoji: `:${config.emoji || 'gulp'}:`, // eslint-disable-line camelcase
}) })
.end((err) => { .end(err => {
if (err) console.error('Unable to post to slack', err); // eslint-disable-line no-console if (err) console.error('Unable to post to slack', err); // eslint-disable-line no-console
}); });
} }
@@ -107,15 +106,15 @@ export function postToSlack (msg, config = {}) {
export function runMochaTests (files, server, cb) { export function runMochaTests (files, server, cb) {
require('../test/helpers/globals.helper'); // eslint-disable-line global-require require('../test/helpers/globals.helper'); // eslint-disable-line global-require
let mocha = new Mocha({reporter: 'spec'}); const mocha = new Mocha({ reporter: 'spec' });
let tests = glob(files); const tests = glob(files);
tests.forEach((test) => { tests.forEach(test => {
delete require.cache[resolve(test)]; delete require.cache[resolve(test)];
mocha.addFile(test); mocha.addFile(test);
}); });
mocha.run((numberOfFailures) => { mocha.run(numberOfFailures => {
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) { // eslint-disable-line no-process-env if (!process.env.RUN_INTEGRATION_TEST_FOREVER) { // eslint-disable-line no-process-env
if (server) kill(server); if (server) kill(server);
process.exit(numberOfFailures); process.exit(numberOfFailures);

View File

@@ -6,7 +6,8 @@
* directory, and it will automatically be included. * directory, and it will automatically be included.
*/ */
require('babel-register'); /* eslint-disable import/no-commonjs */
require('@babel/register');
const gulp = require('gulp'); const gulp = require('gulp');

View File

@@ -1,7 +0,0 @@
{
"root": false,
"rules": {
"no-console": 0,
"no-use-before-define": ["error", { "functions": false }]
}
}

8
migrations/.eslintrc.js Normal file
View File

@@ -0,0 +1,8 @@
/* eslint-disable import/no-commonjs */
module.exports = {
root: false,
rules: {
'no-console': 0,
'no-use-before-define': ['error', { functions: false }]
}
}

View File

@@ -0,0 +1,82 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20191022_pet_color_achievements';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
let set = {
migration: MIGRATION_NAME,
};
if (user && user.items && user.items.pets) {
const pets = user.items.pets;
if (pets['Wolf-Zombie'] > 0
&& pets['TigerCub-Zombie'] > 0
&& pets['PandaCub-Zombie'] > 0
&& pets['LionCub-Zombie'] > 0
&& pets['Fox-Zombie'] > 0
&& pets['FlyingPig-Zombie'] > 0
&& pets['Dragon-Zombie'] > 0
&& pets['Cactus-Zombie'] > 0
&& pets['BearCub-Zombie'] > 0) {
set['achievements.monsterMagus'] = true;
}
}
if (user && user.items && user.items.mounts) {
const mounts = user.items.mounts;
if (mounts['Wolf-Zombie']
&& mounts['TigerCub-Zombie']
&& mounts['PandaCub-Zombie']
&& mounts['LionCub-Zombie']
&& mounts['Fox-Zombie']
&& mounts['FlyingPig-Zombie']
&& mounts['Dragon-Zombie']
&& mounts['Cactus-Zombie']
&& mounts['BearCub-Zombie'] ) {
set['achievements.undeadUndertaker'] = 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('2019-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
}
};

View File

@@ -2,14 +2,14 @@ import { model as Challenges } from '../../website/server/models/challenge';
import { model as User } from '../../website/server/models/user'; import { model as User } from '../../website/server/models/user';
async function syncChallengeToMembers (challenges) { async function syncChallengeToMembers (challenges) {
let challengSyncPromises = challenges.map(async (challenge) => { const challengSyncPromises = challenges.map(async challenge => {
let users = await User.find({ const users = await User.find({
// _id: '', // _id: '',
challenges: challenge._id, challenges: challenge._id,
}).exec(); }).exec();
let promises = []; const promises = [];
users.forEach((user) => { users.forEach(user => {
promises.push(challenge.syncToUser(user)); promises.push(challenge.syncToUser(user));
promises.push(challenge.save()); promises.push(challenge.save());
promises.push(user.save()); promises.push(user.save());
@@ -18,11 +18,11 @@ async function syncChallengeToMembers (challenges) {
return Promise.all(promises); return Promise.all(promises);
}); });
return await Promise.all(challengSyncPromises); return Promise.all(challengSyncPromises);
} }
async function syncChallenges (lastChallengeDate) { async function syncChallenges (lastChallengeDate) {
let query = { const query = {
// _id: '', // _id: '',
}; };
@@ -30,16 +30,16 @@ async function syncChallenges (lastChallengeDate) {
query.createdOn = { $lte: lastChallengeDate }; query.createdOn = { $lte: lastChallengeDate };
} }
let challengesFound = await Challenges.find(query) const challengesFound = await Challenges.find(query)
.limit(10) .limit(10)
.sort('-createdAt') .sort('-createdAt')
.exec(); .exec();
let syncedChallenges = await syncChallengeToMembers(challengesFound) const syncedChallenges = await syncChallengeToMembers(challengesFound)
.catch(reason => console.error(reason)); .catch(reason => console.error(reason));
let lastChallenge = challengesFound[challengesFound.length - 1]; const lastChallenge = challengesFound[challengesFound.length - 1];
if (lastChallenge) syncChallenges(lastChallenge.createdAt); if (lastChallenge) syncChallenges(lastChallenge.createdAt);
return syncedChallenges; return syncedChallenges;
} }
module.exports = syncChallenges; export default syncChallenges;

View File

@@ -1 +1 @@
db.users.update({_id: {$in: ['']}}, {$inc: {balance: 0.5}}, {multi: true}); db.users.update({ _id: { $in: [''] } }, { $inc: { balance: 0.5 } }, { multi: true });

View File

@@ -1,11 +1,14 @@
// mongo habitrpg ./node_modules/moment/moment.js ./migrations/cancelSubscription.js // mongo habitrpg ./node_modules/moment/moment.js ./migrations/cancelSubscription.js
// For some reason people often to contact me to cancel their sub, rather than do it online. Even when I point them to // For some reason people often to contact me to cancel their sub,
// rather than do it online. Even when I point them to
// the FAQ (http://goo.gl/1uoPGQ) they insist... // the FAQ (http://goo.gl/1uoPGQ) they insist...
db.users.update( db.users.update(
{_id: ''}, { _id: '' },
{$set: { {
'purchased.plan.dateTerminated': moment().add('month', 1).toDate(), $set: {
}} 'purchased.plan.dateTerminated': moment().add('month', 1).toDate(),
},
},
); );

View File

@@ -1,8 +1,7 @@
// Give contrib.level 7+ free subscription for life // Give contrib.level 7+ free subscription for life
db.users.update( db.users.update(
{ {
'contributor.level': {$gte: 7}, 'contributor.level': { $gte: 7 },
'purchased.plan.customerId': null, 'purchased.plan.customerId': null,
}, },
@@ -18,6 +17,6 @@ db.users.update(
}, },
}, },
{multi: true} { multi: true },
); );

View File

@@ -1,5 +1,5 @@
// mongo habitrpg ./node_modules/moment/moment.js ./migrations/current_period_end.js // mongo habitrpg ./node_modules/moment/moment.js ./migrations/current_period_end.js
db.users.update( db.users.update(
{_id: ''}, { _id: '' },
{$set: {'purchased.plan.dateTerminated': moment().add({days: 7}).toDate()}} { $set: { 'purchased.plan.dateTerminated': moment().add({ days: 7 }).toDate() } },
); );

View File

@@ -39,38 +39,52 @@
// needed. Do not miss any of them! // needed. Do not miss any of them!
let uuid = '30fb2640-7121-4968-ace5-f385e60ea6c5'; const uuid = '30fb2640-7121-4968-ace5-f385e60ea6c5';
db.users.aggregate([ db.users.aggregate([
{$match: { {
_id: uuid, $match: {
}}, _id: uuid,
{$project: { },
_id: 0, todos: 1, },
}}, {
{$unwind: '$todos'}, $project: {
{$group: { _id: 0, todos: 1,
_id: { taskid: '$todos.id' }, },
count: { $sum: 1 }, },
}}, { $unwind: '$todos' },
{$match: { {
count: { $gt: 1 }, $group: {
}}, _id: { taskid: '$todos.id' },
{$project: { count: { $sum: 1 },
'_id.taskid': 1, },
}}, },
{$group: { {
_id: { taskid: '$todos.id' }, $match: {
troublesomeIds: { $addToSet: '$_id.taskid' }, count: { $gt: 1 },
}}, },
{$project: { },
_id: 0, {
troublesomeIds: 1, $project: {
}}, '_id.taskid': 1,
]).forEach((data) => { },
},
{
$group: {
_id: { taskid: '$todos.id' },
troublesomeIds: { $addToSet: '$_id.taskid' },
},
},
{
$project: {
_id: 0,
troublesomeIds: 1,
},
},
]).forEach(data => {
// print( "\n" ); printjson(data); // print( "\n" ); printjson(data);
data.troublesomeIds.forEach((taskid) => { data.troublesomeIds.forEach(taskid => {
print(`non-unique task: ${ taskid}`); print(`non-unique task: ${taskid}`); // eslint-disable-line no-restricted-globals
db.users.update({ db.users.update({
_id: uuid, _id: uuid,
todos: { $elemMatch: { id: taskid } }, todos: { $elemMatch: { id: taskid } },
@@ -81,8 +95,7 @@ db.users.aggregate([
}); });
db.users.update( db.users.update(
{_id: uuid}, { _id: uuid },
{$pull: { todos: { id: 'de666' } } }, { $pull: { todos: { id: 'de666' } } },
{multi: false } { multi: false },
); );

View File

@@ -1,10 +1,10 @@
let oldId = ''; const oldId = '';
let newId = ''; const newId = '';
let newUser = db.users.findOne({_id: newId}); const newUser = db.users.findOne({ _id: newId });
db.users.update({_id: oldId}, {$set: {auth: newUser.auth}}); db.users.update({ _id: oldId }, { $set: { auth: newUser.auth } });
// remove the auth on the new user (which is a template account). The account will be preened automatically later, // remove the auth on the new user (which is a template account).
// The account will be preened automatically later,
// this allows us to keep the account around a few days in case there was a mistake // this allows us to keep the account around a few days in case there was a mistake
db.users.update({_id: newId}, {$unset: {auth: 1}}); db.users.update({ _id: newId }, { $unset: { auth: 1 } });

View File

@@ -5,8 +5,8 @@
* Past in the text of a unique habit here to find the user, then you can restore their UUID * Past in the text of a unique habit here to find the user, then you can restore their UUID
*/ */
db.users.find().forEach((user) => { db.users.find().forEach(user => {
user.tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards); user.tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards);
let found = _.some(user.tasks, {text: ''}); const found = _.some(user.tasks, { text: '' });
if (found) printjson({id: user._id, auth: user.auth}); if (found) printjson({ id: user._id, auth: user.auth });
}); });

View File

@@ -1,32 +1,34 @@
// mongo habitrpg ./node_modules/moment/moment.js ./migrations/freeMonth.js // mongo habitrpg ./node_modules/moment/moment.js ./migrations/freeMonth.js
db.users.update( db.users.update(
{_id: ''}, { _id: '' },
{$set: { {
'purchased.plan.customerId': 'temporary', $set: {
'purchased.plan.paymentMethod': 'Stripe', 'purchased.plan.customerId': 'temporary',
'purchased.plan.planId': 'basic_earned', 'purchased.plan.paymentMethod': 'Stripe',
'purchased.plan.dateTerminated': moment().add('month', 1).toDate(), 'purchased.plan.planId': 'basic_earned',
}} 'purchased.plan.dateTerminated': moment().add('month', 1).toDate(),
},
},
); );
// var m = 12; // var m = 12;
// db.users.update( // db.users.update(
// {_id:''}, // {_id:''},
// {$set:{'purchased.plan':{ // {$set:{'purchased.plan':{
// planId: 'basic_'+m+'mo', // planId: 'basic_'+m+'mo',
// paymentMethod: 'Paypal', // paymentMethod: 'Paypal',
// customerId: 'Gift', // customerId: 'Gift',
// dateCreated: new Date(), // dateCreated: new Date(),
// dateTerminated: moment().add('month',m).toDate(), // dateTerminated: moment().add('month',m).toDate(),
// dateUpdated: new Date(), // dateUpdated: new Date(),
// extraMonths: 0, // extraMonths: 0,
// gemsBought: 0, // gemsBought: 0,
// mysteryItems: [], // mysteryItems: [],
// consecutive: { // consecutive: {
// count: 0, // count: 0,
// offset: m, // offset: m,
// gemCapExtra: m/3*5, // gemCapExtra: m/3*5,
// trinkets: m/3 // trinkets: m/3
// } // }
// }}} // }}}
// ) // )

View File

@@ -1,5 +1,5 @@
db.users.update( db.users.update(
{}, {},
{$inc: {'achievements.habiticaDays': 1}}, { $inc: { 'achievements.habiticaDays': 1 } },
{multi: 1} { multi: 1 },
); );

View File

@@ -1 +1 @@
db.users.update({_id: ''}, {$inc: {balance: 5}}); db.users.update({ _id: '' }, { $inc: { balance: 5 } });

View File

@@ -13,7 +13,7 @@ import { model as Group } from '../../website/server/models/group';
// @TODO: this should probably be a GroupManager library method // @TODO: this should probably be a GroupManager library method
async function addUnlimitedSubscription (groupId, dateTerminated) { async function addUnlimitedSubscription (groupId, dateTerminated) {
let group = await Group.findOne({_id: groupId}); const group = await Group.findOne({ _id: groupId });
group.purchased.plan.customerId = 'group-unlimited'; group.purchased.plan.customerId = 'group-unlimited';
group.purchased.plan.dateCreated = new Date(); group.purchased.plan.dateCreated = new Date();
@@ -22,7 +22,7 @@ async function addUnlimitedSubscription (groupId, dateTerminated) {
group.purchased.plan.planId = 'group_monthly'; group.purchased.plan.planId = 'group_monthly';
group.purchased.plan.dateTerminated = null; group.purchased.plan.dateTerminated = null;
if (dateTerminated) { if (dateTerminated) {
let dateToEnd = moment(dateTerminated).toDate(); const dateToEnd = moment(dateTerminated).toDate();
group.purchased.plan.dateTerminated = dateToEnd; group.purchased.plan.dateTerminated = dateToEnd;
} }
// group.purchased.plan.owner = ObjectId(); // group.purchased.plan.owner = ObjectId();
@@ -31,12 +31,12 @@ async function addUnlimitedSubscription (groupId, dateTerminated) {
return group.save(); return group.save();
} }
module.exports = async function addUnlimitedSubscriptionCreator () { export default async function addUnlimitedSubscriptionCreator () {
let groupId = process.argv[2]; const groupId = process.argv[2];
if (!groupId) throw Error('Group ID is required'); if (!groupId) throw Error('Group ID is required');
let dateTerminated = process.argv[3]; const dateTerminated = process.argv[3];
await addUnlimitedSubscription(groupId, dateTerminated); await addUnlimitedSubscription(groupId, dateTerminated);
}; }

View File

@@ -3,9 +3,9 @@ import { model as User } from '../../website/server/models/user';
// @TODO: this should probably be a GroupManager library method // @TODO: this should probably be a GroupManager library method
async function createGroup (name, privacy, type, leaderId) { async function createGroup (name, privacy, type, leaderId) {
let user = await User.findOne({_id: leaderId}); const user = await User.findOne({ _id: leaderId });
let group = new Group({ const group = new Group({
name, name,
privacy, privacy,
type, type,
@@ -17,13 +17,11 @@ async function createGroup (name, privacy, type, leaderId) {
return Promise.all([group.save(), user.save()]); return Promise.all([group.save(), user.save()]);
} }
module.exports = async function groupCreator () { export default async function groupCreator () {
let name = process.argv[2]; const name = process.argv[2];
let privacy = process.argv[3]; const privacy = process.argv[3];
let type = process.argv[4]; const type = process.argv[4];
let leaderId = process.argv[5]; const leaderId = process.argv[5];
await createGroup(name, privacy, type, leaderId); await createGroup(name, privacy, type, leaderId);
}; }

View File

@@ -1,44 +1,43 @@
/* let migrationName = 'Jackalopes for Unlimited Subscribers'; */ /* let migrationName = 'Jackalopes for Unlimited Subscribers'; */
/* /*
* This migration will find users with unlimited subscriptions who are also eligible for Jackalope mounts, and award them * This migration will find users with unlimited subscriptions who are also eligible
* for Jackalope mounts, and award them
*/ */
import { model as Group } from '../../website/server/models/group'; import { model as Group } from '../../website/server/models/group';
import { model as User } from '../../website/server/models/user'; import { model as User } from '../../website/server/models/user';
async function handOutJackalopes () { async function handOutJackalopes () {
let promises = []; const promises = [];
let cursor = User.find({ const cursor = User.find({
'purchased.plan.customerId': 'habitrpg', 'purchased.plan.customerId': 'habitrpg',
}).cursor(); }).cursor();
cursor.on('data', async (user) => { cursor.on('data', async user => {
console.log(`User: ${ user._id}`); console.log(`User: ${user._id}`);
let groupList = []; let groupList = [];
if (user.party._id) groupList.push(user.party._id); if (user.party._id) groupList.push(user.party._id);
groupList = groupList.concat(user.guilds); groupList = groupList.concat(user.guilds);
let subscribedGroup = const subscribedGroup = await Group.findOne({
await Group.findOne({ _id: { $in: groupList },
_id: {$in: groupList}, 'purchased.plan.planId': 'group_monthly',
'purchased.plan.planId': 'group_monthly', 'purchased.plan.dateTerminated': null,
'purchased.plan.dateTerminated': null, },
}, { _id: 1 });
{_id: 1}
);
if (subscribedGroup) { if (subscribedGroup) {
User.update({_id: user._id}, {$set: {'items.mounts.Jackalope-RoyalPurple': true}}).exec(); User.update({ _id: user._id }, { $set: { 'items.mounts.Jackalope-RoyalPurple': true } }).exec();
promises.push(user.save()); promises.push(user.save());
} }
}); });
cursor.on('close', async () => { cursor.on('close', async () => {
console.log('done'); console.log('done');
return await Promise.all(promises); return Promise.all(promises);
}); });
} }
module.exports = handOutJackalopes; export default handOutJackalopes;

View File

@@ -8,7 +8,7 @@
*/ */
import { model as Group } from '../../website/server/models/group'; import { model as Group } from '../../website/server/models/group';
import { model as Chat } from '../../website/server/models/chat'; import { chatModel as Chat } from '../../website/server/models/message';
async function moveGroupChatToModel (skip = 0) { async function moveGroupChatToModel (skip = 0) {
const groups = await Group.find({}) const groups = await Group.find({})
@@ -40,7 +40,7 @@ async function moveGroupChatToModel (skip = 0) {
const reducedPromises = promises.reduce((acc, curr) => { const reducedPromises = promises.reduce((acc, curr) => {
acc = acc.concat(curr); acc = acc.concat(curr); // eslint-disable-line no-param-reassign
return acc; return acc;
}, []); }, []);
@@ -49,4 +49,4 @@ async function moveGroupChatToModel (skip = 0) {
moveGroupChatToModel(skip + 50); moveGroupChatToModel(skip + 50);
} }
module.exports = moveGroupChatToModel; export default moveGroupChatToModel;

View File

@@ -1,4 +1,4 @@
import monk from 'monk'; import monk from 'monk'; // eslint-disable-line import/no-extraneous-dependencies
import nconf from 'nconf'; import nconf from 'nconf';
import stripePayments from '../../website/server/libs/payments/stripe'; import stripePayments from '../../website/server/libs/payments/stripe';
@@ -12,8 +12,8 @@ import stripePayments from '../../website/server/libs/payments/stripe';
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
let dbGroups = monk(CONNECTION_STRING).get('groups', { castIds: false }); const dbGroups = monk(CONNECTION_STRING).get('groups', { castIds: false });
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false }); const dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
async function fixGroupPlanMembers () { async function fixGroupPlanMembers () {
console.info('Group ID, Customer ID, Plan ID, Quantity, Recorded Member Count, Actual Member Count'); console.info('Group ID, Customer ID, Plan ID, Quantity, Recorded Member Count, Actual Member Count');
@@ -24,15 +24,15 @@ async function fixGroupPlanMembers () {
{ {
$and: $and:
[ [
{'purchased.plan.planId': {$ne: null}}, { 'purchased.plan.planId': { $ne: null } },
{'purchased.plan.planId': {$ne: ''}}, { 'purchased.plan.planId': { $ne: '' } },
{'purchased.plan.customerId': {$ne: 'cus_9f0DV4g7WHRzpM'}}, // Demo groups { 'purchased.plan.customerId': { $ne: 'cus_9f0DV4g7WHRzpM' } }, // Demo groups
{'purchased.plan.customerId': {$ne: 'cus_9maalqDOFTrvqx'}}, { 'purchased.plan.customerId': { $ne: 'cus_9maalqDOFTrvqx' } },
], ],
$or: $or:
[ [
{'purchased.plan.dateTerminated': null}, { 'purchased.plan.dateTerminated': null },
{'purchased.plan.dateTerminated': ''}, { 'purchased.plan.dateTerminated': '' },
], ],
}, },
{ {
@@ -40,19 +40,19 @@ async function fixGroupPlanMembers () {
memberCount: 1, memberCount: 1,
'purchased.plan': 1, 'purchased.plan': 1,
}, },
} },
).each(async (group, {close, pause, resume}) => { // eslint-disable-line no-unused-vars ).each(async (group, { close, pause, resume }) => { // eslint-disable-line no-unused-vars
pause(); pause();
groupPlanCount++; groupPlanCount += 1;
const canonicalMemberCount = await dbUsers.count( const canonicalMemberCount = await dbUsers.count(
{ {
$or: $or:
[ [
{'party._id': group._id}, { 'party._id': group._id },
{guilds: group._id}, { guilds: group._id },
], ],
} },
); );
const incorrectMemberCount = group.memberCount !== canonicalMemberCount; const incorrectMemberCount = group.memberCount !== canonicalMemberCount;
@@ -73,24 +73,24 @@ async function fixGroupPlanMembers () {
$set: { $set: {
memberCount: canonicalMemberCount, memberCount: canonicalMemberCount,
}, },
} },
); );
if (!groupUpdate) return; if (!groupUpdate) return;
fixedGroupCount++; fixedGroupCount += 1;
if (group.purchased.plan.paymentMethod === 'Stripe') { if (group.purchased.plan.paymentMethod === 'Stripe') {
await stripePayments.chargeForAdditionalGroupMember(group); await stripePayments.chargeForAdditionalGroupMember(group);
await dbGroups.update( await dbGroups.update(
{_id: group._id}, { _id: group._id },
{$set: {'purchased.plan.quantity': canonicalMemberCount + 2}} { $set: { 'purchased.plan.quantity': canonicalMemberCount + 2 } },
); );
} }
if (incorrectQuantity) { if (incorrectQuantity) {
await dbGroups.update( await dbGroups.update(
{_id: group._id}, { _id: group._id },
{$set: {'purchased.plan.quantity': canonicalMemberCount + 2}} { $set: { 'purchased.plan.quantity': canonicalMemberCount + 2 } },
); );
} }
@@ -98,10 +98,10 @@ async function fixGroupPlanMembers () {
}).then(() => { }).then(() => {
console.info(`Fixed ${fixedGroupCount} out of ${groupPlanCount} active Group Plans`); console.info(`Fixed ${fixedGroupCount} out of ${groupPlanCount} active Group Plans`);
return process.exit(0); return process.exit(0);
}).catch((err) => { }).catch(err => {
console.log(err); console.log(err);
return process.exit(1); return process.exit(1);
}); });
} }
module.exports = fixGroupPlanMembers; export default fixGroupPlanMembers;

View File

@@ -5,29 +5,27 @@ let authorUuid = ''; // ... own data is done
*/ */
/* /*
* This migrations will iterate through all groups with a group plan a subscription and resync the free * This migrations will iterate through all groups with a group plan
* subscription to all members * a subscription and resync the free subscription to all members
*/ */
import { model as Group } from '../../website/server/models/group'; import { model as Group } from '../../website/server/models/group';
import * as payments from '../../website/server/libs/payments'; import payments from '../../website/server/libs/payments/payments';
async function updateGroupsWithGroupPlans () { async function updateGroupsWithGroupPlans () {
let cursor = Group.find({ const cursor = Group.find({
'purchased.plan.planId': 'group_monthly', 'purchased.plan.planId': 'group_monthly',
'purchased.plan.dateTerminated': null, 'purchased.plan.dateTerminated': null,
}).cursor(); }).cursor();
let promises = []; const promises = [];
cursor.on('data', (group) => { cursor.on('data', group => {
promises.push(payments.addSubscriptionToGroupUsers(group)); promises.push(payments.addSubscriptionToGroupUsers(group));
promises.push(group.save()); promises.push(group.save());
}); });
cursor.on('close', async () => { cursor.on('close', async () => Promise.all(promises));
return await Promise.all(promises);
});
} }
module.exports = updateGroupsWithGroupPlans; export default updateGroupsWithGroupPlans;

View File

@@ -1,4 +1,5 @@
require('babel-register'); /* eslint-disable import/no-commonjs */
require('@babel/register'); // eslint-disable-line import/no-extraneous-dependencies
// This file must use ES5, everything required can be in ES6 // This file must use ES5, everything required can be in ES6
@@ -17,12 +18,13 @@ function setUpServer () {
setUpServer(); setUpServer();
// Replace this with your migration // Replace this with your migration
const processUsers = require(''); const processUsers = () => {}; // require('').default;
processUsers() processUsers()
.then(function success () { .then(() => {
process.exit(0); process.exit(0);
}) })
.catch(function failure (err) { .catch(err => {
console.log(err); console.log(err);
process.exit(1); process.exit(1);
}); });

View File

@@ -1,19 +1,21 @@
/* eslint-disable import/no-commonjs */
/* let migrationName = 'new_stuff.js'; */ /* let migrationName = 'new_stuff.js'; */
let authorName = 'Sabe'; // in case script author needs to know when their ... const authorName = 'Sabe'; // in case script author needs to know when their ...
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/* /*
* set the newStuff flag in all user accounts so they see a Bailey message * set the newStuff flag in all user accounts so they see a Bailey message
*/ */
let monk = require('monk'); const monk = require('monk'); // eslint-disable-line import/no-extraneous-dependencies
let connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
let dbUsers = monk(connectionString).get('users', { castIds: false }); const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) { function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users): // specify a query to limit the affected users (empty for all users):
let query = { const query = {
'flags.newStuff': {$ne: true}, 'flags.newStuff': { $ne: true },
}; };
if (lastId) { if (lastId) {
@@ -23,29 +25,31 @@ function processUsers (lastId) {
} }
dbUsers.find(query, { dbUsers.find(query, {
sort: {_id: 1}, sort: { _id: 1 },
limit: 250, limit: 250,
fields: [], // specify fields we are interested in to limit retrieved data (empty if we're not reading data): // specify fields we are interested in to limit retrieved data
// (empty if we're not reading data):
fields: [],
}) })
.then(updateUsers) .then(updateUsers)
.catch((err) => { .catch(err => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
let progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
function updateUsers (users) { function updateUsers (users) {
if (!users || users.length === 0) { if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.'); console.warn('All appropriate users found and modified.');
displayData(); displayData();
return; return null;
} }
let userPromises = users.map(updateUser); const userPromises = users.map(updateUser);
let lastUser = users[users.length - 1]; const lastUser = users[users.length - 1];
return Promise.all(userPromises) return Promise.all(userPromises)
.then(() => { .then(() => {
@@ -54,30 +58,31 @@ function updateUsers (users) {
} }
function updateUser (user) { function updateUser (user) {
count++; count += 1;
let set = {'flags.newStuff': true}; const set = { 'flags.newStuff': true };
dbUsers.update({_id: user._id}, {$set: set}); dbUsers.update({ _id: user._id }, { $set: set });
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`); if (user._id === authorUuid) console.warn(`${authorName} processed`);
} }
function displayData () { function displayData () {
console.warn(`\n${ count } users processed\n`); console.warn(`\n${count} users processed\n`);
return exiting(0); return exiting(0);
} }
function exiting (code, msg) { function exiting (code, msg) {
code = code || 0; // 0 = success // 0 = success
code = code || 0; // eslint-disable-line no-param-reassign
if (code && !msg) { if (code && !msg) {
msg = 'ERROR!'; msg = 'ERROR!'; // eslint-disable-line no-param-reassign
} }
if (msg) { if (msg) {
if (code) { if (code) {
console.error(msg); console.error(msg);
} else { } else {
console.log(msg); console.log(msg);
} }
} }

View File

@@ -1,18 +1,20 @@
let migrationName = 'restock_armoire.js'; /* eslint-disable import/no-commonjs */
let authorName = 'Sabe'; // in case script author needs to know when their ... const migrationName = 'restock_armoire.js';
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done const authorName = 'Sabe'; // in case script author needs to know when their ...
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/* /*
* Remove flag stating that the Enchanted Armoire is empty, for when new equipment is added * Remove flag stating that the Enchanted Armoire is empty, for when new equipment is added
*/ */
let monk = require('monk'); const monk = require('monk'); // eslint-disable-line import/no-extraneous-dependencies
let connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
let dbUsers = monk(connectionString).get('users', { castIds: false }); const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) { function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users): // specify a query to limit the affected users (empty for all users):
let query = { const query = {
'flags.armoireEmpty': true, 'flags.armoireEmpty': true,
}; };
@@ -23,29 +25,31 @@ function processUsers (lastId) {
} }
dbUsers.find(query, { dbUsers.find(query, {
sort: {_id: 1}, sort: { _id: 1 },
limit: 250, limit: 250,
fields: [], // specify fields we are interested in to limit retrieved data (empty if we're not reading data): // specify fields we are interested in to limit retrieved data
// (empty if we're not reading data):
fields: [],
}) })
.then(updateUsers) .then(updateUsers)
.catch((err) => { .catch(err => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
let progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
function updateUsers (users) { function updateUsers (users) {
if (!users || users.length === 0) { if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.'); console.warn('All appropriate users found and modified.');
displayData(); displayData();
return; return null;
} }
let userPromises = users.map(updateUser); const userPromises = users.map(updateUser);
let lastUser = users[users.length - 1]; const lastUser = users[users.length - 1];
return Promise.all(userPromises) return Promise.all(userPromises)
.then(() => { .then(() => {
@@ -54,30 +58,31 @@ function updateUsers (users) {
} }
function updateUser (user) { function updateUser (user) {
count++; count += 1;
let set = {migration: migrationName, 'flags.armoireEmpty': false}; const set = { migration: migrationName, 'flags.armoireEmpty': false };
dbUsers.update({_id: user._id}, {$set: set}); dbUsers.update({ _id: user._id }, { $set: set });
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`); if (user._id === authorUuid) console.warn(`${authorName} processed`);
} }
function displayData () { function displayData () {
console.warn(`\n${ count } users processed\n`); console.warn(`\n${count} users processed\n`);
return exiting(0); return exiting(0);
} }
function exiting (code, msg) { function exiting (code, msg) {
code = code || 0; // 0 = success // 0 = success
code = code || 0; // eslint-disable-line no-param-reassign
if (code && !msg) { if (code && !msg) {
msg = 'ERROR!'; msg = 'ERROR!'; // eslint-disable-line no-param-reassign
} }
if (msg) { if (msg) {
if (code) { if (code) {
console.error(msg); console.error(msg);
} else { } else {
console.log(msg); console.log(msg);
} }
} }

View File

@@ -1,6 +1,7 @@
let migrationName = 'restock_armoire_for_users_that_need_it.js'; /* eslint-disable import/no-commonjs */
let authorName = 'Alys (ALittleYellowSpider)'; // in case script author needs to know when their ... const migrationName = 'restock_armoire_for_users_that_need_it.js';
let authorUuid = '3e595299-3d8a-4a10-bfe0-88f555e4aa0c'; // ... own data is done const authorName = 'Alys (ALittleYellowSpider)'; // in case script author needs to know when their ...
const authorUuid = '3e595299-3d8a-4a10-bfe0-88f555e4aa0c'; // ... own data is done
/* /*
* Remove flag stating that the Enchanted Armoire is empty, * Remove flag stating that the Enchanted Armoire is empty,
@@ -18,16 +19,17 @@ let authorUuid = '3e595299-3d8a-4a10-bfe0-88f555e4aa0c'; // ... own data is done
* *
*/ */
let connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
let monk = require('monk'); const monk = require('monk'); // eslint-disable-line import/no-extraneous-dependencies
let dbUsers = monk(connectionString).get('users', { castIds: false });
const dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) { function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users): // specify a query to limit the affected users (empty for all users):
let query = { const query = {
'auth.timestamps.loggedin': {$gt: new Date('2016-01-04')}, 'auth.timestamps.loggedin': { $gt: new Date('2016-01-04') },
// '_id': authorUuid // FOR TESTING // '_id': authorUuid // FOR TESTING
}; };
@@ -35,7 +37,7 @@ function processUsers (lastId) {
/* let fields = { /* let fields = {
'flags.armoireEmpty': 1, 'flags.armoireEmpty': 1,
'items.gear.owned': 1, 'items.gear.owned': 1,
};*/ }; */
if (lastId) { if (lastId) {
query._id = { query._id = {
@@ -44,32 +46,34 @@ function processUsers (lastId) {
} }
dbUsers.find(query, { dbUsers.find(query, {
sort: {_id: 1}, sort: { _id: 1 },
limit: 250, limit: 250,
// specify fields we are interested in to limit retrieved data
// (empty if we're not reading data):
fields: { fields: {
'flags.armoireEmpty': 1, 'flags.armoireEmpty': 1,
'items.gear.owned': 1, 'items.gear.owned': 1,
}, // specify fields we are interested in to limit retrieved data (empty if we're not reading data): },
}) })
.then(updateUsers) .then(updateUsers)
.catch((err) => { .catch(err => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
let progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
function updateUsers (users) { function updateUsers (users) {
if (!users || users.length === 0) { if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.'); console.warn('All appropriate users found and modified.');
displayData(); displayData();
return; return null;
} }
let userPromises = users.map(updateUser); const userPromises = users.map(updateUser);
let lastUser = users[users.length - 1]; const lastUser = users[users.length - 1];
return Promise.all(userPromises) return Promise.all(userPromises)
.then(() => { .then(() => {
@@ -78,19 +82,69 @@ function updateUsers (users) {
} }
function updateUser (user) { function updateUser (user) {
count++; count += 1;
let set = {migration: migrationName, 'flags.armoireEmpty': false}; const set = { migration: migrationName, 'flags.armoireEmpty': false };
if (user.flags.armoireEmpty) { if (user.flags.armoireEmpty) {
// this user believes their armoire has no more items in it // this user believes their armoire has no more items in it
if (user.items.gear.owned.weapon_armoire_barristerGavel && user.items.gear.owned.armor_armoire_barristerRobes && user.items.gear.owned.head_armoire_jesterCap && user.items.gear.owned.armor_armoire_jesterCostume && user.items.gear.owned.head_armoire_barristerWig && user.items.gear.owned.weapon_armoire_jesterBaton && user.items.gear.owned.weapon_armoire_lunarSceptre && user.items.gear.owned.armor_armoire_gladiatorArmor && user.items.gear.owned.weapon_armoire_basicCrossbow && user.items.gear.owned.head_armoire_gladiatorHelm && user.items.gear.owned.armor_armoire_lunarArmor && user.items.gear.owned.head_armoire_redHairbow && user.items.gear.owned.head_armoire_violetFloppyHat && user.items.gear.owned.head_armoire_rancherHat && user.items.gear.owned.shield_armoire_gladiatorShield && user.items.gear.owned.head_armoire_blueHairbow && user.items.gear.owned.weapon_armoire_mythmakerSword && user.items.gear.owned.head_armoire_royalCrown && user.items.gear.owned.head_armoire_hornedIronHelm && user.items.gear.owned.weapon_armoire_rancherLasso && user.items.gear.owned.armor_armoire_rancherRobes && user.items.gear.owned.armor_armoire_hornedIronArmor && user.items.gear.owned.armor_armoire_goldenToga && user.items.gear.owned.weapon_armoire_ironCrook && user.items.gear.owned.head_armoire_goldenLaurels && user.items.gear.owned.head_armoire_redFloppyHat && user.items.gear.owned.armor_armoire_plagueDoctorOvercoat && user.items.gear.owned.head_armoire_plagueDoctorHat && user.items.gear.owned.weapon_armoire_goldWingStaff && user.items.gear.owned.head_armoire_yellowHairbow && user.items.gear.owned.eyewear_armoire_plagueDoctorMask && user.items.gear.owned.head_armoire_blackCat && user.items.gear.owned.weapon_armoire_batWand && user.items.gear.owned.head_armoire_orangeCat && user.items.gear.owned.shield_armoire_midnightShield && user.items.gear.owned.armor_armoire_royalRobes && user.items.gear.owned.head_armoire_blueFloppyHat && user.items.gear.owned.shield_armoire_royalCane && user.items.gear.owned.weapon_armoire_shepherdsCrook && user.items.gear.owned.armor_armoire_shepherdRobes && user.items.gear.owned.head_armoire_shepherdHeaddress && user.items.gear.owned.weapon_armoire_blueLongbow && user.items.gear.owned.weapon_armoire_crystalCrescentStaff && user.items.gear.owned.head_armoire_crystalCrescentHat && user.items.gear.owned.armor_armoire_dragonTamerArmor && user.items.gear.owned.head_armoire_dragonTamerHelm && user.items.gear.owned.armor_armoire_crystalCrescentRobes && user.items.gear.owned.shield_armoire_dragonTamerShield && user.items.gear.owned.weapon_armoire_glowingSpear) { if (
user.items.gear.owned.weapon_armoire_barristerGavel
&& user.items.gear.owned.armor_armoire_barristerRobes
&& user.items.gear.owned.head_armoire_jesterCap
&& user.items.gear.owned.armor_armoire_jesterCostume
&& user.items.gear.owned.head_armoire_barristerWig
&& user.items.gear.owned.weapon_armoire_jesterBaton
&& user.items.gear.owned.weapon_armoire_lunarSceptre
&& user.items.gear.owned.armor_armoire_gladiatorArmor
&& user.items.gear.owned.weapon_armoire_basicCrossbow
&& user.items.gear.owned.head_armoire_gladiatorHelm
&& user.items.gear.owned.armor_armoire_lunarArmor
&& user.items.gear.owned.head_armoire_redHairbow
&& user.items.gear.owned.head_armoire_violetFloppyHat
&& user.items.gear.owned.head_armoire_rancherHat
&& user.items.gear.owned.shield_armoire_gladiatorShield
&& user.items.gear.owned.head_armoire_blueHairbow
&& user.items.gear.owned.weapon_armoire_mythmakerSword
&& user.items.gear.owned.head_armoire_royalCrown
&& user.items.gear.owned.head_armoire_hornedIronHelm
&& user.items.gear.owned.weapon_armoire_rancherLasso
&& user.items.gear.owned.armor_armoire_rancherRobes
&& user.items.gear.owned.armor_armoire_hornedIronArmor
&& user.items.gear.owned.armor_armoire_goldenToga
&& user.items.gear.owned.weapon_armoire_ironCrook
&& user.items.gear.owned.head_armoire_goldenLaurels
&& user.items.gear.owned.head_armoire_redFloppyHat
&& user.items.gear.owned.armor_armoire_plagueDoctorOvercoat
&& user.items.gear.owned.head_armoire_plagueDoctorHat
&& user.items.gear.owned.weapon_armoire_goldWingStaff
&& user.items.gear.owned.head_armoire_yellowHairbow
&& user.items.gear.owned.eyewear_armoire_plagueDoctorMask
&& user.items.gear.owned.head_armoire_blackCat
&& user.items.gear.owned.weapon_armoire_batWand
&& user.items.gear.owned.head_armoire_orangeCat
&& user.items.gear.owned.shield_armoire_midnightShield
&& user.items.gear.owned.armor_armoire_royalRobes
&& user.items.gear.owned.head_armoire_blueFloppyHat
&& user.items.gear.owned.shield_armoire_royalCane
&& user.items.gear.owned.weapon_armoire_shepherdsCrook
&& user.items.gear.owned.armor_armoire_shepherdRobes
&& user.items.gear.owned.head_armoire_shepherdHeaddress
&& user.items.gear.owned.weapon_armoire_blueLongbow
&& user.items.gear.owned.weapon_armoire_crystalCrescentStaff
&& user.items.gear.owned.head_armoire_crystalCrescentHat
&& user.items.gear.owned.armor_armoire_dragonTamerArmor
&& user.items.gear.owned.head_armoire_dragonTamerHelm
&& user.items.gear.owned.armor_armoire_crystalCrescentRobes
&& user.items.gear.owned.shield_armoire_dragonTamerShield
&& user.items.gear.owned.weapon_armoire_glowingSpear
) {
// this user does have all the armoire items so we don't change the flag // this user does have all the armoire items so we don't change the flag
// console.log("don't change: " + user._id); // FOR TESTING // console.log("don't change: " + user._id); // FOR TESTING
} else { } else {
// console.log("change: " + user._id); // FOR TESTING // console.log("change: " + user._id); // FOR TESTING
dbUsers.update({_id: user._id}, {$set: set}); dbUsers.update({ _id: user._id }, { $set: set });
} }
} else { } else {
// this user already has armoire marked as containing items to be bought // this user already has armoire marked as containing items to be bought
@@ -98,24 +152,25 @@ function updateUser (user) {
// console.log("DON'T CHANGE: " + user._id); // FOR TESTING // console.log("DON'T CHANGE: " + user._id); // FOR TESTING
} }
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`); if (user._id === authorUuid) console.warn(`${authorName} processed`);
} }
function displayData () { function displayData () {
console.warn(`\n${ count } users processed\n`); console.warn(`\n${count} users processed\n`);
return exiting(0); return exiting(0);
} }
function exiting (code, msg) { function exiting (code, msg) {
code = code || 0; // 0 = success // 0 = success
code = code || 0; // eslint-disable-line no-param-reassign
if (code && !msg) { if (code && !msg) {
msg = 'ERROR!'; msg = 'ERROR!'; // eslint-disable-line no-param-reassign
} }
if (msg) { if (msg) {
if (code) { if (code) {
console.error(msg); console.error(msg);
} else { } else {
console.log(msg); console.log(msg);
} }
} }

View File

@@ -1,22 +1,25 @@
/* eslint-disable import/no-commonjs */
/* let migrationName = 'restore_profile_data.js'; */ /* let migrationName = 'restore_profile_data.js'; */
let authorName = 'ThehollidayInn'; // in case script author needs to know when their ... const authorName = 'ThehollidayInn'; // in case script author needs to know when their ...
let authorUuid = ''; // ... own data is done const authorUuid = ''; // ... own data is done
/* /*
* Check if users have empty profile data in new database and update it with old database info * Check if users have empty profile data in new database and update it with old database info
*/ */
let monk = require('monk'); const monk = require('monk'); // eslint-disable-line import/no-extraneous-dependencies
let connectionString = ''; // FOR TEST DATABASE
let dbUsers = monk(connectionString).get('users', { castIds: false });
let monk2 = require('monk'); const connectionString = ''; // FOR TEST DATABASE
let oldDbConnectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE const dbUsers = monk(connectionString).get('users', { castIds: false });
let olDbUsers = monk2(oldDbConnectionString).get('users', { castIds: false });
const monk2 = require('monk'); // eslint-disable-line import/no-extraneous-dependencies
const oldDbConnectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const olDbUsers = monk2(oldDbConnectionString).get('users', { castIds: false });
function processUsers (lastId) { function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users): // specify a query to limit the affected users (empty for all users):
let query = { const query = {
// 'profile.name': 'profile name not found', // 'profile.name': 'profile name not found',
'profile.blurb': null, 'profile.blurb': null,
// 'auth.timestamps.loggedin': {$gt: new Date('11/30/2016')}, // 'auth.timestamps.loggedin': {$gt: new Date('11/30/2016')},
@@ -29,49 +32,47 @@ function processUsers (lastId) {
} }
dbUsers.find(query, { dbUsers.find(query, {
sort: {_id: 1}, sort: { _id: 1 },
limit: 250, limit: 250,
fields: ['_id', 'profile', 'auth.timestamps.loggedin'], // specify fields we are interested in to limit retrieved data (empty if we're not reading data): fields: ['_id', 'profile', 'auth.timestamps.loggedin'], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
}) })
.then(updateUsers) .then(updateUsers)
.catch((err) => { .catch(err => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
let progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
function updateUsers (users) { function updateUsers (users) {
if (!users || users.length === 0) { if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.'); console.warn('All appropriate users found and modified.');
setTimeout(displayData, 300000); setTimeout(displayData, 300000);
return; return null;
} }
let userPaymentPromises = users.map(updateUser); const userPaymentPromises = users.map(updateUser);
let lastUser = users[users.length - 1]; const lastUser = users[users.length - 1];
return Promise.all(userPaymentPromises) return Promise.all(userPaymentPromises)
.then(() => { .then(() => processUsers(lastUser._id));
return processUsers(lastUser._id);
});
} }
function updateUser (user) { function updateUser (user) {
count++; count += 1;
if (!user.profile.name || user.profile.name === 'profile name not found' || !user.profile.imageUrl || !user.profile.blurb) { if (!user.profile.name || user.profile.name === 'profile name not found' || !user.profile.imageUrl || !user.profile.blurb) {
return olDbUsers.findOne({_id: user._id}, '_id profile') return olDbUsers.findOne({ _id: user._id }, '_id profile')
.then((oldUserData) => { .then(oldUserData => {
if (!oldUserData) return; if (!oldUserData) return null;
// specify user data to change: // specify user data to change:
let set = {}; const set = {};
if (oldUserData.profile.name === 'profile name not found') return; if (oldUserData.profile.name === 'profile name not found') return null;
let userNeedsProfileName = !user.profile.name || user.profile.name === 'profile name not found'; const userNeedsProfileName = !user.profile.name || user.profile.name === 'profile name not found';
if (userNeedsProfileName && oldUserData.profile.name) { if (userNeedsProfileName && oldUserData.profile.name) {
set['profile.name'] = oldUserData.profile.name; set['profile.name'] = oldUserData.profile.name;
} }
@@ -86,29 +87,34 @@ function updateUser (user) {
if (Object.keys(set).length !== 0 && set.constructor === Object) { if (Object.keys(set).length !== 0 && set.constructor === Object) {
console.log(set); console.log(set);
return dbUsers.update({_id: user._id}, {$set: set}); return dbUsers.update({ _id: user._id }, { $set: set });
} }
return null;
}); });
} }
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`); if (user._id === authorUuid) console.warn(`${authorName} processed`);
return null;
} }
function displayData () { function displayData () {
console.warn(`\n${ count } users processed\n`); console.warn(`\n${count} users processed\n`);
return exiting(0); return exiting(0);
} }
function exiting (code, msg) { function exiting (code, msg) {
code = code || 0; // 0 = success // 0 = success
code = code || 0; // eslint-disable-line no-param-reassign
if (code && !msg) { if (code && !msg) {
msg = 'ERROR!'; msg = 'ERROR!'; // eslint-disable-line no-param-reassign
} }
if (msg) { if (msg) {
if (code) { if (code) {
console.error(msg); console.error(msg);
} else { } else {
console.log(msg); console.log(msg);
} }
} }

View File

@@ -1,8 +1,10 @@
let request = require('superagent'); /* eslint-disable import/no-commonjs */
let last = require('lodash/last'); const request = require('superagent');
let AWS = require('aws-sdk'); const last = require('lodash/last');
const AWS = require('aws-sdk');
const config = require('../config');
let config = require('../config');
const S3_DIRECTORY = 'mobileApp/images'; // config.S3.SPRITES_DIRECTORY; const S3_DIRECTORY = 'mobileApp/images'; // config.S3.SPRITES_DIRECTORY;
AWS.config.update({ AWS.config.update({
@@ -11,8 +13,8 @@ AWS.config.update({
// region: config.get('S3_REGION'), // region: config.get('S3_REGION'),
}); });
let BUCKET_NAME = config.S3.bucket; const BUCKET_NAME = config.S3.bucket;
let s3 = new AWS.S3(); const s3 = new AWS.S3();
// Adapted from http://stackoverflow.com/a/22210077/2601552 // Adapted from http://stackoverflow.com/a/22210077/2601552
function uploadFile (buffer, fileName) { function uploadFile (buffer, fileName) {
@@ -21,7 +23,7 @@ function uploadFile (buffer, fileName) {
Body: buffer, Body: buffer,
Key: fileName, Key: fileName,
Bucket: BUCKET_NAME, Bucket: BUCKET_NAME,
}, (error) => { }, error => {
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
@@ -33,9 +35,9 @@ function uploadFile (buffer, fileName) {
} }
function getFileName (file) { function getFileName (file) {
let piecesOfPath = file.split('/'); const piecesOfPath = file.split('/');
let name = last(piecesOfPath); const name = last(piecesOfPath);
let fullName = S3_DIRECTORY + name; const fullName = S3_DIRECTORY + name;
return fullName; return fullName;
} }
@@ -44,36 +46,32 @@ function getFileFromUrl (url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.get(url).end((err, res) => { request.get(url).end((err, res) => {
if (err) return reject(err); if (err) return reject(err);
let file = res.body; const file = res.body;
resolve(file); return resolve(file);
}); });
}); });
} }
let commit = '78f94e365c72cc58f66857d5941105638db7d35c'; let commit = '78f94e365c72cc58f66857d5941105638db7d35c';
commit = 'df0dbaba636c9ce424cc7040f7bd7fc1aa311015'; commit = 'df0dbaba636c9ce424cc7040f7bd7fc1aa311015';
let gihuburl = `https://api.github.com/repos/HabitRPG/habitica/commits/${commit}`; const gihuburl = `https://api.github.com/repos/HabitRPG/habitica/commits/${commit}`;
let currentIndex = 0; let currentIndex = 0;
function uploadToS3 (start, end, filesUrls) { function uploadToS3 (start, end, filesUrls) {
let urls = filesUrls.slice(start, end); const urls = filesUrls.slice(start, end);
if (urls.length === 0) { if (urls.length === 0) {
console.log('done'); console.log('done');
return; return;
} }
let promises = urls.map(fullUrl => { const promises = urls.map(fullUrl => getFileFromUrl(fullUrl)
return getFileFromUrl(fullUrl) .then(buffer => uploadFile(buffer, getFileName(fullUrl))));
.then((buffer) => {
return uploadFile(buffer, getFileName(fullUrl));
});
});
console.log(promises.length); console.log(promises.length);
return Promise.all(promises) Promise.all(promises)
.then(() => { .then(() => {
currentIndex += 50; currentIndex += 50;
uploadToS3(currentIndex, currentIndex + 50, filesUrls); uploadToS3(currentIndex, currentIndex + 50, filesUrls);
@@ -86,12 +84,10 @@ function uploadToS3 (start, end, filesUrls) {
request.get(gihuburl) request.get(gihuburl)
.end((err, res) => { .end((err, res) => {
console.log(err); console.log(err);
let files = res.body.files; const { files } = res.body;
let filesUrls = ['']; let filesUrls = [''];
filesUrls = files.map(file => { filesUrls = files.map(file => file.raw_url);
return file.raw_url;
});
uploadToS3(currentIndex, currentIndex + 50, filesUrls); uploadToS3(currentIndex, currentIndex + 50, filesUrls);
}); });

View File

@@ -1,21 +1,23 @@
/* eslint-disable import/no-commonjs */
// const migrationName = 'habits-one-history-entry-per-day'; // const migrationName = 'habits-one-history-entry-per-day';
// const authorName = 'paglias'; // in case script author needs to know when their ... // const authorName = 'paglias'; // in case script author needs to know when their ...
// const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done // const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
/* /*
* Iterates over all habits and condense multiple history entries for the same day into a single entry * Iterates over all habits and condense multiple history entries for the same day into a single one
*/ */
const monk = require('monk'); const monk = require('monk'); // eslint-disable-line import/no-extraneous-dependencies
const _ = require('lodash'); const _ = require('lodash');
const moment = require('moment'); const moment = require('moment');
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const dbTasks = monk(connectionString).get('tasks', { castIds: false }); const dbTasks = monk(connectionString).get('tasks', { castIds: false });
function processChallengeHabits (lastId) { function processChallengeHabits (lastId) {
let query = { const query = {
'challenge.id': {$exists: true}, 'challenge.id': { $exists: true },
userId: {$exists: false}, userId: { $exists: false },
type: 'habit', type: 'habit',
}; };
@@ -26,37 +28,35 @@ function processChallengeHabits (lastId) {
} }
dbTasks.find(query, { dbTasks.find(query, {
sort: {_id: 1}, sort: { _id: 1 },
limit: 500, limit: 500,
}) })
.then(updateChallengeHabits) .then(updateChallengeHabits)
.catch((err) => { .catch(err => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
let progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
function updateChallengeHabits (habits) { function updateChallengeHabits (habits) {
if (!habits || habits.length === 0) { if (!habits || habits.length === 0) {
console.warn('All appropriate challenge habits found and modified.'); console.warn('All appropriate challenge habits found and modified.');
displayData(); displayData();
return; return null;
} }
let habitsPromises = habits.map(updateChallengeHabit); const habitsPromises = habits.map(updateChallengeHabit);
let lastHabit = habits[habits.length - 1]; const lastHabit = habits[habits.length - 1];
return Promise.all(habitsPromises) return Promise.all(habitsPromises)
.then(() => { .then(() => processChallengeHabits(lastHabit._id));
return processChallengeHabits(lastHabit._id);
});
} }
function updateChallengeHabit (habit) { function updateChallengeHabit (habit) {
count++; count += 1;
if (habit && habit.history && habit.history.length > 0) { if (habit && habit.history && habit.history.length > 0) {
// First remove missing entries // First remove missing entries
@@ -76,13 +76,12 @@ function updateChallengeHabit (habit) {
entry.scoreDirection = entry.value > previousValue ? 'up' : 'down'; entry.scoreDirection = entry.value > previousValue ? 'up' : 'down';
} }
}) })
.groupBy(entry => { // group entries by aggregateBy // group entries by aggregateBy
return moment(entry.date).format('YYYYMMDD'); .groupBy(entry => moment(entry.date).format('YYYYMMDD'))
})
.toPairs() // [key, entry] .toPairs() // [key, entry]
.sortBy(([key]) => key) // sort by date .sortBy(([key]) => key) // sort by date
.map(keyEntryPair => { .map(keyEntryPair => {
let entries = keyEntryPair[1]; // 1 is entry, 0 is key const entries = keyEntryPair[1]; // 1 is entry, 0 is key
let scoredUp = 0; let scoredUp = 0;
let scoredDown = 0; let scoredDown = 0;
@@ -107,32 +106,34 @@ function updateChallengeHabit (habit) {
}) })
.value(); .value();
return dbTasks.update({_id: habit._id}, { return dbTasks.update({ _id: habit._id }, {
$set: {history: habit.history}, $set: { history: habit.history },
}); });
} }
if (count % progressCount === 0) console.warn(`${count } habits processed`); if (count % progressCount === 0) console.warn(`${count} habits processed`);
return null;
} }
function displayData () { function displayData () {
console.warn(`\n${ count } tasks processed\n`); console.warn(`\n${count} tasks processed\n`);
return exiting(0); return exiting(0);
} }
function exiting (code, msg) { function exiting (code, msg) {
code = code || 0; // 0 = success // 0 = success
code = code || 0; // eslint-disable-line no-param-reassign
if (code && !msg) { if (code && !msg) {
msg = 'ERROR!'; msg = 'ERROR!'; // eslint-disable-line no-param-reassign
} }
if (msg) { if (msg) {
if (code) { if (code) {
console.error(msg); console.error(msg);
} else { } else {
console.log(msg); console.log(msg);
} }
} }
process.exit(code); process.exit(code);
} }
module.exports = processChallengeHabits; export default processChallengeHabits;

View File

@@ -1,21 +1,23 @@
/* eslint-disable import/no-commonjs */
const migrationName = 'habits-one-history-entry-per-day'; const migrationName = 'habits-one-history-entry-per-day';
const authorName = 'paglias'; // in case script author needs to know when their ... const authorName = 'paglias'; // in case script author needs to know when their ...
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
/* /*
* Iterates over all habits and condense multiple history entries for the same day into a single entry * Iterates over all habits and condense multiple history entries for the same day into a single one
*/ */
const monk = require('monk'); const monk = require('monk'); // eslint-disable-line import/no-extraneous-dependencies
const _ = require('lodash'); const _ = require('lodash');
const moment = require('moment'); const moment = require('moment');
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const dbTasks = monk(connectionString).get('tasks', { castIds: false }); const dbTasks = monk(connectionString).get('tasks', { castIds: false });
const dbUsers = monk(connectionString).get('users', { castIds: false }); const dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) { function processUsers (lastId) {
let query = { const query = {
migration: {$ne: migrationName}, migration: { $ne: migrationName },
}; };
if (lastId) { if (lastId) {
@@ -25,34 +27,32 @@ function processUsers (lastId) {
} }
dbUsers.find(query, { dbUsers.find(query, {
sort: {_id: 1}, sort: { _id: 1 },
limit: 50, // just 50 users per time since we have to process all their habits as well limit: 50, // just 50 users per time since we have to process all their habits as well
fields: ['_id', 'preferences.timezoneOffset', 'preferences.dayStart'], fields: ['_id', 'preferences.timezoneOffset', 'preferences.dayStart'],
}) })
.then(updateUsers) .then(updateUsers)
.catch((err) => { .catch(err => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
let progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
function updateUsers (users) { function updateUsers (users) {
if (!users || users.length === 0) { if (!users || users.length === 0) {
console.warn('All appropriate users and their tasks found and modified.'); console.warn('All appropriate users and their tasks found and modified.');
displayData(); displayData();
return; return null;
} }
let usersPromises = users.map(updateUser); const usersPromises = users.map(updateUser);
let lastUser = users[users.length - 1]; const lastUser = users[users.length - 1];
return Promise.all(usersPromises) return Promise.all(usersPromises)
.then(() => { .then(() => processUsers(lastUser._id));
return processUsers(lastUser._id);
});
} }
function updateHabit (habit, timezoneOffset, dayStart) { function updateHabit (habit, timezoneOffset, dayStart) {
@@ -82,7 +82,7 @@ function updateHabit (habit, timezoneOffset, dayStart) {
.toPairs() // [key, entry] .toPairs() // [key, entry]
.sortBy(([key]) => key) // sort by date .sortBy(([key]) => key) // sort by date
.map(keyEntryPair => { .map(keyEntryPair => {
let entries = keyEntryPair[1]; // 1 is entry, 0 is key const entries = keyEntryPair[1]; // 1 is entry, 0 is key
let scoredUp = 0; let scoredUp = 0;
let scoredDown = 0; let scoredDown = 0;
@@ -107,57 +107,56 @@ function updateHabit (habit, timezoneOffset, dayStart) {
}) })
.value(); .value();
return dbTasks.update({_id: habit._id}, { return dbTasks.update({ _id: habit._id }, {
$set: {history: habit.history}, $set: { history: habit.history },
}); });
} }
return null;
} }
function updateUser (user) { function updateUser (user) {
count++; count += 1;
const timezoneOffset = user.preferences.timezoneOffset; const { timezoneOffset } = user.preferences;
const dayStart = user.preferences.dayStart; const { dayStart } = user.preferences;
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } being processed`); if (user._id === authorUuid) console.warn(`${authorName} being processed`);
return dbTasks.find({ return dbTasks.find({
type: 'habit', type: 'habit',
userId: user._id, userId: user._id,
}) })
.then(habits => { .then(habits => Promise.all(habits.map(habit => updateHabit(habit, timezoneOffset, dayStart))))
return Promise.all(habits.map(habit => updateHabit(habit, timezoneOffset, dayStart))); .then(() => dbUsers.update({ _id: user._id }, {
}) $set: { migration: migrationName },
.then(() => { }))
return dbUsers.update({_id: user._id}, { .catch(err => {
$set: {migration: migrationName},
});
})
.catch((err) => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
function displayData () { function displayData () {
console.warn(`\n${ count } tasks processed\n`); console.warn(`\n${count} tasks processed\n`);
return exiting(0); return exiting(0);
} }
function exiting (code, msg) { function exiting (code, msg) {
code = code || 0; // 0 = success // 0 = success
code = code || 0; // eslint-disable-line no-param-reassign
if (code && !msg) { if (code && !msg) {
msg = 'ERROR!'; msg = 'ERROR!'; // eslint-disable-line no-param-reassign
} }
if (msg) { if (msg) {
if (code) { if (code) {
console.error(msg); console.error(msg);
} else { } else {
console.log(msg); console.log(msg);
} }
} }
process.exit(code); process.exit(code);
} }
module.exports = processUsers; export default processUsers;

View File

@@ -1,18 +1,19 @@
import monk from 'monk'; // eslint-disable-line import/no-extraneous-dependencies
/* let migrationName = 'tasks-set-everyX'; */ /* let migrationName = 'tasks-set-everyX'; */
let authorName = 'Sabe'; // in case script author needs to know when their ... const authorName = 'Sabe'; // in case script author needs to know when their ...
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/* /*
* Iterates over all tasks and sets invalid everyX values (less than 0 or more than 9999 or not an int) field to 0 * Iterates over all tasks and sets invalid everyX values
* (less than 0 or more than 9999 or not an int) field to 0
*/ */
let monk = require('monk'); const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true';
let connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; const dbTasks = monk(connectionString).get('tasks', { castIds: false });
let dbTasks = monk(connectionString).get('tasks', { castIds: false });
function processTasks (lastId) { function processTasks (lastId) {
// specify a query to limit the affected tasks (empty for all tasks): // specify a query to limit the affected tasks (empty for all tasks):
let query = { const query = {
type: 'daily', type: 'daily',
everyX: { everyX: {
$not: { $not: {
@@ -30,64 +31,63 @@ function processTasks (lastId) {
} }
dbTasks.find(query, { dbTasks.find(query, {
sort: {_id: 1}, sort: { _id: 1 },
limit: 250, limit: 250,
fields: [], fields: [],
}) })
.then(updateTasks) .then(updateTasks)
.catch((err) => { .catch(err => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
let progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
function updateTasks (tasks) { function updateTasks (tasks) {
if (!tasks || tasks.length === 0) { if (!tasks || tasks.length === 0) {
console.warn('All appropriate tasks found and modified.'); console.warn('All appropriate tasks found and modified.');
displayData(); displayData();
return; return null;
} }
let taskPromises = tasks.map(updatetask); const taskPromises = tasks.map(updatetask);
let lasttask = tasks[tasks.length - 1]; const lasttask = tasks[tasks.length - 1];
return Promise.all(taskPromises) return Promise.all(taskPromises)
.then(() => { .then(() => processTasks(lasttask._id));
return processTasks(lasttask._id);
});
} }
function updatetask (task) { function updatetask (task) {
count++; count += 1;
let set = {everyX: 0}; const set = { everyX: 0 };
dbTasks.update({_id: task._id}, {$set: set}); dbTasks.update({ _id: task._id }, { $set: set });
if (count % progressCount === 0) console.warn(`${count } ${ task._id}`); if (count % progressCount === 0) console.warn(`${count} ${task._id}`);
if (task._id === authorUuid) console.warn(`${authorName } processed`); if (task._id === authorUuid) console.warn(`${authorName} processed`);
} }
function displayData () { function displayData () {
console.warn(`\n${ count } tasks processed\n`); console.warn(`\n${count} tasks processed\n`);
return exiting(0); return exiting(0);
} }
function exiting (code, msg) { function exiting (code, msg) {
code = code || 0; // 0 = success // 0 = success
code = code || 0; // eslint-disable-line no-param-reassign
if (code && !msg) { if (code && !msg) {
msg = 'ERROR!'; msg = 'ERROR!'; // eslint-disable-line no-param-reassign
} }
if (msg) { if (msg) {
if (code) { if (code) {
console.error(msg); console.error(msg);
} else { } else {
console.log(msg); console.log(msg);
} }
} }
process.exit(code); process.exit(code);
} }
module.exports = processTasks; export default processTasks;

View File

@@ -1,28 +1,31 @@
/* let migrationName = 'tasks-set-yesterdaily'; */ /* let migrationName = 'tasks-set-yesterdaily'; */
let authorName = 'TheHollidayInn'; // in case script author needs to know when their ... // ... own data is done
let authorUuid = ''; // ... own data is done
/* /*
* Iterates over all tasks and sets the yseterDaily field to True * Iterates over all tasks and sets the yseterDaily field to True
*/ */
import monk from 'monk'; import monk from 'monk'; // eslint-disable-line import/no-extraneous-dependencies
let connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE const authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
let dbTasks = monk(connectionString).get('tasks', { castIds: false }); const authorUuid = '';
let progressCount = 1000; const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const dbTasks = monk(connectionString).get('tasks', { castIds: false });
const progressCount = 1000;
let count = 0; let count = 0;
function exiting (code, msg) { function exiting (code, msg) {
code = code || 0; // 0 = success // 0 = success
code = code || 0; // eslint-disable-line no-param-reassign
if (code && !msg) { if (code && !msg) {
msg = 'ERROR!'; msg = 'ERROR!'; // eslint-disable-line no-param-reassign
} }
if (msg) { if (msg) {
if (code) { if (code) {
console.error(msg); console.error(msg);
} else { } else {
console.log(msg); console.log(msg);
} }
} }
@@ -30,39 +33,37 @@ function exiting (code, msg) {
} }
function displayData () { function displayData () {
console.warn(`\n${ count } tasks processed\n`); console.warn(`\n${count} tasks processed\n`);
return exiting(0); return exiting(0);
} }
function updatetask (task) { function updatetask (task) {
count++; count += 1;
let set = {yesterDaily: true}; const set = { yesterDaily: true };
dbTasks.update({_id: task._id}, {$set: set}); dbTasks.update({ _id: task._id }, { $set: set });
if (count % progressCount === 0) console.warn(`${count } ${ task._id}`); if (count % progressCount === 0) console.warn(`${count} ${task._id}`);
if (task._id === authorUuid) console.warn(`${authorName } processed`); if (task._id === authorUuid) console.warn(`${authorName} processed`);
} }
function updateTasks (tasks) { function updateTasks (tasks) {
if (!tasks || tasks.length === 0) { if (!tasks || tasks.length === 0) {
console.warn('All appropriate tasks found and modified.'); console.warn('All appropriate tasks found and modified.');
displayData(); displayData();
return; return null;
} }
let taskPromises = tasks.map(updatetask); const taskPromises = tasks.map(updatetask);
let lasttask = tasks[tasks.length - 1]; const lasttask = tasks[tasks.length - 1];
return Promise.all(taskPromises) return Promise.all(taskPromises)
.then(() => { .then(() => processTasks(lasttask._id)); // eslint-disable-line no-use-before-define
return processTasks(lasttask._id); // eslint-disable-line no-use-before-define
});
} }
function processTasks (lastId) { function processTasks (lastId) {
// specify a query to limit the affected tasks (empty for all tasks): // specify a query to limit the affected tasks (empty for all tasks):
let query = { const query = {
yesterDaily: false, yesterDaily: false,
}; };
@@ -73,16 +74,18 @@ function processTasks (lastId) {
} }
dbTasks.find(query, { dbTasks.find(query, {
sort: {_id: 1}, sort: { _id: 1 },
limit: 250, limit: 250,
fields: [ // specify fields we are interested in to limit retrieved data (empty if we're not reading data): // specify fields we are interested in to limit retrieved data
// (empty if we're not reading data):
fields: [
], ],
}) })
.then(updateTasks) .then(updateTasks)
.catch((err) => { .catch(err => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
module.exports = processTasks; export default processTasks;

View File

@@ -8,29 +8,30 @@ let authorUuid = ''; // ... own data is done
* This migraition will copy user data from prod to test * This migraition will copy user data from prod to test
*/ */
const monk = require('monk'); import monk from 'monk'; // eslint-disable-line import/no-extraneous-dependencies
const connectionString = ''; const connectionString = '';
const Users = monk(connectionString).get('users', { castIds: false }); const Users = monk(connectionString).get('users', { castIds: false });
module.exports = async function accountTransfer () { export default async function accountTransfer () {
const fromAccountId = ''; const fromAccountId = '';
const toAccountId = ''; const toAccountId = '';
const fromAccount = await Users.findOne({_id: fromAccountId}); const fromAccount = await Users.findOne({ _id: fromAccountId });
const toAccount = await Users.findOne({_id: toAccountId}); const toAccount = await Users.findOne({ _id: toAccountId });
const newMounts = Object.assign({}, fromAccount.items.mounts, toAccount.items.mounts); const newMounts = { ...fromAccount.items.mounts, ...toAccount.items.mounts };
const newPets = Object.assign({}, fromAccount.items.pets, toAccount.items.pets); const newPets = { ...fromAccount.items.pets, ...toAccount.items.pets };
const newBackgrounds = Object.assign({}, fromAccount.purchased.background, toAccount.purchased.background); const newBackgrounds = { ...fromAccount.purchased.background, ...toAccount.purchased.background };
await Users.update({_id: toAccountId}, { await Users.update({ _id: toAccountId }, {
$set: { $set: {
'items.pets': newPets, 'items.pets': newPets,
'items.mounts': newMounts, 'items.mounts': newMounts,
'purchased.background': newBackgrounds, 'purchased.background': newBackgrounds,
}, },
}) })
.then((result) => { .then(result => {
console.log(result); console.log(result);
}); });
}; }

View File

@@ -8,7 +8,8 @@ const authorUuid = ''; // ... own data is done
* This migraition will copy user data from prod to test * This migraition will copy user data from prod to test
*/ */
const monk = require('monk'); import monk from 'monk'; // eslint-disable-line import/no-extraneous-dependencies
const connectionString = 'mongodb://localhost/new-habit'; const connectionString = 'mongodb://localhost/new-habit';
const Users = monk(connectionString).get('users', { castIds: false }); const Users = monk(connectionString).get('users', { castIds: false });
@@ -19,15 +20,17 @@ function getAchievementUpdate (newUser, oldUser) {
const oldAchievements = oldUser.achievements; const oldAchievements = oldUser.achievements;
const newAchievements = newUser.achievements; const newAchievements = newUser.achievements;
let achievementsUpdate = Object.assign({}, newAchievements); const achievementsUpdate = { ...newAchievements };
// ultimateGearSets // ultimateGearSets
if (!achievementsUpdate.ultimateGearSets && oldAchievements.ultimateGearSets) { if (!achievementsUpdate.ultimateGearSets && oldAchievements.ultimateGearSets) {
achievementsUpdate.ultimateGearSets = oldAchievements.ultimateGearSets; achievementsUpdate.ultimateGearSets = oldAchievements.ultimateGearSets;
} else if (oldAchievements.ultimateGearSets) { } else if (oldAchievements.ultimateGearSets) {
for (let index in oldAchievements.ultimateGearSets) { Object.keys(oldAchievements.ultimateGearSets).forEach(index => {
if (oldAchievements.ultimateGearSets[index]) achievementsUpdate.ultimateGearSets[index] = true; if (oldAchievements.ultimateGearSets[index]) {
} achievementsUpdate.ultimateGearSets[index] = true;
}
});
} }
// challenges // challenges
@@ -37,57 +40,57 @@ function getAchievementUpdate (newUser, oldUser) {
// Quests // Quests
if (!achievementsUpdate.quests) achievementsUpdate.quests = {}; if (!achievementsUpdate.quests) achievementsUpdate.quests = {};
for (let index in oldAchievements.quests) { Object.keys(oldAchievements.quests).forEach(index => {
if (!achievementsUpdate.quests[index]) { if (!achievementsUpdate.quests[index]) {
achievementsUpdate.quests[index] = oldAchievements.quests[index]; achievementsUpdate.quests[index] = oldAchievements.quests[index];
} else { } else {
achievementsUpdate.quests[index] += oldAchievements.quests[index]; achievementsUpdate.quests[index] += oldAchievements.quests[index];
} }
} });
// Rebirth level // Rebirth level
if (achievementsUpdate.rebirthLevel) { if (achievementsUpdate.rebirthLevel) {
achievementsUpdate.rebirthLevel = Math.max(achievementsUpdate.rebirthLevel, oldAchievements.rebirthLevel); achievementsUpdate.rebirthLevel = Math.max(
achievementsUpdate.rebirthLevel, oldAchievements.rebirthLevel,
);
} else if (oldAchievements.rebirthLevel) { } else if (oldAchievements.rebirthLevel) {
achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel; achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel;
} }
// All others // All others
const indexsToIgnore = ['ultimateGearSets', 'challenges', 'quests', 'rebirthLevel']; const indexsToIgnore = ['ultimateGearSets', 'challenges', 'quests', 'rebirthLevel'];
for (let index in oldAchievements) { Object.keys(oldAchievements).forEach(index => {
if (indexsToIgnore.indexOf(index) !== -1) continue; // eslint-disable-line no-continue if (indexsToIgnore.indexOf(index) !== -1) return;
if (!achievementsUpdate[index]) { if (!achievementsUpdate[index]) {
achievementsUpdate[index] = oldAchievements[index]; achievementsUpdate[index] = oldAchievements[index];
continue; // eslint-disable-line no-continue return;
} }
if (Number.isInteger(oldAchievements[index])) { if (Number.isInteger(oldAchievements[index])) {
achievementsUpdate[index] += oldAchievements[index]; achievementsUpdate[index] += oldAchievements[index];
} else if (oldAchievements[index] === true) achievementsUpdate[index] = true; } else if (oldAchievements[index] === true) achievementsUpdate[index] = true;
} });
return achievementsUpdate; return achievementsUpdate;
} }
module.exports = async function achievementRestore () { export default async function achievementRestore () {
const userIds = [ const userIds = [
]; ];
/* eslint-disable no-await-in-loop */ await Promise.all(userIds.map(userId => (async () => {
for (let index in userIds) { const oldUser = await UsersOld.findOne({ _id: userId }, 'achievements');
const userId = userIds[index]; const newUser = await Users.findOne({ _id: userId }, 'achievements');
const oldUser = await UsersOld.findOne({_id: userId}, 'achievements');
const newUser = await Users.findOne({_id: userId}, 'achievements');
const achievementUpdate = getAchievementUpdate(newUser, oldUser); const achievementUpdate = getAchievementUpdate(newUser, oldUser);
await Users.update( await Users.update(
{_id: userId}, { _id: userId },
{ {
$set: { $set: {
achievements: achievementUpdate, achievements: achievementUpdate,
}, },
}); },
);
console.log(`Updated ${userId}`); console.log(`Updated ${userId}`);
/* eslint-enable no-await-in-loop */ })()));
} }
};

View File

@@ -1,8 +1,9 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { sendTxn } from '../../website/server/libs/email';
import { model as User } from '../../website/server/models/user';
import moment from 'moment'; import moment from 'moment';
import nconf from 'nconf'; import nconf from 'nconf';
import { sendTxn } from '../../website/server/libs/email';
import { model as User } from '../../website/server/models/user';
const BASE_URL = nconf.get('BASE_URL'); const BASE_URL = nconf.get('BASE_URL');
const EMAIL_SLUG = 'mandrill-email-slug'; // Set email template to send const EMAIL_SLUG = 'mandrill-email-slug'; // Set email template to send
const MIGRATION_NAME = 'bulk-email'; const MIGRATION_NAME = 'bulk-email';
@@ -11,23 +12,23 @@ const progressCount = 1000;
let count = 0; let count = 0;
async function updateUser (user) { async function updateUser (user) {
count++; count += 1;
if (count % progressCount === 0) console.warn(`${count} ${user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
sendTxn( sendTxn(
user, user,
EMAIL_SLUG, EMAIL_SLUG,
[{name: 'BASE_URL', content: BASE_URL}] // Add variables from template [{ name: 'BASE_URL', content: BASE_URL }], // Add variables from template
); );
return await User.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}}).exec(); return User.update({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
} }
module.exports = async function processUsers () { export default async function processUsers () {
let query = { const query = {
migration: {$ne: MIGRATION_NAME}, migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': {$gt: moment().subtract(2, 'weeks').toDate()}, // customize or remove to target different populations 'auth.timestamps.loggedin': { $gt: moment().subtract(2, 'weeks').toDate() }, // customize or remove to target different populations
}; };
const fields = { const fields = {
@@ -41,7 +42,7 @@ module.exports = async function processUsers () {
const users = await User // eslint-disable-line no-await-in-loop const users = await User // eslint-disable-line no-await-in-loop
.find(query) .find(query)
.limit(250) .limit(250)
.sort({_id: 1}) .sort({ _id: 1 })
.select(fields) .select(fields)
.lean() .lean()
.exec(); .exec();
@@ -58,4 +59,4 @@ module.exports = async function processUsers () {
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
} }
}; }

View File

@@ -1,11 +1,12 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
const MIGRATION_NAME = 'full-stable';
import each from 'lodash/each'; import each from 'lodash/each';
import keys from 'lodash/keys'; import keys from 'lodash/keys';
import content from '../../website/common/script/content/index'; import content from '../../website/common/script/content/index';
import { model as User } from '../../website/server/models/user'; import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = 'full-stable';
const progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
@@ -14,45 +15,45 @@ let count = 0;
*/ */
async function updateUser (user) { async function updateUser (user) {
count++; count += 1;
const set = {}; const set = {};
set.migration = MIGRATION_NAME; set.migration = MIGRATION_NAME;
each(keys(content.pets), (pet) => { each(keys(content.pets), pet => {
set[`items.pets.${pet}`] = 5; set[`items.pets.${pet}`] = 5;
}); });
each(keys(content.premiumPets), (pet) => { each(keys(content.premiumPets), pet => {
set[`items.pets.${pet}`] = 5; set[`items.pets.${pet}`] = 5;
}); });
each(keys(content.questPets), (pet) => { each(keys(content.questPets), pet => {
set[`items.pets.${pet}`] = 5; set[`items.pets.${pet}`] = 5;
}); });
each(keys(content.specialPets), (pet) => { each(keys(content.specialPets), pet => {
set[`items.pets.${pet}`] = 5; set[`items.pets.${pet}`] = 5;
}); });
each(keys(content.mounts), (mount) => { each(keys(content.mounts), mount => {
set[`items.mounts.${mount}`] = true; set[`items.mounts.${mount}`] = true;
}); });
each(keys(content.premiumMounts), (mount) => { each(keys(content.premiumMounts), mount => {
set[`items.mounts.${mount}`] = true; set[`items.mounts.${mount}`] = true;
}); });
each(keys(content.questMounts), (mount) => { each(keys(content.questMounts), mount => {
set[`items.mounts.${mount}`] = true; set[`items.mounts.${mount}`] = true;
}); });
each(keys(content.specialMounts), (mount) => { each(keys(content.specialMounts), mount => {
set[`items.mounts.${mount}`] = true; set[`items.mounts.${mount}`] = true;
}); });
if (count % progressCount === 0) console.warn(`${count} ${user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set}).exec(); return User.update({ _id: user._id }, { $set: set }).exec();
} }
module.exports = async function processUsers () { export default async function processUsers () {
let query = { const query = {
migration: {$ne: MIGRATION_NAME}, migration: { $ne: MIGRATION_NAME },
'auth.local.username': 'olson22', 'auth.local.username': 'olson22',
}; };
@@ -64,7 +65,7 @@ module.exports = async function processUsers () {
const users = await User // eslint-disable-line no-await-in-loop const users = await User // eslint-disable-line no-await-in-loop
.find(query) .find(query)
.limit(250) .limit(250)
.sort({_id: 1}) .sort({ _id: 1 })
.select(fields) .select(fields)
.lean() .lean()
.exec(); .exec();
@@ -81,4 +82,4 @@ module.exports = async function processUsers () {
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
} }
}; }

View File

@@ -1,14 +1,15 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
const MIGRATION_NAME = 'mystery_items_201909';
const MYSTERY_ITEMS = ['armor_mystery_201909', 'head_mystery_201909'];
import { model as User } from '../../website/server/models/user'; import { model as User } from '../../website/server/models/user';
import { model as UserNotification } from '../../website/server/models/userNotification'; import { model as UserNotification } from '../../website/server/models/userNotification';
const MIGRATION_NAME = 'mystery_items_201910';
const MYSTERY_ITEMS = ['armor_mystery_201910', 'head_mystery_201910'];
const progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
async function updateUser (user) { async function updateUser (user) {
count++; count += 1;
const addToSet = { const addToSet = {
'purchased.plan.mysteryItems': { 'purchased.plan.mysteryItems': {
@@ -29,12 +30,12 @@ async function updateUser (user) {
if (count % progressCount === 0) console.warn(`${count} ${user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set, $push: push, $addToSet: addToSet}).exec(); return User.update({ _id: user._id }, { $set: set, $push: push, $addToSet: addToSet }).exec();
} }
module.exports = async function processUsers () { export default async function processUsers () {
let query = { const query = {
migration: {$ne: MIGRATION_NAME}, migration: { $ne: MIGRATION_NAME },
'purchased.plan.customerId': { $ne: null }, 'purchased.plan.customerId': { $ne: null },
$or: [ $or: [
{ 'purchased.plan.dateTerminated': { $gte: new Date() } }, { 'purchased.plan.dateTerminated': { $gte: new Date() } },
@@ -51,7 +52,7 @@ module.exports = async function processUsers () {
const users = await User // eslint-disable-line no-await-in-loop const users = await User // eslint-disable-line no-await-in-loop
.find(query) .find(query)
.limit(250) .limit(250)
.sort({_id: 1}) .sort({ _id: 1 })
.select(fields) .select(fields)
.lean() .lean()
.exec(); .exec();
@@ -68,4 +69,4 @@ module.exports = async function processUsers () {
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
} }
}; }

View File

@@ -1,14 +1,15 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
const MIGRATION_NAME = '20190314_pi_day';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { model as User } from '../../website/server/models/user'; import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20190314_pi_day';
const progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
async function updateUser (user) { async function updateUser (user) {
count++; count *= 1;
const inc = { const inc = {
'items.food.Pie_Skeleton': 1, 'items.food.Pie_Skeleton': 1,
@@ -29,19 +30,21 @@ async function updateUser (user) {
set['items.gear.owned.head_special_piDay'] = false; set['items.gear.owned.head_special_piDay'] = false;
set['items.gear.owned.shield_special_piDay'] = false; set['items.gear.owned.shield_special_piDay'] = false;
const push = [ const push = [
{type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid()}, { type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid() },
{type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid()}, { type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid() },
]; ];
if (count % progressCount === 0) console.warn(`${count} ${user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: {pinnedItems: {$each: push}}}).exec(); return User
.update({ _id: user._id }, { $inc: inc, $set: set, $push: { pinnedItems: { $each: push } } })
.exec();
} }
module.exports = async function processUsers () { export default async function processUsers () {
let query = { const query = {
migration: {$ne: MIGRATION_NAME}, migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': {$gt: new Date('2019-02-15')}, 'auth.timestamps.loggedin': { $gt: new Date('2019-02-15') },
}; };
const fields = { const fields = {
@@ -53,7 +56,7 @@ module.exports = async function processUsers () {
const users = await User // eslint-disable-line no-await-in-loop const users = await User // eslint-disable-line no-await-in-loop
.find(query) .find(query)
.limit(250) .limit(250)
.sort({_id: 1}) .sort({ _id: 1 })
.select(fields) .select(fields)
.lean() .lean()
.exec(); .exec();
@@ -70,4 +73,4 @@ module.exports = async function processUsers () {
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
} }
}; }

View File

@@ -1,3 +1,5 @@
import monk from 'monk'; // eslint-disable-line import/no-extraneous-dependencies
const migrationName = 'remove-social-users-extra-data.js'; const migrationName = 'remove-social-users-extra-data.js';
const authorName = 'paglias'; // in case script author needs to know when their ... const authorName = 'paglias'; // in case script author needs to know when their ...
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
@@ -7,13 +9,13 @@ const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is do
*/ */
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const monk = require('monk');
const dbUsers = monk(connectionString).get('users', { castIds: false }); const dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) { function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users): // specify a query to limit the affected users (empty for all users):
let query = { const query = {
migration: {$ne: migrationName}, migration: { $ne: migrationName },
$or: [ $or: [
{ 'auth.facebook.id': { $exists: true } }, { 'auth.facebook.id': { $exists: true } },
{ 'auth.google.id': { $exists: true } }, { 'auth.google.id': { $exists: true } },
@@ -27,28 +29,28 @@ function processUsers (lastId) {
} }
dbUsers.find(query, { dbUsers.find(query, {
sort: {_id: 1}, sort: { _id: 1 },
limit: 250, limit: 250,
}) })
.then(updateUsers) .then(updateUsers)
.catch((err) => { .catch(err => {
console.log(err); console.log(err);
return exiting(1, `ERROR! ${ err}`); return exiting(1, `ERROR! ${err}`);
}); });
} }
let progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
function updateUsers (users) { function updateUsers (users) {
if (!users || users.length === 0) { if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.'); console.warn('All appropriate users found and modified.');
displayData(); displayData();
return; return null;
} }
let userPromises = users.map(updateUser); const userPromises = users.map(updateUser);
let lastUser = users[users.length - 1]; const lastUser = users[users.length - 1];
return Promise.all(userPromises) return Promise.all(userPromises)
.then(() => { .then(() => {
@@ -57,7 +59,7 @@ function updateUsers (users) {
} }
function updateUser (user) { function updateUser (user) {
count++; count *= 1;
const isFacebook = user.auth.facebook && user.auth.facebook.id; const isFacebook = user.auth.facebook && user.auth.facebook.id;
const isGoogle = user.auth.google && user.auth.google.id; const isGoogle = user.auth.google && user.auth.google.id;
@@ -82,28 +84,29 @@ function updateUser (user) {
_id: user._id, _id: user._id,
}, update); }, update);
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`); if (user._id === authorUuid) console.warn(`${authorName} processed`);
} }
function displayData () { function displayData () {
console.warn(`\n${ count } users processed\n`); console.warn(`\n${count} users processed\n`);
return exiting(0); return exiting(0);
} }
function exiting (code, msg) { function exiting (code, msg) {
code = code || 0; // 0 = success // 0 = success
code = code || 0; // eslint-disable-line no-param-reassign
if (code && !msg) { if (code && !msg) {
msg = 'ERROR!'; msg = 'ERROR!'; // eslint-disable-line no-param-reassign
} }
if (msg) { if (msg) {
if (code) { if (code) {
console.error(msg); console.error(msg);
} else { } else {
console.log(msg); console.log(msg);
} }
} }
process.exit(code); process.exit(code);
} }
module.exports = processUsers; export default processUsers;

View File

@@ -1,14 +1,15 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
const MIGRATION_NAME = '20181203_take_this';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { model as User } from '../../website/server/models/user'; import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20181203_take_this';
const progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
async function updateUser (user) { async function updateUser (user) {
count++; count += 1;
const set = {}; const set = {};
let push; let push;
@@ -19,36 +20,35 @@ async function updateUser (user) {
push = false; push = false;
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') { } else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
set['items.gear.owned.back_special_takeThis'] = false; set['items.gear.owned.back_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', _id: uuid()}}; push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.back_special_takeThis', _id: uuid() } };
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') { } else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
set['items.gear.owned.body_special_takeThis'] = false; set['items.gear.owned.body_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', _id: uuid()}}; push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.body_special_takeThis', _id: uuid() } };
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') { } else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
set['items.gear.owned.head_special_takeThis'] = false; set['items.gear.owned.head_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', _id: uuid()}}; push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.head_special_takeThis', _id: uuid() } };
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') { } else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
set['items.gear.owned.armor_special_takeThis'] = false; set['items.gear.owned.armor_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', _id: uuid()}}; push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.armor_special_takeThis', _id: uuid() } };
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') { } else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
set['items.gear.owned.weapon_special_takeThis'] = false; set['items.gear.owned.weapon_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', _id: uuid()}}; push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', _id: uuid() } };
} else { } else {
set['items.gear.owned.shield_special_takeThis'] = false; set['items.gear.owned.shield_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', _id: uuid()}}; push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.shield_special_takeThis', _id: uuid() } };
} }
if (count % progressCount === 0) console.warn(`${count} ${user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (push) { if (push) {
return await User.update({_id: user._id}, {$set: set, $push: push}).exec(); return User.update({ _id: user._id }, { $set: set, $push: push }).exec();
} else {
return await User.update({_id: user._id}, {$set: set}).exec();
} }
return User.update({ _id: user._id }, { $set: set }).exec();
} }
module.exports = async function processUsers () { export default async function processUsers () {
let query = { const query = {
migration: {$ne: MIGRATION_NAME}, migration: { $ne: MIGRATION_NAME },
challenges: '00708425-d477-41a5-bf27-6270466e7976', challenges: '00708425-d477-41a5-bf27-6270466e7976',
}; };
@@ -61,7 +61,7 @@ module.exports = async function processUsers () {
const users = await User // eslint-disable-line no-await-in-loop const users = await User // eslint-disable-line no-await-in-loop
.find(query) .find(query)
.limit(250) .limit(250)
.sort({_id: 1}) .sort({ _id: 1 })
.select(fields) .select(fields)
.lean() .lean()
.exec(); .exec();
@@ -78,4 +78,4 @@ module.exports = async function processUsers () {
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
} }
}; }

View File

@@ -1,3 +1,4 @@
/* eslint-disable import/no-commonjs */
/* /*
let migrationName = 'UserFromProdToTest'; let migrationName = 'UserFromProdToTest';
let authorName = 'TheHollidayInn'; // in case script author needs to know when their ... let authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
@@ -8,22 +9,24 @@ let authorUuid = ''; // ... own data is done
* This migraition will copy user data from prod to test * This migraition will copy user data from prod to test
*/ */
let monk = require('monk');
let testConnectionSting = ''; // FOR TEST DATABASE
let usersTest = monk(testConnectionSting).get('users', { castIds: false });
let groupsTest = monk(testConnectionSting).get('groups', { castIds: false });
let challengesTest = monk(testConnectionSting).get('challenges', { castIds: false });
let tasksTest = monk(testConnectionSting).get('tasks', { castIds: false });
let monk2 = require('monk');
let liveConnectString = ''; // FOR TEST DATABASE
let userLive = monk2(liveConnectString).get('users', { castIds: false });
let groupsLive = monk2(liveConnectString).get('groups', { castIds: false });
let challengesLive = monk2(liveConnectString).get('challenges', { castIds: false });
let tasksLive = monk2(liveConnectString).get('tasks', { castIds: false });
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
const monk = require('monk'); // eslint-disable-line import/no-extraneous-dependencies
const testConnectionSting = ''; // FOR TEST DATABASE
const usersTest = monk(testConnectionSting).get('users', { castIds: false });
const groupsTest = monk(testConnectionSting).get('groups', { castIds: false });
const challengesTest = monk(testConnectionSting).get('challenges', { castIds: false });
const tasksTest = monk(testConnectionSting).get('tasks', { castIds: false });
const monk2 = require('monk'); // eslint-disable-line import/no-extraneous-dependencies
const liveConnectString = ''; // FOR TEST DATABASE
const userLive = monk2(liveConnectString).get('users', { castIds: false });
const groupsLive = monk2(liveConnectString).get('groups', { castIds: false });
const challengesLive = monk2(liveConnectString).get('challenges', { castIds: false });
const tasksLive = monk2(liveConnectString).get('tasks', { castIds: false });
// Variabls for updating // Variabls for updating
/* /*
let userIds = [ let userIds = [
@@ -36,11 +39,11 @@ let challengeIds = [];
let tasksIds = []; let tasksIds = [];
async function processUsers () { async function processUsers () {
let userPromises = []; const userPromises = [];
// {_id: {$in: userIds}} // {_id: {$in: userIds}}
return userLive.find({guilds: 'b0764d64-8276-45a1-afa5-5ca9a5c64ca0'}) return userLive.find({ guilds: 'b0764d64-8276-45a1-afa5-5ca9a5c64ca0' })
.each((user) => { .each(user => {
if (user.guilds.length > 0) groupIds = groupIds.concat(user.guilds); if (user.guilds.length > 0) groupIds = groupIds.concat(user.guilds);
if (user.party._id) groupIds.push(user.party._id); if (user.party._id) groupIds.push(user.party._id);
if (user.challenges.length > 0) challengeIds = challengeIds.concat(user.challenges); if (user.challenges.length > 0) challengeIds = challengeIds.concat(user.challenges);
@@ -49,64 +52,56 @@ async function processUsers () {
if (user.tasksOrder.dailys.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.dailys); if (user.tasksOrder.dailys.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.dailys);
if (user.tasksOrder.habits.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.habits); if (user.tasksOrder.habits.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.habits);
let userPromise = usersTest.update({_id: user._id}, user, {upsert: true}); const userPromise = usersTest.update({ _id: user._id }, user, { upsert: true });
userPromises.push(userPromise); userPromises.push(userPromise);
}).then(() => { }).then(() => Promise.all(userPromises))
return Promise.all(userPromises);
})
.then(() => { .then(() => {
console.log('Done User'); console.log('Done User');
}); });
} }
function processGroups () { function processGroups () {
let promises = []; const promises = [];
let groupsToQuery = uniq(groupIds); const groupsToQuery = uniq(groupIds);
return groupsLive.find({_id: {$in: groupsToQuery}}) return groupsLive.find({ _id: { $in: groupsToQuery } })
.each((group) => { .each(group => {
let promise = groupsTest.update({_id: group._id}, group, {upsert: true}); const promise = groupsTest.update({ _id: group._id }, group, { upsert: true });
promises.push(promise); promises.push(promise);
}).then(() => { }).then(() => Promise.all(promises))
return Promise.all(promises);
})
.then(() => { .then(() => {
console.log('Done Group'); console.log('Done Group');
}); });
} }
function processChallenges () { function processChallenges () {
let promises = []; const promises = [];
let challengesToQuery = uniq(challengeIds); const challengesToQuery = uniq(challengeIds);
return challengesLive.find({_id: {$in: challengesToQuery}}) return challengesLive.find({ _id: { $in: challengesToQuery } })
.each((challenge) => { .each(challenge => {
let promise = challengesTest.update({_id: challenge._id}, challenge, {upsert: true}); const promise = challengesTest.update({ _id: challenge._id }, challenge, { upsert: true });
promises.push(promise); promises.push(promise);
}).then(() => { }).then(() => Promise.all(promises))
return Promise.all(promises);
})
.then(() => { .then(() => {
console.log('Done Challenge'); console.log('Done Challenge');
}); });
} }
function processTasks () { function processTasks () {
let promises = []; const promises = [];
let tasksToQuery = uniq(tasksIds); const tasksToQuery = uniq(tasksIds);
return tasksLive.find({_id: {$in: tasksToQuery}}) return tasksLive.find({ _id: { $in: tasksToQuery } })
.each((task) => { .each(task => {
let promise = tasksTest.update({_id: task._id}, task, {upsert: true}); const promise = tasksTest.update({ _id: task._id }, task, { upsert: true });
promises.push(promise); promises.push(promise);
}).then(() => { }).then(() => Promise.all(promises))
return Promise.all(promises);
})
.then(() => { .then(() => {
console.log('Done Tasks'); console.log('Done Tasks');
}); });
} }
module.exports = async function prodToTest () { export default async function prodToTest () {
await processUsers(); await processUsers();
await processGroups(); await processGroups();
await processChallenges(); await processChallenges();
await processTasks(); await processTasks();
}; }

View File

@@ -1,6 +1,5 @@
'use strict'; /* eslint-disable import/no-commonjs */
const { MongoClient } = require('mongodb'); // eslint-disable-line import/no-extraneous-dependencies
const MongoClient = require('mongodb').MongoClient;
const logger = require('./logger'); const logger = require('./logger');
let dbConnection; let dbConnection;
@@ -17,7 +16,7 @@ function connectToDb (dbUri) {
logger.success(`Connected to ${dbUri}`); logger.success(`Connected to ${dbUri}`);
resolve(database); return resolve(database);
}); });
}); });
} }

View File

@@ -1,10 +1,11 @@
'use strict'; /* eslint-disable import/no-commonjs */
const chalk = require('chalk'); // eslint-disable-line import/no-extraneous-dependencies
const chalk = require('chalk');
function loggerGenerator (type, color) { function loggerGenerator (type, color) {
return function logger () { return function logger () {
let args = Array.from(arguments).map(arg => chalk[color](arg)); const args = Array
.from(arguments) // eslint-disable-line prefer-rest-params
.map(arg => chalk[color](arg));
console[type].apply(null, args); console[type].apply(null, args);
}; };
} }

View File

@@ -1,30 +1,30 @@
'use strict'; /* eslint-disable import/no-commonjs */
const logger = require('./logger');
let logger = require('./logger');
class Timer { class Timer {
constructor (options) { constructor (options = {}) {
options = options || {}; const warningThreshold = options.minutesWarningThreshold || 10;
let warningThreshold = options.minutesWarningThreshold || 10;
this.count = 0; this.count = 0;
this._minutesWarningThreshold = warningThreshold * 60; this._minutesWarningThreshold = warningThreshold * 60;
if (!options.disableAutoStart) this.start(); if (!options.disableAutoStart) this.start();
} }
start () { start () {
this._internalTimer = setInterval(() => { this._internalTimer = setInterval(() => {
this.count++; this.count *= 1;
let shouldWarn = this._minutesWarningThreshold < this.count; const shouldWarn = this._minutesWarningThreshold < this.count;
let logStyle = shouldWarn ? 'error' : 'warn'; const logStyle = shouldWarn ? 'error' : 'warn';
let dangerMessage = shouldWarn ? 'DANGER: ' : ''; const dangerMessage = shouldWarn ? 'DANGER: ' : '';
if (this.count % 30 === 0) { if (this.count % 30 === 0) {
logger[logStyle](`${dangerMessage}Process has been running for`, this.count / 60, 'minutes'); logger[logStyle](`${dangerMessage}Process has been running for`, this.count / 60, 'minutes');
} }
}, 1000); }, 1000);
} }
stop () { stop () {
if (!this._internalTimer) { if (!this._internalTimer) {
throw new Error('Timer has not started'); throw new Error('Timer has not started');

View File

@@ -1,5 +1,4 @@
'use strict'; /* eslint-disable import/no-commonjs */
function unique (array) { function unique (array) {
return Array.from(new Set(array)); return Array.from(new Set(array));
} }

19760
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,76 +1,49 @@
{ {
"name": "habitica", "name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.", "description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.116.2", "version": "4.118.0",
"main": "./website/server/index.js", "main": "./website/server/index.js",
"dependencies": { "dependencies": {
"@google-cloud/trace-agent": "^4.0.0", "@google-cloud/trace-agent": "^4.2.2",
"@slack/client": "^3.8.1", "@slack/client": "^3.8.1",
"accepts": "^1.3.5", "accepts": "^1.3.5",
"amazon-payments": "^0.2.7", "amazon-payments": "^0.2.7",
"amplitude": "^3.5.0", "amplitude": "^3.5.0",
"amplitude-js": "^5.2.2",
"apidoc": "^0.17.5", "apidoc": "^0.17.5",
"apn": "^2.2.0", "apn": "^2.2.0",
"autoprefixer": "^9.4.0", "aws-sdk": "^2.556.0",
"aws-sdk": "^2.432.0",
"axios": "^0.19.0",
"axios-progress-bar": "^1.2.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-regenerator": "^6.16.1",
"babel-polyfill": "^6.6.1",
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"bcrypt": "^3.0.6", "bcrypt": "^3.0.6",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
"bootstrap": "^4.1.1",
"bootstrap-vue": "^2.0.2",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-session": "^1.3.3", "cookie-session": "^1.3.3",
"coupon-code": "^0.4.5", "coupon-code": "^0.4.5",
"cross-env": "^6.0.0",
"css-loader": "^0.28.11",
"csv-stringify": "^5.1.0", "csv-stringify": "^5.1.0",
"cwait": "^1.1.1", "cwait": "^1.1.1",
"domain-middleware": "~0.1.0", "domain-middleware": "~0.1.0",
"express": "^4.16.3", "express": "^4.16.3",
"express-basic-auth": "^1.1.5", "express-basic-auth": "^1.1.5",
"express-validator": "^5.2.0", "express-validator": "^5.2.0",
"extract-text-webpack-plugin": "^3.0.2", "glob": "^7.1.5",
"glob": "^7.1.2",
"got": "^9.0.0", "got": "^9.0.0",
"gulp": "^4.0.0", "gulp": "^4.0.0",
"gulp-babel": "^7.0.1", "gulp-babel": "^8.0.0",
"gulp-imagemin": "^6.0.0", "gulp-imagemin": "^6.1.1",
"gulp-nodemon": "^2.4.1", "gulp-nodemon": "^2.4.1",
"gulp.spritesmith": "^6.9.0", "gulp.spritesmith": "^6.9.0",
"habitica-markdown": "^1.3.0", "habitica-markdown": "^1.3.0",
"hellojs": "^1.18.1", "helmet": "^3.21.2",
"helmet": "^3.21.0", "image-size": "^0.8.3",
"html-webpack-plugin": "^3.2.0",
"image-size": "^0.8.0",
"in-app-purchase": "^1.11.3", "in-app-purchase": "^1.11.3",
"intro.js": "^2.9.3",
"jquery": ">=3.0.0",
"js2xmlparser": "^4.0.0", "js2xmlparser": "^4.0.0",
"lodash": "^4.17.10", "lodash": "^4.17.15",
"merge-stream": "^2.0.0", "merge-stream": "^2.0.0",
"method-override": "^3.0.0", "method-override": "^3.0.0",
"moment": "^2.22.1", "moment": "^2.24.0",
"moment-recur": "^1.0.7", "moment-recur": "^1.0.7",
"mongoose": "^5.6.9", "mongoose": "^5.7.7",
"morgan": "^1.7.0", "morgan": "^1.7.0",
"nconf": "^0.10.0", "nconf": "^0.10.0",
"node-gcm": "^1.0.2", "node-gcm": "^1.0.2",
"node-sass": "^4.12.0",
"ora": "^3.2.0",
"pageres": "^5.1.0", "pageres": "^5.1.0",
"passport": "^0.4.0", "passport": "^0.4.0",
"passport-facebook": "^3.0.0", "passport-facebook": "^3.0.0",
@@ -78,40 +51,17 @@
"passport-google-oauth20": "1.0.0", "passport-google-oauth20": "1.0.0",
"paypal-ipn": "3.0.0", "paypal-ipn": "3.0.0",
"paypal-rest-sdk": "^1.8.1", "paypal-rest-sdk": "^1.8.1",
"popper.js": "^1.14.3",
"postcss-easy-import": "^3.0.0",
"ps-tree": "^1.0.0", "ps-tree": "^1.0.0",
"pug": "^2.0.3",
"regenerator-runtime": "^0.13.3", "regenerator-runtime": "^0.13.3",
"rimraf": "^2.4.3", "rimraf": "^3.0.0",
"sass-loader": "^7.0.3",
"shelljs": "^0.8.2",
"short-uuid": "^3.0.0", "short-uuid": "^3.0.0",
"smartbanner.js": "^1.11.0", "stripe": "^7.10.0",
"stripe": "^7.9.0",
"superagent": "^5.0.2", "superagent": "^5.0.2",
"svg-inline-loader": "^0.8.0",
"svg-url-loader": "^3.0.0",
"svgo": "^1.2.0",
"svgo-loader": "^2.1.0",
"universal-analytics": "^0.4.17", "universal-analytics": "^0.4.17",
"update": "^0.7.4",
"upgrade": "^1.1.0",
"url-loader": "^1.0.0",
"useragent": "^2.1.9", "useragent": "^2.1.9",
"uuid": "^3.0.1", "uuid": "^3.3.3",
"validator": "^11.0.0", "validator": "^11.0.0",
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
"vue": "^2.6.10",
"vue-loader": "^14.2.2",
"vue-mugen-scroll": "^0.2.1",
"vue-router": "^3.0.0",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.6.10",
"vuedraggable": "^2.20.0",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^3.12.0",
"webpack-merge": "^4.1.3",
"winston": "^2.4.3", "winston": "^2.4.3",
"winston-loggly-bulk": "^2.0.2", "winston-loggly-bulk": "^2.0.2",
"xml2js": "^0.4.4" "xml2js": "^0.4.4"
@@ -122,7 +72,8 @@
"npm": "^6" "npm": "^6"
}, },
"scripts": { "scripts": {
"lint": "eslint --ext .js,.vue .", "lint": "eslint --ext .js --fix . && cd website/client && npm run lint",
"lint-no-fix": "eslint --ext .js . && cd website/client && npm run lint --no-fix",
"test": "npm run lint && gulp test && gulp apidoc", "test": "npm run lint && gulp test && gulp apidoc",
"test:build": "gulp test:prepare:build", "test:build": "gulp test:prepare:build",
"test:api-v3": "gulp test:api-v3", "test:api-v3": "gulp test:api-v3",
@@ -137,60 +88,32 @@
"test:nodemon": "gulp test:nodemon", "test:nodemon": "gulp test:nodemon",
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html", "coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
"sprites": "gulp sprites:compile", "sprites": "gulp sprites:compile",
"client:dev": "node webpack/dev-server.js", "client:dev": "cd website/client && npm run serve",
"client:build": "gulp build:client", "client:build": "cd website/client && npm run build",
"client:unit": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js --single-run", "client:unit": "cd website/client && npm run test:unit",
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",
"client:e2e": "node test/client/e2e/runner.js",
"client:test": "npm run client:unit && npm run client:e2e",
"start": "gulp nodemon", "start": "gulp nodemon",
"postinstall": "gulp build", "postinstall": "gulp build && cd website/client && npm install",
"apidoc": "gulp apidoc" "apidoc": "gulp apidoc"
}, },
"devDependencies": { "devDependencies": {
"@vue/test-utils": "^1.0.0-beta.29", "@babel/core": "^7.6.4",
"babel-plugin-istanbul": "^4.1.6", "@babel/preset-env": "^7.6.3",
"babel-plugin-syntax-object-rest-spread": "^6.13.0", "@babel/register": "^7.6.2",
"axios": "^0.19.0",
"chai": "^4.1.2", "chai": "^4.1.2",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chalk": "^2.4.1", "chalk": "^2.4.1",
"chromedriver": "^77.0.0", "eslint": "^6.6.0",
"connect-history-api-fallback": "^1.1.0", "eslint-config-habitrpg": "^6.2.0",
"cross-spawn": "^7.0.0",
"eslint": "^4.19.1",
"eslint-config-habitrpg": "^4.0.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^2.0.0",
"eslint-plugin-html": "^4.0.3",
"eslint-plugin-mocha": "^5.0.0", "eslint-plugin-mocha": "^5.0.0",
"eventsource-polyfill": "^0.9.6",
"expect.js": "^0.3.1", "expect.js": "^0.3.1",
"http-proxy-middleware": "^0.20.0",
"istanbul": "^1.1.0-alpha.1", "istanbul": "^1.1.0-alpha.1",
"karma": "^4.0.1",
"karma-babel-preprocessor": "^7.0.0",
"karma-chai-plugins": "^0.9.0",
"karma-chrome-launcher": "^3.0.0",
"karma-coverage": "^1.1.2",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.5",
"karma-sinon-chai": "^2.0.0",
"karma-sinon-stub-promise": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^3.0.0",
"mocha": "^5.1.1", "mocha": "^5.1.1",
"monk": "^6.0.6", "monk": "^7.1.1",
"nightwatch": "^1.0.16",
"puppeteer": "^1.14.0",
"require-again": "^2.0.0", "require-again": "^2.0.0",
"selenium-server": "^3.12.0",
"sinon": "^7.2.4", "sinon": "^7.2.4",
"sinon-chai": "^3.0.0", "sinon-chai": "^3.0.0",
"sinon-stub-promise": "^4.0.0", "sinon-stub-promise": "^4.0.0"
"webpack-bundle-analyzer": "^2.12.0",
"webpack-dev-middleware": "^2.0.5",
"webpack-hot-middleware": "^2.22.2"
}, },
"optionalDependencies": {} "optionalDependencies": {}
} }

View File

@@ -1,13 +1,13 @@
/* eslint-disable no-console */ /* eslint-disable no-console, import/no-commonjs */
import axios from 'axios'; import axios from 'axios'; // eslint-disable-line import/no-extraneous-dependencies
import { model as User } from '../website/server/models/user';
import nconf from 'nconf'; import nconf from 'nconf';
import { model as User } from '../website/server/models/user';
const AMPLITUDE_KEY = nconf.get('AMPLITUDE_KEY'); const AMPLITUDE_KEY = nconf.get('AMPLITUDE_KEY');
const AMPLITUDE_SECRET = nconf.get('AMPLITUDE_SECRET'); const AMPLITUDE_SECRET = nconf.get('AMPLITUDE_SECRET');
const BASE_URL = nconf.get('BASE_URL'); const BASE_URL = nconf.get('BASE_URL');
async function _deleteAmplitudeData (userId, email) { async function deleteAmplitudeData (userId, email) {
const response = await axios.post( const response = await axios.post(
'https://amplitude.com/api/2/deletions/users', 'https://amplitude.com/api/2/deletions/users',
{ {
@@ -19,22 +19,24 @@ async function _deleteAmplitudeData (userId, email) {
username: AMPLITUDE_KEY, username: AMPLITUDE_KEY,
password: AMPLITUDE_SECRET, password: AMPLITUDE_SECRET,
}, },
} },
).catch((err) => { ).catch(err => {
console.log(err.response.data); console.log(err.response.data);
}); });
if (response) console.log(`${response.status} ${response.statusText}`); if (response) console.log(`${response.status} ${response.statusText}`);
} }
async function _deleteHabiticaData (user, email) { async function deleteHabiticaData (user, email) {
await User.update( await User.update(
{_id: user._id}, { _id: user._id },
{$set: { {
'auth.local.email': email, $set: {
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW', 'auth.local.email': email,
'auth.local.passwordHashMethod': 'bcrypt', 'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
}} 'auth.local.passwordHashMethod': 'bcrypt',
},
},
); );
const response = await axios.delete( const response = await axios.delete(
`${BASE_URL}/api/v3/user`, `${BASE_URL}/api/v3/user`,
@@ -46,8 +48,8 @@ async function _deleteHabiticaData (user, email) {
'x-api-user': user._id, 'x-api-user': user._id,
'x-api-key': user.apiToken, 'x-api-key': user.apiToken,
}, },
} },
).catch((err) => { ).catch(err => {
console.log(err.response.data); console.log(err.response.data);
}); });
@@ -57,14 +59,15 @@ async function _deleteHabiticaData (user, email) {
} }
} }
async function _processEmailAddress (email) { async function processEmailAddress (email) {
const emailRegex = new RegExp(`^${email}$`, 'i'); const emailRegex = new RegExp(`^${email}$`, 'i');
const users = await User.find({ const users = await User.find({
$or: [ $or: [
{'auth.local.email': emailRegex}, { 'auth.local.email': emailRegex },
{'auth.facebook.emails.value': emailRegex}, { 'auth.facebook.emails.value': emailRegex },
{'auth.google.emails.value': emailRegex}, { 'auth.google.emails.value': emailRegex },
]}, ],
},
{ {
_id: 1, _id: 1,
apiToken: 1, apiToken: 1,
@@ -74,15 +77,15 @@ async function _processEmailAddress (email) {
if (users.length < 1) { if (users.length < 1) {
console.log(`No users found with email address ${email}`); console.log(`No users found with email address ${email}`);
} else { } else {
for (const user of users) { Promise.all(users.map(user => (async () => {
await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop await deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
await _deleteHabiticaData(user, email); // eslint-disable-line no-await-in-loop await deleteHabiticaData(user, email); // eslint-disable-line no-await-in-loop
} })()));
} }
} }
function deleteUserData (emails) { function deleteUserData (emails) {
const emailPromises = emails.map(_processEmailAddress); const emailPromises = emails.map(processEmailAddress);
return Promise.all(emailPromises); return Promise.all(emailPromises);
} }

View File

@@ -1,8 +1,12 @@
require('babel-register'); /* eslint-disable import/no-commonjs */
require('@babel/register'); // eslint-disable-line import/no-extraneous-dependencies
// This file is used for creating paypal billing plans. PayPal doesn't have a web interface for setting up recurring // This file is used for creating paypal billing plans.
// payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this // PayPal doesn't have a web interface for setting up recurring
// file will be used once for initing your billing plan (then you get the resultant plan.id to store in config.json), // payment plan definitions, instead you have to create it
// via their REST SDK and keep it updated the same way. So this
// file will be used once for initing your billing plan
// (then you get the resultant plan.id to store in config.json),
// and once for any time you need to edit the plan thereafter // and once for any time you need to edit the plan thereafter
/* eslint-disable no-console, camelcase, no-case-declarations */ /* eslint-disable no-console, camelcase, no-case-declarations */
@@ -12,11 +16,12 @@ const nconf = require('nconf');
const _ = require('lodash'); const _ = require('lodash');
const paypal = require('paypal-rest-sdk'); const paypal = require('paypal-rest-sdk');
const blocks = require('../website/common').content.subscriptionBlocks; const blocks = require('../website/common').content.subscriptionBlocks;
const live = nconf.get('PAYPAL_MODE') === 'live'; const live = nconf.get('PAYPAL_MODE') === 'live';
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json'))); nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
let OP = 'create'; // list get update create create-webprofile const OP = 'create'; // list get update create create-webprofile
paypal.configure({ paypal.configure({
mode: nconf.get('PAYPAL_MODE'), // sandbox or live mode: nconf.get('PAYPAL_MODE'), // sandbox or live
@@ -25,8 +30,8 @@ paypal.configure({
}); });
// https://developer.paypal.com/docs/api/#billing-plans-and-agreements // https://developer.paypal.com/docs/api/#billing-plans-and-agreements
let billingPlanTitle = 'Habitica Subscription'; const billingPlanTitle = 'Habitica Subscription';
let billingPlanAttributes = { const billingPlanAttributes = {
description: billingPlanTitle, description: billingPlanTitle,
type: 'INFINITE', type: 'INFINITE',
merchant_preferences: { merchant_preferences: {
@@ -41,7 +46,7 @@ let billingPlanAttributes = {
}], }],
}; };
_.each(blocks, (block) => { _.each(blocks, block => {
block.definition = _.cloneDeep(billingPlanAttributes); block.definition = _.cloneDeep(billingPlanAttributes);
_.merge(block.definition.payment_definitions[0], { _.merge(block.definition.payment_definitions[0], {
name: `${billingPlanTitle} ($${block.price} every ${block.months} months, recurring)`, name: `${billingPlanTitle} ($${block.price} every ${block.months} months, recurring)`,
@@ -57,17 +62,17 @@ _.each(blocks, (block) => {
switch (OP) { switch (OP) {
case 'list': case 'list':
paypal.billingPlan.list({status: 'ACTIVE'}, (err, plans) => { paypal.billingPlan.list({ status: 'ACTIVE' }, (err, plans) => {
console.log({err, plans}); console.log({ err, plans });
}); });
break; break;
case 'get': case 'get':
paypal.billingPlan.get(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), (err, plan) => { paypal.billingPlan.get(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), (err, plan) => {
console.log({err, plan}); console.log({ err, plan });
}); });
break; break;
case 'update': case 'update':
let updatePayload = { const updatePayload = {
op: 'replace', op: 'replace',
path: '/merchant_preferences', path: '/merchant_preferences',
value: { value: {
@@ -75,7 +80,7 @@ switch (OP) {
}, },
}; };
paypal.billingPlan.update(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), updatePayload, (err, res) => { paypal.billingPlan.update(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), updatePayload, (err, res) => {
console.log({err, plan: res}); console.log({ err, plan: res });
}); });
break; break;
case 'create': case 'create':
@@ -83,10 +88,10 @@ switch (OP) {
if (err) return console.log(err); if (err) return console.log(err);
if (plan.state === 'ACTIVE') { if (plan.state === 'ACTIVE') {
return console.log({err, plan}); return console.log({ err, plan });
} }
let billingPlanUpdateAttributes = [{ const billingPlanUpdateAttributes = [{
op: 'replace', op: 'replace',
path: '/', path: '/',
value: { value: {
@@ -96,12 +101,14 @@ switch (OP) {
// Activate the plan by changing status to Active // Activate the plan by changing status to Active
paypal.billingPlan.update(plan.id, billingPlanUpdateAttributes, (err2, response) => { paypal.billingPlan.update(plan.id, billingPlanUpdateAttributes, (err2, response) => {
console.log({err: err2, response, id: plan.id}); console.log({ err: err2, response, id: plan.id });
}); });
return null;
}); });
break; break;
case 'create-webprofile': case 'create-webprofile':
let webexpinfo = { const webexpinfo = {
name: 'HabiticaProfile', name: 'HabiticaProfile',
input_fields: { input_fields: {
no_shipping: 1, no_shipping: 1,
@@ -112,4 +119,6 @@ switch (OP) {
console.log(error, result); console.log(error, result);
}); });
break; break;
default:
throw new Error('Invalid op');
} }

View File

@@ -1,12 +0,0 @@
{
"extends": [
"habitrpg/mocha",
"habitrpg/esnext"
],
"env": {
"node": true,
},
"globals": {
"_": true,
}
}

12
test/.eslintrc.js Normal file
View File

@@ -0,0 +1,12 @@
module.exports = {
extends: [
'habitrpg/lib/mocha',
],
globals: {
_: true,
chai: true,
expect: true,
sinon: true,
sandbox: true
},
}

View File

@@ -1,7 +1,7 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import analyticsService from '../../../../website/server/libs/analyticsService';
import Amplitude from 'amplitude'; import Amplitude from 'amplitude';
import { Visitor } from 'universal-analytics'; import { Visitor } from 'universal-analytics';
import * as analyticsService from '../../../../website/server/libs/analyticsService';
describe('analyticsService', () => { describe('analyticsService', () => {
beforeEach(() => { beforeEach(() => {
@@ -16,7 +16,8 @@ describe('analyticsService', () => {
}); });
describe('#track', () => { describe('#track', () => {
let eventType, data; let eventType; let
data;
beforeEach(() => { beforeEach(() => {
Visitor.prototype.event.yields(); Visitor.prototype.event.yields();
@@ -35,12 +36,10 @@ describe('analyticsService', () => {
}); });
context('Amplitude', () => { context('Amplitude', () => {
it('calls out to amplitude', () => { it('calls out to amplitude', () => analyticsService.track(eventType, data)
return analyticsService.track(eventType, data) .then(() => {
.then(() => { expect(Amplitude.prototype.track).to.be.calledOnce;
expect(Amplitude.prototype.track).to.be.calledOnce; }));
});
});
it('uses a dummy user id if none is provided', () => { it('uses a dummy user id if none is provided', () => {
delete data.uuid; delete data.uuid;
@@ -55,7 +54,7 @@ describe('analyticsService', () => {
context('platform', () => { context('platform', () => {
it('logs web platform', () => { it('logs web platform', () => {
data.headers = {'x-client': 'habitica-web'}; data.headers = { 'x-client': 'habitica-web' };
return analyticsService.track(eventType, data) return analyticsService.track(eventType, data)
.then(() => { .then(() => {
@@ -66,7 +65,7 @@ describe('analyticsService', () => {
}); });
it('logs iOS platform', () => { it('logs iOS platform', () => {
data.headers = {'x-client': 'habitica-ios'}; data.headers = { 'x-client': 'habitica-ios' };
return analyticsService.track(eventType, data) return analyticsService.track(eventType, data)
.then(() => { .then(() => {
@@ -77,7 +76,7 @@ describe('analyticsService', () => {
}); });
it('logs Android platform', () => { it('logs Android platform', () => {
data.headers = {'x-client': 'habitica-android'}; data.headers = { 'x-client': 'habitica-android' };
return analyticsService.track(eventType, data) return analyticsService.track(eventType, data)
.then(() => { .then(() => {
@@ -88,7 +87,7 @@ describe('analyticsService', () => {
}); });
it('logs 3rd Party platform', () => { it('logs 3rd Party platform', () => {
data.headers = {'x-client': 'some-third-party'}; data.headers = { 'x-client': 'some-third-party' };
return analyticsService.track(eventType, data) return analyticsService.track(eventType, data)
.then(() => { .then(() => {
@@ -169,18 +168,16 @@ describe('analyticsService', () => {
}); });
}); });
it('sends details about event', () => { it('sends details about event', () => analyticsService.track(eventType, data)
return analyticsService.track(eventType, data) .then(() => {
.then(() => { expect(Amplitude.prototype.track).to.be.calledWithMatch({
expect(Amplitude.prototype.track).to.be.calledWithMatch({ event_properties: {
event_properties: { category: 'behavior',
category: 'behavior', resting: true,
resting: true, cronCount: 5,
cronCount: 5, },
},
});
}); });
}); }));
it('sends english item name for gear if itemKey is provided', () => { it('sends english item name for gear if itemKey is provided', () => {
data.itemKey = 'headAccessory_special_foxEars'; data.itemKey = 'headAccessory_special_foxEars';
@@ -267,16 +264,18 @@ describe('analyticsService', () => {
}); });
it('sends user data if provided', () => { it('sends user data if provided', () => {
let stats = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30 }; const stats = {
let user = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30,
};
const user = {
stats, stats,
contributor: { level: 1 }, contributor: { level: 1 },
purchased: { plan: { planId: 'foo-plan' } }, purchased: { plan: { planId: 'foo-plan' } },
flags: {tour: {intro: -2}}, flags: { tour: { intro: -2 } },
habits: [{_id: 'habit'}], habits: [{ _id: 'habit' }],
dailys: [{_id: 'daily'}], dailys: [{ _id: 'daily' }],
todos: [{_id: 'todo'}], todos: [{ _id: 'todo' }],
rewards: [{_id: 'reward'}], rewards: [{ _id: 'reward' }],
balance: 12, balance: 12,
loginIncentives: 1, loginIncentives: 1,
}; };
@@ -312,27 +311,24 @@ describe('analyticsService', () => {
}); });
context('GA', () => { context('GA', () => {
it('calls out to GA', () => { it('calls out to GA', () => analyticsService.track(eventType, data)
return analyticsService.track(eventType, data) .then(() => {
.then(() => { expect(Visitor.prototype.event).to.be.calledOnce;
expect(Visitor.prototype.event).to.be.calledOnce; }));
});
});
it('sends details about event', () => { it('sends details about event', () => analyticsService.track(eventType, data)
return analyticsService.track(eventType, data) .then(() => {
.then(() => { expect(Visitor.prototype.event).to.be.calledWith({
expect(Visitor.prototype.event).to.be.calledWith({ ea: 'Cron',
ea: 'Cron', ec: 'behavior',
ec: 'behavior',
});
}); });
}); }));
}); });
}); });
describe('#trackPurchase', () => { describe('#trackPurchase', () => {
let data, itemSpy; let data; let
itemSpy;
beforeEach(() => { beforeEach(() => {
Visitor.prototype.event.yields(); Visitor.prototype.event.yields();
@@ -361,12 +357,10 @@ describe('analyticsService', () => {
}); });
context('Amplitude', () => { context('Amplitude', () => {
it('calls out to amplitude', () => { it('calls out to amplitude', () => analyticsService.trackPurchase(data)
return analyticsService.trackPurchase(data) .then(() => {
.then(() => { expect(Amplitude.prototype.track).to.be.calledOnce;
expect(Amplitude.prototype.track).to.be.calledOnce; }));
});
});
it('uses a dummy user id if none is provided', () => { it('uses a dummy user id if none is provided', () => {
delete data.uuid; delete data.uuid;
@@ -381,7 +375,7 @@ describe('analyticsService', () => {
context('platform', () => { context('platform', () => {
it('logs web platform', () => { it('logs web platform', () => {
data.headers = {'x-client': 'habitica-web'}; data.headers = { 'x-client': 'habitica-web' };
return analyticsService.trackPurchase(data) return analyticsService.trackPurchase(data)
.then(() => { .then(() => {
@@ -392,7 +386,7 @@ describe('analyticsService', () => {
}); });
it('logs iOS platform', () => { it('logs iOS platform', () => {
data.headers = {'x-client': 'habitica-ios'}; data.headers = { 'x-client': 'habitica-ios' };
return analyticsService.trackPurchase(data) return analyticsService.trackPurchase(data)
.then(() => { .then(() => {
@@ -403,7 +397,7 @@ describe('analyticsService', () => {
}); });
it('logs Android platform', () => { it('logs Android platform', () => {
data.headers = {'x-client': 'habitica-android'}; data.headers = { 'x-client': 'habitica-android' };
return analyticsService.trackPurchase(data) return analyticsService.trackPurchase(data)
.then(() => { .then(() => {
@@ -414,7 +408,7 @@ describe('analyticsService', () => {
}); });
it('logs 3rd Party platform', () => { it('logs 3rd Party platform', () => {
data.headers = {'x-client': 'some-third-party'}; data.headers = { 'x-client': 'some-third-party' };
return analyticsService.trackPurchase(data) return analyticsService.trackPurchase(data)
.then(() => { .then(() => {
@@ -495,33 +489,33 @@ describe('analyticsService', () => {
}); });
}); });
it('sends details about purchase', () => { it('sends details about purchase', () => analyticsService.trackPurchase(data)
return analyticsService.trackPurchase(data) .then(() => {
.then(() => { expect(Amplitude.prototype.track).to.be.calledWithMatch({
expect(Amplitude.prototype.track).to.be.calledWithMatch({ event_properties: {
event_properties: { gift: false,
gift: false, itemPurchased: 'Gems',
itemPurchased: 'Gems', paymentMethod: 'PayPal',
paymentMethod: 'PayPal', purchaseType: 'checkout',
purchaseType: 'checkout', quantity: 1,
quantity: 1, sku: 'paypal-checkout',
sku: 'paypal-checkout', },
},
});
}); });
}); }));
it('sends user data if provided', () => { it('sends user data if provided', () => {
let stats = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30 }; const stats = {
let user = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30,
};
const user = {
stats, stats,
contributor: { level: 1 }, contributor: { level: 1 },
purchased: { plan: { planId: 'foo-plan' } }, purchased: { plan: { planId: 'foo-plan' } },
flags: {tour: {intro: -2}}, flags: { tour: { intro: -2 } },
habits: [{_id: 'habit'}], habits: [{ _id: 'habit' }],
dailys: [{_id: 'daily'}], dailys: [{ _id: 'daily' }],
todos: [{_id: 'todo'}], todos: [{ _id: 'todo' }],
rewards: [{_id: 'reward'}], rewards: [{ _id: 'reward' }],
}; };
data.user = user; data.user = user;
@@ -552,27 +546,23 @@ describe('analyticsService', () => {
}); });
context('GA', () => { context('GA', () => {
it('calls out to GA', () => { it('calls out to GA', () => analyticsService.trackPurchase(data)
return analyticsService.trackPurchase(data) .then(() => {
.then(() => { expect(Visitor.prototype.event).to.be.calledOnce;
expect(Visitor.prototype.event).to.be.calledOnce; expect(Visitor.prototype.transaction).to.be.calledOnce;
expect(Visitor.prototype.transaction).to.be.calledOnce; }));
});
});
it('sends details about purchase', () => { it('sends details about purchase', () => analyticsService.trackPurchase(data)
return analyticsService.trackPurchase(data) .then(() => {
.then(() => { expect(Visitor.prototype.event).to.be.calledWith({
expect(Visitor.prototype.event).to.be.calledWith({ ea: 'checkout',
ea: 'checkout', ec: 'commerce',
ec: 'commerce', el: 'PayPal',
el: 'PayPal', ev: 8,
ev: 8,
});
expect(Visitor.prototype.transaction).to.be.calledWith('user-id', 8);
expect(itemSpy).to.be.calledWith(8, 1, 'paypal-checkout', 'Gems', 'checkout');
}); });
}); expect(Visitor.prototype.transaction).to.be.calledWith('user-id', 8);
expect(itemSpy).to.be.calledWith(8, 1, 'paypal-checkout', 'Gems', 'checkout');
}));
}); });
}); });

View File

@@ -11,7 +11,7 @@ describe('API Messages', () => {
}); });
it('clones the passed variables', () => { it('clones the passed variables', () => {
let vars = {a: 1}; const vars = { a: 1 };
sandbox.stub(_, 'clone').returns({}); sandbox.stub(_, 'clone').returns({});
apiError('guildsOnlyPaginate', vars); apiError('guildsOnlyPaginate', vars);
expect(_.clone).to.have.been.calledOnce; expect(_.clone).to.have.been.calledOnce;
@@ -19,8 +19,8 @@ describe('API Messages', () => {
}); });
it('pass the message through _.template', () => { it('pass the message through _.template', () => {
let vars = {a: 1}; const vars = { a: 1 };
let stub = sinon.stub().returns('string'); const stub = sinon.stub().returns('string');
sandbox.stub(_, 'template').returns(stub); sandbox.stub(_, 'template').returns(stub);
apiError('guildsOnlyPaginate', vars); apiError('guildsOnlyPaginate', vars);
expect(_.template).to.have.been.calledOnce; expect(_.template).to.have.been.calledOnce;

View File

@@ -1,5 +1,5 @@
import baseModel from '../../../../website/server/libs/baseModel';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import baseModel from '../../../../website/server/libs/baseModel';
describe('Base model plugin', () => { describe('Base model plugin', () => {
let schema; let schema;
@@ -25,7 +25,7 @@ describe('Base model plugin', () => {
}); });
it('can add timestamps fields', () => { it('can add timestamps fields', () => {
schema.plugin(baseModel, {timestamps: true}); schema.plugin(baseModel, { timestamps: true });
expect(schema.add).to.be.calledTwice; expect(schema.add).to.be.calledTwice;
}); });
@@ -36,7 +36,7 @@ describe('Base model plugin', () => {
}); });
expect(schema.statics.sanitize).to.exist; expect(schema.statics.sanitize).to.exist;
let sanitized = schema.statics.sanitize({ok: true, noUpdateForMe: true}); const sanitized = schema.statics.sanitize({ ok: true, noUpdateForMe: true });
expect(sanitized).to.have.property('ok'); expect(sanitized).to.have.property('ok');
expect(sanitized).not.to.have.property('noUpdateForMe'); expect(sanitized).not.to.have.property('noUpdateForMe');
@@ -49,7 +49,7 @@ describe('Base model plugin', () => {
}); });
expect(schema.statics.sanitize).to.exist; expect(schema.statics.sanitize).to.exist;
let sanitized = schema.statics.sanitize({ok: true, noUpdateForMe: true, usuallySettable: true}, ['usuallySettable']); const sanitized = schema.statics.sanitize({ ok: true, noUpdateForMe: true, usuallySettable: true }, ['usuallySettable']);
expect(sanitized).to.have.property('ok'); expect(sanitized).to.have.property('ok');
expect(sanitized).not.to.have.property('noUpdateForMe'); expect(sanitized).not.to.have.property('noUpdateForMe');
@@ -63,31 +63,31 @@ describe('Base model plugin', () => {
}); });
expect(schema.options.toJSON.transform).to.exist; expect(schema.options.toJSON.transform).to.exist;
let objToTransform = {ok: true, amPrivate: true}; const objToTransform = { ok: true, amPrivate: true };
let privatized = schema.options.toJSON.transform({}, objToTransform); const privatized = schema.options.toJSON.transform({}, objToTransform);
expect(privatized).to.have.property('ok'); expect(privatized).to.have.property('ok');
expect(privatized).not.to.have.property('amPrivate'); expect(privatized).not.to.have.property('amPrivate');
}); });
it('accepts a further transform function for toJSON', () => { it('accepts a further transform function for toJSON', () => {
let options = { const options = {
private: ['amPrivate'], private: ['amPrivate'],
toJSONTransform: sandbox.stub().returns(true), toJSONTransform: sandbox.stub().returns(true),
}; };
schema.plugin(baseModel, options); schema.plugin(baseModel, options);
let objToTransform = {ok: true, amPrivate: true}; const objToTransform = { ok: true, amPrivate: true };
let doc = {doc: true}; const doc = { doc: true };
let privatized = schema.options.toJSON.transform(doc, objToTransform); const privatized = schema.options.toJSON.transform(doc, objToTransform);
expect(privatized).to.equals(true); expect(privatized).to.equals(true);
expect(options.toJSONTransform).to.be.calledWith(objToTransform, doc); expect(options.toJSONTransform).to.be.calledWith(objToTransform, doc);
}); });
it('accepts a transform function for sanitize', () => { it('accepts a transform function for sanitize', () => {
let options = { const options = {
private: ['amPrivate'], private: ['amPrivate'],
sanitizeTransform: sandbox.stub().returns(true), sanitizeTransform: sandbox.stub().returns(true),
}; };
@@ -95,8 +95,8 @@ describe('Base model plugin', () => {
schema.plugin(baseModel, options); schema.plugin(baseModel, options);
expect(schema.options.toJSON.transform).to.exist; expect(schema.options.toJSON.transform).to.exist;
let objToSanitize = {ok: true, noUpdateForMe: true}; const objToSanitize = { ok: true, noUpdateForMe: true };
let sanitized = schema.statics.sanitize(objToSanitize); const sanitized = schema.statics.sanitize(objToSanitize);
expect(sanitized).to.equals(true); expect(sanitized).to.equals(true);
expect(options.sanitizeTransform).to.be.calledWith(objToSanitize); expect(options.sanitizeTransform).to.be.calledWith(objToSanitize);

View File

@@ -6,7 +6,7 @@ import {
describe('Collection Manipulators', () => { describe('Collection Manipulators', () => {
describe('removeFromArray', () => { describe('removeFromArray', () => {
it('removes element from array', () => { it('removes element from array', () => {
let array = ['a', 'b', 'c', 'd']; const array = ['a', 'b', 'c', 'd'];
removeFromArray(array, 'c'); removeFromArray(array, 'c');
@@ -14,7 +14,7 @@ describe('Collection Manipulators', () => {
}); });
it('removes object from array', () => { it('removes object from array', () => {
let array = [ const array = [
{ id: 'a', foo: 'bar' }, { id: 'a', foo: 'bar' },
{ id: 'b', foo: 'bar' }, { id: 'b', foo: 'bar' },
{ id: 'c', foo: 'bar' }, { id: 'c', foo: 'bar' },
@@ -28,7 +28,7 @@ describe('Collection Manipulators', () => {
}); });
it('does not change array if value is not found', () => { it('does not change array if value is not found', () => {
let array = ['a', 'b', 'c', 'd']; const array = ['a', 'b', 'c', 'd'];
removeFromArray(array, 'z'); removeFromArray(array, 'z');
@@ -40,15 +40,15 @@ describe('Collection Manipulators', () => {
}); });
it('returns the removed element', () => { it('returns the removed element', () => {
let array = ['a', 'b', 'c']; const array = ['a', 'b', 'c'];
let result = removeFromArray(array, 'b'); const result = removeFromArray(array, 'b');
expect(result).to.eql('b'); expect(result).to.eql('b');
}); });
it('returns the removed object element', () => { it('returns the removed object element', () => {
let array = [ const array = [
{ id: 'a', foo: 'bar' }, { id: 'a', foo: 'bar' },
{ id: 'b', foo: 'bar' }, { id: 'b', foo: 'bar' },
{ id: 'c', foo: 'bar' }, { id: 'c', foo: 'bar' },
@@ -56,31 +56,31 @@ describe('Collection Manipulators', () => {
{ id: 'e', foo: 'bar' }, { id: 'e', foo: 'bar' },
]; ];
let result = removeFromArray(array, { id: 'c' }); const result = removeFromArray(array, { id: 'c' });
expect(result).to.eql({ id: 'c', foo: 'bar' }); expect(result).to.eql({ id: 'c', foo: 'bar' });
}); });
it('returns false if item is not found', () => { it('returns false if item is not found', () => {
let array = ['a', 'b', 'c']; const array = ['a', 'b', 'c'];
let result = removeFromArray(array, 'z'); const result = removeFromArray(array, 'z');
expect(result).to.eql(false); expect(result).to.eql(false);
}); });
it('persists removal of element when mongoose document is saved', async () => { it('persists removal of element when mongoose document is saved', async () => {
let schema = new mongoose.Schema({ const schema = new mongoose.Schema({
array: Array, array: Array,
}); });
let Model = mongoose.model('ModelToTestRemoveFromArray', schema); const Model = mongoose.model('ModelToTestRemoveFromArray', schema);
let model = await new Model({ const model = await new Model({
array: ['a', 'b', 'c'], array: ['a', 'b', 'c'],
}).save(); // Initial creation }).save(); // Initial creation
removeFromArray(model.array, 'b'); removeFromArray(model.array, 'b');
let savedModel = await model.save(); const savedModel = await model.save();
expect(savedModel.array).to.not.include('b'); expect(savedModel.array).to.not.include('b');
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -31,20 +31,20 @@ function getUser () {
} }
describe('emails', () => { describe('emails', () => {
let pathToEmailLib = '../../../../website/server/libs/email'; const pathToEmailLib = '../../../../website/server/libs/email';
describe('getUserInfo', () => { describe('getUserInfo', () => {
it('returns an empty object if no field request', () => { it('returns an empty object if no field request', () => {
let attachEmail = requireAgain(pathToEmailLib); const attachEmail = requireAgain(pathToEmailLib);
let getUserInfo = attachEmail.getUserInfo; const { getUserInfo } = attachEmail;
expect(getUserInfo({}, [])).to.be.empty; expect(getUserInfo({}, [])).to.be.empty;
}); });
it('returns correct user data', () => { it('returns correct user data', () => {
let attachEmail = requireAgain(pathToEmailLib); const attachEmail = requireAgain(pathToEmailLib);
let getUserInfo = attachEmail.getUserInfo; const { getUserInfo } = attachEmail;
let user = getUser(); const user = getUser();
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
expect(data).to.have.property('name', user.auth.local.username); expect(data).to.have.property('name', user.auth.local.username);
expect(data).to.have.property('email', user.auth.local.email); expect(data).to.have.property('email', user.auth.local.email);
@@ -53,13 +53,13 @@ describe('emails', () => {
}); });
it('returns correct user data [facebook users]', () => { it('returns correct user data [facebook users]', () => {
let attachEmail = requireAgain(pathToEmailLib); const attachEmail = requireAgain(pathToEmailLib);
let getUserInfo = attachEmail.getUserInfo; const { getUserInfo } = attachEmail;
let user = getUser(); const user = getUser();
delete user.profile.name; delete user.profile.name;
delete user.auth.local.email; delete user.auth.local.email;
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
expect(data).to.have.property('name', user.auth.local.username); expect(data).to.have.property('name', user.auth.local.username);
expect(data).to.have.property('email', user.auth.facebook.emails[0].value); expect(data).to.have.property('email', user.auth.facebook.emails[0].value);
@@ -68,13 +68,13 @@ describe('emails', () => {
}); });
it('has fallbacks for missing data', () => { it('has fallbacks for missing data', () => {
let attachEmail = requireAgain(pathToEmailLib); const attachEmail = requireAgain(pathToEmailLib);
let getUserInfo = attachEmail.getUserInfo; const { getUserInfo } = attachEmail;
let user = getUser(); const user = getUser();
delete user.auth.local.email; delete user.auth.local.email;
delete user.auth.facebook; delete user.auth.facebook;
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
expect(data).to.have.property('name', user.auth.local.username); expect(data).to.have.property('name', user.auth.local.username);
expect(data).not.to.have.property('email'); expect(data).not.to.have.property('email');
@@ -85,18 +85,18 @@ describe('emails', () => {
describe('getGroupUrl', () => { describe('getGroupUrl', () => {
it('returns correct url if group is the tavern', () => { it('returns correct url if group is the tavern', () => {
let getGroupUrl = require(pathToEmailLib).getGroupUrl; const { getGroupUrl } = require(pathToEmailLib); // eslint-disable-line import/no-dynamic-require, max-len
expect(getGroupUrl({_id: TAVERN_ID, type: 'guild'})).to.eql('/groups/tavern'); expect(getGroupUrl({ _id: TAVERN_ID, type: 'guild' })).to.eql('/groups/tavern');
}); });
it('returns correct url if group is a guild', () => { it('returns correct url if group is a guild', () => {
let getGroupUrl = require(pathToEmailLib).getGroupUrl; const { getGroupUrl } = require(pathToEmailLib); // eslint-disable-line import/no-dynamic-require, max-len
expect(getGroupUrl({_id: 'random _id', type: 'guild'})).to.eql('/groups/guild/random _id'); expect(getGroupUrl({ _id: 'random _id', type: 'guild' })).to.eql('/groups/guild/random _id');
}); });
it('returns correct url if group is a party', () => { it('returns correct url if group is a party', () => {
let getGroupUrl = require(pathToEmailLib).getGroupUrl; const { getGroupUrl } = require(pathToEmailLib); // eslint-disable-line import/no-dynamic-require, max-len
expect(getGroupUrl({_id: 'random _id', type: 'party'})).to.eql('party'); expect(getGroupUrl({ _id: 'random _id', type: 'party' })).to.eql('party');
}); });
}); });
@@ -111,10 +111,10 @@ describe('emails', () => {
it('can send a txn email to one recipient', () => { it('can send a txn email to one recipient', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
let attachEmail = requireAgain(pathToEmailLib); const attachEmail = requireAgain(pathToEmailLib);
let sendTxnEmail = attachEmail.sendTxn; const sendTxnEmail = attachEmail.sendTxn;
let emailType = 'an email type'; const emailType = 'an email type';
let mailingInfo = { const mailingInfo = {
name: 'my name', name: 'my name',
email: 'my@email', email: 'my@email',
}; };
@@ -125,9 +125,7 @@ describe('emails', () => {
body: { body: {
data: { data: {
emailType: sinon.match.same(emailType), emailType: sinon.match.same(emailType),
to: sinon.match((value) => { to: sinon.match(value => Array.isArray(value) && value[0].name === mailingInfo.name, 'matches mailing info array'),
return Array.isArray(value) && value[0].name === mailingInfo.name;
}, 'matches mailing info array'),
}, },
}, },
})); }));
@@ -135,10 +133,10 @@ describe('emails', () => {
it('does not send email if address is missing', () => { it('does not send email if address is missing', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
let attachEmail = requireAgain(pathToEmailLib); const attachEmail = requireAgain(pathToEmailLib);
let sendTxnEmail = attachEmail.sendTxn; const sendTxnEmail = attachEmail.sendTxn;
let emailType = 'an email type'; const emailType = 'an email type';
let mailingInfo = { const mailingInfo = {
name: 'my name', name: 'my name',
// email: 'my@email', // email: 'my@email',
}; };
@@ -149,10 +147,10 @@ describe('emails', () => {
it('uses getUserInfo in case of user data', () => { it('uses getUserInfo in case of user data', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
let attachEmail = requireAgain(pathToEmailLib); const attachEmail = requireAgain(pathToEmailLib);
let sendTxnEmail = attachEmail.sendTxn; const sendTxnEmail = attachEmail.sendTxn;
let emailType = 'an email type'; const emailType = 'an email type';
let mailingInfo = getUser(); const mailingInfo = getUser();
sendTxnEmail(mailingInfo, emailType); sendTxnEmail(mailingInfo, emailType);
expect(got.post).to.be.calledWith('undefined/job', sinon.match({ expect(got.post).to.be.calledWith('undefined/job', sinon.match({
@@ -168,28 +166,24 @@ describe('emails', () => {
it('sends email with some default variables', () => { it('sends email with some default variables', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
let attachEmail = requireAgain(pathToEmailLib); const attachEmail = requireAgain(pathToEmailLib);
let sendTxnEmail = attachEmail.sendTxn; const sendTxnEmail = attachEmail.sendTxn;
let emailType = 'an email type'; const emailType = 'an email type';
let mailingInfo = { const mailingInfo = {
name: 'my name', name: 'my name',
email: 'my@email', email: 'my@email',
}; };
let variables = [1, 2, 3]; const variables = [1, 2, 3];
sendTxnEmail(mailingInfo, emailType, variables); sendTxnEmail(mailingInfo, emailType, variables);
expect(got.post).to.be.calledWith('undefined/job', sinon.match({ expect(got.post).to.be.calledWith('undefined/job', sinon.match({
json: true, json: true,
body: { body: {
data: { data: {
variables: sinon.match((value) => { variables: sinon.match(value => value[0].name === 'BASE_URL', 'matches variables'),
return value[0].name === 'BASE_URL'; personalVariables: sinon.match(value => value[0].rcpt === mailingInfo.email
}, 'matches variables'), && value[0].vars[0].name === 'RECIPIENT_NAME'
personalVariables: sinon.match((value) => { && value[0].vars[1].name === 'RECIPIENT_UNSUB_URL', 'matches personal variables'),
return value[0].rcpt === mailingInfo.email &&
value[0].vars[0].name === 'RECIPIENT_NAME' &&
value[0].vars[1].name === 'RECIPIENT_UNSUB_URL';
}, 'matches personal variables'),
}, },
}, },
})); }));

View File

@@ -5,9 +5,9 @@ import {
describe('encryption', () => { describe('encryption', () => {
it('can encrypt and decrypt', () => { it('can encrypt and decrypt', () => {
let data = 'some secret text'; const data = 'some secret text';
let encrypted = encrypt(data); const encrypted = encrypt(data);
let decrypted = decrypt(encrypted); const decrypted = decrypt(encrypted);
expect(encrypted).not.to.equal(data); expect(encrypted).not.to.equal(data);
expect(data).to.equal(decrypted); expect(data).to.equal(decrypted);

View File

@@ -12,7 +12,7 @@ import i18n from '../../../../website/common/script/i18n';
describe('Custom Errors', () => { describe('Custom Errors', () => {
describe('CustomError', () => { describe('CustomError', () => {
it('is an instance of Error', () => { it('is an instance of Error', () => {
let customError = new CustomError(); const customError = new CustomError();
expect(customError).to.be.an.instanceOf(Error); expect(customError).to.be.an.instanceOf(Error);
}); });
@@ -20,25 +20,25 @@ describe('Custom Errors', () => {
describe('NotAuthorized', () => { describe('NotAuthorized', () => {
it('is an instance of CustomError', () => { it('is an instance of CustomError', () => {
let notAuthorizedError = new NotAuthorized(); const notAuthorizedError = new NotAuthorized();
expect(notAuthorizedError).to.be.an.instanceOf(CustomError); expect(notAuthorizedError).to.be.an.instanceOf(CustomError);
}); });
it('it returns an http code of 401', () => { it('it returns an http code of 401', () => {
let notAuthorizedError = new NotAuthorized(); const notAuthorizedError = new NotAuthorized();
expect(notAuthorizedError.httpCode).to.eql(401); expect(notAuthorizedError.httpCode).to.eql(401);
}); });
it('returns a default message', () => { it('returns a default message', () => {
let notAuthorizedError = new NotAuthorized(); const notAuthorizedError = new NotAuthorized();
expect(notAuthorizedError.message).to.eql('Not authorized.'); expect(notAuthorizedError.message).to.eql('Not authorized.');
}); });
it('allows a custom message', () => { it('allows a custom message', () => {
let notAuthorizedError = new NotAuthorized('Custom Error Message'); const notAuthorizedError = new NotAuthorized('Custom Error Message');
expect(notAuthorizedError.message).to.eql('Custom Error Message'); expect(notAuthorizedError.message).to.eql('Custom Error Message');
}); });
@@ -46,25 +46,25 @@ describe('Custom Errors', () => {
describe('NotFound', () => { describe('NotFound', () => {
it('is an instance of CustomError', () => { it('is an instance of CustomError', () => {
let notAuthorizedError = new NotFound(); const notAuthorizedError = new NotFound();
expect(notAuthorizedError).to.be.an.instanceOf(CustomError); expect(notAuthorizedError).to.be.an.instanceOf(CustomError);
}); });
it('it returns an http code of 404', () => { it('it returns an http code of 404', () => {
let notAuthorizedError = new NotFound(); const notAuthorizedError = new NotFound();
expect(notAuthorizedError.httpCode).to.eql(404); expect(notAuthorizedError.httpCode).to.eql(404);
}); });
it('returns a default message', () => { it('returns a default message', () => {
let notAuthorizedError = new NotFound(); const notAuthorizedError = new NotFound();
expect(notAuthorizedError.message).to.eql('Not found.'); expect(notAuthorizedError.message).to.eql('Not found.');
}); });
it('allows a custom message', () => { it('allows a custom message', () => {
let notAuthorizedError = new NotFound('Custom Error Message'); const notAuthorizedError = new NotFound('Custom Error Message');
expect(notAuthorizedError.message).to.eql('Custom Error Message'); expect(notAuthorizedError.message).to.eql('Custom Error Message');
}); });
@@ -89,25 +89,25 @@ describe('Custom Errors', () => {
describe('BadRequest', () => { describe('BadRequest', () => {
it('is an instance of CustomError', () => { it('is an instance of CustomError', () => {
let badRequestError = new BadRequest(); const badRequestError = new BadRequest();
expect(badRequestError).to.be.an.instanceOf(CustomError); expect(badRequestError).to.be.an.instanceOf(CustomError);
}); });
it('it returns an http code of 401', () => { it('it returns an http code of 401', () => {
let badRequestError = new BadRequest(); const badRequestError = new BadRequest();
expect(badRequestError.httpCode).to.eql(400); expect(badRequestError.httpCode).to.eql(400);
}); });
it('returns a default message', () => { it('returns a default message', () => {
let badRequestError = new BadRequest(); const badRequestError = new BadRequest();
expect(badRequestError.message).to.eql('Bad request.'); expect(badRequestError.message).to.eql('Bad request.');
}); });
it('allows a custom message', () => { it('allows a custom message', () => {
let badRequestError = new BadRequest('Custom Error Message'); const badRequestError = new BadRequest('Custom Error Message');
expect(badRequestError.message).to.eql('Custom Error Message'); expect(badRequestError.message).to.eql('Custom Error Message');
}); });
@@ -115,25 +115,25 @@ describe('Custom Errors', () => {
describe('InternalServerError', () => { describe('InternalServerError', () => {
it('is an instance of CustomError', () => { it('is an instance of CustomError', () => {
let internalServerError = new InternalServerError(); const internalServerError = new InternalServerError();
expect(internalServerError).to.be.an.instanceOf(CustomError); expect(internalServerError).to.be.an.instanceOf(CustomError);
}); });
it('it returns an http code of 500', () => { it('it returns an http code of 500', () => {
let internalServerError = new InternalServerError(); const internalServerError = new InternalServerError();
expect(internalServerError.httpCode).to.eql(500); expect(internalServerError.httpCode).to.eql(500);
}); });
it('returns a default message', () => { it('returns a default message', () => {
let internalServerError = new InternalServerError(); const internalServerError = new InternalServerError();
expect(internalServerError.message).to.eql('An unexpected error occurred.'); expect(internalServerError.message).to.eql('An unexpected error occurred.');
}); });
it('allows a custom message', () => { it('allows a custom message', () => {
let internalServerError = new InternalServerError('Custom Error Message'); const internalServerError = new InternalServerError('Custom Error Message');
expect(internalServerError.message).to.eql('Custom Error Message'); expect(internalServerError.message).to.eql('Custom Error Message');
}); });

View File

@@ -0,0 +1,60 @@
import mongoose from 'mongoose';
import {
highlightMentions,
} from '../../../../website/server/libs/highlightMentions';
describe('highlightMentions', () => {
beforeEach(() => {
const mockFind = {
select () {
return this;
},
lean () {
return this;
},
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' },
]);
},
};
sinon.stub(mongoose.Model, 'find').returns(mockFind);
});
afterEach(() => {
sinon.restore();
});
it('doesn\'t change text without mentions', async () => {
const text = 'some chat text';
const result = await highlightMentions(text);
expect(result[0]).to.equal(text);
});
it('highlights existing users', async () => {
const text = '@user: message';
const result = await highlightMentions(text);
expect(result[0]).to.equal('[@user](/profile/111): message');
});
it('highlights special characters', async () => {
const text = '@user-dash: message @user_underscore';
const result = await highlightMentions(text);
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
});
it('doesn\'t highlight nonexisting users', async () => {
const text = '@nouser message';
const result = await highlightMentions(text);
expect(result[0]).to.equal('@nouser message');
});
it('highlights multiple existing users', async () => {
const text = '@user message (@user2) @user3 @user';
const result = await highlightMentions(text);
expect(result[0]).to.equal('[@user](/profile/111) message ([@user2](/profile/222)) [@user3](/profile/333) [@user](/profile/111)');
});
it('doesn\'t highlight more than 5 users', async () => {
const text = '@user @user2 @user3 @user4 @user5 @user6';
const result = await highlightMentions(text);
expect(result[0]).to.equal(text);
});
});

View File

@@ -5,11 +5,11 @@ import {
} from '../../../../website/server/libs/i18n'; } from '../../../../website/server/libs/i18n';
describe('i18n', () => { describe('i18n', () => {
let listOfLocales = approvedLanguages.sort(); const listOfLocales = approvedLanguages.sort();
describe('translations', () => { describe('translations', () => {
it('includes a translation object for each locale', () => { it('includes a translation object for each locale', () => {
listOfLocales.forEach((locale) => { listOfLocales.forEach(locale => {
expect(translations[locale]).to.be.an('object'); expect(translations[locale]).to.be.an('object');
}); });
}); });

View File

@@ -2,7 +2,7 @@ import winston from 'winston';
import logger from '../../../../website/server/libs/logger'; import logger from '../../../../website/server/libs/logger';
import { import {
NotFound, NotFound,
} from '../../../../website/server/libs//errors'; } from '../../../../website/server/libs/errors';
describe('logger', () => { describe('logger', () => {
let logSpy; let logSpy;
@@ -34,7 +34,7 @@ describe('logger', () => {
context('error object', () => { context('error object', () => {
it('logs the stack and the err data', () => { it('logs the stack and the err data', () => {
let errInstance = new Error('An error.'); const errInstance = new Error('An error.');
logger.error(errInstance, { logger.error(errInstance, {
data: 1, data: 1,
}, 2, 3); }, 2, 3);
@@ -45,13 +45,13 @@ describe('logger', () => {
errInstance.stack, errInstance.stack,
{ data: 1, fullError: errInstance }, { data: 1, fullError: errInstance },
2, 2,
3 3,
); );
}); });
it('logs the stack and the err data with it\'s own fullError property', () => { it('logs the stack and the err data with it\'s own fullError property', () => {
let errInstance = new Error('An error.'); const errInstance = new Error('An error.');
let anotherError = new Error('another error'); const anotherError = new Error('another error');
logger.error(errInstance, { logger.error(errInstance, {
data: 1, data: 1,
@@ -64,12 +64,12 @@ describe('logger', () => {
errInstance.stack, errInstance.stack,
{ data: 1, fullError: anotherError }, { data: 1, fullError: anotherError },
2, 2,
3 3,
); );
}); });
it('logs the error when errorData is null', () => { it('logs the error when errorData is null', () => {
let errInstance = new Error('An error.'); const errInstance = new Error('An error.');
logger.error(errInstance, null, 2, 3); logger.error(errInstance, null, 2, 3);
@@ -79,12 +79,12 @@ describe('logger', () => {
errInstance.stack, errInstance.stack,
null, null,
2, 2,
3 3,
); );
}); });
it('logs the error when errorData is not an object', () => { it('logs the error when errorData is not an object', () => {
let errInstance = new Error('An error.'); const errInstance = new Error('An error.');
logger.error(errInstance, true, 2, 3); logger.error(errInstance, true, 2, 3);
@@ -94,12 +94,12 @@ describe('logger', () => {
errInstance.stack, errInstance.stack,
true, true,
2, 2,
3 3,
); );
}); });
it('logs the error when errorData does not include isHandledError property', () => { it('logs the error when errorData does not include isHandledError property', () => {
let errInstance = new Error('An error.'); const errInstance = new Error('An error.');
logger.error(errInstance, { httpCode: 400 }, 2, 3); logger.error(errInstance, { httpCode: 400 }, 2, 3);
@@ -109,12 +109,12 @@ describe('logger', () => {
errInstance.stack, errInstance.stack,
{ httpCode: 400, fullError: errInstance }, { httpCode: 400, fullError: errInstance },
2, 2,
3 3,
); );
}); });
it('logs the error when errorData includes isHandledError property but is a 500 error', () => { it('logs the error when errorData includes isHandledError property but is a 500 error', () => {
let errInstance = new Error('An error.'); const errInstance = new Error('An error.');
logger.error(errInstance, { logger.error(errInstance, {
isHandledError: true, isHandledError: true,
@@ -127,12 +127,12 @@ describe('logger', () => {
errInstance.stack, errInstance.stack,
{ httpCode: 502, isHandledError: true, fullError: errInstance }, { httpCode: 502, isHandledError: true, fullError: errInstance },
2, 2,
3 3,
); );
}); });
it('logs a warning when errorData includes isHandledError property and is not a 500 error', () => { it('logs a warning when errorData includes isHandledError property and is not a 500 error', () => {
let errInstance = new Error('An error.'); const errInstance = new Error('An error.');
logger.error(errInstance, { logger.error(errInstance, {
isHandledError: true, isHandledError: true,
@@ -145,12 +145,12 @@ describe('logger', () => {
errInstance.stack, errInstance.stack,
{ httpCode: 403, isHandledError: true, fullError: errInstance }, { httpCode: 403, isHandledError: true, fullError: errInstance },
2, 2,
3 3,
); );
}); });
it('logs additional data from a CustomError', () => { it('logs additional data from a CustomError', () => {
let errInstance = new NotFound('An error.'); const errInstance = new NotFound('An error.');
errInstance.customField = 'Some interesting data'; errInstance.customField = 'Some interesting data';
@@ -166,7 +166,7 @@ describe('logger', () => {
}, },
}, },
2, 2,
3 3,
); );
}); });
}); });

View File

@@ -1,9 +1,9 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import moment from 'moment';
import { import {
encrypt, encrypt,
} from '../../../../website/server/libs/encryption'; } from '../../../../website/server/libs/encryption';
import moment from 'moment';
import { import {
generateUser, generateUser,
} from '../../../helpers/api-integration/v3'; } from '../../../helpers/api-integration/v3';
@@ -20,11 +20,11 @@ import {
describe('Password Utilities', () => { describe('Password Utilities', () => {
describe('compare', () => { describe('compare', () => {
it('can compare a correct password hashed with SHA1', async () => { it('can compare a correct password hashed with SHA1', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let salt = sha1MakeSalt(); const salt = sha1MakeSalt();
let hashedPassword = sha1EncryptPassword(textPassword, salt); const hashedPassword = sha1EncryptPassword(textPassword, salt);
let user = { const user = {
auth: { auth: {
local: { local: {
hashed_password: hashedPassword, hashed_password: hashedPassword,
@@ -34,16 +34,16 @@ describe('Password Utilities', () => {
}, },
}; };
let isValidPassword = await compare(user, textPassword); const isValidPassword = await compare(user, textPassword);
expect(isValidPassword).to.eql(true); expect(isValidPassword).to.eql(true);
}); });
it('can compare an invalid password hashed with SHA1', async () => { it('can compare an invalid password hashed with SHA1', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let salt = sha1MakeSalt(); const salt = sha1MakeSalt();
let hashedPassword = sha1EncryptPassword(textPassword, salt); const hashedPassword = sha1EncryptPassword(textPassword, salt);
let user = { const user = {
auth: { auth: {
local: { local: {
hashed_password: hashedPassword, hashed_password: hashedPassword,
@@ -53,15 +53,15 @@ describe('Password Utilities', () => {
}, },
}; };
let isValidPassword = await compare(user, 'wrongPassword'); const isValidPassword = await compare(user, 'wrongPassword');
expect(isValidPassword).to.eql(false); expect(isValidPassword).to.eql(false);
}); });
it('can compare a correct password hashed with bcrypt', async () => { it('can compare a correct password hashed with bcrypt', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let hashedPassword = await bcryptHash(textPassword); const hashedPassword = await bcryptHash(textPassword);
let user = { const user = {
auth: { auth: {
local: { local: {
hashed_password: hashedPassword, hashed_password: hashedPassword,
@@ -70,15 +70,15 @@ describe('Password Utilities', () => {
}, },
}; };
let isValidPassword = await compare(user, textPassword); const isValidPassword = await compare(user, textPassword);
expect(isValidPassword).to.eql(true); expect(isValidPassword).to.eql(true);
}); });
it('can compare an invalid password hashed with bcrypt', async () => { it('can compare an invalid password hashed with bcrypt', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let hashedPassword = await bcryptHash(textPassword); const hashedPassword = await bcryptHash(textPassword);
let user = { const user = {
auth: { auth: {
local: { local: {
hashed_password: hashedPassword, hashed_password: hashedPassword,
@@ -87,7 +87,7 @@ describe('Password Utilities', () => {
}, },
}; };
let isValidPassword = await compare(user, 'wrongPassword'); const isValidPassword = await compare(user, 'wrongPassword');
expect(isValidPassword).to.eql(false); expect(isValidPassword).to.eql(false);
}); });
@@ -101,18 +101,18 @@ describe('Password Utilities', () => {
it('throws an error if passwordToCheck is missing', async () => { it('throws an error if passwordToCheck is missing', async () => {
try { try {
await compare({a: true}); await compare({ a: true });
} catch (e) { } catch (e) {
expect(e.toString()).to.equal('Error: user and passwordToCheck are required parameters.'); expect(e.toString()).to.equal('Error: user and passwordToCheck are required parameters.');
} }
}); });
it('defaults to SHA1 encryption if salt is provided', async () => { it('defaults to SHA1 encryption if salt is provided', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let salt = sha1MakeSalt(); const salt = sha1MakeSalt();
let hashedPassword = sha1EncryptPassword(textPassword, salt); const hashedPassword = sha1EncryptPassword(textPassword, salt);
let user = { const user = {
auth: { auth: {
local: { local: {
hashed_password: hashedPassword, hashed_password: hashedPassword,
@@ -122,7 +122,7 @@ describe('Password Utilities', () => {
}, },
}; };
let isValidPassword = await compare(user, textPassword); const isValidPassword = await compare(user, textPassword);
expect(isValidPassword).to.eql(true); expect(isValidPassword).to.eql(true);
}); });
@@ -141,29 +141,29 @@ describe('Password Utilities', () => {
}); });
it('returns true if comparing the same password', async () => { it('returns true if comparing the same password', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let hashedPassword = await bcryptHash(textPassword); const hashedPassword = await bcryptHash(textPassword);
let isValidPassword = await bcryptCompare(textPassword, hashedPassword); const isValidPassword = await bcryptCompare(textPassword, hashedPassword);
expect(isValidPassword).to.eql(true); expect(isValidPassword).to.eql(true);
}); });
it('returns true if comparing a different password', async () => { it('returns true if comparing a different password', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let hashedPassword = await bcryptHash(textPassword); const hashedPassword = await bcryptHash(textPassword);
let isValidPassword = await bcryptCompare('anotherPassword', hashedPassword); const isValidPassword = await bcryptCompare('anotherPassword', hashedPassword);
expect(isValidPassword).to.eql(false); expect(isValidPassword).to.eql(false);
}); });
}); });
describe('convertToBcrypt', () => { describe('convertToBcrypt', () => {
it('converts an user password hashed with sha1 to bcrypt', async () => { it('converts an user password hashed with sha1 to bcrypt', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let salt = sha1MakeSalt(); const salt = sha1MakeSalt();
let hashedPassword = sha1EncryptPassword(textPassword, salt); const hashedPassword = sha1EncryptPassword(textPassword, salt);
let user = { const user = {
auth: { auth: {
local: { local: {
hashed_password: hashedPassword, hashed_password: hashedPassword,
@@ -178,7 +178,7 @@ describe('Password Utilities', () => {
expect(user.auth.local.passwordHashMethod).to.equal('bcrypt'); expect(user.auth.local.passwordHashMethod).to.equal('bcrypt');
expect(user.auth.local.hashed_password).to.be.a.string; expect(user.auth.local.hashed_password).to.be.a.string;
let isValidPassword = await compare(user, textPassword); const isValidPassword = await compare(user, textPassword);
expect(isValidPassword).to.eql(true); expect(isValidPassword).to.eql(true);
}); });
@@ -192,7 +192,7 @@ describe('Password Utilities', () => {
it('throws an error if plainTextPassword is missing', async () => { it('throws an error if plainTextPassword is missing', async () => {
try { try {
await convertToBcrypt({a: true}); await convertToBcrypt({ a: true });
} catch (e) { } catch (e) {
expect(e.toString()).to.equal('Error: user and plainTextPassword are required parameters.'); expect(e.toString()).to.equal('Error: user and plainTextPassword are required parameters.');
} }
@@ -201,18 +201,18 @@ describe('Password Utilities', () => {
describe('validatePasswordResetCodeAndFindUser', () => { describe('validatePasswordResetCodeAndFindUser', () => {
it('returns false if the code is missing', async () => { it('returns false if the code is missing', async () => {
let res = await validatePasswordResetCodeAndFindUser(); const res = await validatePasswordResetCodeAndFindUser();
expect(res).to.equal(false); expect(res).to.equal(false);
}); });
it('returns false if the code is invalid json', async () => { it('returns false if the code is invalid json', async () => {
let res = await validatePasswordResetCodeAndFindUser('invalid json'); const res = await validatePasswordResetCodeAndFindUser('invalid json');
expect(res).to.equal(false); expect(res).to.equal(false);
}); });
it('returns false if the code cannot be decrypted', async () => { it('returns false if the code cannot be decrypted', async () => {
let user = await generateUser(); const user = await generateUser();
let res = await validatePasswordResetCodeAndFindUser(JSON.stringify({ // not encrypted const res = await validatePasswordResetCodeAndFindUser(JSON.stringify({ // not encrypted
userId: user._id, userId: user._id,
expiresAt: new Date(), expiresAt: new Date(),
})); }));
@@ -220,71 +220,71 @@ describe('Password Utilities', () => {
}); });
it('returns false if the code is expired', async () => { it('returns false if the code is expired', async () => {
let user = await generateUser(); const user = await generateUser();
let code = encrypt(JSON.stringify({ const code = encrypt(JSON.stringify({
userId: user._id, userId: user._id,
expiresAt: moment().subtract({minutes: 1}), expiresAt: moment().subtract({ minutes: 1 }),
})); }));
await user.update({ await user.update({
'auth.local.passwordResetCode': code, 'auth.local.passwordResetCode': code,
}); });
let res = await validatePasswordResetCodeAndFindUser(code); const res = await validatePasswordResetCodeAndFindUser(code);
expect(res).to.equal(false); expect(res).to.equal(false);
}); });
it('returns false if the user does not exist', async () => { it('returns false if the user does not exist', async () => {
let res = await validatePasswordResetCodeAndFindUser(encrypt(JSON.stringify({ const res = await validatePasswordResetCodeAndFindUser(encrypt(JSON.stringify({
userId: Date.now().toString(), userId: Date.now().toString(),
expiresAt: moment().add({days: 1}), expiresAt: moment().add({ days: 1 }),
}))); })));
expect(res).to.equal(false); expect(res).to.equal(false);
}); });
it('returns false if the user has no local auth', async () => { it('returns false if the user has no local auth', async () => {
let user = await generateUser({ const user = await generateUser({
auth: { auth: {
facebook: {}, facebook: {},
}, },
}); });
let res = await validatePasswordResetCodeAndFindUser(encrypt(JSON.stringify({ const res = await validatePasswordResetCodeAndFindUser(encrypt(JSON.stringify({
userId: user._id, userId: user._id,
expiresAt: moment().add({days: 1}), expiresAt: moment().add({ days: 1 }),
}))); })));
expect(res).to.equal(false); expect(res).to.equal(false);
}); });
it('returns false if the code doesn\'t match the one saved at user.auth.passwordResetCode', async () => { it('returns false if the code doesn\'t match the one saved at user.auth.passwordResetCode', async () => {
let user = await generateUser(); const user = await generateUser();
let code = encrypt(JSON.stringify({ const code = encrypt(JSON.stringify({
userId: user._id, userId: user._id,
expiresAt: moment().add({days: 1}), expiresAt: moment().add({ days: 1 }),
})); }));
await user.update({ await user.update({
'auth.local.passwordResetCode': 'invalid', 'auth.local.passwordResetCode': 'invalid',
}); });
let res = await validatePasswordResetCodeAndFindUser(code); const res = await validatePasswordResetCodeAndFindUser(code);
expect(res).to.equal(false); expect(res).to.equal(false);
}); });
it('returns the user if the password reset code is valid', async () => { it('returns the user if the password reset code is valid', async () => {
let user = await generateUser(); const user = await generateUser();
let code = encrypt(JSON.stringify({ const code = encrypt(JSON.stringify({
userId: user._id, userId: user._id,
expiresAt: moment().add({days: 1}), expiresAt: moment().add({ days: 1 }),
})); }));
await user.update({ await user.update({
'auth.local.passwordResetCode': code, 'auth.local.passwordResetCode': code,
}); });
let res = await validatePasswordResetCodeAndFindUser(code); const res = await validatePasswordResetCodeAndFindUser(code);
expect(res).not.to.equal(false); expect(res).not.to.equal(false);
expect(res._id).to.equal(user._id); expect(res._id).to.equal(user._id);
}); });
@@ -293,8 +293,8 @@ describe('Password Utilities', () => {
describe('bcrypt', () => { describe('bcrypt', () => {
describe('Hash', () => { describe('Hash', () => {
it('returns a hashed string', async () => { it('returns a hashed string', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let hashedPassword = await bcryptHash(textPassword); const hashedPassword = await bcryptHash(textPassword);
expect(hashedPassword).to.be.a.string; expect(hashedPassword).to.be.a.string;
}); });
@@ -302,18 +302,18 @@ describe('Password Utilities', () => {
describe('Compare', () => { describe('Compare', () => {
it('returns true if comparing the same password', async () => { it('returns true if comparing the same password', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let hashedPassword = await bcryptHash(textPassword); const hashedPassword = await bcryptHash(textPassword);
let isValidPassword = await bcryptCompare(textPassword, hashedPassword); const isValidPassword = await bcryptCompare(textPassword, hashedPassword);
expect(isValidPassword).to.eql(true); expect(isValidPassword).to.eql(true);
}); });
it('returns true if comparing a different password', async () => { it('returns true if comparing a different password', async () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let hashedPassword = await bcryptHash(textPassword); const hashedPassword = await bcryptHash(textPassword);
let isValidPassword = await bcryptCompare('anotherPassword', hashedPassword); const isValidPassword = await bcryptCompare('anotherPassword', hashedPassword);
expect(isValidPassword).to.eql(false); expect(isValidPassword).to.eql(false);
}); });
}); });
@@ -322,19 +322,19 @@ describe('Password Utilities', () => {
describe('SHA1', () => { describe('SHA1', () => {
describe('Encrypt', () => { describe('Encrypt', () => {
it('always encrypt the same password to the same value when using the same salt', () => { it('always encrypt the same password to the same value when using the same salt', () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let salt = sha1MakeSalt(); const salt = sha1MakeSalt();
let encryptedPassword = sha1EncryptPassword(textPassword, salt); const encryptedPassword = sha1EncryptPassword(textPassword, salt);
expect(sha1EncryptPassword(textPassword, salt)).to.eql(encryptedPassword); expect(sha1EncryptPassword(textPassword, salt)).to.eql(encryptedPassword);
}); });
it('never encrypt the same password to the same value when using a different salt', () => { it('never encrypt the same password to the same value when using a different salt', () => {
let textPassword = 'mySecretPassword'; const textPassword = 'mySecretPassword';
let aSalt = sha1MakeSalt(); const aSalt = sha1MakeSalt();
let anotherSalt = sha1MakeSalt(); const anotherSalt = sha1MakeSalt();
let anEncryptedPassword = sha1EncryptPassword(textPassword, aSalt); const anEncryptedPassword = sha1EncryptPassword(textPassword, aSalt);
let anotherEncryptedPassword = sha1EncryptPassword(textPassword, anotherSalt); const anotherEncryptedPassword = sha1EncryptPassword(textPassword, anotherSalt);
expect(anEncryptedPassword).not.to.eql(anotherEncryptedPassword); expect(anEncryptedPassword).not.to.eql(anotherEncryptedPassword);
}); });
@@ -342,14 +342,14 @@ describe('Password Utilities', () => {
describe('Make Salt', () => { describe('Make Salt', () => {
it('creates a salt with length 10 by default', () => { it('creates a salt with length 10 by default', () => {
let salt = sha1MakeSalt(); const salt = sha1MakeSalt();
expect(salt.length).to.eql(10); expect(salt.length).to.eql(10);
}); });
it('can create a salt of any length', () => { it('can create a salt of any length', () => {
let length = 24; const length = 24;
let salt = sha1MakeSalt(length); const salt = sha1MakeSalt(length);
expect(salt.length).to.eql(length); expect(salt.length).to.eql(length);
}); });

View File

@@ -2,19 +2,20 @@ import moment from 'moment';
import { import {
generateGroup, generateGroup,
} from '../../../../../helpers/api-unit.helper.js'; } from '../../../../../helpers/api-unit.helper';
import { model as User } from '../../../../../../website/server/models/user'; import { model as User } from '../../../../../../website/server/models/user';
import amzLib from '../../../../../../website/server/libs/payments/amazon'; import amzLib from '../../../../../../website/server/libs/payments/amazon';
import payments from '../../../../../../website/server/libs/payments/payments'; import payments from '../../../../../../website/server/libs/payments/payments';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
import { createNonLeaderGroupMember } from '../paymentHelpers'; import { createNonLeaderGroupMember } from '../paymentHelpers';
const i18n = common.i18n; const { i18n } = common;
describe('Amazon Payments - Cancel Subscription', () => { describe('Amazon Payments - Cancel Subscription', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
let user, group, headers, billingAgreementId, subscriptionBlock, subscriptionLength; let user; let group; let headers; let billingAgreementId; let subscriptionBlock; let
subscriptionLength;
let getBillingAgreementDetailsSpy; let getBillingAgreementDetailsSpy;
let paymentCancelSubscriptionSpy; let paymentCancelSubscriptionSpy;
@@ -50,7 +51,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails') getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails')
.resolves({ .resolves({
BillingAgreementDetails: { BillingAgreementDetails: {
BillingAgreementStatus: {State: 'Open'}, BillingAgreementStatus: { State: 'Open' },
}, },
}); });
} }
@@ -81,7 +82,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails'); getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails');
getBillingAgreementDetailsSpy.resolves({ getBillingAgreementDetailsSpy.resolves({
BillingAgreementDetails: { BillingAgreementDetails: {
BillingAgreementStatus: {State: 'Closed'}, BillingAgreementStatus: { State: 'Closed' },
}, },
}); });
@@ -89,7 +90,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
paymentCancelSubscriptionSpy.resolves({}); paymentCancelSubscriptionSpy.resolves({});
}); });
afterEach(function () { afterEach(() => {
amzLib.getBillingAgreementDetails.restore(); amzLib.getBillingAgreementDetails.restore();
payments.cancelSubscription.restore(); payments.cancelSubscription.restore();
}); });
@@ -97,7 +98,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
it('should throw an error if we are missing a subscription', async () => { it('should throw an error if we are missing a subscription', async () => {
user.purchased.plan.customerId = undefined; user.purchased.plan.customerId = undefined;
await expect(amzLib.cancelSubscription({user})) await expect(amzLib.cancelSubscription({ user }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -108,7 +109,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
it('should cancel a user subscription', async () => { it('should cancel a user subscription', async () => {
billingAgreementId = user.purchased.plan.customerId; billingAgreementId = user.purchased.plan.customerId;
await amzLib.cancelSubscription({user, headers}); await amzLib.cancelSubscription({ user, headers });
expectAmazonCancelUserSubscriptionSpy(); expectAmazonCancelUserSubscriptionSpy();
expectAmazonStubs(); expectAmazonStubs();
@@ -117,10 +118,10 @@ describe('Amazon Payments - Cancel Subscription', () => {
it('should close a user subscription if amazon not closed', async () => { it('should close a user subscription if amazon not closed', async () => {
amzLib.getBillingAgreementDetails.restore(); amzLib.getBillingAgreementDetails.restore();
expectBillingAggreementDetailSpy(); expectBillingAggreementDetailSpy();
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({}); const closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({});
billingAgreementId = user.purchased.plan.customerId; billingAgreementId = user.purchased.plan.customerId;
await amzLib.cancelSubscription({user, headers}); await amzLib.cancelSubscription({ user, headers });
expectAmazonStubs(); expectAmazonStubs();
expect(closeBillingAgreementSpy).to.be.calledOnce; expect(closeBillingAgreementSpy).to.be.calledOnce;
@@ -132,7 +133,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
}); });
it('should throw an error if group is not found', async () => { it('should throw an error if group is not found', async () => {
await expect(amzLib.cancelSubscription({user, groupId: 'fake-id'})) await expect(amzLib.cancelSubscription({ user, groupId: 'fake-id' }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 404, httpCode: 404,
name: 'NotFound', name: 'NotFound',
@@ -141,9 +142,9 @@ describe('Amazon Payments - Cancel Subscription', () => {
}); });
it('should throw an error if user is not group leader', async () => { it('should throw an error if user is not group leader', async () => {
let nonLeader = await createNonLeaderGroupMember(group); const nonLeader = await createNonLeaderGroupMember(group);
await expect(amzLib.cancelSubscription({user: nonLeader, groupId: group._id})) await expect(amzLib.cancelSubscription({ user: nonLeader, groupId: group._id }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -154,7 +155,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
it('should cancel a group subscription', async () => { it('should cancel a group subscription', async () => {
billingAgreementId = group.purchased.plan.customerId; billingAgreementId = group.purchased.plan.customerId;
await amzLib.cancelSubscription({user, groupId: group._id, headers}); await amzLib.cancelSubscription({ user, groupId: group._id, headers });
expectAmazonCancelGroupSubscriptionSpy(group._id); expectAmazonCancelGroupSubscriptionSpy(group._id);
expectAmazonStubs(); expectAmazonStubs();
@@ -163,10 +164,10 @@ describe('Amazon Payments - Cancel Subscription', () => {
it('should close a group subscription if amazon not closed', async () => { it('should close a group subscription if amazon not closed', async () => {
amzLib.getBillingAgreementDetails.restore(); amzLib.getBillingAgreementDetails.restore();
expectBillingAggreementDetailSpy(); expectBillingAggreementDetailSpy();
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({}); const closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({});
billingAgreementId = group.purchased.plan.customerId; billingAgreementId = group.purchased.plan.customerId;
await amzLib.cancelSubscription({user, groupId: group._id, headers}); await amzLib.cancelSubscription({ user, groupId: group._id, headers });
expectAmazonStubs(); expectAmazonStubs();
expect(closeBillingAgreementSpy).to.be.calledOnce; expect(closeBillingAgreementSpy).to.be.calledOnce;

View File

@@ -3,11 +3,12 @@ import amzLib from '../../../../../../website/server/libs/payments/amazon';
import payments from '../../../../../../website/server/libs/payments/payments'; import payments from '../../../../../../website/server/libs/payments/payments';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
const i18n = common.i18n; const { i18n } = common;
describe('Amazon Payments - Checkout', () => { describe('Amazon Payments - Checkout', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
let user, orderReferenceId, headers; let user; let orderReferenceId; let
headers;
let setOrderReferenceDetailsSpy; let setOrderReferenceDetailsSpy;
let confirmOrderReferenceSpy; let confirmOrderReferenceSpy;
let authorizeSpy; let authorizeSpy;
@@ -62,7 +63,7 @@ describe('Amazon Payments - Checkout', () => {
expect(closeOrderReferenceSpy).to.be.calledWith({ AmazonOrderReferenceId: orderReferenceId }); expect(closeOrderReferenceSpy).to.be.calledWith({ AmazonOrderReferenceId: orderReferenceId });
} }
beforeEach(function () { beforeEach(() => {
user = new User(); user = new User();
headers = {}; headers = {};
orderReferenceId = 'orderReferenceId'; orderReferenceId = 'orderReferenceId';
@@ -88,7 +89,7 @@ describe('Amazon Payments - Checkout', () => {
sinon.stub(common, 'uuid').returns('uuid-generated'); sinon.stub(common, 'uuid').returns('uuid-generated');
}); });
afterEach(function () { afterEach(() => {
amzLib.setOrderReferenceDetails.restore(); amzLib.setOrderReferenceDetails.restore();
amzLib.confirmOrderReference.restore(); amzLib.confirmOrderReference.restore();
amzLib.authorize.restore(); amzLib.authorize.restore();
@@ -101,7 +102,7 @@ describe('Amazon Payments - Checkout', () => {
function expectBuyGemsStub (paymentMethod, gift) { function expectBuyGemsStub (paymentMethod, gift) {
expect(paymentBuyGemsStub).to.be.calledOnce; expect(paymentBuyGemsStub).to.be.calledOnce;
let expectedArgs = { const expectedArgs = {
user, user,
paymentMethod, paymentMethod,
headers, headers,
@@ -112,7 +113,7 @@ describe('Amazon Payments - Checkout', () => {
it('should purchase gems', async () => { it('should purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(true); sinon.stub(user, 'canGetGems').resolves(true);
await amzLib.checkout({user, orderReferenceId, headers}); await amzLib.checkout({ user, orderReferenceId, headers });
expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD); expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD);
expectAmazonStubs(); expectAmazonStubs();
@@ -121,9 +122,9 @@ describe('Amazon Payments - Checkout', () => {
}); });
it('should error if gem amount is too low', async () => { it('should error if gem amount is too low', async () => {
let receivingUser = new User(); const receivingUser = new User();
receivingUser.save(); receivingUser.save();
let gift = { const gift = {
type: 'gems', type: 'gems',
gems: { gems: {
amount: 0, amount: 0,
@@ -131,7 +132,9 @@ describe('Amazon Payments - Checkout', () => {
}, },
}; };
await expect(amzLib.checkout({gift, user, orderReferenceId, headers})) await expect(amzLib.checkout({
gift, user, orderReferenceId, headers,
}))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 400, httpCode: 400,
message: 'Amount must be at least 1.', message: 'Amount must be at least 1.',
@@ -141,18 +144,19 @@ describe('Amazon Payments - Checkout', () => {
it('should error if user cannot get gems gems', async () => { it('should error if user cannot get gems gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false); sinon.stub(user, 'canGetGems').resolves(false);
await expect(amzLib.checkout({user, orderReferenceId, headers})).to.eventually.be.rejected.and.to.eql({ await expect(amzLib.checkout({ user, orderReferenceId, headers }))
httpCode: 401, .to.eventually.be.rejected.and.to.eql({
message: i18n.t('groupPolicyCannotGetGems'), httpCode: 401,
name: 'NotAuthorized', message: i18n.t('groupPolicyCannotGetGems'),
}); name: 'NotAuthorized',
});
user.canGetGems.restore(); user.canGetGems.restore();
}); });
it('should gift gems', async () => { it('should gift gems', async () => {
let receivingUser = new User(); const receivingUser = new User();
await receivingUser.save(); await receivingUser.save();
let gift = { const gift = {
type: 'gems', type: 'gems',
uuid: receivingUser._id, uuid: receivingUser._id,
gems: { gems: {
@@ -160,16 +164,18 @@ describe('Amazon Payments - Checkout', () => {
}, },
}; };
amount = 16 / 4; amount = 16 / 4;
await amzLib.checkout({gift, user, orderReferenceId, headers}); await amzLib.checkout({
gift, user, orderReferenceId, headers,
});
expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD_GIFT, gift); expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD_GIFT, gift);
expectAmazonStubs(); expectAmazonStubs();
}); });
it('should gift a subscription', async () => { it('should gift a subscription', async () => {
let receivingUser = new User(); const receivingUser = new User();
receivingUser.save(); receivingUser.save();
let gift = { const gift = {
type: 'subscription', type: 'subscription',
subscription: { subscription: {
key: subKey, key: subKey,
@@ -178,7 +184,9 @@ describe('Amazon Payments - Checkout', () => {
}; };
amount = common.content.subscriptionBlocks[subKey].price; amount = common.content.subscriptionBlocks[subKey].price;
await amzLib.checkout({user, orderReferenceId, headers, gift}); await amzLib.checkout({
user, orderReferenceId, headers, gift,
});
gift.member = receivingUser; gift.member = receivingUser;
expect(paymentCreateSubscritionStub).to.be.calledOnce; expect(paymentCreateSubscritionStub).to.be.calledOnce;

View File

@@ -2,18 +2,19 @@ import cc from 'coupon-code';
import { import {
generateGroup, generateGroup,
} from '../../../../../helpers/api-unit.helper.js'; } from '../../../../../helpers/api-unit.helper';
import { model as User } from '../../../../../../website/server/models/user'; import { model as User } from '../../../../../../website/server/models/user';
import { model as Coupon } from '../../../../../../website/server/models/coupon'; import { model as Coupon } from '../../../../../../website/server/models/coupon';
import amzLib from '../../../../../../website/server/libs/payments/amazon'; import amzLib from '../../../../../../website/server/libs/payments/amazon';
import payments from '../../../../../../website/server/libs/payments/payments'; import payments from '../../../../../../website/server/libs/payments/payments';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
const i18n = common.i18n; const { i18n } = common;
describe('Amazon Payments - Subscribe', () => { describe('Amazon Payments - Subscribe', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
let user, group, amount, billingAgreementId, sub, coupon, groupId, headers; let user; let group; let amount; let billingAgreementId; let sub; let coupon; let groupId; let
headers;
let amazonSetBillingAgreementDetailsSpy; let amazonSetBillingAgreementDetailsSpy;
let amazonConfirmBillingAgreementSpy; let amazonConfirmBillingAgreementSpy;
let amazonAuthorizeOnBillingAgreementSpy; let amazonAuthorizeOnBillingAgreementSpy;
@@ -60,7 +61,7 @@ describe('Amazon Payments - Subscribe', () => {
sinon.stub(common, 'uuid').returns('uuid-generated'); sinon.stub(common, 'uuid').returns('uuid-generated');
}); });
afterEach(function () { afterEach(() => {
amzLib.setBillingAgreementDetails.restore(); amzLib.setBillingAgreementDetails.restore();
amzLib.confirmBillingAgreement.restore(); amzLib.confirmBillingAgreement.restore();
amzLib.authorizeOnBillingAgreement.restore(); amzLib.authorizeOnBillingAgreement.restore();
@@ -168,7 +169,7 @@ describe('Amazon Payments - Subscribe', () => {
sub.key = 'google_6mo'; sub.key = 'google_6mo';
coupon = 'example-coupon'; coupon = 'example-coupon';
let couponModel = new Coupon(); const couponModel = new Coupon();
couponModel.event = 'google_6mo'; couponModel.event = 'google_6mo';
await couponModel.save(); await couponModel.save();
@@ -195,9 +196,9 @@ describe('Amazon Payments - Subscribe', () => {
sub.key = 'google_6mo'; sub.key = 'google_6mo';
coupon = 'example-coupon'; coupon = 'example-coupon';
let couponModel = new Coupon(); const couponModel = new Coupon();
couponModel.event = 'google_6mo'; couponModel.event = 'google_6mo';
let updatedCouponModel = await couponModel.save(); const updatedCouponModel = await couponModel.save();
sinon.stub(cc, 'validate').returns(updatedCouponModel._id); sinon.stub(cc, 'validate').returns(updatedCouponModel._id);

View File

@@ -2,16 +2,17 @@ import uuid from 'uuid';
import { import {
generateGroup, generateGroup,
} from '../../../../../helpers/api-unit.helper.js'; } from '../../../../../helpers/api-unit.helper';
import { model as User } from '../../../../../../website/server/models/user'; import { model as User } from '../../../../../../website/server/models/user';
import { model as Group } from '../../../../../../website/server/models/group'; import { model as Group } from '../../../../../../website/server/models/group';
import amzLib from '../../../../../../website/server/libs/payments/amazon'; import amzLib from '../../../../../../website/server/libs/payments/amazon';
import payments from '../../../../../../website/server/libs/payments/payments'; import payments from '../../../../../../website/server/libs/payments/payments';
describe('#upgradeGroupPlan', () => { describe('#upgradeGroupPlan', () => {
let spy, data, user, group, uuidString; let spy; let data; let user; let group; let
uuidString;
beforeEach(async function () { beforeEach(async () => {
user = new User(); user = new User();
user.profile.name = 'sender'; user.profile.name = 'sender';
@@ -46,7 +47,7 @@ describe('#upgradeGroupPlan', () => {
data.sub.quantity = 3; data.sub.quantity = 3;
}); });
afterEach(function () { afterEach(() => {
amzLib.authorizeOnBillingAgreement.restore(); amzLib.authorizeOnBillingAgreement.restore();
uuid.v4.restore(); uuid.v4.restore();
}); });
@@ -55,7 +56,7 @@ describe('#upgradeGroupPlan', () => {
data.paymentMethod = amzLib.constants.PAYMENT_METHOD; data.paymentMethod = amzLib.constants.PAYMENT_METHOD;
await payments.createSubscription(data); await payments.createSubscription(data);
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
updatedGroup.memberCount += 1; updatedGroup.memberCount += 1;
await updatedGroup.save(); await updatedGroup.save();

View File

@@ -1,21 +1,22 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import iapModule from '../../../../../website/server/libs/inAppPurchases'; import moment from 'moment';
import payments from '../../../../../website/server/libs/payments/payments'; import payments from '../../../../../website/server/libs/payments/payments';
import applePayments from '../../../../../website/server/libs/payments/apple'; import applePayments from '../../../../../website/server/libs/payments/apple';
import iap from '../../../../../website/server/libs/inAppPurchases'; import iap from '../../../../../website/server/libs/inAppPurchases';
import {model as User} from '../../../../../website/server/models/user'; import { model as User } from '../../../../../website/server/models/user';
import common from '../../../../../website/common'; import common from '../../../../../website/common';
import moment from 'moment'; import { mockFindById, restoreFindById } from '../../../../helpers/mongoose.helper';
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
const i18n = common.i18n; const { i18n } = common;
describe('Apple Payments', () => { describe('Apple Payments', () => {
let subKey = 'basic_3mo'; const subKey = 'basic_3mo';
describe('verifyGemPurchase', () => { describe('verifyGemPurchase', () => {
let sku, user, token, receipt, headers; let sku; let user; let token; let receipt; let
let iapSetupStub, iapValidateStub, iapIsValidatedStub, paymentBuyGemsStub, iapGetPurchaseDataStub; headers;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuyGemsStub; let
iapGetPurchaseDataStub;
beforeEach(() => { beforeEach(() => {
token = 'testToken'; token = 'testToken';
@@ -24,33 +25,34 @@ describe('Apple Payments', () => {
receipt = `{"token": "${token}", "productId": "${sku}"}`; receipt = `{"token": "${token}", "productId": "${sku}"}`;
headers = {}; headers = {};
iapSetupStub = sinon.stub(iapModule, 'setup') iapSetupStub = sinon.stub(iap, 'setup')
.resolves(); .resolves();
iapValidateStub = sinon.stub(iapModule, 'validate') iapValidateStub = sinon.stub(iap, 'validate')
.resolves({}); .resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true); .returns(true);
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{productId: 'com.habitrpg.ios.Habitica.21gems', .returns([{
transactionId: token, productId: 'com.habitrpg.ios.Habitica.21gems',
transactionId: token,
}]); }]);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({}); paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
}); });
afterEach(() => { afterEach(() => {
iapModule.setup.restore(); iap.setup.restore();
iapModule.validate.restore(); iap.validate.restore();
iapModule.isValidated.restore(); iap.isValidated.restore();
iapModule.getPurchaseData.restore(); iap.getPurchaseData.restore();
payments.buyGems.restore(); payments.buyGems.restore();
}); });
it('should throw an error if receipt is invalid', async () => { it('should throw an error if receipt is invalid', async () => {
iapModule.isValidated.restore(); iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false); .returns(false);
await expect(applePayments.verifyGemPurchase({user, receipt, headers})) await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -60,9 +62,9 @@ describe('Apple Payments', () => {
it('should throw an error if getPurchaseData is invalid', async () => { it('should throw an error if getPurchaseData is invalid', async () => {
iapGetPurchaseDataStub.restore(); iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData').returns([]); iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData').returns([]);
await expect(applePayments.verifyGemPurchase({user, receipt, headers})) await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -72,7 +74,7 @@ describe('Apple Payments', () => {
it('errors if the user cannot purchase gems', async () => { it('errors if the user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false); sinon.stub(user, 'canGetGems').resolves(false);
await expect(applePayments.verifyGemPurchase({user, receipt, headers})) await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -85,12 +87,13 @@ describe('Apple Payments', () => {
it('errors if amount does not exist', async () => { it('errors if amount does not exist', async () => {
sinon.stub(user, 'canGetGems').resolves(true); sinon.stub(user, 'canGetGems').resolves(true);
iapGetPurchaseDataStub.restore(); iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{productId: 'badProduct', .returns([{
transactionId: token, productId: 'badProduct',
transactionId: token,
}]); }]);
await expect(applePayments.verifyGemPurchase({user, receipt, headers})) await expect(applePayments.verifyGemPurchase({ user, receipt, headers }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -126,13 +129,14 @@ describe('Apple Payments', () => {
gemsCanPurchase.forEach(gemTest => { gemsCanPurchase.forEach(gemTest => {
it(`purchases ${gemTest.productId} gems`, async () => { it(`purchases ${gemTest.productId} gems`, async () => {
iapGetPurchaseDataStub.restore(); iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{productId: gemTest.productId, .returns([{
transactionId: token, productId: gemTest.productId,
transactionId: token,
}]); }]);
sinon.stub(user, 'canGetGems').resolves(true); sinon.stub(user, 'canGetGems').resolves(true);
await applePayments.verifyGemPurchase({user, receipt, headers}); await applePayments.verifyGemPurchase({ user, receipt, headers });
expect(iapSetupStub).to.be.calledOnce; expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce; expect(iapValidateStub).to.be.calledOnce;
@@ -160,13 +164,16 @@ describe('Apple Payments', () => {
mockFindById(receivingUser); mockFindById(receivingUser);
iapGetPurchaseDataStub.restore(); iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{productId: gemsCanPurchase[0].productId, .returns([{
transactionId: token, productId: gemsCanPurchase[0].productId,
transactionId: token,
}]); }]);
const gift = {uuid: receivingUser._id}; const gift = { uuid: receivingUser._id };
await applePayments.verifyGemPurchase({user, gift, receipt, headers}); await applePayments.verifyGemPurchase({
user, gift, receipt, headers,
});
expect(iapSetupStub).to.be.calledOnce; expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce; expect(iapValidateStub).to.be.calledOnce;
@@ -187,8 +194,11 @@ describe('Apple Payments', () => {
}); });
describe('subscribe', () => { describe('subscribe', () => {
let sub, sku, user, token, receipt, headers, nextPaymentProcessing; let sub; let sku; let user; let token; let receipt; let headers; let
let iapSetupStub, iapValidateStub, iapIsValidatedStub, paymentsCreateSubscritionStub, iapGetPurchaseDataStub; nextPaymentProcessing;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub;
let paymentsCreateSubscritionStub; let
iapGetPurchaseDataStub;
beforeEach(() => { beforeEach(() => {
sub = common.content.subscriptionBlocks[subKey]; sub = common.content.subscriptionBlocks[subKey];
@@ -197,25 +207,25 @@ describe('Apple Payments', () => {
token = 'test-token'; token = 'test-token';
headers = {}; headers = {};
receipt = `{"token": "${token}"}`; receipt = `{"token": "${token}"}`;
nextPaymentProcessing = moment.utc().add({days: 2}); nextPaymentProcessing = moment.utc().add({ days: 2 });
iapSetupStub = sinon.stub(iapModule, 'setup') iapSetupStub = sinon.stub(iap, 'setup')
.resolves(); .resolves();
iapValidateStub = sinon.stub(iapModule, 'validate') iapValidateStub = sinon.stub(iap, 'validate')
.resolves({}); .resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true); .returns(true);
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ .returns([{
expirationDate: moment.utc().subtract({day: 1}).toDate(), expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
productId: sku, productId: sku,
transactionId: token, transactionId: token,
}, { }, {
expirationDate: moment.utc().add({day: 1}).toDate(), expirationDate: moment.utc().add({ day: 1 }).toDate(),
productId: 'wrongsku', productId: 'wrongsku',
transactionId: token, transactionId: token,
}, { }, {
expirationDate: moment.utc().add({day: 1}).toDate(), expirationDate: moment.utc().add({ day: 1 }).toDate(),
productId: sku, productId: sku,
transactionId: token, transactionId: token,
}]); }]);
@@ -223,10 +233,10 @@ describe('Apple Payments', () => {
}); });
afterEach(() => { afterEach(() => {
iapModule.setup.restore(); iap.setup.restore();
iapModule.validate.restore(); iap.validate.restore();
iapModule.isValidated.restore(); iap.isValidated.restore();
iapModule.getPurchaseData.restore(); iap.getPurchaseData.restore();
if (payments.createSubscription.restore) payments.createSubscription.restore(); if (payments.createSubscription.restore) payments.createSubscription.restore();
}); });
@@ -240,8 +250,8 @@ describe('Apple Payments', () => {
}); });
it('should throw an error if receipt is invalid', async () => { it('should throw an error if receipt is invalid', async () => {
iapModule.isValidated.restore(); iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false); .returns(false);
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing)) await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
@@ -272,10 +282,10 @@ describe('Apple Payments', () => {
]; ];
subOptions.forEach(option => { subOptions.forEach(option => {
it(`creates a user subscription for ${option.sku}`, async () => { it(`creates a user subscription for ${option.sku}`, async () => {
iapModule.getPurchaseData.restore(); iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{ .returns([{
expirationDate: moment.utc().add({day: 1}).toDate(), expirationDate: moment.utc().add({ day: 1 }).toDate(),
productId: option.sku, productId: option.sku,
transactionId: token, transactionId: token,
}]); }]);
@@ -319,8 +329,10 @@ describe('Apple Payments', () => {
}); });
describe('cancelSubscribe ', () => { describe('cancelSubscribe ', () => {
let user, token, receipt, headers, customerId, expirationDate; let user; let token; let receipt; let headers; let customerId; let
let iapSetupStub, iapValidateStub, iapIsValidatedStub, iapGetPurchaseDataStub, paymentCancelSubscriptionSpy; expirationDate;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let iapGetPurchaseDataStub; let
paymentCancelSubscriptionSpy;
beforeEach(async () => { beforeEach(async () => {
token = 'test-token'; token = 'test-token';
@@ -329,15 +341,15 @@ describe('Apple Payments', () => {
customerId = 'test-customerId'; customerId = 'test-customerId';
expirationDate = moment.utc(); expirationDate = moment.utc();
iapSetupStub = sinon.stub(iapModule, 'setup') iapSetupStub = sinon.stub(iap, 'setup')
.resolves(); .resolves();
iapValidateStub = sinon.stub(iapModule, 'validate') iapValidateStub = sinon.stub(iap, 'validate')
.resolves({ .resolves({
expirationDate, expirationDate,
}); });
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{expirationDate: expirationDate.toDate()}]); .returns([{ expirationDate: expirationDate.toDate() }]);
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true); .returns(true);
user = new User(); user = new User();
@@ -350,11 +362,11 @@ describe('Apple Payments', () => {
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({}); paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
}); });
afterEach(function () { afterEach(() => {
iapModule.setup.restore(); iap.setup.restore();
iapModule.validate.restore(); iap.validate.restore();
iapModule.isValidated.restore(); iap.isValidated.restore();
iapModule.getPurchaseData.restore(); iap.getPurchaseData.restore();
payments.cancelSubscription.restore(); payments.cancelSubscription.restore();
}); });
@@ -370,9 +382,9 @@ describe('Apple Payments', () => {
}); });
it('should throw an error if subscription is still valid', async () => { it('should throw an error if subscription is still valid', async () => {
iapModule.getPurchaseData.restore(); iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{expirationDate: expirationDate.add({day: 1}).toDate()}]); .returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]);
await expect(applePayments.cancelSubscribe(user, headers)) await expect(applePayments.cancelSubscribe(user, headers))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
@@ -383,8 +395,8 @@ describe('Apple Payments', () => {
}); });
it('should throw an error if receipt is invalid', async () => { it('should throw an error if receipt is invalid', async () => {
iapModule.isValidated.restore(); iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false); .returns(false);
await expect(applePayments.cancelSubscribe(user, headers)) await expect(applePayments.cancelSubscribe(user, headers))

View File

@@ -1,21 +1,22 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import iapModule from '../../../../../website/server/libs/inAppPurchases'; import moment from 'moment';
import payments from '../../../../../website/server/libs/payments/payments'; import payments from '../../../../../website/server/libs/payments/payments';
import googlePayments from '../../../../../website/server/libs/payments/google'; import googlePayments from '../../../../../website/server/libs/payments/google';
import iap from '../../../../../website/server/libs/inAppPurchases'; import iap from '../../../../../website/server/libs/inAppPurchases';
import {model as User} from '../../../../../website/server/models/user'; import { model as User } from '../../../../../website/server/models/user';
import common from '../../../../../website/common'; import common from '../../../../../website/common';
import moment from 'moment'; import { mockFindById, restoreFindById } from '../../../../helpers/mongoose.helper';
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
const i18n = common.i18n; const { i18n } = common;
describe('Google Payments', () => { describe('Google Payments', () => {
let subKey = 'basic_3mo'; const subKey = 'basic_3mo';
describe('verifyGemPurchase', () => { describe('verifyGemPurchase', () => {
let sku, user, token, receipt, signature, headers; let sku; let user; let token; let receipt; let signature; let
let iapSetupStub, iapValidateStub, iapIsValidatedStub, paymentBuyGemsStub; headers;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
paymentBuyGemsStub;
beforeEach(() => { beforeEach(() => {
sku = 'com.habitrpg.android.habitica.iap.21gems'; sku = 'com.habitrpg.android.habitica.iap.21gems';
@@ -24,28 +25,30 @@ describe('Google Payments', () => {
signature = ''; signature = '';
headers = {}; headers = {};
iapSetupStub = sinon.stub(iapModule, 'setup') iapSetupStub = sinon.stub(iap, 'setup')
.resolves(); .resolves();
iapValidateStub = sinon.stub(iapModule, 'validate') iapValidateStub = sinon.stub(iap, 'validate')
.resolves({}); .resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true); .returns(true);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({}); paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
}); });
afterEach(() => { afterEach(() => {
iapModule.setup.restore(); iap.setup.restore();
iapModule.validate.restore(); iap.validate.restore();
iapModule.isValidated.restore(); iap.isValidated.restore();
payments.buyGems.restore(); payments.buyGems.restore();
}); });
it('should throw an error if receipt is invalid', async () => { it('should throw an error if receipt is invalid', async () => {
iapModule.isValidated.restore(); iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false); .returns(false);
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers})) await expect(googlePayments.verifyGemPurchase({
user, receipt, signature, headers,
}))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -56,7 +59,9 @@ describe('Google Payments', () => {
it('should throw an error if productId is invalid', async () => { it('should throw an error if productId is invalid', async () => {
receipt = `{"token": "${token}", "productId": "invalid"}`; receipt = `{"token": "${token}", "productId": "invalid"}`;
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers})) await expect(googlePayments.verifyGemPurchase({
user, receipt, signature, headers,
}))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -67,7 +72,9 @@ describe('Google Payments', () => {
it('should throw an error if user cannot purchase gems', async () => { it('should throw an error if user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false); sinon.stub(user, 'canGetGems').resolves(false);
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers})) await expect(googlePayments.verifyGemPurchase({
user, receipt, signature, headers,
}))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -79,7 +86,9 @@ describe('Google Payments', () => {
it('purchases gems', async () => { it('purchases gems', async () => {
sinon.stub(user, 'canGetGems').resolves(true); sinon.stub(user, 'canGetGems').resolves(true);
await googlePayments.verifyGemPurchase({user, receipt, signature, headers}); await googlePayments.verifyGemPurchase({
user, receipt, signature, headers,
});
expect(iapSetupStub).to.be.calledOnce; expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce; expect(iapValidateStub).to.be.calledOnce;
@@ -107,8 +116,10 @@ describe('Google Payments', () => {
mockFindById(receivingUser); mockFindById(receivingUser);
const gift = {uuid: receivingUser._id}; const gift = { uuid: receivingUser._id };
await googlePayments.verifyGemPurchase({user, gift, receipt, signature, headers}); await googlePayments.verifyGemPurchase({
user, gift, receipt, signature, headers,
});
expect(iapSetupStub).to.be.calledOnce; expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce; expect(iapValidateStub).to.be.calledOnce;
@@ -131,8 +142,10 @@ describe('Google Payments', () => {
}); });
describe('subscribe', () => { describe('subscribe', () => {
let sub, sku, user, token, receipt, signature, headers, nextPaymentProcessing; let sub; let sku; let user; let token; let receipt; let signature; let headers; let
let iapSetupStub, iapValidateStub, iapIsValidatedStub, paymentsCreateSubscritionStub; nextPaymentProcessing;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
paymentsCreateSubscritionStub;
beforeEach(() => { beforeEach(() => {
sub = common.content.subscriptionBlocks[subKey]; sub = common.content.subscriptionBlocks[subKey];
@@ -142,30 +155,31 @@ describe('Google Payments', () => {
headers = {}; headers = {};
receipt = `{"token": "${token}"}`; receipt = `{"token": "${token}"}`;
signature = ''; signature = '';
nextPaymentProcessing = moment.utc().add({days: 2}); nextPaymentProcessing = moment.utc().add({ days: 2 });
iapSetupStub = sinon.stub(iapModule, 'setup') iapSetupStub = sinon.stub(iap, 'setup')
.resolves(); .resolves();
iapValidateStub = sinon.stub(iapModule, 'validate') iapValidateStub = sinon.stub(iap, 'validate')
.resolves({}); .resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true); .returns(true);
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({}); paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
}); });
afterEach(() => { afterEach(() => {
iapModule.setup.restore(); iap.setup.restore();
iapModule.validate.restore(); iap.validate.restore();
iapModule.isValidated.restore(); iap.isValidated.restore();
payments.createSubscription.restore(); payments.createSubscription.restore();
}); });
it('should throw an error if receipt is invalid', async () => { it('should throw an error if receipt is invalid', async () => {
iapModule.isValidated.restore(); iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false); .returns(false);
await expect(googlePayments.subscribe(sku, user, receipt, signature, headers, nextPaymentProcessing)) await expect(googlePayments
.subscribe(sku, user, receipt, signature, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -176,7 +190,8 @@ describe('Google Payments', () => {
it('should throw an error if sku is invalid', async () => { it('should throw an error if sku is invalid', async () => {
sku = 'invalid'; sku = 'invalid';
await expect(googlePayments.subscribe(sku, user, receipt, signature, headers, nextPaymentProcessing)) await expect(googlePayments
.subscribe(sku, user, receipt, signature, headers, nextPaymentProcessing))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -203,15 +218,17 @@ describe('Google Payments', () => {
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE, paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
sub, sub,
headers, headers,
additionalData: {data: receipt, signature}, additionalData: { data: receipt, signature },
nextPaymentProcessing, nextPaymentProcessing,
}); });
}); });
}); });
describe('cancelSubscribe ', () => { describe('cancelSubscribe ', () => {
let user, token, receipt, signature, headers, customerId, expirationDate; let user; let token; let receipt; let signature; let headers; let customerId; let
let iapSetupStub, iapValidateStub, iapIsValidatedStub, iapGetPurchaseDataStub, paymentCancelSubscriptionSpy; expirationDate;
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let iapGetPurchaseDataStub; let
paymentCancelSubscriptionSpy;
beforeEach(async () => { beforeEach(async () => {
token = 'test-token'; token = 'test-token';
@@ -221,15 +238,15 @@ describe('Google Payments', () => {
customerId = 'test-customerId'; customerId = 'test-customerId';
expirationDate = moment.utc(); expirationDate = moment.utc();
iapSetupStub = sinon.stub(iapModule, 'setup') iapSetupStub = sinon.stub(iap, 'setup')
.resolves(); .resolves();
iapValidateStub = sinon.stub(iapModule, 'validate') iapValidateStub = sinon.stub(iap, 'validate')
.resolves({ .resolves({
expirationDate, expirationDate,
}); });
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{expirationDate: expirationDate.toDate()}]); .returns([{ expirationDate: expirationDate.toDate() }]);
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(true); .returns(true);
user = new User(); user = new User();
@@ -237,16 +254,16 @@ describe('Google Payments', () => {
user.purchased.plan.customerId = customerId; user.purchased.plan.customerId = customerId;
user.purchased.plan.paymentMethod = googlePayments.constants.PAYMENT_METHOD_GOOGLE; user.purchased.plan.paymentMethod = googlePayments.constants.PAYMENT_METHOD_GOOGLE;
user.purchased.plan.planId = subKey; user.purchased.plan.planId = subKey;
user.purchased.plan.additionalData = {data: receipt, signature}; user.purchased.plan.additionalData = { data: receipt, signature };
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({}); paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
}); });
afterEach(function () { afterEach(() => {
iapModule.setup.restore(); iap.setup.restore();
iapModule.validate.restore(); iap.validate.restore();
iapModule.isValidated.restore(); iap.isValidated.restore();
iapModule.getPurchaseData.restore(); iap.getPurchaseData.restore();
payments.cancelSubscription.restore(); payments.cancelSubscription.restore();
}); });
@@ -262,9 +279,9 @@ describe('Google Payments', () => {
}); });
it('should throw an error if subscription is still valid', async () => { it('should throw an error if subscription is still valid', async () => {
iapModule.getPurchaseData.restore(); iap.getPurchaseData.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData') iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{expirationDate: expirationDate.add({day: 1}).toDate()}]); .returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]);
await expect(googlePayments.cancelSubscribe(user, headers)) await expect(googlePayments.cancelSubscribe(user, headers))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
@@ -275,8 +292,8 @@ describe('Google Payments', () => {
}); });
it('should throw an error if receipt is invalid', async () => { it('should throw an error if receipt is invalid', async () => {
iapModule.isValidated.restore(); iap.isValidated.restore();
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated') iapIsValidatedStub = sinon.stub(iap, 'isValidated')
.returns(false); .returns(false);
await expect(googlePayments.cancelSubscribe(user, headers)) await expect(googlePayments.cancelSubscribe(user, headers))

View File

@@ -1,16 +1,17 @@
import moment from 'moment'; import moment from 'moment';
import * as sender from '../../../../../../website/server/libs/email'; import * as sender from '../../../../../../website/server/libs/email';
import * as api from '../../../../../../website/server/libs/payments/payments'; import api from '../../../../../../website/server/libs/payments/payments';
import { model as User } from '../../../../../../website/server/models/user'; import { model as User } from '../../../../../../website/server/models/user';
import { model as Group } from '../../../../../../website/server/models/group'; import { model as Group } from '../../../../../../website/server/models/group';
import { import {
generateGroup, generateGroup,
} from '../../../../../helpers/api-unit.helper.js'; } from '../../../../../helpers/api-unit.helper';
import i18n from '../../../../../../website/common/script/i18n'; import i18n from '../../../../../../website/common/script/i18n';
describe('Canceling a subscription for group', () => { describe('Canceling a subscription for group', () => {
let plan, group, user, data; let plan; let group; let user; let
data;
beforeEach(async () => { beforeEach(async () => {
user = new User(); user = new User();
@@ -67,9 +68,9 @@ describe('Canceling a subscription for group', () => {
data.groupId = group._id; data.groupId = group._id;
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
let daysTillTermination = moment(updatedGroup.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(updatedGroup.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(29, 30); // 1 month +/- 1 days expect(daysTillTermination).to.be.within(29, 30); // 1 month +/- 1 days
}); });
@@ -81,9 +82,9 @@ describe('Canceling a subscription for group', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
let daysTillTermination = moment(updatedGroup.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(updatedGroup.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(89, 90); // 3 months +/- 1 days expect(daysTillTermination).to.be.within(89, 90); // 3 months +/- 1 days
}); });
@@ -95,9 +96,9 @@ describe('Canceling a subscription for group', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
let daysTillTermination = moment(updatedGroup.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(updatedGroup.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(38, 39); // should be about 1 month + 1/3 month expect(daysTillTermination).to.be.within(38, 39); // should be about 1 month + 1/3 month
}); });
@@ -108,9 +109,9 @@ describe('Canceling a subscription for group', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
let daysTillTermination = moment(updatedGroup.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(updatedGroup.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(13, 15); expect(daysTillTermination).to.be.within(13, 15);
}); });
@@ -122,7 +123,7 @@ describe('Canceling a subscription for group', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
expect(updatedGroup.purchased.plan.extraMonths).to.eql(0); expect(updatedGroup.purchased.plan.extraMonths).to.eql(0);
}); });
@@ -134,12 +135,12 @@ describe('Canceling a subscription for group', () => {
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(user._id); expect(sender.sendTxn.firstCall.args[0]._id).to.equal(user._id);
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-cancel-subscription'); expect(sender.sendTxn.firstCall.args[1]).to.equal('group-cancel-subscription');
expect(sender.sendTxn.firstCall.args[2]).to.eql([ expect(sender.sendTxn.firstCall.args[2]).to.eql([
{name: 'GROUP_NAME', content: group.name}, { name: 'GROUP_NAME', content: group.name },
]); ]);
}); });
it('prevents non group leader from managing subscription', async () => { it('prevents non group leader from managing subscription', async () => {
let groupMember = new User(); const groupMember = new User();
data.user = groupMember; data.user = groupMember;
data.groupId = group._id; data.groupId = group._id;
@@ -160,7 +161,7 @@ describe('Canceling a subscription for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedGroup = await Group.findById(group._id).exec(); let updatedGroup = await Group.findById(group._id).exec();
let newLeader = new User(); const newLeader = new User();
updatedGroup.leader = newLeader._id; updatedGroup.leader = newLeader._id;
await updatedGroup.save(); await updatedGroup.save();
@@ -192,15 +193,15 @@ describe('Canceling a subscription for group', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
now.setHours(0, 0, 0, 0); now.setHours(0, 0, 0, 0);
let updatedLeader = await User.findById(user._id).exec(); const updatedLeader = await User.findById(user._id).exec();
let daysTillTermination = moment(updatedLeader.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(updatedLeader.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(2, 3); // only a few days expect(daysTillTermination).to.be.within(2, 3); // only a few days
}); });
it('sends an email to members of group', async () => { it('sends an email to members of group', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
@@ -214,8 +215,8 @@ describe('Canceling a subscription for group', () => {
expect(sender.sendTxn.thirdCall.args[0]._id).to.equal(recipient._id); 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[1]).to.equal('group-member-cancel');
expect(sender.sendTxn.thirdCall.args[2]).to.eql([ expect(sender.sendTxn.thirdCall.args[2]).to.eql([
{name: 'LEADER', content: user.profile.name}, { name: 'LEADER', content: user.profile.name },
{name: 'GROUP_NAME', content: group.name}, { name: 'GROUP_NAME', content: group.name },
]); ]);
}); });
@@ -223,7 +224,7 @@ describe('Canceling a subscription for group', () => {
plan.key = 'basic_earned'; plan.key = 'basic_earned';
plan.customerId = api.constants.UNLIMITED_CUSTOMER_ID; plan.customerId = api.constants.UNLIMITED_CUSTOMER_ID;
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
@@ -233,12 +234,12 @@ describe('Canceling a subscription for group', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let updatedLeader = await User.findById(user._id).exec(); const updatedLeader = await User.findById(user._id).exec();
expect(updatedLeader.purchased.plan.dateTerminated).to.not.exist; expect(updatedLeader.purchased.plan.dateTerminated).to.not.exist;
}); });
it('does not cancel a user subscription if they are still in another active group plan', async () => { it('does not cancel a user subscription if they are still in another active group plan', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
@@ -252,10 +253,10 @@ describe('Canceling a subscription for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); let updatedUser = await User.findById(recipient._id).exec();
let firstDateCreated = updatedUser.purchased.plan.dateCreated; const firstDateCreated = updatedUser.purchased.plan.dateCreated;
let extraMonthsBeforeSecond = updatedUser.purchased.plan.extraMonths; const extraMonthsBeforeSecond = updatedUser.purchased.plan.extraMonths;
let group2 = generateGroup({ const group2 = generateGroup({
name: 'test group2', name: 'test group2',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'public',
@@ -291,10 +292,10 @@ describe('Canceling a subscription for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(user._id).exec(); let updatedUser = await User.findById(user._id).exec();
let firstDateCreated = updatedUser.purchased.plan.dateCreated; const firstDateCreated = updatedUser.purchased.plan.dateCreated;
let extraMonthsBeforeSecond = updatedUser.purchased.plan.extraMonths; const extraMonthsBeforeSecond = updatedUser.purchased.plan.extraMonths;
let group2 = generateGroup({ const group2 = generateGroup({
name: 'test group2', name: 'test group2',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'public',

View File

@@ -3,7 +3,7 @@ import stripeModule from 'stripe';
import nconf from 'nconf'; import nconf from 'nconf';
import * as sender from '../../../../../../website/server/libs/email'; import * as sender from '../../../../../../website/server/libs/email';
import * as api from '../../../../../../website/server/libs/payments/payments'; import api from '../../../../../../website/server/libs/payments/payments';
import amzLib from '../../../../../../website/server/libs/payments/amazon'; import amzLib from '../../../../../../website/server/libs/payments/amazon';
import paypalPayments from '../../../../../../website/server/libs/payments/paypal'; import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
import stripePayments from '../../../../../../website/server/libs/payments/stripe'; import stripePayments from '../../../../../../website/server/libs/payments/stripe';
@@ -11,7 +11,7 @@ import { model as User } from '../../../../../../website/server/models/user';
import { model as Group } from '../../../../../../website/server/models/group'; import { model as Group } from '../../../../../../website/server/models/group';
import { import {
generateGroup, generateGroup,
} from '../../../../../helpers/api-unit.helper.js'; } from '../../../../../helpers/api-unit.helper';
describe('Purchasing a group plan for group', () => { describe('Purchasing a group plan for group', () => {
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription';
@@ -19,10 +19,11 @@ describe('Purchasing a group plan for group', () => {
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL = 'normal_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL = 'normal_subscription';
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE = 'no_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE = 'no_subscription';
let plan, group, user, data; let plan; let group; let user; let
let stripe = stripeModule('test'); data;
let groupLeaderName = 'sender'; const stripe = stripeModule('test');
let groupName = 'test group'; const groupLeaderName = 'sender';
const groupName = 'test group';
beforeEach(async () => { beforeEach(async () => {
user = new User(); user = new User();
@@ -68,14 +69,17 @@ describe('Purchasing a group plan for group', () => {
}, },
}; };
let subscriptionId = 'subId'; const subscriptionId = 'subId';
sinon.stub(stripe.customers, 'del').resolves({}); sinon.stub(stripe.customers, 'del').resolves({});
let currentPeriodEndTimeStamp = moment().add(3, 'months').unix(); const currentPeriodEndTimeStamp = moment().add(3, 'months').unix();
sinon.stub(stripe.customers, 'retrieve') sinon.stub(stripe.customers, 'retrieve')
.resolves({ .resolves({
subscriptions: { subscriptions: {
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase data: [{
id: subscriptionId,
current_period_end: currentPeriodEndTimeStamp,
}], // eslint-disable-line camelcase
}, },
}); });
@@ -95,7 +99,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
expect(updatedGroup.purchased.plan.planId).to.eql('basic_3mo'); expect(updatedGroup.purchased.plan.planId).to.eql('basic_3mo');
expect(updatedGroup.purchased.plan.customerId).to.eql('customer-id'); expect(updatedGroup.purchased.plan.customerId).to.eql('customer-id');
@@ -126,7 +130,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
expect(updatedGroup.purchased.plan.extraMonths).to.within(1.9, 2); expect(updatedGroup.purchased.plan.extraMonths).to.within(1.9, 2);
}); });
@@ -139,7 +143,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
expect(updatedGroup.purchased.plan.extraMonths).to.eql(0); expect(updatedGroup.purchased.plan.extraMonths).to.eql(0);
}); });
@@ -150,7 +154,7 @@ describe('Purchasing a group plan for group', () => {
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
let updatedLeader = await User.findById(user._id).exec(); const updatedLeader = await User.findById(user._id).exec();
expect(updatedLeader.purchased.plan.planId).to.eql('group_plan_auto'); expect(updatedLeader.purchased.plan.planId).to.eql('group_plan_auto');
expect(updatedLeader.purchased.plan.customerId).to.eql('group-plan'); expect(updatedLeader.purchased.plan.customerId).to.eql('group-plan');
@@ -166,7 +170,7 @@ describe('Purchasing a group plan for group', () => {
}); });
it('sends an email to member of group who was not a subscriber', async () => { it('sends an email to member of group who was not a subscriber', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
@@ -179,9 +183,9 @@ describe('Purchasing a group plan for group', () => {
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id); 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[1]).to.equal('group-member-join');
expect(sender.sendTxn.firstCall.args[2]).to.eql([ expect(sender.sendTxn.firstCall.args[2]).to.eql([
{name: 'LEADER', content: user.profile.name}, { name: 'LEADER', content: user.profile.name },
{name: 'GROUP_NAME', content: group.name}, { name: 'GROUP_NAME', content: group.name },
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE}, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE },
]); ]);
// confirm that the other email sent is appropriate: // confirm that the other email sent is appropriate:
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader); expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
@@ -189,7 +193,7 @@ describe('Purchasing a group plan for group', () => {
}); });
it('sends one email to subscribed member of group, stating subscription is cancelled (Stripe)', async () => { it('sends one email to subscribed member of group, stating subscription is cancelled (Stripe)', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD; plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD;
@@ -205,9 +209,9 @@ describe('Purchasing a group plan for group', () => {
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id); 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[1]).to.equal('group-member-join');
expect(sender.sendTxn.firstCall.args[2]).to.eql([ expect(sender.sendTxn.firstCall.args[2]).to.eql([
{name: 'LEADER', content: user.profile.name}, { name: 'LEADER', content: user.profile.name },
{name: 'GROUP_NAME', content: group.name}, { name: 'GROUP_NAME', content: group.name },
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL}, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL },
]); ]);
// confirm that the other email sent is not a cancel-subscription email: // confirm that the other email sent is not a cancel-subscription email:
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader); expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
@@ -218,11 +222,11 @@ describe('Purchasing a group plan for group', () => {
sinon.stub(amzLib, 'getBillingAgreementDetails') sinon.stub(amzLib, 'getBillingAgreementDetails')
.resolves({ .resolves({
BillingAgreementDetails: { BillingAgreementDetails: {
BillingAgreementStatus: {State: 'Closed'}, BillingAgreementStatus: { State: 'Closed' },
}, },
}); });
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.planId = 'basic_earned'; plan.planId = 'basic_earned';
plan.paymentMethod = amzLib.constants.PAYMENT_METHOD; plan.paymentMethod = amzLib.constants.PAYMENT_METHOD;
@@ -238,9 +242,9 @@ describe('Purchasing a group plan for group', () => {
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id); 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[1]).to.equal('group-member-join');
expect(sender.sendTxn.firstCall.args[2]).to.eql([ expect(sender.sendTxn.firstCall.args[2]).to.eql([
{name: 'LEADER', content: user.profile.name}, { name: 'LEADER', content: user.profile.name },
{name: 'GROUP_NAME', content: group.name}, { name: 'GROUP_NAME', content: group.name },
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL}, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL },
]); ]);
// confirm that the other email sent is not a cancel-subscription email: // confirm that the other email sent is not a cancel-subscription email:
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader); expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
@@ -259,7 +263,7 @@ describe('Purchasing a group plan for group', () => {
}, },
}); });
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.planId = 'basic_earned'; plan.planId = 'basic_earned';
plan.paymentMethod = paypalPayments.constants.PAYMENT_METHOD; plan.paymentMethod = paypalPayments.constants.PAYMENT_METHOD;
@@ -275,9 +279,9 @@ describe('Purchasing a group plan for group', () => {
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id); 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[1]).to.equal('group-member-join');
expect(sender.sendTxn.firstCall.args[2]).to.eql([ expect(sender.sendTxn.firstCall.args[2]).to.eql([
{name: 'LEADER', content: user.profile.name}, { name: 'LEADER', content: user.profile.name },
{name: 'GROUP_NAME', content: group.name}, { name: 'GROUP_NAME', content: group.name },
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL}, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL },
]); ]);
// confirm that the other email sent is not a cancel-subscription email: // confirm that the other email sent is not a cancel-subscription email:
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader); expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
@@ -292,7 +296,7 @@ describe('Purchasing a group plan for group', () => {
plan.customerId = 'random'; plan.customerId = 'random';
plan.paymentMethod = api.constants.GOOGLE_PAYMENT_METHOD; plan.paymentMethod = api.constants.GOOGLE_PAYMENT_METHOD;
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
@@ -310,9 +314,9 @@ describe('Purchasing a group plan for group', () => {
expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id); expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id);
expect(sender.sendTxn.args[1][1]).to.equal('group-member-join'); expect(sender.sendTxn.args[1][1]).to.equal('group-member-join');
expect(sender.sendTxn.args[1][2]).to.eql([ expect(sender.sendTxn.args[1][2]).to.eql([
{name: 'LEADER', content: groupLeaderName}, { name: 'LEADER', content: groupLeaderName },
{name: 'GROUP_NAME', content: groupName}, { name: 'GROUP_NAME', content: groupName },
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE}, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE },
]); ]);
expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader); expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader);
expect(sender.sendTxn.args[2][1]).to.equal('group-member-join'); expect(sender.sendTxn.args[2][1]).to.equal('group-member-join');
@@ -325,7 +329,7 @@ describe('Purchasing a group plan for group', () => {
plan.customerId = 'random'; plan.customerId = 'random';
plan.paymentMethod = api.constants.IOS_PAYMENT_METHOD; plan.paymentMethod = api.constants.IOS_PAYMENT_METHOD;
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
@@ -343,9 +347,9 @@ describe('Purchasing a group plan for group', () => {
expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id); expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id);
expect(sender.sendTxn.args[1][1]).to.equal('group-member-join'); expect(sender.sendTxn.args[1][1]).to.equal('group-member-join');
expect(sender.sendTxn.args[1][2]).to.eql([ expect(sender.sendTxn.args[1][2]).to.eql([
{name: 'LEADER', content: groupLeaderName}, { name: 'LEADER', content: groupLeaderName },
{name: 'GROUP_NAME', content: groupName}, { name: 'GROUP_NAME', content: groupName },
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS}, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS },
]); ]);
expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader); expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader);
expect(sender.sendTxn.args[2][1]).to.equal('group-member-join'); expect(sender.sendTxn.args[2][1]).to.equal('group-member-join');
@@ -354,7 +358,7 @@ describe('Purchasing a group plan for group', () => {
}); });
it('adds months to members with existing gift subscription', async () => { it('adds months to members with existing gift subscription', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
@@ -378,7 +382,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.planId).to.eql('group_plan_auto'); expect(updatedUser.purchased.plan.planId).to.eql('group_plan_auto');
expect(updatedUser.purchased.plan.customerId).to.eql('group-plan'); expect(updatedUser.purchased.plan.customerId).to.eql('group-plan');
@@ -392,7 +396,7 @@ describe('Purchasing a group plan for group', () => {
}); });
it('adds months to members with existing multi-month gift subscription', async () => { it('adds months to members with existing multi-month gift subscription', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
@@ -414,7 +418,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.planId).to.eql('group_plan_auto'); expect(updatedUser.purchased.plan.planId).to.eql('group_plan_auto');
expect(updatedUser.purchased.plan.customerId).to.eql('group-plan'); expect(updatedUser.purchased.plan.customerId).to.eql('group-plan');
@@ -428,7 +432,7 @@ describe('Purchasing a group plan for group', () => {
}); });
it('adds months to members with existing recurring subscription (Stripe)', async () => { it('adds months to members with existing recurring subscription (Stripe)', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD; plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD;
@@ -450,11 +454,11 @@ describe('Purchasing a group plan for group', () => {
sinon.stub(amzLib, 'getBillingAgreementDetails') sinon.stub(amzLib, 'getBillingAgreementDetails')
.resolves({ .resolves({
BillingAgreementDetails: { BillingAgreementDetails: {
BillingAgreementStatus: {State: 'Closed'}, BillingAgreementStatus: { State: 'Closed' },
}, },
}); });
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.planId = 'basic_earned'; plan.planId = 'basic_earned';
plan.paymentMethod = amzLib.constants.PAYMENT_METHOD; plan.paymentMethod = amzLib.constants.PAYMENT_METHOD;
@@ -470,7 +474,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.extraMonths).to.within(3, 5); expect(updatedUser.purchased.plan.extraMonths).to.within(3, 5);
}); });
@@ -485,7 +489,7 @@ describe('Purchasing a group plan for group', () => {
}, },
}); });
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.planId = 'basic_earned'; plan.planId = 'basic_earned';
plan.paymentMethod = paypalPayments.constants.PAYMENT_METHOD; plan.paymentMethod = paypalPayments.constants.PAYMENT_METHOD;
@@ -500,7 +504,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.extraMonths).to.within(2, 3); expect(updatedUser.purchased.plan.extraMonths).to.within(2, 3);
paypalPayments.paypalBillingAgreementGet.restore(); paypalPayments.paypalBillingAgreementGet.restore();
@@ -511,7 +515,7 @@ describe('Purchasing a group plan for group', () => {
it('adds months to members with existing recurring subscription (iOS)'); it('adds months to members with existing recurring subscription (iOS)');
it('adds months to members who already cancelled but not yet terminated recurring subscription', async () => { it('adds months to members who already cancelled but not yet terminated recurring subscription', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD; plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD;
@@ -527,13 +531,13 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.extraMonths).to.within(2, 3); expect(updatedUser.purchased.plan.extraMonths).to.within(2, 3);
}); });
it('adds months to members who already cancelled but not yet terminated group plan subscription', async () => { it('adds months to members who already cancelled but not yet terminated group plan subscription', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
plan.paymentMethod = api.constants.GROUP_PLAN_PAYMENT_METHOD; plan.paymentMethod = api.constants.GROUP_PLAN_PAYMENT_METHOD;
@@ -550,12 +554,12 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.extraMonths).to.within(3, 4); expect(updatedUser.purchased.plan.extraMonths).to.within(3, 4);
}); });
it('resets date terminated if user has old subscription', async () => { it('resets date terminated if user has old subscription', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD; plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD;
@@ -570,13 +574,13 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.dateTerminated).to.not.exist; expect(updatedUser.purchased.plan.dateTerminated).to.not.exist;
}); });
it('adds months to members with existing recurring subscription and includes existing extraMonths', async () => { it('adds months to members with existing recurring subscription and includes existing extraMonths', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD; plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD;
@@ -591,13 +595,13 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.extraMonths).to.within(7, 9); expect(updatedUser.purchased.plan.extraMonths).to.within(7, 9);
}); });
it('adds months to members with existing recurring subscription and ignores existing negative extraMonths', async () => { it('adds months to members with existing recurring subscription and ignores existing negative extraMonths', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD; plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD;
@@ -612,23 +616,23 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.extraMonths).to.within(2, 3); expect(updatedUser.purchased.plan.extraMonths).to.within(2, 3);
}); });
it('does not override gemsBought, mysteryItems, dateCreated, and consective fields', async () => { it('does not override gemsBought, mysteryItems, dateCreated, and consective fields', async () => {
let planCreatedDate = moment().toDate(); const planCreatedDate = moment().toDate();
let mysteryItem = {title: 'item'}; const mysteryItem = { title: 'item' };
let mysteryItems = [mysteryItem]; const mysteryItems = [mysteryItem];
let consecutive = { const consecutive = {
trinkets: 3, trinkets: 3,
gemCapExtra: 20, gemCapExtra: 20,
offset: 1, offset: 1,
count: 13, count: 13,
}; };
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
@@ -647,7 +651,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.gemsBought).to.equal(3); expect(updatedUser.purchased.plan.gemsBought).to.equal(3);
expect(updatedUser.purchased.plan.mysteryItems[0]).to.eql(mysteryItem); expect(updatedUser.purchased.plan.mysteryItems[0]).to.eql(mysteryItem);
@@ -659,7 +663,7 @@ describe('Purchasing a group plan for group', () => {
}); });
it('does not modify a user with a group subscription when they join another group', async () => { it('does not modify a user with a group subscription when they join another group', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
@@ -673,10 +677,10 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); let updatedUser = await User.findById(recipient._id).exec();
let firstDateCreated = updatedUser.purchased.plan.dateCreated; const firstDateCreated = updatedUser.purchased.plan.dateCreated;
let extraMonthsBeforeSecond = updatedUser.purchased.plan.extraMonths; const extraMonthsBeforeSecond = updatedUser.purchased.plan.extraMonths;
let group2 = generateGroup({ const group2 = generateGroup({
name: 'test group2', name: 'test group2',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'public',
@@ -703,7 +707,7 @@ describe('Purchasing a group plan for group', () => {
}); });
it('does not remove a user who is in two groups plans and leaves one', async () => { it('does not remove a user who is in two groups plans and leaves one', async () => {
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
plan.key = 'basic_earned'; plan.key = 'basic_earned';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
@@ -717,10 +721,10 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); let updatedUser = await User.findById(recipient._id).exec();
let firstDateCreated = updatedUser.purchased.plan.dateCreated; const firstDateCreated = updatedUser.purchased.plan.dateCreated;
let extraMonthsBeforeSecond = updatedUser.purchased.plan.extraMonths; const extraMonthsBeforeSecond = updatedUser.purchased.plan.extraMonths;
let group2 = generateGroup({ const group2 = generateGroup({
name: 'test group2', name: 'test group2',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'public',
@@ -733,7 +737,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
await updatedGroup.leave(recipient); await updatedGroup.leave(recipient);
updatedUser = await User.findById(recipient._id).exec(); updatedUser = await User.findById(recipient._id).exec();
@@ -753,7 +757,7 @@ describe('Purchasing a group plan for group', () => {
plan.key = 'basic_earned'; plan.key = 'basic_earned';
plan.customerId = api.constants.UNLIMITED_CUSTOMER_ID; plan.customerId = api.constants.UNLIMITED_CUSTOMER_ID;
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
@@ -765,7 +769,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.planId).to.eql('basic_3mo'); expect(updatedUser.purchased.plan.planId).to.eql('basic_3mo');
expect(updatedUser.purchased.plan.customerId).to.eql(api.constants.UNLIMITED_CUSTOMER_ID); expect(updatedUser.purchased.plan.customerId).to.eql(api.constants.UNLIMITED_CUSTOMER_ID);
@@ -782,7 +786,7 @@ describe('Purchasing a group plan for group', () => {
plan.customerId = 'random'; plan.customerId = 'random';
plan.paymentMethod = api.constants.GOOGLE_PAYMENT_METHOD; plan.paymentMethod = api.constants.GOOGLE_PAYMENT_METHOD;
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
@@ -794,7 +798,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.planId).to.eql('basic_3mo'); expect(updatedUser.purchased.plan.planId).to.eql('basic_3mo');
expect(updatedUser.purchased.plan.customerId).to.eql('random'); expect(updatedUser.purchased.plan.customerId).to.eql('random');
@@ -811,7 +815,7 @@ describe('Purchasing a group plan for group', () => {
plan.customerId = 'random'; plan.customerId = 'random';
plan.paymentMethod = api.constants.IOS_PAYMENT_METHOD; plan.paymentMethod = api.constants.IOS_PAYMENT_METHOD;
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
@@ -823,7 +827,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.planId).to.eql('basic_3mo'); expect(updatedUser.purchased.plan.planId).to.eql('basic_3mo');
expect(updatedUser.purchased.plan.customerId).to.eql('random'); expect(updatedUser.purchased.plan.customerId).to.eql('random');
@@ -841,7 +845,7 @@ describe('Purchasing a group plan for group', () => {
plan.customerId = api.constants.GROUP_PLAN_CUSTOMER_ID; plan.customerId = api.constants.GROUP_PLAN_CUSTOMER_ID;
plan.dateTerminated = moment().add(1, 'months'); plan.dateTerminated = moment().add(1, 'months');
let recipient = new User(); const recipient = new User();
recipient.profile.name = 'recipient'; recipient.profile.name = 'recipient';
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
@@ -853,7 +857,7 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
let updatedUser = await User.findById(recipient._id).exec(); const updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.planId).to.eql('group_plan_auto'); expect(updatedUser.purchased.plan.planId).to.eql('group_plan_auto');
expect(updatedUser.purchased.plan.customerId).to.eql(api.constants.GROUP_PLAN_CUSTOMER_ID); expect(updatedUser.purchased.plan.customerId).to.eql(api.constants.GROUP_PLAN_CUSTOMER_ID);

View File

@@ -1,7 +1,7 @@
import { model as User } from '../../../../../website/server/models/user'; import { model as User } from '../../../../../website/server/models/user';
export async function createNonLeaderGroupMember (group) { export async function createNonLeaderGroupMember (group) { // eslint-disable-line import/prefer-default-export, max-len
let nonLeader = new User(); const nonLeader = new User();
nonLeader.guilds.push(group._id); nonLeader.guilds.push(group._id);
return await nonLeader.save(); return nonLeader.save();
} }

View File

@@ -1,17 +1,18 @@
import moment from 'moment'; import moment from 'moment';
import * as sender from '../../../../../website/server/libs/email'; import * as sender from '../../../../../website/server/libs/email';
import * as api from '../../../../../website/server/libs/payments/payments'; import api from '../../../../../website/server/libs/payments/payments';
import analytics from '../../../../../website/server/libs/analyticsService'; import * as analytics from '../../../../../website/server/libs/analyticsService';
import notifications from '../../../../../website/server/libs/pushNotifications'; import * as notifications from '../../../../../website/server/libs/pushNotifications';
import { model as User } from '../../../../../website/server/models/user'; import { model as User } from '../../../../../website/server/models/user';
import { translate as t } from '../../../../helpers/api-integration/v3'; import { translate as t } from '../../../../helpers/api-integration/v3';
import { import {
generateGroup, generateGroup,
} from '../../../../helpers/api-unit.helper.js'; } from '../../../../helpers/api-unit.helper';
describe('payments/index', () => { describe('payments/index', () => {
let user, group, data, plan; let user; let group; let data; let
plan;
beforeEach(async () => { beforeEach(async () => {
user = new User(); user = new User();
@@ -102,7 +103,7 @@ describe('payments/index', () => {
}); });
it('does not set negative extraMonths if plan has past dateTerminated date', async () => { it('does not set negative extraMonths if plan has past dateTerminated date', async () => {
let dateTerminated = moment().subtract(2, 'months').toDate(); const dateTerminated = moment().subtract(2, 'months').toDate();
recipient.purchased.plan.dateTerminated = dateTerminated; recipient.purchased.plan.dateTerminated = dateTerminated;
await api.createSubscription(data); await api.createSubscription(data);
@@ -120,7 +121,7 @@ describe('payments/index', () => {
}); });
it('adds to date terminated for an existing plan with a future terminated date', async () => { it('adds to date terminated for an existing plan with a future terminated date', async () => {
let dateTerminated = moment().add(1, 'months').toDate(); const dateTerminated = moment().add(1, 'months').toDate();
recipient.purchased.plan = plan; recipient.purchased.plan = plan;
recipient.purchased.plan.dateTerminated = dateTerminated; recipient.purchased.plan.dateTerminated = dateTerminated;
@@ -130,7 +131,7 @@ describe('payments/index', () => {
}); });
it('replaces date terminated for an account with a past terminated date', async () => { it('replaces date terminated for an account with a past terminated date', async () => {
let dateTerminated = moment().subtract(1, 'months').toDate(); const dateTerminated = moment().subtract(1, 'months').toDate();
recipient.purchased.plan.dateTerminated = dateTerminated; recipient.purchased.plan.dateTerminated = dateTerminated;
await api.createSubscription(data); await api.createSubscription(data);
@@ -208,18 +209,21 @@ describe('payments/index', () => {
it('sends a private message about the gift', async () => { it('sends a private message about the gift', async () => {
await api.createSubscription(data); await api.createSubscription(data);
let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`'; const msg = '`Hello recipient, sender has sent you 3 months of subscription!`';
expect(user.sendMessage).to.be.calledOnce; expect(user.sendMessage).to.be.calledOnce;
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false }); expect(user.sendMessage).to.be.calledWith(
recipient,
{ receiverMsg: msg, senderMsg: msg, save: false },
);
}); });
it('sends an email about the gift', async () => { it('sends an email about the gift', async () => {
await api.createSubscription(data); await api.createSubscription(data);
expect(sender.sendTxn).to.be.calledWith(recipient, 'gifted-subscription', [ expect(sender.sendTxn).to.be.calledWith(recipient, 'gifted-subscription', [
{name: 'GIFTER', content: 'sender'}, { name: 'GIFTER', content: 'sender' },
{name: 'X_MONTHS_SUBSCRIPTION', content: 3}, { name: 'X_MONTHS_SUBSCRIPTION', content: 3 },
]); ]);
}); });
@@ -416,8 +420,8 @@ describe('payments/index', () => {
context('Mystery Items', () => { context('Mystery Items', () => {
it('awards mystery items when within the timeframe for a mystery item', async () => { it('awards mystery items when within the timeframe for a mystery item', async () => {
let mayMysteryItemTimeframe = 1464725113000; // May 31st 2016 const mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
let fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe); const fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } }; data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
@@ -437,7 +441,7 @@ describe('payments/index', () => {
it('does not awards mystery items when not within the timeframe for a mystery item', async () => { it('does not awards mystery items when not within the timeframe for a mystery item', async () => {
const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016 const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016
let fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe); const fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe);
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } }; data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
await api.createSubscription(data); await api.createSubscription(data);
@@ -449,7 +453,7 @@ describe('payments/index', () => {
it('does not add a notification for mystery items if none was awarded', async () => { it('does not add a notification for mystery items if none was awarded', async () => {
const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016 const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016
let fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe); const fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe);
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } }; data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
await api.createSubscription(data); await api.createSubscription(data);
@@ -461,9 +465,9 @@ describe('payments/index', () => {
}); });
it('does not award mystery item when user already owns the item', async () => { it('does not award mystery item when user already owns the item', async () => {
let mayMysteryItemTimeframe = 1464725113000; // May 31st 2016 const mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
let fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe); const fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
let mayMysteryItem = 'armor_mystery_201605'; const mayMysteryItem = 'armor_mystery_201605';
user.items.gear.owned[mayMysteryItem] = true; user.items.gear.owned[mayMysteryItem] = true;
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } }; data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
@@ -477,9 +481,9 @@ describe('payments/index', () => {
}); });
it('does not award mystery item when user already has the item in the mystery box', async () => { it('does not award mystery item when user already has the item in the mystery box', async () => {
let mayMysteryItemTimeframe = 1464725113000; // May 31st 2016 const mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
let fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe); const fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
let mayMysteryItem = 'armor_mystery_201605'; const mayMysteryItem = 'armor_mystery_201605';
user.purchased.plan.mysteryItems = [mayMysteryItem]; user.purchased.plan.mysteryItems = [mayMysteryItem];
sandbox.spy(user.purchased.plan.mysteryItems, 'push'); sandbox.spy(user.purchased.plan.mysteryItems, 'push');
@@ -504,8 +508,8 @@ describe('payments/index', () => {
it('adds a month termination date by default', async () => { it('adds a month termination date by default', async () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
let daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(29, 30); // 1 month +/- 1 days expect(daysTillTermination).to.be.within(29, 30); // 1 month +/- 1 days
}); });
@@ -515,8 +519,8 @@ describe('payments/index', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
let daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(89, 90); // 3 months +/- 1 days expect(daysTillTermination).to.be.within(89, 90); // 3 months +/- 1 days
}); });
@@ -526,8 +530,8 @@ describe('payments/index', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
let daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(38, 39); // should be about 1 month + 1/3 month expect(daysTillTermination).to.be.within(38, 39); // should be about 1 month + 1/3 month
}); });
@@ -537,8 +541,8 @@ describe('payments/index', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
let daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(13, 15); expect(daysTillTermination).to.be.within(13, 15);
}); });
@@ -549,8 +553,8 @@ describe('payments/index', () => {
await api.cancelSubscription(data); await api.cancelSubscription(data);
let now = new Date(); const now = new Date();
let daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days'); const daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days');
expect(daysTillTermination).to.be.within(13, 15); expect(daysTillTermination).to.be.within(13, 15);
}); });
@@ -641,9 +645,10 @@ describe('payments/index', () => {
it('sends a message from purchaser to recipient', async () => { it('sends a message from purchaser to recipient', async () => {
await api.buyGems(data); await api.buyGems(data);
let msg = '\`Hello recipient, sender has sent you 4 gems!\`'; const msg = '`Hello recipient, sender has sent you 4 gems!`';
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false }); expect(user.sendMessage).to.be
.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
}); });
it('sends a message from purchaser to recipient wtih custom message', async () => { it('sends a message from purchaser to recipient wtih custom message', async () => {
@@ -652,7 +657,8 @@ describe('payments/index', () => {
await api.buyGems(data); await api.buyGems(data);
const msg = `\`Hello recipient, sender has sent you 4 gems!\` ${data.gift.message}`; const msg = `\`Hello recipient, sender has sent you 4 gems!\` ${data.gift.message}`;
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false }); expect(user.sendMessage).to.be
.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
}); });
it('sends a push notification if user did not gift to self', async () => { it('sends a push notification if user did not gift to self', async () => {
@@ -671,8 +677,8 @@ describe('payments/index', () => {
}); });
await api.buyGems(data); await api.buyGems(data);
let [recipientsMessageContent, sendersMessageContent] = ['en', 'en'].map((lang) => { const [recipientsMessageContent, sendersMessageContent] = ['en', 'en'].map(lang => {
let messageContent = t('giftedGemsFull', { const messageContent = t('giftedGemsFull', {
username: recipient.profile.name, username: recipient.profile.name,
sender: user.profile.name, sender: user.profile.name,
gemAmount: data.gift.gems.amount, gemAmount: data.gift.gems.amount,
@@ -681,7 +687,10 @@ describe('payments/index', () => {
return `\`${messageContent}\``; return `\`${messageContent}\``;
}); });
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: recipientsMessageContent, senderMsg: sendersMessageContent, save: false }); expect(user.sendMessage).to.be.calledWith(
recipient,
{ receiverMsg: recipientsMessageContent, senderMsg: sendersMessageContent, save: false },
);
}); });
}); });
}); });
@@ -693,7 +702,7 @@ describe('payments/index', () => {
await api.addSubToGroupUser(user, group); await api.addSubToGroupUser(user, group);
let updatedUser = await User.findById(user._id).exec(); const updatedUser = await User.findById(user._id).exec();
expect(updatedUser.purchased.plan.planId).to.eql('group_plan_auto'); expect(updatedUser.purchased.plan.planId).to.eql('group_plan_auto');
expect(updatedUser.purchased.plan.customerId).to.eql('group-plan'); expect(updatedUser.purchased.plan.customerId).to.eql('group-plan');
@@ -709,17 +718,17 @@ describe('payments/index', () => {
it('awards the Royal Purple Jackalope pet', async () => { it('awards the Royal Purple Jackalope pet', async () => {
await api.addSubToGroupUser(user, group); await api.addSubToGroupUser(user, group);
let updatedUser = await User.findById(user._id).exec(); const updatedUser = await User.findById(user._id).exec();
expect(updatedUser.items.pets['Jackalope-RoyalPurple']).to.eql(5); expect(updatedUser.items.pets['Jackalope-RoyalPurple']).to.eql(5);
}); });
it('saves previously unused Mystery Items and Hourglasses for an expired subscription', async () => { it('saves previously unused Mystery Items and Hourglasses for an expired subscription', async () => {
let planExpirationDate = new Date(); const planExpirationDate = new Date();
planExpirationDate.setDate(planExpirationDate.getDate() - 2); planExpirationDate.setDate(planExpirationDate.getDate() - 2);
let mysteryItem = 'item'; const mysteryItem = 'item';
let mysteryItems = [mysteryItem]; const mysteryItems = [mysteryItem];
let consecutive = { const consecutive = {
trinkets: 3, trinkets: 3,
}; };
@@ -735,7 +744,7 @@ describe('payments/index', () => {
await user.save(); await user.save();
await api.addSubToGroupUser(user, group); await api.addSubToGroupUser(user, group);
let updatedUser = await User.findById(user._id).exec(); const updatedUser = await User.findById(user._id).exec();
expect(updatedUser.purchased.plan.mysteryItems[0]).to.eql(mysteryItem); expect(updatedUser.purchased.plan.mysteryItems[0]).to.eql(mysteryItem);
expect(updatedUser.purchased.plan.consecutive.trinkets).to.equal(consecutive.trinkets); expect(updatedUser.purchased.plan.consecutive.trinkets).to.equal(consecutive.trinkets);

View File

@@ -5,8 +5,10 @@ import { model as User } from '../../../../../../website/server/models/user';
describe('checkout success', () => { describe('checkout success', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
let user, gift, customerId, paymentId; let user; let gift; let customerId; let
let paypalPaymentExecuteStub, paymentBuyGemsStub, paymentsCreateSubscritionStub; paymentId;
let paypalPaymentExecuteStub; let paymentBuyGemsStub; let
paymentsCreateSubscritionStub;
beforeEach(() => { beforeEach(() => {
user = new User(); user = new User();
@@ -25,7 +27,9 @@ describe('checkout success', () => {
}); });
it('purchases gems', async () => { it('purchases gems', async () => {
await paypalPayments.checkoutSuccess({user, gift, paymentId, customerId}); await paypalPayments.checkoutSuccess({
user, gift, paymentId, customerId,
});
expect(paypalPaymentExecuteStub).to.be.calledOnce; expect(paypalPaymentExecuteStub).to.be.calledOnce;
expect(paypalPaymentExecuteStub).to.be.calledWith(paymentId, { payer_id: customerId }); expect(paypalPaymentExecuteStub).to.be.calledWith(paymentId, { payer_id: customerId });
@@ -38,7 +42,7 @@ describe('checkout success', () => {
}); });
it('gifts gems', async () => { it('gifts gems', async () => {
let receivingUser = new User(); const receivingUser = new User();
await receivingUser.save(); await receivingUser.save();
gift = { gift = {
type: 'gems', type: 'gems',
@@ -48,7 +52,9 @@ describe('checkout success', () => {
}, },
}; };
await paypalPayments.checkoutSuccess({user, gift, paymentId, customerId}); await paypalPayments.checkoutSuccess({
user, gift, paymentId, customerId,
});
expect(paypalPaymentExecuteStub).to.be.calledOnce; expect(paypalPaymentExecuteStub).to.be.calledOnce;
expect(paypalPaymentExecuteStub).to.be.calledWith(paymentId, { payer_id: customerId }); expect(paypalPaymentExecuteStub).to.be.calledWith(paymentId, { payer_id: customerId });
@@ -62,7 +68,7 @@ describe('checkout success', () => {
}); });
it('gifts subscription', async () => { it('gifts subscription', async () => {
let receivingUser = new User(); const receivingUser = new User();
await receivingUser.save(); await receivingUser.save();
gift = { gift = {
type: 'subscription', type: 'subscription',
@@ -72,7 +78,9 @@ describe('checkout success', () => {
}, },
}; };
await paypalPayments.checkoutSuccess({user, gift, paymentId, customerId}); await paypalPayments.checkoutSuccess({
user, gift, paymentId, customerId,
});
expect(paypalPaymentExecuteStub).to.be.calledOnce; expect(paypalPaymentExecuteStub).to.be.calledOnce;
expect(paypalPaymentExecuteStub).to.be.calledWith(paymentId, { payer_id: customerId }); expect(paypalPaymentExecuteStub).to.be.calledWith(paymentId, { payer_id: customerId });

View File

@@ -6,7 +6,7 @@ import { model as User } from '../../../../../../website/server/models/user';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
const BASE_URL = nconf.get('BASE_URL'); const BASE_URL = nconf.get('BASE_URL');
const i18n = common.i18n; const { i18n } = common;
describe('checkout', () => { describe('checkout', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
@@ -53,7 +53,7 @@ describe('checkout', () => {
}); });
it('creates a link for gem purchases', async () => { it('creates a link for gem purchases', async () => {
let link = await paypalPayments.checkout({user: new User()}); const link = await paypalPayments.checkout({ user: new User() });
expect(paypalPaymentCreateStub).to.be.calledOnce; expect(paypalPaymentCreateStub).to.be.calledOnce;
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 5.00)); expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 5.00));
@@ -61,9 +61,9 @@ describe('checkout', () => {
}); });
it('should error if gem amount is too low', async () => { it('should error if gem amount is too low', async () => {
let receivingUser = new User(); const receivingUser = new User();
receivingUser.save(); receivingUser.save();
let gift = { const gift = {
type: 'gems', type: 'gems',
gems: { gems: {
amount: 0, amount: 0,
@@ -71,7 +71,7 @@ describe('checkout', () => {
}, },
}; };
await expect(paypalPayments.checkout({gift})) await expect(paypalPayments.checkout({ gift }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 400, httpCode: 400,
message: 'Amount must be at least 1.', message: 'Amount must be at least 1.',
@@ -80,10 +80,10 @@ describe('checkout', () => {
}); });
it('should error if the user cannot get gems', async () => { it('should error if the user cannot get gems', async () => {
let user = new User(); const user = new User();
sinon.stub(user, 'canGetGems').resolves(false); sinon.stub(user, 'canGetGems').resolves(false);
await expect(paypalPayments.checkout({user})).to.eventually.be.rejected.and.to.eql({ await expect(paypalPayments.checkout({ user })).to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
message: i18n.t('groupPolicyCannotGetGems'), message: i18n.t('groupPolicyCannotGetGems'),
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -91,9 +91,9 @@ describe('checkout', () => {
}); });
it('creates a link for gifting gems', async () => { it('creates a link for gifting gems', async () => {
let receivingUser = new User(); const receivingUser = new User();
await receivingUser.save(); await receivingUser.save();
let gift = { const gift = {
type: 'gems', type: 'gems',
uuid: receivingUser._id, uuid: receivingUser._id,
gems: { gems: {
@@ -101,7 +101,7 @@ describe('checkout', () => {
}, },
}; };
let link = await paypalPayments.checkout({gift}); const link = await paypalPayments.checkout({ gift });
expect(paypalPaymentCreateStub).to.be.calledOnce; expect(paypalPaymentCreateStub).to.be.calledOnce;
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems (Gift)', '4.00')); expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems (Gift)', '4.00'));
@@ -109,9 +109,9 @@ describe('checkout', () => {
}); });
it('creates a link for gifting a subscription', async () => { it('creates a link for gifting a subscription', async () => {
let receivingUser = new User(); const receivingUser = new User();
receivingUser.save(); receivingUser.save();
let gift = { const gift = {
type: 'subscription', type: 'subscription',
subscription: { subscription: {
key: subKey, key: subKey,
@@ -119,7 +119,7 @@ describe('checkout', () => {
}, },
}; };
let link = await paypalPayments.checkout({gift}); const link = await paypalPayments.checkout({ gift });
expect(paypalPaymentCreateStub).to.be.calledOnce; expect(paypalPaymentCreateStub).to.be.calledOnce;
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('mo. Habitica Subscription (Gift)', '15.00')); expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('mo. Habitica Subscription (Gift)', '15.00'));

View File

@@ -3,13 +3,15 @@ import paypalPayments from '../../../../../../website/server/libs/payments/paypa
import payments from '../../../../../../website/server/libs/payments/payments'; import payments from '../../../../../../website/server/libs/payments/payments';
import { import {
generateGroup, generateGroup,
} from '../../../../../helpers/api-unit.helper.js'; } from '../../../../../helpers/api-unit.helper';
import { model as User } from '../../../../../../website/server/models/user'; import { model as User } from '../../../../../../website/server/models/user';
describe('ipn', () => { describe('ipn', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
let user, group, txn_type, userPaymentId, groupPaymentId; let user; let group; let txn_type; let userPaymentId; let
let ipnVerifyAsyncStub, paymentCancelSubscriptionSpy; groupPaymentId;
let ipnVerifyAsyncStub; let
paymentCancelSubscriptionSpy;
beforeEach(async () => { beforeEach(async () => {
txn_type = 'recurring_payment_profile_cancel'; txn_type = 'recurring_payment_profile_cancel';
@@ -38,16 +40,16 @@ describe('ipn', () => {
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({}); paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
}); });
afterEach(function () { afterEach(() => {
paypalPayments.ipnVerifyAsync.restore(); paypalPayments.ipnVerifyAsync.restore();
payments.cancelSubscription.restore(); payments.cancelSubscription.restore();
}); });
it('should cancel a user subscription', async () => { it('should cancel a user subscription', async () => {
await paypalPayments.ipn({txn_type, recurring_payment_id: userPaymentId}); await paypalPayments.ipn({ txn_type, recurring_payment_id: userPaymentId });
expect(ipnVerifyAsyncStub).to.be.calledOnce; expect(ipnVerifyAsyncStub).to.be.calledOnce;
expect(ipnVerifyAsyncStub).to.be.calledWith({txn_type, recurring_payment_id: userPaymentId}); expect(ipnVerifyAsyncStub).to.be.calledWith({ txn_type, recurring_payment_id: userPaymentId });
expect(paymentCancelSubscriptionSpy).to.be.calledOnce; expect(paymentCancelSubscriptionSpy).to.be.calledOnce;
expect(paymentCancelSubscriptionSpy.args[0][0].user._id).to.eql(user._id); expect(paymentCancelSubscriptionSpy.args[0][0].user._id).to.eql(user._id);
@@ -55,10 +57,10 @@ describe('ipn', () => {
}); });
it('should cancel a group subscription', async () => { it('should cancel a group subscription', async () => {
await paypalPayments.ipn({txn_type, recurring_payment_id: groupPaymentId}); await paypalPayments.ipn({ txn_type, recurring_payment_id: groupPaymentId });
expect(ipnVerifyAsyncStub).to.be.calledOnce; expect(ipnVerifyAsyncStub).to.be.calledOnce;
expect(ipnVerifyAsyncStub).to.be.calledWith({txn_type, recurring_payment_id: groupPaymentId}); expect(ipnVerifyAsyncStub).to.be.calledWith({ txn_type, recurring_payment_id: groupPaymentId });
expect(paymentCancelSubscriptionSpy).to.be.calledOnce; expect(paymentCancelSubscriptionSpy).to.be.calledOnce;
expect(paymentCancelSubscriptionSpy).to.be.calledWith({ groupId: group._id, paymentMethod: 'Paypal' }); expect(paymentCancelSubscriptionSpy).to.be.calledWith({ groupId: group._id, paymentMethod: 'Paypal' });

View File

@@ -3,17 +3,19 @@ import paypalPayments from '../../../../../../website/server/libs/payments/paypa
import payments from '../../../../../../website/server/libs/payments/payments'; import payments from '../../../../../../website/server/libs/payments/payments';
import { import {
generateGroup, generateGroup,
} from '../../../../../helpers/api-unit.helper.js'; } from '../../../../../helpers/api-unit.helper';
import { model as User } from '../../../../../../website/server/models/user'; import { model as User } from '../../../../../../website/server/models/user';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
import { createNonLeaderGroupMember } from '../paymentHelpers'; import { createNonLeaderGroupMember } from '../paymentHelpers';
const i18n = common.i18n; const { i18n } = common;
describe('subscribeCancel', () => { describe('subscribeCancel', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
let user, group, groupId, customerId, groupCustomerId, nextBillingDate; let user; let group; let groupId; let customerId; let groupCustomerId; let
let paymentCancelSubscriptionSpy, paypalBillingAgreementCancelStub, paypalBillingAgreementGetStub; nextBillingDate;
let paymentCancelSubscriptionSpy; let paypalBillingAgreementCancelStub; let
paypalBillingAgreementGetStub;
beforeEach(async () => { beforeEach(async () => {
customerId = 'customer-id'; customerId = 'customer-id';
@@ -49,7 +51,7 @@ describe('subscribeCancel', () => {
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({}); paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
}); });
afterEach(function () { afterEach(() => {
paypalPayments.paypalBillingAgreementGet.restore(); paypalPayments.paypalBillingAgreementGet.restore();
paypalPayments.paypalBillingAgreementCancel.restore(); paypalPayments.paypalBillingAgreementCancel.restore();
payments.cancelSubscription.restore(); payments.cancelSubscription.restore();
@@ -58,7 +60,7 @@ describe('subscribeCancel', () => {
it('should throw an error if we are missing a subscription', async () => { it('should throw an error if we are missing a subscription', async () => {
user.purchased.plan.customerId = undefined; user.purchased.plan.customerId = undefined;
await expect(paypalPayments.subscribeCancel({user})) await expect(paypalPayments.subscribeCancel({ user }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -67,7 +69,7 @@ describe('subscribeCancel', () => {
}); });
it('should throw an error if group is not found', async () => { it('should throw an error if group is not found', async () => {
await expect(paypalPayments.subscribeCancel({user, groupId: 'fake-id'})) await expect(paypalPayments.subscribeCancel({ user, groupId: 'fake-id' }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 404, httpCode: 404,
name: 'NotFound', name: 'NotFound',
@@ -76,9 +78,9 @@ describe('subscribeCancel', () => {
}); });
it('should throw an error if user is not group leader', async () => { it('should throw an error if user is not group leader', async () => {
let nonLeader = await createNonLeaderGroupMember(group); const nonLeader = await createNonLeaderGroupMember(group);
await expect(paypalPayments.subscribeCancel({user: nonLeader, groupId: group._id})) await expect(paypalPayments.subscribeCancel({ user: nonLeader, groupId: group._id }))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
httpCode: 401, httpCode: 401,
name: 'NotAuthorized', name: 'NotAuthorized',
@@ -87,7 +89,7 @@ describe('subscribeCancel', () => {
}); });
it('should cancel a user subscription', async () => { it('should cancel a user subscription', async () => {
await paypalPayments.subscribeCancel({user}); await paypalPayments.subscribeCancel({ user });
expect(paypalBillingAgreementGetStub).to.be.calledOnce; expect(paypalBillingAgreementGetStub).to.be.calledOnce;
expect(paypalBillingAgreementGetStub).to.be.calledWith(customerId); expect(paypalBillingAgreementGetStub).to.be.calledWith(customerId);
@@ -105,7 +107,7 @@ describe('subscribeCancel', () => {
}); });
it('should cancel a group subscription', async () => { it('should cancel a group subscription', async () => {
await paypalPayments.subscribeCancel({user, groupId: group._id}); await paypalPayments.subscribeCancel({ user, groupId: group._id });
expect(paypalBillingAgreementGetStub).to.be.calledOnce; expect(paypalBillingAgreementGetStub).to.be.calledOnce;
expect(paypalBillingAgreementGetStub).to.be.calledWith(groupCustomerId); expect(paypalBillingAgreementGetStub).to.be.calledWith(groupCustomerId);

View File

@@ -3,14 +3,16 @@ import paypalPayments from '../../../../../../website/server/libs/payments/paypa
import payments from '../../../../../../website/server/libs/payments/payments'; import payments from '../../../../../../website/server/libs/payments/payments';
import { import {
generateGroup, generateGroup,
} from '../../../../../helpers/api-unit.helper.js'; } from '../../../../../helpers/api-unit.helper';
import { model as User } from '../../../../../../website/server/models/user'; import { model as User } from '../../../../../../website/server/models/user';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
describe('subscribeSuccess', () => { describe('subscribeSuccess', () => {
const subKey = 'basic_3mo'; const subKey = 'basic_3mo';
let user, group, block, groupId, token, headers, customerId; let user; let group; let block; let groupId; let token; let headers; let
let paypalBillingAgreementExecuteStub, paymentsCreateSubscritionStub; customerId;
let paypalBillingAgreementExecuteStub; let
paymentsCreateSubscritionStub;
beforeEach(async () => { beforeEach(async () => {
user = new User(); user = new User();
@@ -40,7 +42,9 @@ describe('subscribeSuccess', () => {
}); });
it('creates a user subscription', async () => { it('creates a user subscription', async () => {
await paypalPayments.subscribeSuccess({user, block, groupId, token, headers}); await paypalPayments.subscribeSuccess({
user, block, groupId, token, headers,
});
expect(paypalBillingAgreementExecuteStub).to.be.calledOnce; expect(paypalBillingAgreementExecuteStub).to.be.calledOnce;
expect(paypalBillingAgreementExecuteStub).to.be.calledWith(token, {}); expect(paypalBillingAgreementExecuteStub).to.be.calledWith(token, {});
@@ -59,7 +63,9 @@ describe('subscribeSuccess', () => {
it('create a group subscription', async () => { it('create a group subscription', async () => {
groupId = group._id; groupId = group._id;
await paypalPayments.subscribeSuccess({user, block, groupId, token, headers}); await paypalPayments.subscribeSuccess({
user, block, groupId, token, headers,
});
expect(paypalBillingAgreementExecuteStub).to.be.calledOnce; expect(paypalBillingAgreementExecuteStub).to.be.calledOnce;
expect(paypalBillingAgreementExecuteStub).to.be.calledWith(token, {}); expect(paypalBillingAgreementExecuteStub).to.be.calledWith(token, {});

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