mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-13 04:37:36 +01:00
Compare commits
292 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3e41cc5e1 | ||
|
|
cae0120beb | ||
|
|
10b978da4e | ||
|
|
5987145b86 | ||
|
|
ff65c4da78 | ||
|
|
163d56afe9 | ||
|
|
2f46d1bc65 | ||
|
|
6ccb841b32 | ||
|
|
cdcee8d169 | ||
|
|
fd5ab32c43 | ||
|
|
e0933dc0a1 | ||
|
|
d69e7e66ee | ||
|
|
fa06628361 | ||
|
|
7f1067e1ab | ||
|
|
db6644a572 | ||
|
|
b7b8faedd6 | ||
|
|
7da0b641dd | ||
|
|
82ff241e39 | ||
|
|
cf4aaf1618 | ||
|
|
31eeb13598 | ||
|
|
818c201afe | ||
|
|
f78bcc20c9 | ||
|
|
9017eea0c4 | ||
|
|
92abb19f9c | ||
|
|
3bee0446b8 | ||
|
|
e1e5a6cc34 | ||
|
|
9eb70ee9ba | ||
|
|
7485300ee9 | ||
|
|
762c202154 | ||
|
|
7613e50917 | ||
|
|
4c77434bf0 | ||
|
|
1ab26a200e | ||
|
|
b738824f76 | ||
|
|
694c440b55 | ||
|
|
667bb28fe3 | ||
|
|
8993337ad7 | ||
|
|
a251a5d089 | ||
|
|
dcdeec6256 | ||
|
|
9fd0df9f2f | ||
|
|
1061fb0c31 | ||
|
|
c14fdd3fed | ||
|
|
e219ad6bdf | ||
|
|
42d7fd0861 | ||
|
|
7ade91a8b8 | ||
|
|
7c6dd6a6bd | ||
|
|
9a00779698 | ||
|
|
a61d911c48 | ||
|
|
fbacb56700 | ||
|
|
185717e6c3 | ||
|
|
505dd4969d | ||
|
|
944e4fe399 | ||
|
|
804dd087f8 | ||
|
|
cb1136aadc | ||
|
|
0f4b8f5f30 | ||
|
|
2d612b655d | ||
|
|
46877fb20c | ||
|
|
38cded2083 | ||
|
|
65074df668 | ||
|
|
b0ab09c352 | ||
|
|
3a60e8de66 | ||
|
|
886a96dac9 | ||
|
|
20556e2af4 | ||
|
|
3e849ec9a8 | ||
|
|
03946e6a87 | ||
|
|
fdcc69fe4a | ||
|
|
322b6a5e44 | ||
|
|
2243a2f3dc | ||
|
|
59c848add5 | ||
|
|
2d6a1fe709 | ||
|
|
fac0338437 | ||
|
|
e46f30894c | ||
|
|
553dc116c5 | ||
|
|
2029cd884c | ||
|
|
ab89941ed5 | ||
|
|
5dc0f5bb9b | ||
|
|
d46a7ba985 | ||
|
|
86b3228a59 | ||
|
|
4efbbd7bac | ||
|
|
d39e8a3587 | ||
|
|
8bd3ef6f24 | ||
|
|
6d0917964b | ||
|
|
d463e2373e | ||
|
|
2af99d7c65 | ||
|
|
4e01b14874 | ||
|
|
944781c2f8 | ||
|
|
027eed1b25 | ||
|
|
803f63d991 | ||
|
|
78ad1cd8b0 | ||
|
|
a3ddd0746c | ||
|
|
94845ec629 | ||
|
|
7e63856e64 | ||
|
|
3727d69d51 | ||
|
|
1fbdb7dbd0 | ||
|
|
b430b6ccb6 | ||
|
|
7859f20a40 | ||
|
|
389d6f18b4 | ||
|
|
fd1d1da509 | ||
|
|
014b367f56 | ||
|
|
4580b33e43 | ||
|
|
48bcc1e968 | ||
|
|
35368eb6ad | ||
|
|
9b337983ed | ||
|
|
cd4951d204 | ||
|
|
bd83d6f5aa | ||
|
|
9c241a6159 | ||
|
|
7a6baeadbd | ||
|
|
5495acea96 | ||
|
|
a3aa2cc175 | ||
|
|
8b0d02a16b | ||
|
|
c3c0eb974a | ||
|
|
56539100e3 | ||
|
|
b706db43e4 | ||
|
|
3e0a7c70ed | ||
|
|
6a5bd1b0a5 | ||
|
|
7f8a9be766 | ||
|
|
dbdf679e4a | ||
|
|
eb28dfadf9 | ||
|
|
ede28ac33a | ||
|
|
33b249d078 | ||
|
|
a85282763f | ||
|
|
ba9d7b3b5e | ||
|
|
3b794c017a | ||
|
|
47dbe4561f | ||
|
|
97135a1ac3 | ||
|
|
a636e15d11 | ||
|
|
3cc15e869e | ||
|
|
88c8b92a68 | ||
|
|
cee4d7e87b | ||
|
|
9488ec2eb0 | ||
|
|
4fe6c8db64 | ||
|
|
ccf8e0b320 | ||
|
|
1dc558ddba | ||
|
|
ae27ae0090 | ||
|
|
47c2a3a21a | ||
|
|
5d4e1362bb | ||
|
|
25cecf298f | ||
|
|
2de3b63e87 | ||
|
|
7abb8a81a7 | ||
|
|
3eb3891899 | ||
|
|
4b0ad422f1 | ||
|
|
3c603e3bb1 | ||
|
|
4ee788f541 | ||
|
|
99ab9726b4 | ||
|
|
23dd402e79 | ||
|
|
6bd90807f3 | ||
|
|
563a5845f0 | ||
|
|
2e580baf27 | ||
|
|
44ded25f6d | ||
|
|
70da5940a7 | ||
|
|
12aa8a78c1 | ||
|
|
94619737e8 | ||
|
|
ccc9e6611c | ||
|
|
1f1459b0d8 | ||
|
|
6489e74b6b | ||
|
|
c1e264955f | ||
|
|
f302d15bc4 | ||
|
|
8c70c8839b | ||
|
|
3fcd04fd8a | ||
|
|
d85f18751c | ||
|
|
1390c4eae5 | ||
|
|
18ade8ca65 | ||
|
|
7b026fa32c | ||
|
|
33698c219f | ||
|
|
b76d731cee | ||
|
|
4d1ac51543 | ||
|
|
3818fbdd3e | ||
|
|
af245b63d9 | ||
|
|
028da1d6a9 | ||
|
|
49397244c4 | ||
|
|
2b04ed3246 | ||
|
|
51aebb540c | ||
|
|
f5d7777b2c | ||
|
|
be1ffbd671 | ||
|
|
5640139ef1 | ||
|
|
0959499450 | ||
|
|
90ffe587dd | ||
|
|
38aafb6c7b | ||
|
|
ecfcf09184 | ||
|
|
7083dc7e05 | ||
|
|
d4e0417c48 | ||
|
|
ec7c25de9f | ||
|
|
6f9db87843 | ||
|
|
46c9038f54 | ||
|
|
1ce09aeb34 | ||
|
|
2ba327ef14 | ||
|
|
de93b47493 | ||
|
|
b0a21e116a | ||
|
|
53d1a5f9dc | ||
|
|
274f942b1e | ||
|
|
4aad52242c | ||
|
|
166a48e139 | ||
|
|
13de97dde6 | ||
|
|
6d8407ff94 | ||
|
|
663b794435 | ||
|
|
c0276e3663 | ||
|
|
6d57ce3050 | ||
|
|
2159df785f | ||
|
|
9762258975 | ||
|
|
deea64e839 | ||
|
|
9e615ba862 | ||
|
|
d34beca3cc | ||
|
|
07ed989862 | ||
|
|
049844ea7d | ||
|
|
ff4c76165a | ||
|
|
c3220e7c03 | ||
|
|
cb4c6b3ca6 | ||
|
|
ba36ba0157 | ||
|
|
dd95acf436 | ||
|
|
a73b03452a | ||
|
|
935fa1baae | ||
|
|
745f930749 | ||
|
|
d87db40c52 | ||
|
|
0ea91016f8 | ||
|
|
d4f634c3d8 | ||
|
|
286566fc0c | ||
|
|
2ed4df0b7c | ||
|
|
9bb7c6ece0 | ||
|
|
db0a6f6bb8 | ||
|
|
b6305826be | ||
|
|
f00ab86eff | ||
|
|
a44f29dad8 | ||
|
|
67b396bf16 | ||
|
|
ce14a9dadb | ||
|
|
183c90ac3a | ||
|
|
9e1a262f96 | ||
|
|
06dd9fe859 | ||
|
|
2a2c525c2d | ||
|
|
b2c1c9d9dc | ||
|
|
c33eba6736 | ||
|
|
56434cce71 | ||
|
|
c41123c36c | ||
|
|
043a6cd4ba | ||
|
|
0ca2f9034f | ||
|
|
4c7157807b | ||
|
|
0afe797bae | ||
|
|
1c8797e473 | ||
|
|
e0bf6d2e55 | ||
|
|
e96d0659cb | ||
|
|
72d70236ea | ||
|
|
ee2fc8c763 | ||
|
|
b53c03bca8 | ||
|
|
9545f692ef | ||
|
|
0112bd9b5a | ||
|
|
d235576e18 | ||
|
|
3d5d5da933 | ||
|
|
9b19477e2f | ||
|
|
5a9c95f07e | ||
|
|
3000e2b72c | ||
|
|
c1f6f0398e | ||
|
|
cb6488fa05 | ||
|
|
126d90f471 | ||
|
|
98d4fb0f34 | ||
|
|
d3ee3ca53d | ||
|
|
7eac5cebf5 | ||
|
|
6a109adbc5 | ||
|
|
587847f5e9 | ||
|
|
7842cd8a41 | ||
|
|
2f9cf02932 | ||
|
|
daa796454c | ||
|
|
c531239618 | ||
|
|
f6ac7b890a | ||
|
|
229e39facf | ||
|
|
75b00ce2df | ||
|
|
4576353f26 | ||
|
|
acf4b4da63 | ||
|
|
8b5933177a | ||
|
|
a6ddd6d233 | ||
|
|
5ca5adc774 | ||
|
|
005ffe850a | ||
|
|
71cb4e8510 | ||
|
|
40244ab81b | ||
|
|
15b65b342a | ||
|
|
7df3aba71b | ||
|
|
6bb535c129 | ||
|
|
e3bf3d29f7 | ||
|
|
df9c42c1b5 | ||
|
|
7e241bb76f | ||
|
|
6e7f4a231d | ||
|
|
822a0e56af | ||
|
|
da73c5c418 | ||
|
|
9a3a104ba4 | ||
|
|
63bba13b5f | ||
|
|
d90d781740 | ||
|
|
a3bf329c44 | ||
|
|
446e0422c7 | ||
|
|
e8976b40f4 | ||
|
|
d725b5be19 | ||
|
|
028b9d569d | ||
|
|
95c99295c1 | ||
|
|
a7617fa947 | ||
|
|
afc1ffd90b | ||
|
|
6e0b6171c6 |
21
.travis.yml
21
.travis.yml
@@ -1,36 +1,27 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
sudo: required
|
||||
dist: precise
|
||||
services:
|
||||
- mongodb
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
cache:
|
||||
directories:
|
||||
- 'node_modules'
|
||||
before_install:
|
||||
- $CXX --version
|
||||
- npm install -g npm@5
|
||||
- if [ $REQUIRES_SERVER ]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10; echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list; sudo apt-get update; sudo apt-get install mongodb-org-server; fi
|
||||
install:
|
||||
- npm install &> npm.install.log || (cat npm.install.log; false)
|
||||
before_script:
|
||||
- npm run test:build
|
||||
- cp config.json.example config.json
|
||||
- sleep 15
|
||||
- sleep 5
|
||||
script:
|
||||
- npm run $TEST
|
||||
- if [ $COVERAGE ]; then ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js; fi
|
||||
env:
|
||||
global:
|
||||
- CXX=g++-4.8
|
||||
- DISABLE_REQUEST_LOGGING=true
|
||||
matrix:
|
||||
- TEST="lint"
|
||||
- TEST="test:api-v3" REQUIRES_SERVER=true COVERAGE=true
|
||||
- TEST="test:api-v3:unit" REQUIRES_SERVER=true COVERAGE=true
|
||||
- TEST="test:api-v3:integration" REQUIRES_SERVER=true COVERAGE=true
|
||||
- TEST="test:sanity"
|
||||
- TEST="test:content" COVERAGE=true
|
||||
- TEST="test:common" COVERAGE=true
|
||||
|
||||
@@ -15,12 +15,12 @@ ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
||||
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
|
||||
RUN yarn global add npm@5
|
||||
# Install global packages
|
||||
RUN npm install -g gulp mocha
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone --branch v4.16.2 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN git clone --branch v4.27.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN gulp build:prod --force
|
||||
|
||||
|
||||
@@ -16,5 +16,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
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
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
"CRON_SEMI_SAFE_MODE":"false",
|
||||
"MAINTENANCE_MODE": "false",
|
||||
"SESSION_SECRET":"YOUR SECRET HERE",
|
||||
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
||||
"SESSION_SECRET_IV": "12345678912345678912345678912345",
|
||||
"ADMIN_EMAIL": "you@example.com",
|
||||
"SMTP_USER":"user@example.com",
|
||||
"SMTP_PASS":"password",
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
web:
|
||||
volumes:
|
||||
- '.:/usr/src/habitrpg'
|
||||
version: "3"
|
||||
services:
|
||||
|
||||
client:
|
||||
volumes:
|
||||
- '.:/usr/src/habitrpg'
|
||||
|
||||
server:
|
||||
volumes:
|
||||
- '.:/usr/src/habitrpg'
|
||||
|
||||
@@ -1,13 +1,36 @@
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
links:
|
||||
- mongo
|
||||
environment:
|
||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||
version: "3"
|
||||
services:
|
||||
|
||||
mongo:
|
||||
image: mongo
|
||||
ports:
|
||||
- "27017:27017"
|
||||
client:
|
||||
build: .
|
||||
networks:
|
||||
- habitica
|
||||
environment:
|
||||
- BASE_URL=http://server:3000
|
||||
ports:
|
||||
- "8080:8080"
|
||||
command: ["npm", "run", "client:dev"]
|
||||
depends_on:
|
||||
- server
|
||||
|
||||
server:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
networks:
|
||||
- habitica
|
||||
environment:
|
||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||
depends_on:
|
||||
- mongo
|
||||
|
||||
mongo:
|
||||
image: mongo
|
||||
ports:
|
||||
- "27017:27017"
|
||||
networks:
|
||||
- habitica
|
||||
|
||||
networks:
|
||||
habitica:
|
||||
driver: bridge
|
||||
|
||||
@@ -8,7 +8,7 @@ gulp.task('apidoc:clean', (done) => {
|
||||
clean(APIDOC_DEST_PATH, done);
|
||||
});
|
||||
|
||||
gulp.task('apidoc', ['apidoc:clean'], (done) => {
|
||||
gulp.task('apidoc', gulp.series('apidoc:clean', (done) => {
|
||||
let result = apidoc.createDoc({
|
||||
src: APIDOC_SRC_PATH,
|
||||
dest: APIDOC_DEST_PATH,
|
||||
@@ -19,8 +19,8 @@ gulp.task('apidoc', ['apidoc:clean'], (done) => {
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
gulp.task('apidoc:watch', ['apidoc'], () => {
|
||||
return gulp.watch(`${APIDOC_SRC_PATH}/**/*.js`, ['apidoc']);
|
||||
});
|
||||
gulp.task('apidoc:watch', gulp.series('apidoc', (done) => {
|
||||
return gulp.watch(`${APIDOC_SRC_PATH}/**/*.js`, gulp.series('apidoc', done));
|
||||
}));
|
||||
|
||||
@@ -2,12 +2,6 @@ import gulp from 'gulp';
|
||||
import babel from 'gulp-babel';
|
||||
import webpackProductionBuild from '../webpack/build';
|
||||
|
||||
gulp.task('build', () => {
|
||||
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
|
||||
gulp.start('build:prod');
|
||||
}
|
||||
});
|
||||
|
||||
gulp.task('build:src', () => {
|
||||
return gulp.src('website/server/**/*.js')
|
||||
.pipe(babel())
|
||||
@@ -20,18 +14,30 @@ gulp.task('build:common', () => {
|
||||
.pipe(gulp.dest('website/common/transpiled-babel/'));
|
||||
});
|
||||
|
||||
gulp.task('build:server', ['build:src', 'build:common']);
|
||||
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.task('build:prod', gulp.series(
|
||||
'build:server',
|
||||
'build:client',
|
||||
'apidoc',
|
||||
]);
|
||||
done => done()
|
||||
));
|
||||
|
||||
let buildArgs = [];
|
||||
|
||||
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
|
||||
buildArgs.push('build:prod');
|
||||
}
|
||||
|
||||
gulp.task('build', gulp.series(buildArgs, (done) => {
|
||||
done();
|
||||
}));
|
||||
@@ -1,5 +1,4 @@
|
||||
import mongoose from 'mongoose';
|
||||
import autoinc from 'mongoose-id-autoinc';
|
||||
import logger from '../website/server/libs/logger';
|
||||
import nconf from 'nconf';
|
||||
import repl from 'repl';
|
||||
@@ -25,23 +24,23 @@ let improveRepl = (context) => {
|
||||
|
||||
const isProd = nconf.get('NODE_ENV') === 'production';
|
||||
const mongooseOptions = !isProd ? {} : {
|
||||
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
|
||||
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
|
||||
keepAlive: 1,
|
||||
connectTimeoutMS: 30000,
|
||||
useMongoClient: true,
|
||||
};
|
||||
autoinc.init(
|
||||
mongoose.connect(
|
||||
nconf.get('NODE_DB_URI'),
|
||||
mongooseOptions,
|
||||
(err) => {
|
||||
if (err) throw err;
|
||||
logger.info('Connected with Mongoose');
|
||||
}
|
||||
)
|
||||
mongoose.connect(
|
||||
nconf.get('NODE_DB_URI'),
|
||||
mongooseOptions,
|
||||
(err) => {
|
||||
if (err) throw err;
|
||||
logger.info('Connected with Mongoose');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
gulp.task('console', () => {
|
||||
gulp.task('console', (done) => {
|
||||
improveRepl(repl.start({
|
||||
prompt: 'Habitica > ',
|
||||
}).context);
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import mergeStream from 'merge-stream';
|
||||
import {basename} from 'path';
|
||||
import {sync} from 'glob';
|
||||
import {each} from 'lodash';
|
||||
import vinylBuffer from 'vinyl-buffer';
|
||||
|
||||
// https://github.com/Ensighten/grunt-spritesmith/issues/67#issuecomment-34786248
|
||||
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
|
||||
@@ -104,6 +105,7 @@ function createSpritesStream (name, src) {
|
||||
}));
|
||||
|
||||
let imgStream = spriteData.img
|
||||
.pipe(vinylBuffer())
|
||||
.pipe(imagemin())
|
||||
.pipe(gulp.dest(IMG_DIST_PATH));
|
||||
|
||||
@@ -117,8 +119,6 @@ function createSpritesStream (name, src) {
|
||||
return stream;
|
||||
}
|
||||
|
||||
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
|
||||
|
||||
gulp.task('sprites:main', () => {
|
||||
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
|
||||
return createSpritesStream('main', mainSrc);
|
||||
@@ -133,7 +133,7 @@ gulp.task('sprites:clean', (done) => {
|
||||
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
|
||||
});
|
||||
|
||||
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
|
||||
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
|
||||
|
||||
let numberOfSheetsThatAreTooBig = 0;
|
||||
@@ -159,4 +159,7 @@ gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSpri
|
||||
} else {
|
||||
console.log('All images are within the correct dimensions'); // eslint-disable-line no-console
|
||||
}
|
||||
});
|
||||
done();
|
||||
}));
|
||||
|
||||
gulp.task('sprites:compile', gulp.series('sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions', done => done()));
|
||||
|
||||
@@ -3,7 +3,7 @@ import nodemon from 'gulp-nodemon';
|
||||
|
||||
let pkg = require('../package.json');
|
||||
|
||||
gulp.task('nodemon', () => {
|
||||
gulp.task('nodemon', (done) => {
|
||||
nodemon({
|
||||
script: pkg.main,
|
||||
ignore: [
|
||||
@@ -12,4 +12,5 @@ gulp.task('nodemon', () => {
|
||||
'common/dist/script/content/*',
|
||||
],
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
import mongoose from 'mongoose';
|
||||
import { exec } from 'child_process';
|
||||
import gulp from 'gulp';
|
||||
import runSequence from 'run-sequence';
|
||||
import os from 'os';
|
||||
import nconf from 'nconf';
|
||||
|
||||
@@ -39,23 +38,23 @@ let testBin = (string, additionalEnvVariables = '') => {
|
||||
}
|
||||
};
|
||||
|
||||
gulp.task('test:nodemon', () => {
|
||||
gulp.task('test:nodemon', gulp.series(function setupNodemon (done) {
|
||||
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env
|
||||
process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
|
||||
|
||||
runSequence('nodemon');
|
||||
});
|
||||
done();
|
||||
}, 'nodemon'));
|
||||
|
||||
gulp.task('test:prepare:mongo', (cb) => {
|
||||
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}`);
|
||||
mongoose.connection.db.dropDatabase();
|
||||
mongoose.connection.close();
|
||||
cb();
|
||||
mongoose.connection.dropDatabase((err2) => {
|
||||
if (err2) return cb(err2);
|
||||
mongoose.connection.close(cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
|
||||
gulp.task('test:prepare:server', gulp.series('test:prepare:mongo', (done) => {
|
||||
if (!server) {
|
||||
server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
@@ -64,16 +63,18 @@ gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
|
||||
if (stderr) {
|
||||
console.error(stderr); // eslint-disable-line no-console
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
gulp.task('test:prepare:build', ['build']);
|
||||
gulp.task('test:prepare:build', gulp.series('build', done => done()));
|
||||
|
||||
gulp.task('test:prepare', [
|
||||
gulp.task('test:prepare', gulp.series(
|
||||
'test:prepare:build',
|
||||
'test:prepare:mongo',
|
||||
]);
|
||||
done => done()
|
||||
));
|
||||
|
||||
gulp.task('test:sanity', (cb) => {
|
||||
let runner = exec(
|
||||
@@ -88,7 +89,7 @@ gulp.task('test:sanity', (cb) => {
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:common', ['test:prepare:build'], (cb) => {
|
||||
gulp.task('test:common', gulp.series('test:prepare:build', (cb) => {
|
||||
let runner = exec(
|
||||
testBin(COMMON_TEST_COMMAND),
|
||||
(err) => {
|
||||
@@ -99,17 +100,17 @@ gulp.task('test:common', ['test:prepare:build'], (cb) => {
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
}));
|
||||
|
||||
gulp.task('test:common:clean', (cb) => {
|
||||
pipe(exec(testBin(COMMON_TEST_COMMAND), () => cb()));
|
||||
});
|
||||
|
||||
gulp.task('test:common:watch', ['test:common:clean'], () => {
|
||||
gulp.watch(['common/script/**/*', 'test/common/**/*'], ['test:common:clean']);
|
||||
});
|
||||
gulp.task('test:common:watch', gulp.series('test:common:clean', () => {
|
||||
return gulp.watch(['common/script/**/*', 'test/common/**/*'], gulp.series('test:common:clean', done => done()));
|
||||
}));
|
||||
|
||||
gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
|
||||
gulp.task('test:common:safe', gulp.series('test:prepare:build', (cb) => {
|
||||
let runner = exec(
|
||||
testBin(COMMON_TEST_COMMAND),
|
||||
(err, stdout) => { // eslint-disable-line handle-callback-err
|
||||
@@ -123,9 +124,9 @@ gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
}));
|
||||
|
||||
gulp.task('test:content', ['test:prepare:build'], (cb) => {
|
||||
gulp.task('test:content', gulp.series('test:prepare:build', (cb) => {
|
||||
let runner = exec(
|
||||
testBin(CONTENT_TEST_COMMAND),
|
||||
CONTENT_OPTIONS,
|
||||
@@ -137,17 +138,17 @@ gulp.task('test:content', ['test:prepare:build'], (cb) => {
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
}));
|
||||
|
||||
gulp.task('test:content:clean', (cb) => {
|
||||
pipe(exec(testBin(CONTENT_TEST_COMMAND), CONTENT_OPTIONS, () => cb()));
|
||||
});
|
||||
|
||||
gulp.task('test:content:watch', ['test:content:clean'], () => {
|
||||
gulp.watch(['common/script/content/**', 'test/**'], ['test:content:clean']);
|
||||
});
|
||||
gulp.task('test:content:watch', gulp.series('test:content:clean', () => {
|
||||
return gulp.watch(['common/script/content/**', 'test/**'], gulp.series('test:content:clean', done => done()));
|
||||
}));
|
||||
|
||||
gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
|
||||
gulp.task('test:content:safe', gulp.series('test:prepare:build', (cb) => {
|
||||
let runner = exec(
|
||||
testBin(CONTENT_TEST_COMMAND),
|
||||
CONTENT_OPTIONS,
|
||||
@@ -162,7 +163,7 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
}));
|
||||
|
||||
gulp.task('test:api-v3:unit', (done) => {
|
||||
let runner = exec(
|
||||
@@ -179,7 +180,7 @@ gulp.task('test:api-v3:unit', (done) => {
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:unit:watch', () => {
|
||||
gulp.watch(['website/server/libs/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], ['test:api-v3:unit']);
|
||||
return gulp.watch(['website/server/libs/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api-v3:unit', done => done()));
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration', (done) => {
|
||||
@@ -198,8 +199,10 @@ gulp.task('test:api-v3:integration', (done) => {
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration:watch', () => {
|
||||
gulp.watch(['website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
|
||||
'test/api/v3/integration/**/*'], ['test:api-v3:integration']);
|
||||
return gulp.watch([
|
||||
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
|
||||
'test/api/v3/integration/**/*',
|
||||
], gulp.series('test:api-v3:integration', done => done()));
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration:separate-server', (done) => {
|
||||
@@ -212,21 +215,17 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test', (done) => {
|
||||
runSequence(
|
||||
'test:sanity',
|
||||
'test:content',
|
||||
'test:common',
|
||||
'test:api-v3:unit',
|
||||
'test:api-v3:integration',
|
||||
done
|
||||
);
|
||||
});
|
||||
gulp.task('test', gulp.series(
|
||||
'test:sanity',
|
||||
'test:content',
|
||||
'test:common',
|
||||
'test:api-v3:unit',
|
||||
'test:api-v3:integration',
|
||||
done => done()
|
||||
));
|
||||
|
||||
gulp.task('test:api-v3', (done) => {
|
||||
runSequence(
|
||||
'test:api-v3:unit',
|
||||
'test:api-v3:integration',
|
||||
done
|
||||
);
|
||||
});
|
||||
gulp.task('test:api-v3', gulp.series(
|
||||
'test:api-v3:unit',
|
||||
'test:api-v3:integration',
|
||||
done => done()
|
||||
));
|
||||
|
||||
@@ -93,9 +93,7 @@ const malformedStringExceptions = {
|
||||
feedPet: true,
|
||||
};
|
||||
|
||||
gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']);
|
||||
|
||||
gulp.task('transifex:missingFiles', () => {
|
||||
gulp.task('transifex:missingFiles', (done) => {
|
||||
let missingStrings = [];
|
||||
|
||||
eachTranslationFile(ALL_LANGUAGES, (error) => {
|
||||
@@ -109,9 +107,10 @@ gulp.task('transifex:missingFiles', () => {
|
||||
let formattedMessage = formatMessageForPosting(message, missingStrings);
|
||||
postToSlack(formattedMessage, SLACK_CONFIG);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('transifex:missingStrings', () => {
|
||||
gulp.task('transifex:missingStrings', (done) => {
|
||||
let missingStrings = [];
|
||||
|
||||
eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => {
|
||||
@@ -126,9 +125,10 @@ gulp.task('transifex:missingStrings', () => {
|
||||
let formattedMessage = formatMessageForPosting(message, missingStrings);
|
||||
postToSlack(formattedMessage, SLACK_CONFIG);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('transifex:malformedStrings', () => {
|
||||
gulp.task('transifex:malformedStrings', (done) => {
|
||||
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
||||
let interpolationRegex = /<%= [a-zA-Z]* %>/g;
|
||||
let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex);
|
||||
@@ -170,4 +170,11 @@ gulp.task('transifex:malformedStrings', () => {
|
||||
let formattedMessage = formatMessageForPosting(message, stringsWithIncorrectNumberOfInterpolations);
|
||||
postToSlack(formattedMessage, SLACK_CONFIG);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
'transifex',
|
||||
gulp.series('transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings'),
|
||||
(done) => done()
|
||||
);
|
||||
@@ -8,10 +8,12 @@
|
||||
|
||||
require('babel-register');
|
||||
|
||||
const gulp = require('gulp');
|
||||
|
||||
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
|
||||
require('./gulp/gulp-apidoc'); // eslint-disable-line global-require
|
||||
require('./gulp/gulp-build'); // eslint-disable-line global-require
|
||||
} else {
|
||||
require('glob').sync('./gulp/gulp-*').forEach(require); // eslint-disable-line global-require
|
||||
require('gulp').task('default', ['test']); // eslint-disable-line global-require
|
||||
require('gulp').task('default', gulp.series('test')); // eslint-disable-line global-require
|
||||
}
|
||||
|
||||
79
migrations/20180110_nextPaymentProcessing.js
Normal file
79
migrations/20180110_nextPaymentProcessing.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Convert purchased.plan.nextPaymentProcessing from a double to a date field for Apple subscribers
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'purchased.plan.paymentMethod': "Apple",
|
||||
'purchased.plan.nextPaymentProcessing': {$type: 'double'},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 100;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var set = {
|
||||
'purchased.plan.nextPaymentProcessing': new Date(user.purchased.plan.nextPaymentProcessing),
|
||||
};
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set});
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
93
migrations/20180125_clean_new_notifications.js
Normal file
93
migrations/20180125_clean_new_notifications.js
Normal file
@@ -0,0 +1,93 @@
|
||||
const UserNotification = require('../website/server/models/userNotification').model;
|
||||
const content = require('../website/common/script/content/index');
|
||||
|
||||
const migrationName = '20180125_clean_new_migrations';
|
||||
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Clean new migration types for processed users
|
||||
*/
|
||||
|
||||
const monk = require('monk');
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const types = ['NEW_MYSTERY_ITEMS', 'CARD_RECEIVED', 'NEW_CHAT_MESSAGE'];
|
||||
|
||||
dbUsers.update({_id: user._id}, {
|
||||
$pull: {notifications: { type: {$in: types } } },
|
||||
$set: {migration: migrationName},
|
||||
});
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
}
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
const userPromises = users.map(updateUser);
|
||||
const lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
const query = {
|
||||
migration: {$ne: migrationName},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
149
migrations/20180125_notifications.js
Normal file
149
migrations/20180125_notifications.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const UserNotification = require('../website/server/models/userNotification').model;
|
||||
const content = require('../website/common/script/content/index');
|
||||
|
||||
const migrationName = '20180125_migrations-v2';
|
||||
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Migrate to new notifications system
|
||||
*/
|
||||
|
||||
const monk = require('monk');
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const notifications = [];
|
||||
|
||||
// UNALLOCATED_STATS_POINTS skipped because added on each save
|
||||
// NEW_STUFF skipped because it's a new type
|
||||
// GROUP_TASK_NEEDS_WORK because it's a new type
|
||||
// NEW_INBOX_MESSAGE not implemented yet
|
||||
|
||||
|
||||
// NEW_MYSTERY_ITEMS
|
||||
const mysteryItems = user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems;
|
||||
if (Array.isArray(mysteryItems) && mysteryItems.length > 0) {
|
||||
const newMysteryNotif = new UserNotification({
|
||||
type: 'NEW_MYSTERY_ITEMS',
|
||||
data: {
|
||||
items: mysteryItems,
|
||||
},
|
||||
}).toJSON();
|
||||
notifications.push(newMysteryNotif);
|
||||
}
|
||||
|
||||
// CARD_RECEIVED
|
||||
Object.keys(content.cardTypes).forEach(cardType => {
|
||||
const existingCards = user.items.special[`${cardType}Received`] || [];
|
||||
existingCards.forEach(sender => {
|
||||
const newNotif = new UserNotification({
|
||||
type: 'CARD_RECEIVED',
|
||||
data: {
|
||||
card: cardType,
|
||||
from: {
|
||||
// id is missing in old notifications
|
||||
name: sender,
|
||||
},
|
||||
},
|
||||
}).toJSON();
|
||||
|
||||
notifications.push(newNotif);
|
||||
});
|
||||
});
|
||||
|
||||
// NEW_CHAT_MESSAGE
|
||||
Object.keys(user.newMessages).forEach(groupId => {
|
||||
const existingNotif = user.newMessages[groupId];
|
||||
|
||||
if (existingNotif) {
|
||||
const newNotif = new UserNotification({
|
||||
type: 'NEW_CHAT_MESSAGE',
|
||||
data: {
|
||||
group: {
|
||||
id: groupId,
|
||||
name: existingNotif.name,
|
||||
},
|
||||
},
|
||||
}).toJSON();
|
||||
|
||||
notifications.push(newNotif);
|
||||
}
|
||||
});
|
||||
|
||||
dbUsers.update({_id: user._id}, {
|
||||
$push: {notifications: { $each: notifications } },
|
||||
$set: {migration: migrationName},
|
||||
});
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
}
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
const userPromises = users.map(updateUser);
|
||||
const lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
const query = {
|
||||
migration: {$ne: migrationName},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
118
migrations/20180130_habit_birthday.js
Normal file
118
migrations/20180130_habit_birthday.js
Normal file
@@ -0,0 +1,118 @@
|
||||
var migrationName = '20180130_habit_birthday.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Award party robes: most recent user doesn't have of 2014-2018. Also cake!
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'auth.timestamps.loggedin':{$gt:new Date('2018-01-01')}, // remove after first run to cover remaining users
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [ // specify fields we are interested in to limit retrieved data (empty if we're not reading data)
|
||||
'items.gear.owned'
|
||||
],
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var push;
|
||||
var set = {'migration':migrationName};
|
||||
|
||||
if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2017')) {
|
||||
set['items.gear.owned.armor_special_birthday2018'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2018', '_id': monk.id()}};
|
||||
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2016')) {
|
||||
set['items.gear.owned.armor_special_birthday2017'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2017', '_id': monk.id()}};
|
||||
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2015')) {
|
||||
set['items.gear.owned.armor_special_birthday2016'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2016', '_id': monk.id()}};
|
||||
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday')) {
|
||||
set['items.gear.owned.armor_special_birthday2015'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2015', '_id': monk.id()}};
|
||||
} else {
|
||||
set['items.gear.owned.armor_special_birthday'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday', '_id': monk.id()}};
|
||||
}
|
||||
|
||||
var inc = {
|
||||
'items.food.Cake_Skeleton':1,
|
||||
'items.food.Cake_Base':1,
|
||||
'items.food.Cake_CottonCandyBlue':1,
|
||||
'items.food.Cake_CottonCandyPink':1,
|
||||
'items.food.Cake_Shade':1,
|
||||
'items.food.Cake_White':1,
|
||||
'items.food.Cake_Golden':1,
|
||||
'items.food.Cake_Zombie':1,
|
||||
'items.food.Cake_Desert':1,
|
||||
'items.food.Cake_Red':1,
|
||||
'achievements.habitBirthdays':1
|
||||
};
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set, $inc: inc, $push: push});
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
|
||||
59
migrations/docs/mongo-indexes.md
Normal file
59
migrations/docs/mongo-indexes.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Indexes
|
||||
|
||||
This file contains a list of indexes that are on Habitica's production Mongo server.
|
||||
If we ever have an issue, use this list to reindex.
|
||||
|
||||
## Challenges
|
||||
- `{ "group": 1, "official": -1, "timestamp": -1 }`
|
||||
- `{ "leader": 1, "official": -1, "timestamp": -1 }`
|
||||
- `{ "official": -1, "timestamp": -1 }`
|
||||
|
||||
## Groups
|
||||
- `{ "privacy": 1, "type": 1, "memberCount": -1 }`
|
||||
- `{ "privacy": 1 }`
|
||||
- `{ "purchased.plan.customerId": 1 }`
|
||||
- `{ "purchased.plan.paymentMethod": 1 }`
|
||||
- `{ "purchased.plan.planId": 1, "purchased.plan.dateTerminated": 1 }`
|
||||
- `{ "type": 1, "memberCount": -1, "_id": 1 }`
|
||||
- `{ "type": 1 }`
|
||||
|
||||
## Tasks
|
||||
- `{ "challenge.id": 1 }`
|
||||
- `{ "challenge.taskId": 1 }`
|
||||
- `{ "group.id": 1 }`
|
||||
- `{ "group.taskId": 1 }`
|
||||
- `{ "type": 1, "everyX": 1, "frequency": 1 }`
|
||||
- `{ "userId": 1 }`
|
||||
- `{ "yesterDaily": 1, "type": 1 }`
|
||||
|
||||
## Users
|
||||
- `{ "_id": 1, "apiToken": 1 }`
|
||||
- `{ "auth.facebook.emails.value": 1 }`
|
||||
- `{ "auth.facebook.id": 1 }`
|
||||
- `{ "auth.google.emails.value": 1 }`
|
||||
- `{ "auth.google.id": 1 }`
|
||||
- `{ "auth.local.email": 1 }`
|
||||
- `{ "auth.local.lowerCaseUsername": 1 }`
|
||||
- `{ "auth.local.username": 1 }`
|
||||
- `{ "auth.timestamps.created": 1 }`
|
||||
- `{ "auth.timestamps.loggedin": 1, "_lastPushNotification": 1, "preferences.timezoneOffset": 1 }`
|
||||
- `{ "auth.timestamps.loggedin": 1 }`
|
||||
- `{ "backer.tier": -1 }`
|
||||
- `{ "challenges": 1, "_id": 1 }`
|
||||
- `{ "contributor.admin": 1, "contributor.level": -1, "backer.npc": -1, "profile.name": 1 }`
|
||||
- `{ "contributor.level": 1 }`
|
||||
- `{ "flags.newStuff": 1 }`
|
||||
- `{ "flags.armoireEmpty": 1 }`
|
||||
- `{ "guilds": 1, "_id": 1 }`
|
||||
- `{ "invitations.guilds.id": 1, "_id": 1 }`
|
||||
- `{ "invitations.party.id": 1 }`
|
||||
- `{ "loginIncentives": 1 }`
|
||||
- `{ "migration": 1 }`
|
||||
- `{ "party._id": 1, "_id": 1 }`
|
||||
- `{ "preferences.sleep": 1, "_id": 1, "flags.lastWeeklyRecap": 1, "preferences.emailNotifications.unsubscribeFromAll": 1, "preferences.emailNotifications.weeklyRecaps": 1 }`
|
||||
- `{ "preferences.sleep": 1, "_id": 1, "lastCron": 1, "preferences.emailNotifications.importantAnnouncements": 1, "preferences.emailNotifications.unsubscribeFromAll": 1, "flags.recaptureEmailsPhase": 1 }`
|
||||
- `{ "profile.name": 1 }`
|
||||
- `{ "purchased.plan.customerId": 1 }`
|
||||
- `{ "purchased.plan.paymentMethod": 1 }`
|
||||
- `{ "stats.score.overall": 1 }`
|
||||
- `{ "webhooks.type": 1 }`
|
||||
@@ -17,5 +17,5 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
const processUsers = require('./takeThis');
|
||||
const processUsers = require('./20180125_clean_new_notifications.js');
|
||||
processUsers();
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
var UserNotification = require('../website/server/models/userNotification').model
|
||||
|
||||
var _id = '';
|
||||
|
||||
var items = ['back_mystery_201801','headAccessory_mystery_201801']
|
||||
|
||||
var update = {
|
||||
$addToSet: {
|
||||
'purchased.plan.mysteryItems':{
|
||||
$each:['armor_mystery_201712','head_mystery_201712']
|
||||
$each: items,
|
||||
}
|
||||
}
|
||||
},
|
||||
$push: {
|
||||
notifications: (new UserNotification({
|
||||
type: 'NEW_MYSTERY_ITEMS',
|
||||
data: {
|
||||
items: items,
|
||||
},
|
||||
})).toJSON(),
|
||||
},
|
||||
};
|
||||
|
||||
/*var update = {
|
||||
|
||||
@@ -18,71 +18,94 @@ var authorUuid = '3e595299-3d8a-4a10-bfe0-88f555e4aa0c'; //... own data is done
|
||||
*
|
||||
*/
|
||||
|
||||
var dbserver = 'localhost:27017'; // FOR TEST DATABASE
|
||||
var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE
|
||||
var dbname = 'habitrpg';
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
var monk = require('monk');
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'auth.timestamps.loggedin':{$gt:new Date('2016-01-04')}
|
||||
// '_id': authorUuid // FOR TESTING
|
||||
};
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'auth.timestamps.loggedin':{$gt:new Date('2016-01-04')}
|
||||
// '_id': authorUuid // FOR TESTING
|
||||
};
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'flags.armoireEmpty':1,
|
||||
'items.gear.owned':1
|
||||
};
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var fields = {
|
||||
'flags.armoireEmpty':1,
|
||||
'items.gear.owned':1
|
||||
};
|
||||
|
||||
// specify user data to change:
|
||||
var set = {'migration':migrationName, 'flags.armoireEmpty':false};
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var countSearched = 0;
|
||||
var countModified = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
return displayData();
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
countSearched++;
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: {
|
||||
'flags.armoireEmpty':1,
|
||||
'items.gear.owned':1
|
||||
} // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var set = {'migration':migrationName, 'flags.armoireEmpty':false};
|
||||
|
||||
|
||||
if (user.flags.armoireEmpty) {
|
||||
// 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) {
|
||||
// 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 {
|
||||
// console.log("change: " + user._id); // FOR TESTING
|
||||
dbUsers.update({_id: user._id}, {$set: set});
|
||||
}
|
||||
else {
|
||||
countModified++;
|
||||
// console.log("change: " + user._id); // FOR TESTING
|
||||
dbUsers.update({_id:user._id}, {$set:set});
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// this user already has armoire marked as containing items to be bought
|
||||
// so don't change the flag
|
||||
// console.log("DON'T CHANGE: " + user._id); // FOR TESTING
|
||||
// console.log("DON'T CHANGE: " + user._id); // FOR TESTING
|
||||
}
|
||||
|
||||
if (countSearched%progressCount == 0) console.warn(countSearched + ' ' + user._id);
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
});
|
||||
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + countSearched + ' users searched\n');
|
||||
console.warn('\n' + countModified + ' users modified\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
@@ -93,3 +116,5 @@ function exiting(code, msg) {
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
var migrationName = 'tasks-set-everyX';
|
||||
var authorName = ''; // in case script author needs to know when their ...
|
||||
var authorUuid = ''; //... own data is done
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var 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
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var connectionString = 'mongodb://sabrecat:z8e8jyRA8CTofMQ@ds013393-a0.mlab.com:13393/habitica?auto_reconnect=true';
|
||||
var dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
||||
|
||||
function processTasks(lastId) {
|
||||
|
||||
11594
package-lock.json
generated
11594
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
104
package.json
104
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.18.0",
|
||||
"version": "4.27.2",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -10,7 +10,6 @@
|
||||
"amplitude": "^2.0.3",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^1.7.6",
|
||||
"async": "^1.5.0",
|
||||
"autoprefixer": "^6.4.0",
|
||||
"aws-sdk": "^2.0.25",
|
||||
"axios": "^0.16.0",
|
||||
@@ -27,95 +26,77 @@
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.6.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"babelify": "^7.2.0",
|
||||
"bcrypt": "^1.0.2",
|
||||
"bluebird": "^3.3.5",
|
||||
"body-parser": "^1.15.0",
|
||||
"bootstrap": "4.0.0-beta.2",
|
||||
"bootstrap-vue": "^1.0.2",
|
||||
"browserify": "~12.0.1",
|
||||
"bootstrap": "^4.0.0",
|
||||
"bootstrap-vue": "^1.5.0",
|
||||
"compression": "^1.6.1",
|
||||
"connect-ratelimit": "0.0.7",
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^4.0.0",
|
||||
"cross-env": "^5.1.3",
|
||||
"css-loader": "^0.28.0",
|
||||
"csv-stringify": "^1.0.2",
|
||||
"cwait": "~1.0.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"estraverse": "^4.1.1",
|
||||
"express": "~4.14.0",
|
||||
"express-basic-auth": "^1.0.1",
|
||||
"express-csv": "~0.6.0",
|
||||
"express-validator": "^2.18.0",
|
||||
"extract-text-webpack-plugin": "^2.0.0-rc.3",
|
||||
"file-loader": "^0.10.0",
|
||||
"glob": "^4.3.5",
|
||||
"glob": "^7.1.2",
|
||||
"got": "^6.1.1",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^6.1.2",
|
||||
"gulp-imagemin": "^2.4.0",
|
||||
"gulp-nodemon": "^2.0.4",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-uglify": "^1.4.2",
|
||||
"gulp.spritesmith": "^4.1.0",
|
||||
"gulp-imagemin": "^4.1.0",
|
||||
"gulp-nodemon": "^2.2.1",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.15.1",
|
||||
"html-webpack-plugin": "^2.8.1",
|
||||
"image-size": "~0.3.2",
|
||||
"image-size": "^0.6.2",
|
||||
"in-app-purchase": "^1.1.6",
|
||||
"intro.js": "^2.6.0",
|
||||
"jade": "~1.11.0",
|
||||
"jquery": ">=3.0.0",
|
||||
"js2xmlparser": "~1.0.0",
|
||||
"lodash": "^4.17.4",
|
||||
"merge-stream": "^1.0.0",
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.13.0",
|
||||
"moment-recur": "git://github.com/habitrpg/moment-recur#f147ef27bbc26ca67638385f3db4a44084c76626",
|
||||
"mongoose": "~4.8.6",
|
||||
"mongoose-id-autoinc": "~2013.7.14-4",
|
||||
"moment-recur": "git://github.com/habitrpg/moment-recur.git#f147ef27bbc26ca67638385f3db4a44084c76626",
|
||||
"mongoose": "^4.13.10",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "~0.8.2",
|
||||
"nib": "^1.1.0",
|
||||
"node-gcm": "^0.14.4",
|
||||
"node-sass": "^4.5.0",
|
||||
"nodemailer": "^2.3.2",
|
||||
"object-path": "^0.9.2",
|
||||
"ora": "^1.1.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.3.2",
|
||||
"passport-facebook": "^2.0.0",
|
||||
"passport-google-oauth20": "1.0.0",
|
||||
"paypal-ipn": "3.0.0",
|
||||
"paypal-rest-sdk": "^1.2.1",
|
||||
"popper.js": "^1.11.0",
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"popper.js": "^1.13.0",
|
||||
"postcss-easy-import": "^2.0.0",
|
||||
"pretty-data": "^0.40.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"pug": "^2.0.0-beta.12",
|
||||
"push-notify": "git://github.com/habitrpg/push-notify#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
|
||||
"pug": "^2.0.0-rc.4",
|
||||
"push-notify": "git://github.com/habitrpg/push-notify.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
|
||||
"pusher": "^1.3.0",
|
||||
"request": "~2.74.0",
|
||||
"request": "^2.83.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"run-sequence": "^1.1.4",
|
||||
"s3-upload-stream": "^1.0.6",
|
||||
"sass-loader": "^6.0.2",
|
||||
"serve-favicon": "^2.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"sortablejs": "^1.6.1",
|
||||
"shelljs": "^0.8.1",
|
||||
"stripe": "^4.2.0",
|
||||
"superagent": "^3.4.3",
|
||||
"svg-inline-loader": "^0.7.1",
|
||||
"svg-url-loader": "^2.0.2",
|
||||
"svgo-loader": "^1.2.1",
|
||||
"universal-analytics": "~0.3.2",
|
||||
"universal-analytics": "^0.4.16",
|
||||
"url-loader": "^0.5.7",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^3.0.1",
|
||||
"validator": "^4.9.0",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vue": "^2.5.2",
|
||||
"vue-loader": "^13.3.0",
|
||||
"vue-mugen-scroll": "^0.2.1",
|
||||
@@ -123,11 +104,11 @@
|
||||
"vue-style-loader": "^3.0.0",
|
||||
"vue-template-compiler": "^2.5.2",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker#825a866b6a9c52dd8c588a3e8b900880875ce914",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"winston": "^2.1.0",
|
||||
"winston-loggly-bulk": "^1.4.2",
|
||||
"winston-loggly-bulk": "^2.0.2",
|
||||
"xml2js": "^0.4.4"
|
||||
},
|
||||
"private": true,
|
||||
@@ -163,55 +144,44 @@
|
||||
"babel-plugin-istanbul": "^4.0.0",
|
||||
"chai": "^3.4.0",
|
||||
"chai-as-promised": "^5.1.0",
|
||||
"chalk": "^1.1.3",
|
||||
"chalk": "^2.3.0",
|
||||
"chromedriver": "^2.27.2",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^2.11.2",
|
||||
"cross-spawn": "^5.0.1",
|
||||
"coveralls": "^3.0.0",
|
||||
"cross-spawn": "^6.0.4",
|
||||
"csv": "~0.3.6",
|
||||
"deep-diff": "~0.1.4",
|
||||
"eslint": "^3.0.0",
|
||||
"eslint-config-habitrpg": "^3.0.0",
|
||||
"eslint-friendly-formatter": "^2.0.5",
|
||||
"eslint-loader": "^1.3.0",
|
||||
"eslint-plugin-html": "^2.0.0",
|
||||
"eslint-plugin-mocha": "^4.7.0",
|
||||
"event-stream": "^3.2.2",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"expect.js": "~0.2.0",
|
||||
"expect.js": "^0.3.1",
|
||||
"http-proxy-middleware": "^0.17.0",
|
||||
"inject-loader": "^3.0.0-beta4",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"karma": "^1.3.0",
|
||||
"karma-babel-preprocessor": "^6.0.1",
|
||||
"karma-chai-plugins": "~0.6.0",
|
||||
"karma-coverage": "^0.5.3",
|
||||
"karma-mocha": "^0.2.0",
|
||||
"karma-mocha-reporter": "^1.1.1",
|
||||
"karma": "^2.0.0",
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
"karma-chai-plugins": "^0.9.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-phantomjs-launcher": "^1.0.0",
|
||||
"karma-sinon-chai": "~1.2.0",
|
||||
"karma-sinon-chai": "^1.3.3",
|
||||
"karma-sinon-stub-promise": "^1.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.24",
|
||||
"karma-spec-reporter": "0.0.32",
|
||||
"karma-webpack": "^2.0.2",
|
||||
"lcov-result-merger": "^1.0.2",
|
||||
"lolex": "^1.4.0",
|
||||
"mocha": "^3.2.0",
|
||||
"mongodb": "^2.0.46",
|
||||
"mongoskin": "~2.1.0",
|
||||
"lcov-result-merger": "^2.0.0",
|
||||
"mocha": "^5.0.0",
|
||||
"monk": "^4.0.0",
|
||||
"nightwatch": "^0.9.12",
|
||||
"phantomjs-prebuilt": "^2.1.12",
|
||||
"protractor": "^3.1.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"require-again": "^2.0.0",
|
||||
"rewire": "^2.3.3",
|
||||
"selenium-server": "^3.0.1",
|
||||
"sinon": "^1.17.2",
|
||||
"sinon": "^4.2.2",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"superagent-defaults": "^0.1.13",
|
||||
"vinyl-transform": "^1.0.0",
|
||||
"webpack-bundle-analyzer": "^2.2.1",
|
||||
"webpack-dev-middleware": "^1.10.0",
|
||||
"webpack-hot-middleware": "^2.6.1"
|
||||
|
||||
@@ -149,7 +149,10 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
|
||||
let usersToGenerate = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
usersToGenerate.push(generateUser({challenges: [challenge._id]}));
|
||||
usersToGenerate.push(generateUser({
|
||||
challenges: [challenge._id],
|
||||
'profile.name': `${i}profilename`,
|
||||
}));
|
||||
}
|
||||
let generatedUsers = await Promise.all(usersToGenerate);
|
||||
let profileNames = generatedUsers.map(generatedUser => generatedUser.profile.name);
|
||||
|
||||
@@ -95,13 +95,23 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
||||
expect(memberProgress.tasks[0].challenge.taskId).to.equal(chalTasks[0]._id);
|
||||
});
|
||||
|
||||
it('returns the tasks without the tags', async () => {
|
||||
it('returns the tasks without the tags and checklist', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
let taskText = 'Test Text';
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, [{
|
||||
type: 'todo',
|
||||
text: taskText,
|
||||
checklist: [
|
||||
{
|
||||
_id: 123,
|
||||
text: 'test',
|
||||
},
|
||||
],
|
||||
}]);
|
||||
|
||||
let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`);
|
||||
expect(memberProgress.tasks[0]).not.to.have.key('tags');
|
||||
expect(memberProgress.tasks[0].checklist).to.eql([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -314,5 +314,33 @@ describe('POST /challenges', () => {
|
||||
groupLeader = await groupLeader.sync();
|
||||
expect(groupLeader.achievements.joinedChallenge).to.be.true;
|
||||
});
|
||||
|
||||
it('sets summary to challenges name when not supplied', async () => {
|
||||
const name = 'Test Challenge';
|
||||
const challenge = await groupLeader.post('/challenges', {
|
||||
group: group._id,
|
||||
name,
|
||||
shortName: 'TC Label',
|
||||
});
|
||||
|
||||
const updatedChallenge = await groupLeader.get(`/challenges/${challenge._id}`);
|
||||
|
||||
expect(updatedChallenge.summary).to.eql(name);
|
||||
});
|
||||
|
||||
it('sets summary to challenges', async () => {
|
||||
const name = 'Test Challenge';
|
||||
const summary = 'Test Summary Challenge';
|
||||
const challenge = await groupLeader.post('/challenges', {
|
||||
group: group._id,
|
||||
name,
|
||||
shortName: 'TC Label',
|
||||
summary,
|
||||
});
|
||||
|
||||
const updatedChallenge = await groupLeader.get(`/challenges/${challenge._id}`);
|
||||
|
||||
expect(updatedChallenge.summary).to.eql(summary);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,19 +101,21 @@ describe('POST /challenges/:challengeId/join', () => {
|
||||
});
|
||||
|
||||
it('syncs challenge tasks to joining user', async () => {
|
||||
let taskText = 'A challenge task text';
|
||||
|
||||
const taskText = 'A challenge task text';
|
||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
|
||||
{type: 'habit', text: taskText},
|
||||
{type: 'daily', text: taskText},
|
||||
]);
|
||||
|
||||
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
let tasks = await authorizedUser.get('/tasks/user');
|
||||
let tasksTexts = tasks.map((task) => {
|
||||
return task.text;
|
||||
|
||||
const tasks = await authorizedUser.get('/tasks/user');
|
||||
const syncedTask = tasks.find((task) => {
|
||||
return task.text === taskText;
|
||||
});
|
||||
|
||||
expect(tasksTexts).to.include(taskText);
|
||||
expect(syncedTask.text).to.eql(taskText);
|
||||
expect(syncedTask.isDue).to.exist;
|
||||
expect(syncedTask.nextDue).to.exist;
|
||||
});
|
||||
|
||||
it('adds challenge tag to user tags', async () => {
|
||||
|
||||
@@ -149,13 +149,19 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
let tasks = await winningUser.get('/tasks/user');
|
||||
let testTask = _.find(tasks, (task) => {
|
||||
const tasks = await winningUser.get('/tasks/user');
|
||||
const testTask = _.find(tasks, (task) => {
|
||||
return task.text === taskText;
|
||||
});
|
||||
|
||||
const updatedUser = await winningUser.sync();
|
||||
const challengeTag = updatedUser.tags.find(tags => {
|
||||
return tags.id === challenge._id;
|
||||
});
|
||||
|
||||
expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED');
|
||||
expect(testTask.challenge.winner).to.eql(winningUser.profile.name);
|
||||
expect(challengeTag.challenge).to.eql('false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('POST /challenges/:challengeId/clone', () => {
|
||||
it('clones a challenge', async () => {
|
||||
const user = await generateUser({balance: 10});
|
||||
const group = await generateGroup(user);
|
||||
|
||||
const name = 'Test Challenge';
|
||||
const shortName = 'TC Label';
|
||||
const description = 'Test Description';
|
||||
const prize = 1;
|
||||
|
||||
const challenge = await user.post('/challenges', {
|
||||
group: group._id,
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
prize,
|
||||
});
|
||||
const challengeTask = await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
notes: 1976,
|
||||
});
|
||||
|
||||
const cloneChallengeResponse = await user.post(`/challenges/${challenge._id}/clone`, {
|
||||
group: group._id,
|
||||
name: `${name} cloned`,
|
||||
shortName,
|
||||
description,
|
||||
prize,
|
||||
});
|
||||
|
||||
expect(cloneChallengeResponse.clonedTasks[0].text).to.eql(challengeTask.text);
|
||||
expect(cloneChallengeResponse.clonedTasks[0]._id).to.not.eql(challengeTask._id);
|
||||
expect(cloneChallengeResponse.clonedTasks[0].challenge.id).to.eql(cloneChallengeResponse.clonedChallenge._id);
|
||||
});
|
||||
});
|
||||
@@ -71,11 +71,9 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
||||
});
|
||||
|
||||
it('returns the update chat when previous message parameter is passed and the chat is updated', async () => {
|
||||
await expect(user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${message.id}`))
|
||||
.eventually
|
||||
.is.an('array')
|
||||
.to.include(message)
|
||||
.to.be.lengthOf(1);
|
||||
let deleteResult = await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${message.id}`);
|
||||
|
||||
expect(deleteResult[0].id).to.eql(message.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -234,7 +234,7 @@ describe('POST /chat', () => {
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('slur-report-to-mods');
|
||||
expect(email.sendTxn.args[0][1]).to.eql('slur-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
@@ -287,7 +287,7 @@ describe('POST /chat', () => {
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledThrice;
|
||||
expect(email.sendTxn.args[2][1]).to.be.eql('slur-report-to-mods');
|
||||
expect(email.sendTxn.args[2][1]).to.eql('slur-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
@@ -364,6 +364,30 @@ describe('POST /chat', () => {
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('creates a chat with user styles', async () => {
|
||||
const mount = 'test-mount';
|
||||
const pet = 'test-pet';
|
||||
const style = 'test-style';
|
||||
const userWithStyle = await generateUser({
|
||||
'items.currentMount': mount,
|
||||
'items.currentPet': pet,
|
||||
'preferences.style': style,
|
||||
});
|
||||
await userWithStyle.sync();
|
||||
|
||||
const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(message.message.userStyles.items.currentMount).to.eql(userWithStyle.items.currentMount);
|
||||
expect(message.message.userStyles.items.currentPet).to.eql(userWithStyle.items.currentPet);
|
||||
expect(message.message.userStyles.preferences.style).to.eql(userWithStyle.preferences.style);
|
||||
expect(message.message.userStyles.preferences.hair).to.eql(userWithStyle.preferences.hair);
|
||||
expect(message.message.userStyles.preferences.skin).to.eql(userWithStyle.preferences.skin);
|
||||
expect(message.message.userStyles.preferences.shirt).to.eql(userWithStyle.preferences.shirt);
|
||||
expect(message.message.userStyles.preferences.chair).to.eql(userWithStyle.preferences.chair);
|
||||
expect(message.message.userStyles.preferences.background).to.eql(userWithStyle.preferences.background);
|
||||
});
|
||||
|
||||
it('adds backer info to chat', async () => {
|
||||
const backerInfo = {
|
||||
npc: 'Town Crier',
|
||||
@@ -426,6 +450,9 @@ describe('POST /chat', () => {
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist;
|
||||
expect(memberWithNotification.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id;
|
||||
})).to.exist;
|
||||
});
|
||||
|
||||
it('notifies other users of new messages for a party', async () => {
|
||||
@@ -443,6 +470,9 @@ describe('POST /chat', () => {
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
||||
expect(memberWithNotification.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id;
|
||||
})).to.exist;
|
||||
});
|
||||
|
||||
context('Spam prevention', () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('POST /groups/:id/chat/seen', () => {
|
||||
@@ -24,10 +25,15 @@ describe('POST /groups/:id/chat/seen', () => {
|
||||
});
|
||||
|
||||
it('clears new messages for a guild', async () => {
|
||||
await guildMember.sync();
|
||||
const initialNotifications = guildMember.notifications.length;
|
||||
await guildMember.post(`/groups/${guild._id}/chat/seen`);
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
let guildThatHasSeenChat = await guildMember.get('/user');
|
||||
|
||||
expect(guildThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
|
||||
expect(guildThatHasSeenChat.newMessages).to.be.empty;
|
||||
});
|
||||
});
|
||||
@@ -53,10 +59,13 @@ describe('POST /groups/:id/chat/seen', () => {
|
||||
});
|
||||
|
||||
it('clears new messages for a party', async () => {
|
||||
await partyMember.sync();
|
||||
const initialNotifications = partyMember.notifications.length;
|
||||
await partyMember.post(`/groups/${party._id}/chat/seen`);
|
||||
|
||||
let partyMemberThatHasSeenChat = await partyMember.get('/user');
|
||||
|
||||
expect(partyMemberThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
|
||||
expect(partyMemberThatHasSeenChat.newMessages).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
@@ -93,7 +93,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
|
||||
@@ -44,6 +44,32 @@ describe('POST /group', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sets summary to groups name when not supplied', async () => {
|
||||
const name = 'Test Group';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
const updatedGroup = await user.get(`/groups/${group._id}`);
|
||||
|
||||
expect(updatedGroup.summary).to.eql(name);
|
||||
});
|
||||
|
||||
it('sets summary to groups', async () => {
|
||||
const name = 'Test Group';
|
||||
const summary = 'Test Summary';
|
||||
const group = await user.post('/groups', {
|
||||
name,
|
||||
type: 'guild',
|
||||
summary,
|
||||
});
|
||||
|
||||
const updatedGroup = await user.get(`/groups/${group._id}`);
|
||||
|
||||
expect(updatedGroup.summary).to.eql(summary);
|
||||
});
|
||||
});
|
||||
|
||||
context('Guilds', () => {
|
||||
|
||||
@@ -70,13 +70,21 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
|
||||
})).to.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
|
||||
})).to.not.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
|
||||
});
|
||||
|
||||
@@ -256,7 +264,7 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
it('deletes non existant party from user when user tries to leave', async () => {
|
||||
let nonExistentPartyId = generateUUID();
|
||||
let userWithNonExistentParty = await generateUser({'party._id': nonExistentPartyId});
|
||||
expect(userWithNonExistentParty.party._id).to.be.eql(nonExistentPartyId);
|
||||
expect(userWithNonExistentParty.party._id).to.eql(nonExistentPartyId);
|
||||
|
||||
await expect(userWithNonExistentParty.post(`/groups/${nonExistentPartyId}/leave`))
|
||||
.to.eventually.be.rejected;
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
@@ -119,16 +120,16 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(invitedUser._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('guild-invite-rescinded');
|
||||
expect(email.sendTxn.args[0][0]._id).to.eql(invitedUser._id);
|
||||
expect(email.sendTxn.args[0][1]).to.eql('guild-invite-rescinded');
|
||||
});
|
||||
|
||||
it('sends email to removed user', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(member._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('kicked-from-guild');
|
||||
expect(email.sendTxn.args[0][0]._id).to.eql(member._id);
|
||||
expect(email.sendTxn.args[0][1]).to.eql('kicked-from-guild');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,13 +189,20 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
|
||||
it('removes new messages from a member who is removed', async () => {
|
||||
await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some message' });
|
||||
await sleep(0.5);
|
||||
await removedMember.sync();
|
||||
|
||||
expect(removedMember.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
|
||||
})).to.exist;
|
||||
expect(removedMember.newMessages[party._id]).to.not.be.empty;
|
||||
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${removedMember._id}`);
|
||||
await removedMember.sync();
|
||||
|
||||
expect(removedMember.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
|
||||
})).to.not.exist;
|
||||
expect(removedMember.newMessages[party._id]).to.be.empty;
|
||||
});
|
||||
|
||||
@@ -247,16 +255,16 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(partyInvitedUser._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('party-invite-rescinded');
|
||||
expect(email.sendTxn.args[0][0]._id).to.eql(partyInvitedUser._id);
|
||||
expect(email.sendTxn.args[0][1]).to.eql('party-invite-rescinded');
|
||||
});
|
||||
|
||||
it('sends email to removed user', async () => {
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(partyMember._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('kicked-from-party');
|
||||
expect(email.sendTxn.args[0][0]._id).to.eql(partyMember._id);
|
||||
expect(email.sendTxn.args[0][1]).to.eql('kicked-from-party');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,6 +110,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
}]);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
@@ -127,11 +128,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('GET /members/:memberId', () => {
|
||||
let memberRes = await user.get(`/members/${member._id}`);
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
|
||||
@@ -98,6 +98,7 @@ describe('POST /members/send-private-message', () => {
|
||||
|
||||
it('sends a private message to a user', async () => {
|
||||
let receiver = await generateUser();
|
||||
// const initialNotifications = receiver.notifications.length;
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
@@ -115,10 +116,44 @@ describe('POST /members/send-private-message', () => {
|
||||
return message.uuid === receiver._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
// @TODO waiting for mobile support
|
||||
// expect(updatedReceiver.notifications.length).to.equal(initialNotifications + 1);
|
||||
// const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
|
||||
|
||||
// expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
|
||||
// expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
|
||||
// expect(notification.data.excerpt).to.equal(messageToSend);
|
||||
// expect(notification.data.sender.id).to.equal(updatedSender._id);
|
||||
// expect(notification.data.sender.name).to.equal(updatedSender.profile.name);
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
});
|
||||
|
||||
// @TODO waiting for mobile support
|
||||
xit('creates a notification with an excerpt if the message is too long', async () => {
|
||||
let receiver = await generateUser();
|
||||
let longerMessageToSend = 'A very long message, that for sure exceeds the limit of 100 chars for the excerpt that we set to 100 chars';
|
||||
let messageExcerpt = `${longerMessageToSend.substring(0, 100)}...`;
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: longerMessageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let updatedReceiver = await receiver.get('/user');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === longerMessageToSend;
|
||||
});
|
||||
|
||||
const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
|
||||
|
||||
expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
|
||||
expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
|
||||
expect(notification.data.excerpt).to.equal(messageExcerpt);
|
||||
});
|
||||
|
||||
it('allows admin to send when sender has blocked the admin', async () => {
|
||||
userToSendMessage = await generateUser({
|
||||
'contributor.admin': 1,
|
||||
|
||||
16
test/api/v3/integration/news/GET-news.test.js
Normal file
16
test/api/v3/integration/news/GET-news.test.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import {
|
||||
requester,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('GET /news', () => {
|
||||
let api;
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester();
|
||||
});
|
||||
|
||||
it('returns the latest news in html format, does not require authentication', async () => {
|
||||
const res = await api.get('/news');
|
||||
expect(res).to.be.a.string;
|
||||
});
|
||||
});
|
||||
42
test/api/v3/integration/news/POST-news_tell_me_later.test.js
Normal file
42
test/api/v3/integration/news/POST-news_tell_me_later.test.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('POST /news/tell-me-later', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'flags.newStuff': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('marks new stuff as read and adds notification', async () => {
|
||||
expect(user.flags.newStuff).to.equal(true);
|
||||
const initialNotifications = user.notifications.length;
|
||||
|
||||
await user.post('/news/tell-me-later');
|
||||
await user.sync();
|
||||
|
||||
expect(user.flags.newStuff).to.equal(false);
|
||||
expect(user.notifications.length).to.equal(initialNotifications + 1);
|
||||
|
||||
const notification = user.notifications[user.notifications.length - 1];
|
||||
|
||||
expect(notification.type).to.equal('NEW_STUFF');
|
||||
// should be marked as seen by default so it's not counted in the number of notifications
|
||||
expect(notification.seen).to.equal(true);
|
||||
expect(notification.data.title).to.be.a.string;
|
||||
});
|
||||
|
||||
it('never adds two notifications', async () => {
|
||||
const initialNotifications = user.notifications.length;
|
||||
|
||||
await user.post('/news/tell-me-later');
|
||||
await user.post('/news/tell-me-later');
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.notifications.length).to.equal(initialNotifications + 1);
|
||||
});
|
||||
});
|
||||
@@ -47,6 +47,7 @@ describe('POST /notifications/:notificationId/read', () => {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
seen: false,
|
||||
}]);
|
||||
|
||||
await user.sync();
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /notifications/:notificationId/see', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('errors when notification is not found', async () => {
|
||||
let dummyId = generateUUID();
|
||||
|
||||
await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('mark a notification as seen', async () => {
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
|
||||
const id = generateUUID();
|
||||
const id2 = generateUUID();
|
||||
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
}, {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
}],
|
||||
});
|
||||
|
||||
const userObj = await user.get('/user'); // so we can check that defaults have been applied
|
||||
expect(userObj.notifications.length).to.equal(2);
|
||||
expect(userObj.notifications[0].seen).to.equal(false);
|
||||
|
||||
const res = await user.post(`/notifications/${id}/see`);
|
||||
expect(res).to.deep.equal({
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
seen: true,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(2);
|
||||
expect(user.notifications[0].id).to.equal(id);
|
||||
expect(user.notifications[0].seen).to.equal(true);
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /notifications/:notificationId/read', () => {
|
||||
describe('POST /notifications/read', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
@@ -57,6 +57,7 @@ describe('POST /notifications/:notificationId/read', () => {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
seen: false,
|
||||
}]);
|
||||
|
||||
await user.sync();
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /notifications/see', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('errors when notification is not found', async () => {
|
||||
let dummyId = generateUUID();
|
||||
|
||||
await expect(user.post('/notifications/see', {
|
||||
notificationIds: [dummyId],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('mark multiple notifications as seen', async () => {
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
|
||||
const id = generateUUID();
|
||||
const id2 = generateUUID();
|
||||
const id3 = generateUUID();
|
||||
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
seen: false,
|
||||
}, {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
seen: false,
|
||||
}, {
|
||||
id: id3,
|
||||
type: 'CRON',
|
||||
data: {},
|
||||
seen: false,
|
||||
}],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(3);
|
||||
|
||||
const res = await user.post('/notifications/see', {
|
||||
notificationIds: [id, id3],
|
||||
});
|
||||
|
||||
expect(res).to.deep.equal([
|
||||
{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
seen: true,
|
||||
}, {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
seen: false,
|
||||
}, {
|
||||
id: id3,
|
||||
type: 'CRON',
|
||||
data: {},
|
||||
seen: true,
|
||||
}]);
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(3);
|
||||
expect(user.notifications[0].id).to.equal(id);
|
||||
expect(user.notifications[0].seen).to.equal(true);
|
||||
|
||||
expect(user.notifications[1].id).to.equal(id2);
|
||||
expect(user.notifications[1].seen).to.equal(false);
|
||||
|
||||
expect(user.notifications[2].id).to.equal(id3);
|
||||
expect(user.notifications[2].seen).to.equal(true);
|
||||
});
|
||||
});
|
||||
@@ -139,6 +139,23 @@ describe('PUT /tasks/:id', () => {
|
||||
expect(savedHabit.up).to.eql(false);
|
||||
expect(savedHabit.down).to.eql(false);
|
||||
});
|
||||
|
||||
it('allows user to update their copy', async () => {
|
||||
const userTasks = await user.get('/tasks/user');
|
||||
const userChallengeTasks = userTasks.filter(task => task.challenge.id === challenge._id);
|
||||
const userCopyOfChallengeTask = userChallengeTasks[0];
|
||||
|
||||
await user.put(`/tasks/${userCopyOfChallengeTask._id}`, {
|
||||
notes: 'some new notes',
|
||||
counterDown: 1,
|
||||
counterUp: 2,
|
||||
});
|
||||
const savedHabit = await user.get(`/tasks/${userCopyOfChallengeTask._id}`);
|
||||
|
||||
expect(savedHabit.notes).to.eql('some new notes');
|
||||
expect(savedHabit.counterDown).to.eql(1);
|
||||
expect(savedHabit.counterUp).to.eql(2);
|
||||
});
|
||||
});
|
||||
|
||||
context('todos', () => {
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { find } from 'lodash';
|
||||
|
||||
describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
let user, guild, member, member2, task;
|
||||
|
||||
function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === guild._id;
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
let {group, members, groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
member = members[0];
|
||||
member2 = members[1];
|
||||
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
requiresApproval: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when user is not assigned', async () => {
|
||||
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when user is not the group leader', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await expect(member.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanEditTasks'),
|
||||
});
|
||||
});
|
||||
|
||||
it('marks as task as needing more work', async () => {
|
||||
const initialNotifications = member.notifications.length;
|
||||
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
// score task to require approval
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task._id}/needs-work/${member._id}`);
|
||||
|
||||
[memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]);
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
// Check that the notification approval request has been removed
|
||||
expect(syncedTask.group.approval.requested).to.equal(false);
|
||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||
|
||||
// Check that the notification is correct
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||
const notification = member.notifications[member.notifications.length - 1];
|
||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||
|
||||
const taskText = syncedTask.text;
|
||||
const managerName = user.profile.name;
|
||||
|
||||
expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName}));
|
||||
|
||||
expect(notification.data.task.id).to.equal(syncedTask._id);
|
||||
expect(notification.data.task.text).to.equal(taskText);
|
||||
|
||||
expect(notification.data.group.id).to.equal(syncedTask.group.id);
|
||||
expect(notification.data.group.name).to.equal(guild.name);
|
||||
|
||||
expect(notification.data.manager.id).to.equal(user._id);
|
||||
expect(notification.data.manager.name).to.equal(managerName);
|
||||
|
||||
// Check that the managers' GROUP_TASK_APPROVAL notifications have been removed
|
||||
await user.sync();
|
||||
|
||||
expect(user.notifications.find(n => {
|
||||
n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
|
||||
})).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('allows a manager to mark a task as needing work', async () => {
|
||||
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||
managerId: member2._id,
|
||||
});
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
// score task to require approval
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
const initialNotifications = member.notifications.length;
|
||||
|
||||
await member2.post(`/tasks/${task._id}/needs-work/${member._id}`);
|
||||
|
||||
[memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]);
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
// Check that the notification approval request has been removed
|
||||
expect(syncedTask.group.approval.requested).to.equal(false);
|
||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||
const notification = member.notifications[member.notifications.length - 1];
|
||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||
|
||||
const taskText = syncedTask.text;
|
||||
const managerName = member2.profile.name;
|
||||
|
||||
expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName}));
|
||||
|
||||
expect(notification.data.task.id).to.equal(syncedTask._id);
|
||||
expect(notification.data.task.text).to.equal(taskText);
|
||||
|
||||
expect(notification.data.group.id).to.equal(syncedTask.group.id);
|
||||
expect(notification.data.group.name).to.equal(guild.name);
|
||||
|
||||
expect(notification.data.manager.id).to.equal(member2._id);
|
||||
expect(notification.data.manager.name).to.equal(managerName);
|
||||
|
||||
// Check that the managers' GROUP_TASK_APPROVAL notifications have been removed
|
||||
await Promise.all([user.sync(), member2.sync()]);
|
||||
|
||||
expect(user.notifications.find(n => {
|
||||
n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
|
||||
})).to.equal(undefined);
|
||||
|
||||
expect(member2.notifications.find(n => {
|
||||
n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
|
||||
})).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('prevents marking a task as needing work if it was already approved', async () => {
|
||||
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||
managerId: member2._id,
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('canOnlyApproveTaskOnce'),
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents marking a task as needing work if it is not waiting for approval', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalWasNotRequested'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -25,6 +25,7 @@ describe('GET /user/anonymized', () => {
|
||||
'achievements.challenges': 'some',
|
||||
'inbox.messages': [{ text: 'some text' }],
|
||||
tags: [{ name: 'some name', challenge: 'some challenge' }],
|
||||
notifications: [],
|
||||
});
|
||||
|
||||
await generateHabit({ userId: user._id });
|
||||
@@ -65,6 +66,7 @@ describe('GET /user/anonymized', () => {
|
||||
expect(returnedUser.stats.toNextLevel).to.eql(common.tnl(user.stats.lvl));
|
||||
expect(returnedUser.stats.maxMP).to.eql(30); // TODO why 30?
|
||||
expect(returnedUser.newMessages).to.not.exist;
|
||||
expect(returnedUser.notifications).to.not.exist;
|
||||
expect(returnedUser.profile).to.not.exist;
|
||||
expect(returnedUser.purchased.plan).to.not.exist;
|
||||
expect(returnedUser.contributor).to.not.exist;
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('POST /user/feed/:pet/:food', () => {
|
||||
data: user.items.pets['Wolf-Base'],
|
||||
message: t('messageDontEnjoyFood', {
|
||||
egg: pet.text(),
|
||||
foodText: food.text(),
|
||||
foodText: food.textThe(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -13,15 +13,20 @@ describe('POST /user/open-mystery-item', () => {
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'purchased.plan.mysteryItems': [mysteryItemKey],
|
||||
notifications: [
|
||||
{type: 'NEW_MYSTERY_ITEMS', data: { items: [mysteryItemKey] }},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('opens a mystery item', async () => {
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
let response = await user.post('/user/open-mystery-item');
|
||||
await user.sync();
|
||||
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
expect(user.items.gear.owned[mysteryItemKey]).to.be.true;
|
||||
expect(response.message).to.equal(t('mysteryItemOpened'));
|
||||
expect(response.data.key).to.eql(mysteryItemKey);
|
||||
|
||||
@@ -26,13 +26,21 @@ describe('POST /user/read-card/:cardType', () => {
|
||||
await user.update({
|
||||
'items.special.greetingReceived': [true],
|
||||
'flags.cardReceived': true,
|
||||
notifications: [{
|
||||
type: 'CARD_RECEIVED',
|
||||
data: {card: cardType},
|
||||
}],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
|
||||
let response = await user.post(`/user/read-card/${cardType}`);
|
||||
await user.sync();
|
||||
|
||||
expect(response.message).to.equal(t('readCard', {cardType}));
|
||||
expect(user.items.special[`${cardType}Received`]).to.be.empty;
|
||||
expect(user.flags.cardReceived).to.be.false;
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService';
|
||||
|
||||
describe('POST /user/sleep', () => {
|
||||
let user;
|
||||
@@ -22,4 +23,15 @@ describe('POST /user/sleep', () => {
|
||||
await user.sync();
|
||||
expect(user.preferences.sleep).to.be.false;
|
||||
});
|
||||
|
||||
it('sends sleep status to analytics service', async () => {
|
||||
sandbox.spy(analytics, 'track');
|
||||
|
||||
await user.post('/user/sleep');
|
||||
await user.sync();
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
expect(analytics.track).to.be.calledWith('sleep', sandbox.match.has('status', user.preferences.sleep));
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,10 +6,14 @@ import {
|
||||
getProperty,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { ApiUser } from '../../../../../helpers/api-integration/api-classes';
|
||||
import { v4 as generateRandomUserName } from 'uuid';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { each } from 'lodash';
|
||||
import { encrypt } from '../../../../../../website/server/libs/encryption';
|
||||
|
||||
function generateRandomUserName () {
|
||||
return (Date.now() + uuid()).substring(0, 20);
|
||||
}
|
||||
|
||||
describe('POST /user/auth/local/register', () => {
|
||||
context('username and email are free', () => {
|
||||
let api;
|
||||
@@ -37,6 +41,71 @@ describe('POST /user/auth/local/register', () => {
|
||||
expect(user.newUser).to.eql(true);
|
||||
});
|
||||
|
||||
xit('remove spaces from username', async () => {
|
||||
// TODO can probably delete this test now
|
||||
let username = ' usernamewithspaces ';
|
||||
let email = 'test@example.com';
|
||||
let password = 'password';
|
||||
|
||||
let user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.auth.local.username).to.eql(username.trim());
|
||||
expect(user.profile.name).to.eql(username.trim());
|
||||
});
|
||||
|
||||
context('validates username', () => {
|
||||
const email = 'test@example.com';
|
||||
const password = 'password';
|
||||
|
||||
it('requires to username to be less than 20', async () => {
|
||||
const username = (Date.now() + uuid()).substring(0, 21);
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects chracters not in [-_a-zA-Z0-9]', async () => {
|
||||
const username = 'a-zA_Z09*';
|
||||
|
||||
await expect(api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows only [-_a-zA-Z0-9] characters', async () => {
|
||||
const username = 'a-zA_Z09';
|
||||
|
||||
const user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
});
|
||||
});
|
||||
|
||||
context('provides default tags and tasks', async () => {
|
||||
it('for a generic API consumer', async () => {
|
||||
let username = generateRandomUserName();
|
||||
|
||||
35
test/api/v3/integration/world-state/GET-world-state.test.js
Normal file
35
test/api/v3/integration/world-state/GET-world-state.test.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { TAVERN_ID } from '../../../../../website/server/models/group';
|
||||
import { updateDocument } from '../../../../helpers/mongo';
|
||||
import {
|
||||
requester,
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('GET /world-state', () => {
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
});
|
||||
|
||||
it('returns empty worldBoss object when world boss is not active (and does not require authentication)', async () => {
|
||||
const res = await requester().get('/world-state');
|
||||
expect(res.worldBoss).to.eql({});
|
||||
});
|
||||
|
||||
it('returns Tavern quest data when world boss is active', async () => {
|
||||
await updateDocument('groups', {_id: TAVERN_ID}, {quest: {active: true, key: 'dysheartener', progress: {hp: 50000, rage: 9999}}});
|
||||
|
||||
const res = await requester().get('/world-state');
|
||||
expect(res).to.have.deep.property('worldBoss');
|
||||
|
||||
expect(res.worldBoss).to.eql({
|
||||
active: true,
|
||||
extra: {},
|
||||
key: 'dysheartener',
|
||||
progress: {
|
||||
collect: {},
|
||||
hp: 50000,
|
||||
rage: 9999,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -153,6 +153,24 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateUpdated if it did exist but the user has cancelled', async () => {
|
||||
recipient.purchased.plan.dateUpdated = moment().subtract(1, 'days').toDate();
|
||||
recipient.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
|
||||
recipient.purchased.plan.customerId = 'testing';
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(moment(recipient.purchased.plan.dateUpdated).date()).to.eql(moment().date());
|
||||
});
|
||||
|
||||
it('sets plan.dateUpdated if it did exist but the user has a corrupt plan', async () => {
|
||||
recipient.purchased.plan.dateUpdated = moment().subtract(1, 'days').toDate();
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(moment(recipient.purchased.plan.dateUpdated).date()).to.eql(moment().date());
|
||||
});
|
||||
|
||||
it('sets plan.dateCreated if it did not previously exist', async () => {
|
||||
expect(recipient.purchased.plan.dateCreated).to.not.exist;
|
||||
|
||||
@@ -191,7 +209,7 @@ describe('payments/index', () => {
|
||||
await api.createSubscription(data);
|
||||
let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledTwice;
|
||||
expect(user.sendMessage).to.be.calledOnce;
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg });
|
||||
});
|
||||
|
||||
@@ -229,77 +247,6 @@ describe('payments/index', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('Winter 2017-18 Gift-1-Get-1 Promotion', async () => {
|
||||
it('creates a gift subscription for purchaser and recipient if none exist', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(user.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
|
||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => {
|
||||
user.purchased.plan = plan;
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||
|
||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('adds extraMonths to existing subscription for recipient and creates a gift subscription for purchaser without sub', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||
|
||||
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
expect(user.purchased.plan.customerId).to.eql('Gift');
|
||||
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('adds extraMonths to existing subscriptions for purchaser and recipient', async () => {
|
||||
user.purchased.plan = plan;
|
||||
recipient.purchased.plan = plan;
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||
});
|
||||
|
||||
it('sends a private message about the promotion', async () => {
|
||||
await api.createSubscription(data);
|
||||
let msg = '\`Hello sender, you received 3 months of subscription as part of our holiday gift-giving promotion!\`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledTwice;
|
||||
expect(user.sendMessage).to.be.calledWith(user, { senderMsg: msg });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Purchasing a subscription for self', () => {
|
||||
@@ -470,13 +417,19 @@ describe('payments/index', () => {
|
||||
it('awards mystery items when within the timeframe for a mystery item', async () => {
|
||||
let mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
|
||||
let fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
|
||||
|
||||
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
||||
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.notifications.find(n => n.type === 'NEW_MYSTERY_ITEMS')).to.not.be.undefined;
|
||||
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(2);
|
||||
expect(user.purchased.plan.mysteryItems).to.include('armor_mystery_201605');
|
||||
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount + 1);
|
||||
expect(user.notifications[0].type).to.equal('NEW_MYSTERY_ITEMS');
|
||||
|
||||
fakeClock.restore();
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('#upgradeGroupPlan', () => {
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore(amzLib.authorizeOnBillingAgreement);
|
||||
amzLib.authorizeOnBillingAgreement.restore();
|
||||
uuid.v4.restore();
|
||||
});
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('subscribe', () => {
|
||||
cc.validate.restore();
|
||||
});
|
||||
|
||||
it('subscribes with amazon with a coupon', async () => {
|
||||
it('subscribes with paypal with a coupon', async () => {
|
||||
sub.discount = 40;
|
||||
sub.key = 'google_6mo';
|
||||
coupon = 'example-coupon';
|
||||
|
||||
@@ -73,7 +73,7 @@ describe('checkout with subscription', () => {
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore(stripe.subscriptions.update);
|
||||
stripe.subscriptions.update.restore();
|
||||
stripe.customers.create.restore();
|
||||
payments.createSubscription.restore();
|
||||
});
|
||||
@@ -144,7 +144,7 @@ describe('checkout with subscription', () => {
|
||||
cc.validate.restore();
|
||||
});
|
||||
|
||||
it('subscribes with amazon with a coupon', async () => {
|
||||
it('subscribes with stripe with a coupon', async () => {
|
||||
sub.discount = 40;
|
||||
sub.key = 'google_6mo';
|
||||
coupon = 'example-coupon';
|
||||
|
||||
@@ -35,7 +35,10 @@ describe('Stripe - Webhooks', () => {
|
||||
const error = new Error(`Missing handler for Stripe webhook ${eventType}`);
|
||||
await stripePayments.handleWebhooks({requestBody: event}, stripe);
|
||||
expect(logger.error).to.have.been.called.once;
|
||||
expect(logger.error).to.have.been.calledWith(error, {event: eventRetrieved});
|
||||
|
||||
const calledWith = logger.error.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal(error.message);
|
||||
expect(calledWith[1].event).to.equal(eventRetrieved);
|
||||
});
|
||||
|
||||
it('retrieves and validates the event from Stripe', async () => {
|
||||
|
||||
@@ -45,7 +45,7 @@ describe('Stripe - Upgrade Group Plan', () => {
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore(stripe.subscriptions.update);
|
||||
stripe.subscriptions.update.restore();
|
||||
});
|
||||
|
||||
it('updates a group plan quantity', async () => {
|
||||
|
||||
@@ -8,7 +8,10 @@ describe('preenHistory', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// Replace system clocks so we can get predictable results
|
||||
clock = sinon.useFakeTimers(Number(moment('2013-10-20').zone(0).startOf('day').toDate()), 'Date');
|
||||
clock = sinon.useFakeTimers({
|
||||
now: Number(moment('2013-10-20').zone(0).startOf('day').toDate()),
|
||||
toFake: ['Date'],
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
return clock.restore();
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('pushNotifications', () => {
|
||||
|
||||
sandbox.stub(nconf, 'get').returns('true-key');
|
||||
|
||||
sandbox.stub(gcmLib.Sender.prototype, 'send', fcmSendSpy);
|
||||
sandbox.stub(gcmLib.Sender.prototype, 'send').callsFake(fcmSendSpy);
|
||||
|
||||
sandbox.stub(pushNotify, 'apn').returns({
|
||||
on: () => null,
|
||||
|
||||
@@ -24,7 +24,9 @@ describe('ensure access middlewares', () => {
|
||||
|
||||
ensureAdmin(req, res, next);
|
||||
|
||||
expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noAdminAccess')));
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal(i18n.t('noAdminAccess'));
|
||||
expect(calledWith[0] instanceof NotAuthorized).to.equal(true);
|
||||
});
|
||||
|
||||
it('passes when user is an admin', () => {
|
||||
@@ -43,7 +45,9 @@ describe('ensure access middlewares', () => {
|
||||
|
||||
ensureSudo(req, res, next);
|
||||
|
||||
expect(next).to.be.calledWith(new NotAuthorized(apiMessages('noSudoAccess')));
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(calledWith[0].message).to.equal(apiMessages('noSudoAccess'));
|
||||
expect(calledWith[0] instanceof NotAuthorized).to.equal(true);
|
||||
});
|
||||
|
||||
it('passes when user is a sudo user', () => {
|
||||
|
||||
@@ -22,7 +22,8 @@ describe('developmentMode middleware', () => {
|
||||
|
||||
ensureDevelpmentMode(req, res, next);
|
||||
|
||||
expect(next).to.be.calledWith(new NotFound());
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(calledWith[0] instanceof NotFound).to.equal(true);
|
||||
});
|
||||
|
||||
it('passes when not in production', () => {
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('errorHandler', () => {
|
||||
});
|
||||
|
||||
it('handle Mongoose Validation errors', () => {
|
||||
let error = new Error('User validation failed.');
|
||||
let error = new Error('User validation failed');
|
||||
error.name = 'ValidationError';
|
||||
|
||||
error.errors = {
|
||||
@@ -151,7 +151,7 @@ describe('errorHandler', () => {
|
||||
expect(res.json).to.be.calledWith({
|
||||
success: false,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed.',
|
||||
message: 'User validation failed',
|
||||
errors: [
|
||||
{ path: 'auth.local.email', message: 'Invalid email.', value: 'not an email' },
|
||||
],
|
||||
|
||||
@@ -90,8 +90,15 @@ describe('response middleware', () => {
|
||||
});
|
||||
|
||||
it('returns notifications if a user is authenticated', () => {
|
||||
res.locals.user.notifications.push({type: 'NEW_CONTRIBUTOR_LEVEL'});
|
||||
let notification = res.locals.user.notifications[0].toJSON();
|
||||
const user = res.locals.user;
|
||||
|
||||
user.notifications = [
|
||||
null, // invalid, not an object
|
||||
{seen: true}, // invalid, no type or id
|
||||
{id: 123}, // invalid, no type
|
||||
// {type: 'ABC'}, // invalid, no id, not included here because the id would be added automatically
|
||||
{type: 'ABC', id: '123'}, // valid
|
||||
];
|
||||
|
||||
responseMiddleware(req, res, next);
|
||||
res.respond(200, {field: 1});
|
||||
@@ -103,9 +110,10 @@ describe('response middleware', () => {
|
||||
data: {field: 1},
|
||||
notifications: [
|
||||
{
|
||||
type: notification.type,
|
||||
id: notification.id,
|
||||
type: 'ABC',
|
||||
id: '123',
|
||||
data: {},
|
||||
seen: false,
|
||||
},
|
||||
],
|
||||
userV: res.locals.user._v,
|
||||
|
||||
@@ -74,14 +74,15 @@ describe('Challenge Model', () => {
|
||||
it('adds tasks to challenge and challenge members', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) {
|
||||
const updatedLeader = await User.findOne({_id: leader._id});
|
||||
const updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
const syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) {
|
||||
return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text;
|
||||
});
|
||||
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.notes).to.eql(task.notes);
|
||||
expect(syncedTask.tags[0]).to.eql(challenge._id);
|
||||
});
|
||||
|
||||
it('syncs a challenge to a user', async () => {
|
||||
|
||||
@@ -1011,13 +1011,6 @@ describe('Group Model', () => {
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: '' },
|
||||
}, {
|
||||
$set: {
|
||||
[`newMessages.${party._id}`]: {
|
||||
name: party.name,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1032,13 +1025,6 @@ describe('Group Model', () => {
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
guilds: group._id,
|
||||
_id: { $ne: '' },
|
||||
}, {
|
||||
$set: {
|
||||
[`newMessages.${group._id}`]: {
|
||||
name: group.name,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1049,13 +1035,6 @@ describe('Group Model', () => {
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: 'user-id' },
|
||||
}, {
|
||||
$set: {
|
||||
[`newMessages.${party._id}`]: {
|
||||
name: party.name,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -58,25 +58,46 @@ describe('User Model', () => {
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
});
|
||||
|
||||
it('can add notifications with data', () => {
|
||||
it('removes invalid notifications when calling toJSON', () => {
|
||||
let user = new User();
|
||||
|
||||
user.addNotification('CRON', {field: 1});
|
||||
user.notifications = [
|
||||
null, // invalid, not an object
|
||||
{seen: true}, // invalid, no type or id
|
||||
{id: 123}, // invalid, no type
|
||||
// {type: 'ABC'}, // invalid, no id, not included here because the id would be added automatically
|
||||
{type: 'ABC', id: '123'}, // valid
|
||||
];
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(userToJSON.notifications.length).to.equal(1);
|
||||
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('ABC');
|
||||
expect(userToJSON.notifications[0].id).to.equal('123');
|
||||
});
|
||||
|
||||
it('can add notifications with data and already marked as seen', () => {
|
||||
let user = new User();
|
||||
|
||||
user.addNotification('CRON', {field: 1}, true);
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(true);
|
||||
});
|
||||
|
||||
context('static push method', () => {
|
||||
it('adds notifications for a single member via static method', async() => {
|
||||
it('adds notifications for a single member via static method', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
|
||||
@@ -86,16 +107,17 @@ describe('User Model', () => {
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
});
|
||||
|
||||
it('validates notifications via static method', async() => {
|
||||
it('validates notifications via static method', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
|
||||
expect(User.pushNotification({_id: user._id}, 'BAD_TYPE')).to.eventually.be.rejected;
|
||||
expect(User.pushNotification({_id: user._id}, 'CRON', null, 'INVALID_SEEN')).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('adds notifications without data for all given users via static method', async() => {
|
||||
@@ -109,41 +131,45 @@ describe('User Model', () => {
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
});
|
||||
|
||||
it('adds notifications with data for all given users via static method', async() => {
|
||||
it('adds notifications with data and seen status for all given users via static method', async () => {
|
||||
let user = new User();
|
||||
let otherUser = new User();
|
||||
await Bluebird.all([user.save(), otherUser.save()]);
|
||||
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1});
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1}, true);
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(true);
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -322,6 +348,65 @@ describe('User Model', () => {
|
||||
user = await user.save();
|
||||
expect(user.achievements.beastMaster).to.not.equal(true);
|
||||
});
|
||||
|
||||
context('manage unallocated stats points notifications', () => {
|
||||
it('doesn\'t add a notification if there are no points to allocate', async () => {
|
||||
let user = new User();
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 0;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount);
|
||||
});
|
||||
|
||||
it('removes a notification if there are no more points to allocate', async () => {
|
||||
let user = new User();
|
||||
user.stats.points = 9;
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 0;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount - 1);
|
||||
});
|
||||
|
||||
it('adds a notification if there are points to allocate', async () => {
|
||||
let user = new User();
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 9;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount + 1);
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
expect(user.notifications[0].data.points).to.equal(9);
|
||||
});
|
||||
|
||||
it('adds a notification if the points to allocate have changed', async () => {
|
||||
let user = new User();
|
||||
user.stats.points = 9;
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
const oldNotificationsUUID = user.notifications[0].id;
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
expect(user.notifications[0].data.points).to.equal(9);
|
||||
|
||||
user.stats.points = 11;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount);
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
expect(user.notifications[0].data.points).to.equal(11);
|
||||
expect(user.notifications[0].id).to.not.equal(oldNotificationsUUID);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('days missed', () => {
|
||||
|
||||
21
test/api/v3/unit/models/userNotification.test.js
Normal file
21
test/api/v3/unit/models/userNotification.test.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { model as UserNotification } from '../../../../../website/server/models/userNotification';
|
||||
|
||||
describe('UserNotification Model', () => {
|
||||
context('convertNotificationsToSafeJson', () => {
|
||||
it('converts an array of notifications to a safe version', () => {
|
||||
const notifications = [
|
||||
null, // invalid, not an object
|
||||
{seen: true}, // invalid, no type or id
|
||||
{id: 123}, // invalid, no type
|
||||
{type: 'ABC'}, // invalid, no id
|
||||
new UserNotification({type: 'ABC', id: 123}), // valid
|
||||
];
|
||||
|
||||
const notificationsToJSON = UserNotification.convertNotificationsToSafeJson(notifications);
|
||||
expect(notificationsToJSON.length).to.equal(1);
|
||||
expect(notificationsToJSON[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(notificationsToJSON[0].type).to.equal('ABC');
|
||||
expect(notificationsToJSON[0].id).to.equal('123');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -34,6 +34,31 @@ describe('shops', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows relevant non class gear in special category', () => {
|
||||
let contributor = generateUser({
|
||||
contributor: {
|
||||
level: 7,
|
||||
critical: true,
|
||||
},
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
weapon_armoire_basicCrossbow: true, // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let gearCategories = shared.shops.getMarketGearCategories(contributor);
|
||||
let specialCategory = gearCategories.find(o => o.identifier === 'none');
|
||||
expect(specialCategory.items.find((item) => item.key === 'weapon_special_1'));
|
||||
expect(specialCategory.items.find((item) => item.key === 'armor_special_1'));
|
||||
expect(specialCategory.items.find((item) => item.key === 'head_special_1'));
|
||||
expect(specialCategory.items.find((item) => item.key === 'shield_special_1'));
|
||||
expect(specialCategory.items.find((item) => item.key === 'weapon_special_critical'));
|
||||
expect(specialCategory.items.find((item) => item.key === 'weapon_armoire_basicCrossbow'));// eslint-disable-line camelcase
|
||||
});
|
||||
});
|
||||
|
||||
describe('questShop', () => {
|
||||
|
||||
@@ -138,5 +138,27 @@ describe('shared.ops.buyGear', () => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not buyGear equipment if user does not own prior item in sequence', (done) => {
|
||||
user.stats.gp = 200;
|
||||
|
||||
try {
|
||||
buyGear(user, {params: {key: 'armor_warrior_2'}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('previousGearNotOwned'));
|
||||
expect(user.items.gear.owned).to.not.have.property('armor_warrior_2');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does buyGear equipment if item is a numbered special item user qualifies for', () => {
|
||||
user.stats.gp = 200;
|
||||
user.items.gear.owned.head_special_2 = false;
|
||||
|
||||
buyGear(user, {params: {key: 'head_special_2'}});
|
||||
|
||||
expect(user.items.gear.owned).to.have.property('head_special_2', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('shared.ops.feed', () => {
|
||||
expect(data).to.eql(user.items.pets['Wolf-Base']);
|
||||
expect(message).to.eql(i18n.t('messageLikesFood', {
|
||||
egg: pet.text(),
|
||||
foodText: food.text(),
|
||||
foodText: food.textThe(),
|
||||
}));
|
||||
|
||||
expect(user.items.food.Meat).to.equal(1);
|
||||
@@ -143,7 +143,7 @@ describe('shared.ops.feed', () => {
|
||||
expect(data).to.eql(user.items.pets['Wolf-Spooky']);
|
||||
expect(message).to.eql(i18n.t('messageLikesFood', {
|
||||
egg: pet.text(),
|
||||
foodText: food.text(),
|
||||
foodText: food.textThe(),
|
||||
}));
|
||||
|
||||
expect(user.items.food.Milk).to.equal(1);
|
||||
@@ -161,7 +161,7 @@ describe('shared.ops.feed', () => {
|
||||
expect(data).to.eql(user.items.pets['Wolf-Base']);
|
||||
expect(message).to.eql(i18n.t('messageDontEnjoyFood', {
|
||||
egg: pet.text(),
|
||||
foodText: food.text(),
|
||||
foodText: food.textThe(),
|
||||
}));
|
||||
|
||||
expect(user.items.food.Milk).to.equal(1);
|
||||
|
||||
@@ -29,11 +29,14 @@ describe('shared.ops.openMysteryItem', () => {
|
||||
let mysteryItemKey = 'eyewear_special_summerRogue';
|
||||
|
||||
user.purchased.plan.mysteryItems = [mysteryItemKey];
|
||||
user.notifications.push({type: 'NEW_MYSTERY_ITEMS', data: {items: [mysteryItemKey]}});
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
|
||||
let [data, message] = openMysteryItem(user);
|
||||
|
||||
expect(user.items.gear.owned[mysteryItemKey]).to.be.true;
|
||||
expect(message).to.equal(i18n.t('mysteryItemOpened'));
|
||||
expect(data).to.eql(content.gear.flat[mysteryItemKey]);
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,10 +39,17 @@ describe('shared.ops.readCard', () => {
|
||||
});
|
||||
|
||||
it('reads a card', () => {
|
||||
user.notifications.push({
|
||||
type: 'CARD_RECEIVED',
|
||||
data: {card: cardType},
|
||||
});
|
||||
const initialNotificationNuber = user.notifications.length;
|
||||
|
||||
let [, message] = readCard(user, {params: {cardType: 'greeting'}});
|
||||
|
||||
expect(message).to.equal(i18n.t('readCard', {cardType}));
|
||||
expect(user.items.special[`${cardType}Received`]).to.be.empty;
|
||||
expect(user.flags.cardReceived).to.be.false;
|
||||
expect(user.notifications.length).to.equal(initialNotificationNuber - 1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,13 +74,6 @@ describe('shared.ops.scoreTask', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('checks that the streak parameters affects the score', () => {
|
||||
let task = generateDaily({ userId: ref.afterUser._id, text: 'task to check streak' });
|
||||
scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false });
|
||||
scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false });
|
||||
expect(task.streak).to.eql(2);
|
||||
});
|
||||
|
||||
it('completes when the task direction is up', () => {
|
||||
let task = generateTodo({ userId: ref.afterUser._id, text: 'todo to complete', cron: false });
|
||||
scoreTask({ user: ref.afterUser, task, direction: 'up' });
|
||||
@@ -123,6 +116,64 @@ describe('shared.ops.scoreTask', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('checks that the streak parameters affects the score', () => {
|
||||
let task = generateDaily({ userId: ref.afterUser._id, text: 'task to check streak' });
|
||||
scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false });
|
||||
scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false });
|
||||
expect(task.streak).to.eql(2);
|
||||
});
|
||||
|
||||
describe('verifies that 21-day streak achievements are given/removed correctly', () => {
|
||||
let initialStreakCount = 20; // 1 before the streak achievement is awarded
|
||||
beforeEach(() => {
|
||||
ref = beforeAfter();
|
||||
});
|
||||
|
||||
it('awards the first streak achievement', () => {
|
||||
let task = generateDaily({ userId: ref.afterUser._id, text: 'some daily', streak: initialStreakCount });
|
||||
scoreTask({ user: ref.afterUser, task, direction: 'up' });
|
||||
expect(ref.afterUser.achievements.streak).to.equal(1);
|
||||
});
|
||||
|
||||
it('increments the streak achievement for a second streak', () => {
|
||||
let task1 = generateDaily({ userId: ref.afterUser._id, text: 'first daily', streak: initialStreakCount });
|
||||
scoreTask({ user: ref.afterUser, task: task1, direction: 'up' });
|
||||
let task2 = generateDaily({ userId: ref.afterUser._id, text: 'second daily', streak: initialStreakCount });
|
||||
scoreTask({ user: ref.afterUser, task: task2, direction: 'up' });
|
||||
expect(ref.afterUser.achievements.streak).to.equal(2);
|
||||
});
|
||||
|
||||
it('removes the first streak achievement when unticking a Daily', () => {
|
||||
let task = generateDaily({ userId: ref.afterUser._id, text: 'some daily', streak: initialStreakCount });
|
||||
scoreTask({ user: ref.afterUser, task, direction: 'up' });
|
||||
scoreTask({ user: ref.afterUser, task, direction: 'down' });
|
||||
expect(ref.afterUser.achievements.streak).to.equal(0);
|
||||
});
|
||||
|
||||
it('decrements a multiple streak achievement when unticking a Daily', () => {
|
||||
let task1 = generateDaily({ userId: ref.afterUser._id, text: 'first daily', streak: initialStreakCount });
|
||||
scoreTask({ user: ref.afterUser, task: task1, direction: 'up' });
|
||||
let task2 = generateDaily({ userId: ref.afterUser._id, text: 'second daily', streak: initialStreakCount });
|
||||
scoreTask({ user: ref.afterUser, task: task2, direction: 'up' });
|
||||
scoreTask({ user: ref.afterUser, task: task2, direction: 'down' });
|
||||
expect(ref.afterUser.achievements.streak).to.equal(1);
|
||||
});
|
||||
|
||||
it('does not give a streak achievement for a streak of zero', () => {
|
||||
let task = generateDaily({ userId: ref.afterUser._id, text: 'some daily', streak: -1 });
|
||||
scoreTask({ user: ref.afterUser, task, direction: 'up' });
|
||||
expect(ref.afterUser.achievements.streak).to.be.undefined;
|
||||
});
|
||||
|
||||
it('does not remove a streak achievement when unticking a Daily gives a streak of zero', () => {
|
||||
let task1 = generateDaily({ userId: ref.afterUser._id, text: 'first daily', streak: initialStreakCount });
|
||||
scoreTask({ user: ref.afterUser, task: task1, direction: 'up' });
|
||||
let task2 = generateDaily({ userId: ref.afterUser._id, text: 'second daily', streak: 1 });
|
||||
scoreTask({ user: ref.afterUser, task: task2, direction: 'down' });
|
||||
expect(ref.afterUser.achievements.streak).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('scores', () => {
|
||||
let options = {};
|
||||
let habit;
|
||||
@@ -240,11 +291,14 @@ describe('shared.ops.scoreTask', () => {
|
||||
scoreTask({user: ref.afterUser, task: daily, direction: 'up'});
|
||||
expectGainedPoints(ref.beforeUser, ref.afterUser, freshDaily, daily);
|
||||
expect(daily.completed).to.eql(true);
|
||||
expect(daily.history.length).to.eql(1);
|
||||
});
|
||||
|
||||
it('up, down', () => {
|
||||
scoreTask({user: ref.afterUser, task: daily, direction: 'up'});
|
||||
expect(daily.history.length).to.eql(1);
|
||||
scoreTask({user: ref.afterUser, task: daily, direction: 'down'});
|
||||
expect(daily.history.length).to.eql(0);
|
||||
expectClosePoints(ref.beforeUser, ref.afterUser, freshDaily, daily);
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import * as Tasks from '../../../../website/server/models/task';
|
||||
// , you can do so by passing in the full path as a string:
|
||||
// { 'items.eggs.Wolf': 10 }
|
||||
export async function generateUser (update = {}) {
|
||||
let username = generateUUID();
|
||||
let username = (Date.now() + generateUUID()).substring(0, 20);
|
||||
let password = 'password';
|
||||
let email = `${username}@example.com`;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as Tasks from '../../website/server/models/task';
|
||||
|
||||
afterEach((done) => {
|
||||
sandbox.restore();
|
||||
mongoose.connection.db.dropDatabase(done);
|
||||
mongoose.connection.dropDatabase(done);
|
||||
});
|
||||
|
||||
export { sleep } from './sleep';
|
||||
|
||||
@@ -17,3 +17,6 @@ let sinonStubPromise = require('sinon-stub-promise');
|
||||
sinonStubPromise(global.sinon);
|
||||
global.sandbox = sinon.sandbox.create();
|
||||
global.Promise = Bluebird;
|
||||
|
||||
import setupNconf from '../../website/server/libs/setupNconf';
|
||||
setupNconf('./config.json.example');
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function getProperty (collectionName, id, path) {
|
||||
// resets the db to an empty state and creates a tavern document
|
||||
export async function resetHabiticaDB () {
|
||||
return new Promise((resolve, reject) => {
|
||||
mongoose.connection.db.dropDatabase((dbErr) => {
|
||||
mongoose.connection.dropDatabase((dbErr) => {
|
||||
if (dbErr) return reject(dbErr);
|
||||
let groups = mongoose.connection.db.collection('groups');
|
||||
let users = mongoose.connection.db.collection('users');
|
||||
@@ -119,7 +119,7 @@ before((done) => {
|
||||
});
|
||||
|
||||
after((done) => {
|
||||
mongoose.connection.db.dropDatabase((err) => {
|
||||
mongoose.connection.dropDatabase((err) => {
|
||||
if (err) return done(err);
|
||||
mongoose.connection.close(done);
|
||||
});
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
--growl
|
||||
--globals io
|
||||
-r babel-polyfill
|
||||
--compilers js:babel-register
|
||||
--require babel-register
|
||||
--require ./test/helpers/globals.helper
|
||||
--exit
|
||||
|
||||
@@ -7,7 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
// add hot-reload related code to entry chunks
|
||||
Object.keys(baseWebpackConfig.entry).forEach((name) => {
|
||||
baseWebpackConfig.entry[name] = ['./webpack/dev-client'].concat(baseWebpackConfig.entry[name]);
|
||||
baseWebpackConfig.entry[name] = baseWebpackConfig.entry[name].concat('./webpack/dev-client');
|
||||
});
|
||||
|
||||
module.exports = merge(baseWebpackConfig, {
|
||||
|
||||
@@ -13,32 +13,31 @@ div
|
||||
snackbars
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
template(v-if="isUserLoaded")
|
||||
notifications-display
|
||||
app-menu
|
||||
.container-fluid
|
||||
app-header
|
||||
buyModal(
|
||||
:item="selectedItemToBuy || {}",
|
||||
:withPin="true",
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="customPurchase($event)",
|
||||
:genericPurchase="genericPurchase(selectedItemToBuy)",
|
||||
template(v-if="isUserLoaded")
|
||||
notifications-display
|
||||
app-menu
|
||||
.container-fluid
|
||||
app-header
|
||||
buyModal(
|
||||
:item="selectedItemToBuy || {}",
|
||||
:withPin="true",
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="customPurchase($event)",
|
||||
:genericPurchase="genericPurchase(selectedItemToBuy)",
|
||||
|
||||
)
|
||||
selectMembersModal(
|
||||
:item="selectedSpellToBuy || {}",
|
||||
:group="user.party",
|
||||
@memberSelected="memberSelected($event)",
|
||||
)
|
||||
)
|
||||
selectMembersModal(
|
||||
:item="selectedSpellToBuy || {}",
|
||||
:group="user.party",
|
||||
@memberSelected="memberSelected($event)",
|
||||
)
|
||||
|
||||
div(:class='{sticky: user.preferences.stickyHeader}')
|
||||
router-view
|
||||
app-footer
|
||||
|
||||
audio#sound(autoplay, ref="sound")
|
||||
source#oggSource(type="audio/ogg", :src="sound.oggSource")
|
||||
source#mp3Source(type="audio/mp3", :src="sound.mp3Source")
|
||||
div(:class='{sticky: user.preferences.stickyHeader}')
|
||||
router-view
|
||||
app-footer
|
||||
audio#sound(autoplay, ref="sound")
|
||||
source#oggSource(type="audio/ogg", :src="sound.oggSource")
|
||||
source#mp3Source(type="audio/mp3", :src="sound.mp3Source")
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@@ -49,6 +48,10 @@ div
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
@@ -79,10 +82,14 @@ div
|
||||
|
||||
.container-fluid {
|
||||
overflow-x: hidden;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: calc(100% - 56px); /* 56px is the menu */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -96,6 +103,11 @@ div
|
||||
opacity: 1 !important;
|
||||
background-color: rgba(67, 40, 116, 0.9) !important;
|
||||
}
|
||||
|
||||
/* Push progress bar above modals */
|
||||
#nprogress .bar {
|
||||
z-index: 1043 !important; /* Must stay above nav bar */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -203,7 +215,7 @@ export default {
|
||||
if (error.response.status >= 400) {
|
||||
// Check for conditions to reset the user auth
|
||||
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
|
||||
if (invalidUserMessage.indexOf(error.response.data.message) !== -1) {
|
||||
if (invalidUserMessage.indexOf(error.response.data) !== -1) {
|
||||
this.$store.dispatch('auth:logout');
|
||||
}
|
||||
|
||||
@@ -218,7 +230,7 @@ export default {
|
||||
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: error.response.data.message,
|
||||
text: error.response.data,
|
||||
type: 'error',
|
||||
timeout: true,
|
||||
});
|
||||
@@ -355,6 +367,13 @@ export default {
|
||||
if (modalOnTop) this.$root.$emit('bv::show::modal', modalOnTop, {fromRoot: true});
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('playSound');
|
||||
this.$root.$off('bv::modal::hidden');
|
||||
this.$root.$off('bv::show::modal');
|
||||
this.$root.$off('buyModal::showItem');
|
||||
this.$root.$off('selectMembersModal::showItem');
|
||||
},
|
||||
mounted () {
|
||||
// Remove the index.html loading screen and now show the inapp loading
|
||||
const loadingScreen = document.getElementById('loading-screen');
|
||||
@@ -384,12 +403,29 @@ export default {
|
||||
if (item.purchaseType === 'card') {
|
||||
this.selectedSpellToBuy = item;
|
||||
|
||||
// hide the dialog
|
||||
this.$root.$emit('bv::hide::modal', 'buy-modal');
|
||||
// remove the dialog from our modal-stack,
|
||||
// the default hidden event is delayed
|
||||
this.$root.$emit('bv::modal::hidden', {
|
||||
target: {
|
||||
id: 'buy-modal',
|
||||
},
|
||||
});
|
||||
|
||||
this.$root.$emit('bv::show::modal', 'select-member-modal');
|
||||
}
|
||||
},
|
||||
async memberSelected (member) {
|
||||
this.$store.dispatch('user:castSpell', {key: this.selectedSpellToBuy.key, targetId: member.id});
|
||||
let castResult = await this.$store.dispatch('user:castSpell', {key: this.selectedSpellToBuy.key, targetId: member.id});
|
||||
|
||||
// Subtract gold for cards
|
||||
if (this.selectedSpellToBuy.pinType === 'card') {
|
||||
const newUserGp = castResult.data.data.user.stats.gp;
|
||||
this.$store.state.user.data.stats.gp = newUserGp;
|
||||
this.text(this.$t('sentCardToUser', { profileName: member.profile.name }));
|
||||
}
|
||||
|
||||
this.selectedSpellToBuy = null;
|
||||
|
||||
if (this.user.party._id) {
|
||||
@@ -429,4 +465,5 @@ export default {
|
||||
<style src="assets/css/sprites/spritesmith-main-18.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-19.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-20.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-21.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/* Comment out for holiday events */
|
||||
/* .npc_ian {
|
||||
background: url("/npc_ian.gif") no-repeat;
|
||||
width: 78px;
|
||||
height: 135px;
|
||||
} */
|
||||
|
||||
.quest_burnout {
|
||||
background: url("~assets/images/quest_burnout.gif") no-repeat;
|
||||
width: 219px;
|
||||
height: 249px;
|
||||
}
|
||||
|
||||
.quest_bewilder {
|
||||
background: url("~assets/images/quest_bewilder.gif") no-repeat;
|
||||
.quest_dysheartener {
|
||||
background: url("~assets/images/quest_dysheartener.gif") no-repeat;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,90 @@
|
||||
.promo_armoire_bgs_201801 {
|
||||
.promo_armoire_backgrounds_201802 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -369px;
|
||||
background-position: -142px -664px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_cupid_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -425px -664px;
|
||||
width: 138px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_dysheartener {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -441px 0px;
|
||||
width: 730px;
|
||||
height: 170px;
|
||||
}
|
||||
.promo_habit_birthday_2018 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -441px -171px;
|
||||
width: 432px;
|
||||
height: 144px;
|
||||
}
|
||||
.promo_ios {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -327px;
|
||||
width: 325px;
|
||||
height: 336px;
|
||||
}
|
||||
.promo_mystery_201801 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -654px -327px;
|
||||
width: 376px;
|
||||
height: 196px;
|
||||
}
|
||||
.promo_starry_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -664px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -595px;
|
||||
background-position: -874px -171px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.scene_calendar {
|
||||
.promo_valentines {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -564px -860px;
|
||||
width: 309px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_winter_customizations {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -284px -664px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.scene_lady_glaciate {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -874px -860px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.scene_setting_up_todos {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -564px -664px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_task_list {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -805px -664px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_tavern {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 436px;
|
||||
height: 368px;
|
||||
width: 440px;
|
||||
height: 326px;
|
||||
}
|
||||
.scene_winter_cleaning {
|
||||
.scene_yesterdailies_repeatables {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -369px;
|
||||
width: 276px;
|
||||
height: 225px;
|
||||
background-position: -326px -327px;
|
||||
width: 327px;
|
||||
height: 276px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user