Merge branch 'develop' into ksami-develop

This commit is contained in:
Blade Barringer
2015-10-26 17:56:05 -05:00
66 changed files with 4077 additions and 3116 deletions
File diff suppressed because one or more lines are too long
+33 -27
View File
@@ -1,6 +1,6 @@
.2014_Fall_HealerPROMO2 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -472px -950px;
background-position: -475px -950px;
width: 90px;
height: 90px;
}
@@ -18,7 +18,7 @@
}
.2014_Fall_Warrior_PROMO {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -563px -950px;
background-position: -566px -950px;
width: 90px;
height: 90px;
}
@@ -48,7 +48,7 @@
}
.promo_dilatoryDistress {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -836px -950px;
background-position: -839px -950px;
width: 90px;
height: 90px;
}
@@ -60,7 +60,7 @@
}
.promo_enchanted_armoire_201507 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -289px -859px;
background-position: -507px -859px;
width: 217px;
height: 90px;
}
@@ -72,7 +72,7 @@
}
.promo_enchanted_armoire_201509 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -290px -950px;
background-position: -293px -950px;
width: 90px;
height: 90px;
}
@@ -102,7 +102,7 @@
}
.promo_mystery_201405 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -199px -950px;
background-position: -1111px -91px;
width: 90px;
height: 90px;
}
@@ -114,73 +114,73 @@
}
.promo_mystery_201407 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -1111px -431px;
background-position: -1111px -613px;
width: 42px;
height: 62px;
}
.promo_mystery_201408 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -1111px -155px;
background-position: -1111px -337px;
width: 60px;
height: 71px;
}
.promo_mystery_201409 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -745px -950px;
background-position: -748px -950px;
width: 90px;
height: 90px;
}
.promo_mystery_201410 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -1111px -91px;
background-position: -1111px -273px;
width: 72px;
height: 63px;
}
.promo_mystery_201411 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -927px -950px;
background-position: -930px -950px;
width: 90px;
height: 90px;
}
.promo_mystery_201412 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -1154px -361px;
background-position: -1154px -543px;
width: 42px;
height: 66px;
}
.promo_mystery_201501 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -1111px -297px;
background-position: -1111px -479px;
width: 48px;
height: 63px;
}
.promo_mystery_201502 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -381px -950px;
background-position: -384px -950px;
width: 90px;
height: 90px;
}
.promo_mystery_201503 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -1111px 0px;
background-position: -1111px -182px;
width: 90px;
height: 90px;
}
.promo_mystery_201504 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -1111px -227px;
background-position: -1111px -409px;
width: 60px;
height: 69px;
}
.promo_mystery_201505 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -654px -950px;
background-position: -657px -950px;
width: 90px;
height: 90px;
}
.promo_mystery_201506 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -1111px -361px;
background-position: -1111px -543px;
width: 42px;
height: 69px;
}
@@ -192,19 +192,25 @@
}
.promo_mystery_201508 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -970px -777px;
background-position: -199px -950px;
width: 93px;
height: 90px;
}
.promo_mystery_201509 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -1018px -950px;
background-position: -1111px 0px;
width: 90px;
height: 90px;
}
.promo_mystery_201510 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -970px -777px;
width: 93px;
height: 90px;
}
.promo_mystery_3014 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -507px -859px;
background-position: -289px -859px;
width: 217px;
height: 90px;
}
@@ -222,13 +228,13 @@
}
.promo_pastel_skin {
background-image: url(spritesmith-largeSprites-0.png);
background-position: 0px -775px;
background-position: -331px -775px;
width: 330px;
height: 83px;
}
.customize-option.promo_pastel_skin {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -25px -790px;
background-position: -356px -790px;
width: 60px;
height: 60px;
}
@@ -246,13 +252,13 @@
}
.promo_shimmer_hair {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -331px -775px;
background-position: 0px -775px;
width: 330px;
height: 83px;
}
.customize-option.promo_shimmer_hair {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -356px -790px;
background-position: -25px -790px;
width: 60px;
height: 60px;
}
@@ -270,13 +276,13 @@
}
.promo_springclasses2014 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: -430px -461px;
background-position: 0px -859px;
width: 288px;
height: 90px;
}
.promo_springclasses2015 {
background-image: url(spritesmith-largeSprites-0.png);
background-position: 0px -859px;
background-position: -430px -461px;
width: 288px;
height: 90px;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 154 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 127 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 387 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 146 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

+4
View File
@@ -662,6 +662,8 @@
"backMystery201504Notes": "Buzz buzz buzz! Flit from task to task. Confers no benefit. April 2015 Subscriber Item.",
"backMystery201507Text": "Rad Surfboard",
"backMystery201507Notes": "Surf off the Diligent Docks and ride the waves in Inkomplete Bay! Confers no benefit. July 2015 Subscriber Item.",
"backMystery201510Text": "Goblin Tail",
"backMystery201510Notes": "Prehensile and powerful! Confers no benefit. October 2015 Subscriber Item.",
"backSpecialWonderconRedText": "Mighty Cape",
"backSpecialWonderconRedNotes": "Swishes with strength and beauty. Confers no benefit. Special Edition Convention Item.",
@@ -743,6 +745,8 @@
"headAccessoryMystery201409Notes": "These powerful antlers change colors with the leaves. Confers no benefit. September 2014 Subscriber Item.",
"headAccessoryMystery201502Text": "Wings of Thought",
"headAccessoryMystery201502Notes": "Let your imagination take flight! Confers no benefit. February 2015 Subscriber Item.",
"headAccessoryMystery201510Text": "Goblin Horns",
"headAccessoryMystery201510Notes": "These fearsome horns are slightly slimy. Confers no benefit. October 2015 Subscriber Item.",
"headAccessoryMystery301405Text": "Headwear Goggles",
"headAccessoryMystery301405Notes": "\"Goggles are for your eyes,\" they said. \"Nobody wants goggles that you can only wear on your head,\" they said. Hah! You sure showed them! Confers no benefit. August 3015 Subscriber Item.",
+3
View File
@@ -50,6 +50,7 @@ api.mystery =
201507: {start:'2015-07-24',end:'2015-08-02', text:'Rad Surfer Set'}
201508: {start:'2015-08-23',end:'2015-09-02', text:'Cheetah Costume Set'}
201509: {start:'2015-09-24',end:'2015-10-02', text:'Werewolf Set'}
201510: {start:'2015-10-26',end:'2015-11-02', text:'Horned Goblin Set'}
301404: {start:'3014-03-24',end:'3014-04-02', text:'Steampunk Standard Set'}
301405: {start:'3014-04-24',end:'3014-05-02', text:'Steampunk Accessories Set'}
wondercon: {start:'2014-03-24',end:'2014-04-01'} # not really, but the mechanic works
@@ -459,6 +460,7 @@ gear =
201410: text: t('backMystery201410Text'), notes: t('backMystery201410Notes'), mystery:'201410', value: 0
201504: text: t('backMystery201504Text'), notes: t('backMystery201504Notes'), mystery:'201504', value: 0
201507: text: t('backMystery201507Text'), notes: t('backMystery201507Notes'), mystery:'201507', value: 0
201510: text: t('backMystery201510Text'), notes: t('backMystery201510Notes'), mystery:'201510', value: 0
special:
wondercon_red: text: t('backSpecialWonderconRedText'), notes: t('backSpecialWonderconRedNotes'), value: 0, mystery:'wondercon'
wondercon_black: text: t('backSpecialWonderconBlackText'), notes: t('backSpecialWonderconBlackNotes'), value: 0, mystery:'wondercon'
@@ -507,6 +509,7 @@ gear =
201404: text: t('headAccessoryMystery201404Text'), notes: t('headAccessoryMystery201404Notes'), mystery:'201404', value: 0
201409: text: t('headAccessoryMystery201409Text'), notes: t('headAccessoryMystery201409Notes'), mystery:'201409', value: 0
201502: text: t('headAccessoryMystery201502Text'), notes: t('headAccessoryMystery201502Notes'), mystery:'201502', value: 0
201510: text: t('headAccessoryMystery201510Text'), notes: t('headAccessoryMystery201510Notes'), mystery:'201510', value: 0
301405: text: t('headAccessoryMystery301405Text'), notes: t('headAccessoryMystery301405Notes'), mystery:'301405', value: 0
eyewear:
@@ -25,6 +25,7 @@ let back = {
201410: { mystery: '201410', value: 0 },
201504: { mystery: '201504', value: 0 },
201507: { mystery: '201507', value: 0 },
201510: { mystery: '201510', value: 0 },
};
let eyewear = {
@@ -46,7 +47,7 @@ let head = {
201501: { mystery: '201501', value: 0 },
201505: { mystery: '201505', value: 0 },
201508: { mystery: '201508', value: 0 },
201509: { mystery:'201509', value: 0 },
201509: { mystery: '201509', value: 0 },
301404: { mystery: '301404', value: 0 },
301405: { mystery: '301405', value: 0 },
};
@@ -56,6 +57,7 @@ let headAccessory = {
201404: { mystery: '201404', value: 0 },
201409: { mystery: '201409', value: 0 },
201502: { mystery: '201502', value: 0 },
201510: { mystery: '201510', value: 0 },
301405: { mystery: '301405', value: 0 },
};
@@ -102,6 +102,11 @@ let mysterySets = {
end:'2015-10-02',
text:'Werewolf Set'
},
201510: {
start:'2015-10-26',
end:'2015-11-02',
text:'Horned Goblin Set'
},
301404: {
start: '3014-03-24',
end: '3014-04-02',
+1 -1
View File
@@ -2,7 +2,7 @@ var _id = '';
var update = {
$addToSet: {
'purchased.plan.mysteryItems':{
$each:['head_mystery_201509','armor_mystery_201509']
$each:['headAccessory_mystery_201510','back_mystery_201510']
}
}
};
+11 -8
View File
@@ -72,7 +72,7 @@
"request": "~2.44.0",
"s3-upload-stream": "^1.0.6",
"stripe": "*",
"superagent": "~0.15.7",
"superagent": "~1.4.0",
"swagger-node-express": "lefnire/swagger-node-express#habitrpg",
"universal-analytics": "~0.3.2",
"validator": "~3.19.0",
@@ -97,7 +97,8 @@
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html"
},
"devDependencies": {
"chai": "^2.3.0",
"chai": "^3.4.0",
"chai-as-promised": "^5.1.0",
"coffee-coverage": "~0.4.2",
"coveralls": "^2.11.2",
"csv": "~0.3.6",
@@ -113,21 +114,23 @@
"karma-firefox-launcher": "~0.1.0",
"karma-html2js-preprocessor": "~0.1.0",
"karma-jasmine": "~0.1.3",
"karma-mocha": "0.1.3",
"karma-mocha-reporter": "^1.0.2",
"karma-mocha": "^0.2.0",
"karma-mocha-reporter": "^1.1.1",
"karma-ng-html2js-preprocessor": "~0.1.0",
"karma-phantomjs-launcher": "~0.1.0",
"karma-requirejs": "~0.2.0",
"karma-script-launcher": "~0.1.0",
"lcov-result-merger": "^1.0.2",
"mocha": "~1.12.1",
"mocha": "^2.3.3",
"mongodb": "^2.0.46",
"mongoskin": "~0.6.1",
"protractor": "~2.0.0",
"rewire": "^2.3.3",
"shelljs": "^0.4.0",
"sinon": "1.15.4",
"sinon-chai": "^2.7.0",
"superagent-defaults": "~0.1.5",
"sinon": "^1.17.2",
"sinon-chai": "^2.8.0",
"superagent-defaults": "^0.1.13",
"uuid": "^2.0.1",
"vinyl-source-stream": "^1.0.0",
"vinyl-transform": "^1.0.0"
}
+1 -1
View File
@@ -14,7 +14,7 @@ exports.config = {
// A base URL for your application under test. Calls to protractor.get()
// with relative paths will be prepended with this.
baseUrl: 'http://localhost:3001',
baseUrl: 'http://localhost:3003',
// Options to be passed to Jasmine-node.
jasmineNodeOpts: {
+86 -24
View File
@@ -4,21 +4,26 @@ import mongoose from 'mongoose';
import { exec } from 'child_process';
import psTree from 'ps-tree';
import gulp from 'gulp';
import { sync as glob } from 'glob';
import Q from 'q';
import Mocha from 'mocha';
import { resolve } from 'path';
const TEST_SERVER_PORT = 3001
const TEST_SERVER_PORT = 3003
const TEST_DB = 'habitrpg_test'
let server;
const TEST_DB_URI = `mongodb://localhost/${TEST_DB}`
const API_TEST_COMMAND = 'mocha test/api';
const COMMON_TEST_COMMAND = 'mocha test/common';
const CONTENT_TEST_COMMAND = 'mocha test/content --opts test/content/mocha.content.opts';
const API_TEST_COMMAND = 'mocha test/api --recursive --compilers js:babel/register';
const LEGACY_API_TEST_COMMAND = 'mocha test/api-legacy';
const COMMON_TEST_COMMAND = 'mocha test/common --compilers coffee:coffee-script';
const CONTENT_TEST_COMMAND = 'mocha test/content --compilers js:babel/register';
const CONTENT_OPTIONS = {maxBuffer: 1024 * 500};
const KARMA_TEST_COMMAND = 'karma start';
const SERVER_SIDE_TEST_COMMAND = 'mocha test/server_side';
const INSTANBUL_TEST_COMMAND = `istanbul cover -i "website/src/**" --dir coverage/api $(npm bin)/${API_TEST_COMMAND}`;
const ISTANBUL_TEST_COMMAND = `istanbul cover -i "website/src/**" --dir coverage/api ./node_modules/.bin/${LEGACY_API_TEST_COMMAND}`;
/* Helper methods for reporting test summary */
let testResults = [];
@@ -32,13 +37,23 @@ let testBin = (string) => {
};
gulp.task('test:prepare:mongo', (cb) => {
mongoose.connect(TEST_DB_URI, () => {
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();
});
});
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
if (!server) {
server = exec(`NODE_DB_URI="${TEST_DB_URI}" PORT="${TEST_SERVER_PORT}" node ./website/src/server.js`, (error, stdout, stderr) => {
if (error) { throw `Problem with the server: ${error}`; }
if (stderr) { console.error(stderr); }
});
}
});
gulp.task('test:prepare:build', (cb) => {
exec(testBin('grunt build:test'), cb);
});
@@ -149,9 +164,9 @@ gulp.task('test:server_side:safe', ['test:prepare:build'], (cb) => {
pipe(runner);
});
gulp.task('test:api', ['test:prepare:mongo'], (cb) => {
gulp.task('test:api-legacy', ['test:prepare:mongo'], (cb) => {
let runner = exec(
testBin(INSTANBUL_TEST_COMMAND),
testBin(ISTANBUL_TEST_COMMAND),
(err, stdout, stderr) => {
cb(err);
}
@@ -159,12 +174,12 @@ gulp.task('test:api', ['test:prepare:mongo'], (cb) => {
pipe(runner);
});
gulp.task('test:api:safe', ['test:prepare:mongo'], (cb) => {
gulp.task('test:api-legacy:safe', ['test:prepare:mongo'], (cb) => {
let runner = exec(
testBin(INSTANBUL_TEST_COMMAND),
testBin(ISTANBUL_TEST_COMMAND),
(err, stdout, stderr) => {
testResults.push({
suite: 'API Specs\t',
suite: 'API (legacy) Specs',
pass: testCount(stdout, /(\d+) passing/),
fail: testCount(stderr, /(\d+) failing/),
pend: testCount(stdout, /(\d+) pending/)
@@ -175,15 +190,15 @@ gulp.task('test:api:safe', ['test:prepare:mongo'], (cb) => {
pipe(runner);
});
gulp.task('test:api:clean', (cb) => {
pipe(exec(testBin(API_TEST_COMMAND), () => cb()));
gulp.task('test:api-legacy:clean', (cb) => {
pipe(exec(testBin(LEGACY_API_TEST_COMMAND), () => cb()));
});
gulp.task('test:api:watch', [
gulp.task('test:api-legacy:watch', [
'test:prepare:mongo',
'test:api:clean'
'test:api-legacy:clean'
], () => {
gulp.watch(['website/src/**', 'test/api/**'], ['test:api:clean']);
gulp.watch(['website/src/**', 'test/api-legacy/**'], ['test:api-legacy:clean']);
});
gulp.task('test:karma', ['test:prepare:build'], (cb) => {
@@ -222,15 +237,15 @@ gulp.task('test:karma:safe', ['test:prepare:build'], (cb) => {
pipe(runner);
});
gulp.task('test:e2e', ['test:prepare'], (cb) => {
gulp.task('test:e2e', ['test:prepare', 'test:prepare:server'], (cb) => {
let support = [
'Xvfb :99 -screen 0 1024x768x24 -extension RANDR',
`NODE_DB_URI="${TEST_DB_URI}" PORT="${TEST_SERVER_PORT}" node ./website/src/server.js`,
'./node_modules/protractor/bin/webdriver-manager start',
].map(exec);
support.push(server);
Q.all([
awaitPort(3001),
awaitPort(TEST_SERVER_PORT),
awaitPort(4444)
]).then(() => {
let runner = exec(
@@ -247,15 +262,14 @@ gulp.task('test:e2e', ['test:prepare'], (cb) => {
});
});
gulp.task('test:e2e:safe', ['test:prepare'], (cb) => {
gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
let support = [
'Xvfb :99 -screen 0 1024x768x24 -extension RANDR',
`NODE_DB_URI="${TEST_DB_URI}" PORT="${TEST_SERVER_PORT}" node ./website/src/server.js`,
'./node_modules/protractor/bin/webdriver-manager start',
].map(exec);
Q.all([
awaitPort(3001),
awaitPort(TEST_SERVER_PORT),
awaitPort(4444)
]).then(() => {
let runner = exec(
@@ -279,13 +293,59 @@ gulp.task('test:e2e:safe', ['test:prepare'], (cb) => {
});
});
gulp.task('test:api', ['test:prepare:server'], (done) => {
require('../test/helpers/globals.helper');
awaitPort(TEST_SERVER_PORT).then(() => {
let mocha = new Mocha({reporter: 'spec'});
let tests = glob('./test/api/**/*.js');
tests.forEach((test) => {
delete require.cache[resolve(test)];
mocha.addFile(test);
});
mocha.run((numberOfFailures) => {
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) {
kill(server);
process.exit(numberOfFailures);
}
done();
});
});
});
gulp.task('test:api:watch', ['test:prepare:server'], () => {
process.env.RUN_INTEGRATION_TEST_FOREVER = true;
gulp.watch(['website/src/**', 'test/api/**'], ['test:api']);
});
gulp.task('test:api:safe', ['test:prepare:server'], (done) => {
awaitPort(TEST_SERVER_PORT).then(() => {
let runner = exec(
testBin(API_TEST_COMMAND),
(err, stdout, stderr) => {
testResults.push({
suite: 'API Specs\t',
pass: testCount(stdout, /(\d+) passing/),
fail: testCount(stderr, /(\d+) failing/),
pend: testCount(stdout, /(\d+) pending/)
});
done();
}
);
pipe(runner);
});
});
gulp.task('test', [
'test:e2e:safe',
'test:common:safe',
'test:content:safe',
// 'test:content:safe',
'test:server_side:safe',
'test:karma:safe',
'test:api-legacy:safe',
'test:api:safe',
'test:e2e:safe'
], () => {
let totals = [0,0,0];
@@ -311,6 +371,8 @@ gulp.task('test', [
if (totals[1] > 0) throw "ERROR: There are failing tests!"
else {
kill(server);
console.log('\n\x1b[36mThanks for helping keep Habitica clean!\x1b[0m');
process.exit();
}
});
+5
View File
@@ -0,0 +1,5 @@
# Legacy API Tests
These tests have been deprecated. Any tests that you see in this folder should be converted to ES2015 and ported to the `/test/api` directory.
Once all tests have been ported, this directory will be removed.
@@ -10,7 +10,7 @@ describe "GET /api/v2/user/anonymized", ->
# TODO: Seed user with messages, rewards, dailys, checklists, webhooks, etc
registerNewUser ->
request.get(baseURL + "/user/anonymized")
.end (res) ->
.end (err, res) ->
anon = res.body
done()
, true
+90
View File
@@ -0,0 +1,90 @@
require('coffee-script');
var path, superagentDefaults;
superagentDefaults = require("superagent-defaults");
global.request = superagentDefaults();
global.mongoose = require("mongoose");
global.moment = require("moment");
global.async = require("async");
global._ = require("lodash");
global.shared = require("../../common");
global.User = require("../../website/src/models/user").model;
global.chai = require("chai");
chai.use(require("sinon-chai"));
global.expect = chai.expect;
path = require("path");
global.conf = require("nconf");
conf.argv().env().file({
file: path.join(__dirname, "../config.json")
}).defaults();
conf.set("PORT", "1337");
process.env.NODE_DB_URI = "mongodb://localhost/habitrpg_test_api_legacy";
global.baseURL = "http://localhost:" + conf.get("PORT") + "/api/v2";
global.user = void 0;
global.expectCode = function(res, code) {
if (code === 200) {
expect(res.body.err).to.not.exist;
}
return expect(res.statusCode).to.equal(code);
};
global.registerNewUser = function(cb, main) {
var password, randomID, username;
if (main == null) {
main = true;
}
randomID = shared.uuid();
if (main) {
username = password = randomID;
}
return request.post(baseURL + "/register").set("Accept", "application/json").set("X-API-User", null).set("X-API-Key", null).send({
username: randomID,
password: randomID,
confirmPassword: randomID,
email: randomID + "@gmail.com"
}).end(function(err, res) {
var _id, apiToken;
if (!main) {
return cb(null, res.body);
}
_id = res.body._id;
apiToken = res.body.apiToken;
return User.findOne({
_id: _id,
apiToken: apiToken
}, function(err, _user) {
expect(err).to.not.be.ok;
global.user = _user;
request.set("Accept", "application/json").set("X-API-User", _id).set("X-API-Key", apiToken);
return cb(null, res.body);
});
});
};
global.registerManyUsers = function(number, callback) {
return async.times(number, function(n, next) {
return registerNewUser(function(err, user) {
return next(err, user);
}, false);
}, function(err, users) {
return callback(err, users);
});
};
@@ -18,7 +18,7 @@ describe "Challenges", ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "party"
).end (res) ->
).end (err, res) ->
expectCode res, 200
group = res.body
expect(group.members.length).to.equal 1
@@ -38,7 +38,7 @@ describe "Challenges", ->
}]
rewards: []
habits: []
).end (res) ->
).end (err, res) ->
challenge = res.body
done()
]
@@ -64,7 +64,7 @@ describe "Challenges", ->
rewards: []
habits: []
official: true
).end (res) ->
).end (err, res) ->
expectCode res, 200
async.parallel [
(cb) ->
@@ -84,7 +84,7 @@ describe "Challenges", ->
updateTodo.notes = "User overriden notes"
async.waterfall [
(cb) ->
request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end (res) ->
request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end (err, res) ->
cb()
, (cb) ->
Challenge.findById challenge._id, cb
@@ -93,7 +93,7 @@ describe "Challenges", ->
cb()
, (cb) ->
request.get(baseURL + "/user/tasks/" + updateTodo.id)
.end (res) ->
.end (err, res) ->
expect(res.body.notes).to.eql("User overriden notes")
done()
]
@@ -102,7 +102,7 @@ describe "Challenges", ->
challenge.dailys[0].text = "Updated Daily"
request.post(baseURL + "/challenges/" + challenge._id)
.send(challenge)
.end (res) ->
.end (err, res) ->
challenge = res.body
expect(challenge.dailys[0].text).to.equal "Updated Daily"
User.findById user._id, (err, _user) ->
@@ -114,7 +114,7 @@ describe "Challenges", ->
challenge.todos[0].notes = "Challenge Updated Todo Notes"
request.post(baseURL + "/challenges/" + challenge._id)
.send(challenge)
.end (res) ->
.end (err, res) ->
challenge = res.body
expect(challenge.todos[0].notes).to.equal "Challenge Updated Todo Notes"
User.findById user._id, (err, _user) ->
@@ -128,7 +128,7 @@ describe "Challenges", ->
updateTodo.notes = "User overriden notes"
async.waterfall [
(cb) ->
request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end (res) ->
request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end (err, res) ->
cb()
, (cb) ->
Challenge.findById challenge._id, cb
@@ -136,7 +136,7 @@ describe "Challenges", ->
expect(chal.todos[0].notes).to.eql("Challenge Notes")
cb()
, (cb) ->
request.get(baseURL + "/challenges/" + challenge._id + "/member/" + user._id).end (res) ->
request.get(baseURL + "/challenges/" + challenge._id + "/member/" + user._id).end (err, res) ->
expect(res.body.todos[res.body.todos.length - 1].notes).to.equal "User overriden notes"
done()
]
@@ -145,14 +145,14 @@ describe "Challenges", ->
User.findById user._id, (err, _user) ->
u = _user
numTasks = (_.size(u.todos))
request.post(baseURL + "/user/tasks/" + u.todos[0].id + "/up").end (res) ->
request.post(baseURL + "/user/tasks/clear-completed").end (res) ->
request.post(baseURL + "/user/tasks/" + u.todos[0].id + "/up").end (err, res) ->
request.post(baseURL + "/user/tasks/clear-completed").end (err, res) ->
expect(_.size(res.body)).to.equal numTasks - 1
done()
it "Challenge deleted, breaks task link", (done) ->
itThis = this
request.del(baseURL + "/challenges/" + challenge._id).end (res) ->
request.del(baseURL + "/challenges/" + challenge._id).end (err, res) ->
User.findById user._id, (err, user) ->
len = user.dailys.length - 1
daily = user.dailys[user.dailys.length - 1]
@@ -164,7 +164,7 @@ describe "Challenges", ->
User.findByIdAndUpdate user._id, unset, (err, user) ->
expect(err).to.not.exist
expect(user.dailys[len].challenge.broken).to.not.exist
request.post(baseURL + "/user/tasks/" + daily.id + "/up").end (res) ->
request.post(baseURL + "/user/tasks/" + daily.id + "/up").end (err, res) ->
setTimeout (->
User.findById user._id, (err, user) ->
expect(user.dailys[len].challenge.broken).to.equal "CHALLENGE_DELETED"
@@ -186,7 +186,7 @@ describe "Challenges", ->
rewards: []
habits: []
official: false
).end (res) ->
).end (err, res) ->
expect(res.body.official).to.equal false
cb()
(cb) ->
@@ -197,7 +197,7 @@ describe "Challenges", ->
rewards: []
habits: []
official: true
).end (res) ->
).end (err, res) ->
expect(res.body.official).to.equal true
cb()
], done
@@ -215,7 +215,7 @@ describe "Challenges", ->
rewards: []
habits: []
prize: 10
).end (res) ->
).end (err, res) ->
expect(res.body.prize).to.equal 10
async.parallel [
(cb) ->
@@ -226,7 +226,7 @@ describe "Challenges", ->
user = results[0]
challenge = results[1]
expect(user.balance).to.equal 5.5
request.del(baseURL + "/challenges/" + challenge._id).end (res) ->
request.del(baseURL + "/challenges/" + challenge._id).end (err, res) ->
User.findById user._id, (err, _user) ->
expect(_user.balance).to.equal 8
done()
@@ -244,7 +244,7 @@ describe "Challenges", ->
rewards: []
habits: []
prize: 10
).end (res) ->
).end (err, res) ->
expect(res.body.prize).to.equal 10
async.parallel [
(cb) ->
@@ -255,7 +255,7 @@ describe "Challenges", ->
user = results[0]
challenge = results[1]
expect(user.balance).to.equal 5.5
request.del(baseURL + "/challenges/" + challenge._id).end (res) ->
request.del(baseURL + "/challenges/" + challenge._id).end (err, res) ->
User.findById user._id, (err, _user) ->
expect(_user.balance).to.equal 5.5
done()
@@ -273,7 +273,7 @@ describe "Challenges", ->
type: "daily"
text: "Challenge Daily"
]
).end (res) ->
).end (err, res) ->
challenge = res.body
cb()
@@ -287,7 +287,7 @@ describe "Challenges", ->
challenge.name = 'foobar'
request.post(baseURL + "/challenges/" + challenge._id)
.send(challenge)
.end (res) ->
.end (err, res) ->
error = res.body.err
expect(error).to.eql("You don't have permissions to edit this challenge")
@@ -295,7 +295,7 @@ describe "Challenges", ->
it 'can not close challenge', (done) ->
request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id)
.end (res) ->
.end (err, res) ->
error = res.body.err
expect(error).to.eql("You don't have permissions to close this challenge")
@@ -303,7 +303,7 @@ describe "Challenges", ->
it 'can not delete challenge', (done) ->
request.del(baseURL + "/challenges/" + challenge._id)
.end (res) ->
.end (err, res) ->
error = res.body.err
expect(error).to.eql("You don't have permissions to delete this challenge")
@@ -318,7 +318,7 @@ describe "Challenges", ->
challenge.name = 'foobar'
request.post(baseURL + "/challenges/" + challenge._id)
.send(challenge)
.end (res) ->
.end (err, res) ->
expect(res.body.err).to.not.exist
Challenge.findById challenge._id, (err, chal) ->
expect(chal.name).to.eql('foobar')
@@ -326,7 +326,7 @@ describe "Challenges", ->
it 'can close challenge', (done) ->
request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id)
.end (res) ->
.end (err, res) ->
expect(res.body.err).to.not.exist
User.findById user._id, (err, usr) ->
expect(usr.achievements.challenges[0]).to.eql(challenge.name)
@@ -334,10 +334,10 @@ describe "Challenges", ->
it 'can delete challenge', (done) ->
request.del(baseURL + "/challenges/" + challenge._id)
.end (res) ->
.end (err, res) ->
expect(res.body.err).to.not.exist
request.get(baseURL + "/challenges/" + challenge._id)
.end (res) ->
.end (err, res) ->
error = res.body.err
expect(error).to.eql("Challenge #{challenge._id} not found")
done()
@@ -16,7 +16,7 @@ describe "Chat", ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "party"
).end (res) ->
).end (err, res) ->
expectCode res, 200
group = res.body
expect(group.members.length).to.equal 1
@@ -28,7 +28,7 @@ describe "Chat", ->
chat = undefined
it "posts a message to party chat", (done) ->
msg = "TestMsg"
request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg).end (res) ->
request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg).end (err, res) ->
expectCode res, 200
chat = res.body.message
expect(chat.id).to.be.ok
@@ -47,14 +47,14 @@ describe "Chat", ->
it "does not post an empty message", (done) ->
msg = ""
request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg).send(
).end (res) ->
).end (err, res) ->
expectCode res, 400
expect(res.body.err).to.equal 'You cannot send a blank message'
done()
it "can not like own chat message", (done) ->
request.post(baseURL + "/groups/" + group._id + "/chat/" + chat.id + "/like").send(
).end (res) ->
).end (err, res) ->
expectCode res, 401
body = res.body
expect(body.err).to.equal "Can't like your own message. Don't be that person."
@@ -62,7 +62,7 @@ describe "Chat", ->
it "can not flag own message", (done) ->
request.post(baseURL + "/groups/" + group._id + "/chat/" + chat.id + "/flag").send(
).end (res) ->
).end (err, res) ->
expectCode res, 401
body = res.body
expect(body.err).to.equal "Can't report your own message."
@@ -70,7 +70,7 @@ describe "Chat", ->
it "gets chat messages from party chat", (done) ->
request.get(baseURL + "/groups/" + group._id + "/chat").send(
).end (res) ->
).end (err, res) ->
expectCode res, 200
message = res.body[0]
expect(message.id).to.equal chat.id
@@ -86,14 +86,14 @@ describe "Chat", ->
it "deletes a chat messages from party chat", (done) ->
request.del(baseURL + "/groups/" + group._id + "/chat/" + chat.id).send(
).end (res) ->
).end (err, res) ->
expectCode res, 204
expect(res.body).to.be.empty
done()
it "can not delete already deleted message", (done) ->
request.del(baseURL + "/groups/" + group._id + "/chat/" + chat.id).send(
).end (res) ->
).end (err, res) ->
expectCode res, 404
body = res.body
expect(body.err).to.equal "Message not found!"
@@ -116,30 +116,30 @@ describe "Chat", ->
request.post(baseURL + "/groups/" + group._id + "/join")
.set("X-API-User", userToRemove._id)
.set("X-API-Key", userToRemove.apiToken)
.end (res) -> cb()
.end (err, res) -> cb()
(cb) ->
msg = "TestMsg"
request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg)
.end (res) -> cb()
.end (err, res) -> cb()
(cb) ->
request.get(baseURL + "/user")
.set("X-API-User", userToRemove._id)
.set("X-API-Key", userToRemove.apiToken)
.end (res) ->
.end (err, res) ->
expect(res.body.newMessages[group._id]).to.exist
cb()
(cb) ->
request.post(baseURL + "/groups/" + group._id + "/removeMember?uuid=" + userToRemove._id)
.end (res) -> cb()
.end (err, res) -> cb()
(cb) ->
request.get(baseURL + "/user")
.set("X-API-User", userToRemove._id)
.set("X-API-Key", userToRemove.apiToken)
.end (res) ->
.end (err, res) ->
expect(res.body.newMessages[group._id]).to.not.exist
cb()
], done
@@ -34,7 +34,7 @@ describe "Coupons", ->
queries = '?count=10'
request
.post(baseURL + '/coupons/generate/wondercon' + queries)
.end (res) ->
.end (err, res) ->
expectCode res, 200
Coupon.find { event: 'wondercon' }, (err, _coupons) ->
coupons = _coupons
@@ -52,7 +52,7 @@ describe "Coupons", ->
queries = '?count=10'
request
.post(baseURL + '/coupons/generate/wondercon' + queries)
.end (res) ->
.end (err, res) ->
expectCode res, 401
expect(res.body.err).to.equal 'You don\'t have admin access'
done()
@@ -68,7 +68,7 @@ describe "Coupons", ->
queries = '?_id=' + user._id + '&apiToken=' + user.apiToken
request
.get(baseURL + '/coupons' + queries)
.end (res) ->
.end (err, res) ->
expectCode res, 200
codes = res.text
expect(codes).to.contain('code')
@@ -81,7 +81,7 @@ describe "Coupons", ->
queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&limit=5'
request
.get(baseURL + '/coupons' + queries)
.end (res) ->
.end (err, res) ->
expectCode res, 200
codes = res.text
sortedCoupons = _.sortBy(coupons, 'seq')
@@ -98,7 +98,7 @@ describe "Coupons", ->
queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&skip=5'
request
.get(baseURL + '/coupons' + queries)
.end (res) ->
.end (err, res) ->
expectCode res, 200
codes = res.text
sortedCoupons = _.sortBy(coupons, 'seq')
@@ -121,7 +121,7 @@ describe "Coupons", ->
queries = '?_id=' + user._id + '&apiToken=' + user.apiToken
request
.get(baseURL + '/coupons' + queries)
.end (res) ->
.end (err, res) ->
expectCode res, 401
expect(res.body.err).to.equal 'You don\'t have admin access'
done()
@@ -156,7 +156,7 @@ describe "Coupons", ->
code = coupons[0]._id
request
.post(baseURL + '/user/coupon/' + code)
.end (res) ->
.end (err, res) ->
expectCode res, 200
gear = res.body.items.gear.owned
specialGear(gear, true)
@@ -168,7 +168,7 @@ describe "Coupons", ->
code = coupons[0]._id
request
.post(baseURL + '/user/coupon/' + code)
.end (res) ->
.end (err, res) ->
expectCode res, 400
expect(res.body.err).to.equal "Coupon already used"
User.findById user._id, (err, _user) ->
@@ -182,7 +182,7 @@ describe "Coupons", ->
code = "not-a-real-coupon"
request
.post(baseURL + '/user/coupon/' + code)
.end (res) ->
.end (err, res) ->
expectCode res, 400
expect(res.body.err).to.equal "Invalid coupon code"
User.findById user._id, (err, _user) ->
@@ -16,7 +16,7 @@ describe "Party", ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "party"
).end (res) ->
).end (err, res) ->
expectCode res, 200
group = res.body
expect(group.members.length).to.equal 1
@@ -28,7 +28,7 @@ describe "Party", ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "party"
).end (res) ->
).end (err, res) ->
expectCode res, 400
expect(res.body.err).to.equal "Already in a party, try refreshing."
done()
@@ -44,7 +44,7 @@ describe "Party", ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "party"
).end (res) ->
).end (err, res) ->
expectCode res, 200
group = res.body
expect(group.members.length).to.equal 1
@@ -55,7 +55,7 @@ describe "Party", ->
it "can be found by querying for group type party", (done) ->
request.get(baseURL + "/groups/").send(
type: "party"
).end (res) ->
).end (err, res) ->
expectCode res, 200
party = _.find res.body, (g) -> return g._id == group._id
expect(party._id).to.equal group._id
@@ -76,7 +76,7 @@ describe "Party", ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "party"
).end (res) ->
).end (err, res) ->
expectCode res, 200
group = res.body
expect(group.members.length).to.equal 1
@@ -89,7 +89,7 @@ describe "Party", ->
request.post(baseURL + "/groups/" + group._id + "/join").send()
.set("X-API-User", user._id)
.set("X-API-Key", user.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 401
expect(res.body.err).to.equal "Can't join a group you're not invited to."
done()
@@ -114,7 +114,7 @@ describe "Party", ->
request.post(baseURL + "/groups/" + group._id + "/join")
.set("X-API-User", tmpUser._id)
.set("X-API-Key", tmpUser.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -153,7 +153,7 @@ describe "Party", ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "party"
).end (res) ->
).end (err, res) ->
expectCode res, 200
group = res.body
expect(group.members.length).to.equal 1
@@ -164,13 +164,13 @@ describe "Party", ->
request.post(baseURL + '/user/tasks').send({
type: 'daily'
text: 'daily one'
}).end (res) ->
}).end (err, res) ->
cb()
(cb) ->
request.post(baseURL + '/user/tasks').send({
type: 'daily'
text: 'daily two'
}).end (res) ->
}).end (err, res) ->
cb()
(cb) ->
User.findByIdAndUpdate user._id,
@@ -198,7 +198,7 @@ describe "Party", ->
body:
"stats.lvl": 50
}
]).end (res) ->
]).end (err, res) ->
user = res.body
expect(user.party.quest.progress.up).to.be.above 0
@@ -227,7 +227,7 @@ describe "Party", ->
(cb2) ->
request.post(inviteURL).send(
uuids: [party[2]._id]
).end (res)->
).end (err, res)->
cb2()
], cb
@@ -255,7 +255,7 @@ describe "Party", ->
# Start the quest
async.waterfall [
(cb) ->
request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end (res) ->
request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end (err, res) ->
expectCode res, 400
User.findByIdAndUpdate user._id,
$set:
@@ -263,7 +263,7 @@ describe "Party", ->
, cb
(_user, cb) ->
request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end (res) ->
request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end (err, res) ->
expectCode res, 200
Group.findById group._id, cb
@@ -271,8 +271,8 @@ describe "Party", ->
expect(_group.quest.key).to.equal "vice3"
expect(_group.quest.active).to.equal false
request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[0]._id).set("X-API-Key", party[0].apiToken).end ->
request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[1]._id).set("X-API-Key", party[1].apiToken).end (res) ->
request.post(baseURL + "/groups/" + group._id + "/questReject").set("X-API-User", party[2]._id).set("X-API-Key", party[2].apiToken).end (res) ->
request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[1]._id).set("X-API-Key", party[1].apiToken).end (err, res) ->
request.post(baseURL + "/groups/" + group._id + "/questReject").set("X-API-User", party[2]._id).set("X-API-Key", party[2].apiToken).end (err, res) ->
group = res.body
expect(group.quest.active).to.equal true
cb()
@@ -282,12 +282,12 @@ describe "Party", ->
it "Casts a spell", (done) ->
mp = user.stats.mp
request.get(baseURL + "/members/" + party[0]._id).end (res) ->
request.get(baseURL + "/members/" + party[0]._id).end (err, res) ->
party[0] = res.body
request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end (res) ->
request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end (err, res) ->
#expect(res.body.stats.mp).to.be.below(mp);
request.get(baseURL + "/members/" + party[0]._id).end (res) ->
request.get(baseURL + "/members/" + party[0]._id).end (err, res) ->
member = res.body
expect(member.achievements.snowball).to.equal 1
expect(member.stats.buffs.snowball).to.exist
@@ -295,18 +295,18 @@ describe "Party", ->
expect(_.size(difference)).to.equal 2
# level up user so str is > 0
request.put(baseURL + "/user").send("stats.lvl": 5).end (res) ->
request.put(baseURL + "/user").send("stats.lvl": 5).end (err, res) ->
# Refill mana so user can cast
request.put(baseURL + "/user").send("stats.mp": 100).end (res) ->
request.post(baseURL + "/user/class/cast/valorousPresence?targetType=party").end (res) ->
request.get(baseURL + "/members/" + member._id).end (res) ->
request.put(baseURL + "/user").send("stats.mp": 100).end (err, res) ->
request.post(baseURL + "/user/class/cast/valorousPresence?targetType=party").end (err, res) ->
request.get(baseURL + "/members/" + member._id).end (err, res) ->
expect(res.body.stats.buffs.str).to.be.above 0
expect(diff(res.body, member).length).to.equal 1
done()
it "Doesn't include people who aren't participating", (done) ->
request.get(baseURL + "/groups/" + group._id).end (res) ->
request.get(baseURL + "/groups/" + group._id).end (err, res) ->
expect(_.size(res.body.quest.members)).to.equal 3
done()
@@ -325,7 +325,7 @@ describe "Party", ->
done()
xit "Hurts the boss", (done) ->
request.post(baseURL + "/user/batch-update").end (res) ->
request.post(baseURL + "/user/batch-update").end (err, res) ->
user = res.body
up = user.party.quest.progress.up
expect(up).to.be.above 0
@@ -344,10 +344,10 @@ describe "Party", ->
body:
lastCron: moment().subtract(1, "days")
}
]).end (res) ->
]).end (err, res) ->
expect(res.body.party.quest.progress.up).to.be.above up
request.post(baseURL + "/user/batch-update").end ->
request.get(baseURL + "/groups/party").end (res) ->
request.get(baseURL + "/groups/party").end (err, res) ->
# Check boss damage
async.waterfall [
@@ -419,7 +419,7 @@ describe "Party", ->
cb()
(cb) ->
request.post(baseURL + "/user/batch-update").end (res) ->
request.post(baseURL + "/user/batch-update").end (err, res) ->
cb null, res.body
(_user, cb) ->
@@ -15,7 +15,7 @@ describe "Push-Notifications", ->
it "Registers a DeviceID", (done) ->
request.post(baseURL + "/user/pushDevice").send(
{ regId: "123123", type: "android"}
).end (res) ->
).end (err, res) ->
expectCode res, 200
User.findOne
@@ -11,9 +11,9 @@ describe "Score", ->
request.post(baseURL + "/user/tasks/withUp/up").send(
type: "todo"
text: "withUp"
).end (res) ->
).end (err, res) ->
expectCode res, 200
request.get(baseURL + "/user/tasks/withUp").end (res) ->
request.get(baseURL + "/user/tasks/withUp").end (err, res) ->
upTodo = res.body
expect(upTodo.completed).to.equal true
done()
@@ -22,9 +22,9 @@ describe "Score", ->
request.post(baseURL + "/user/tasks/withDown/down").send(
type: "todo"
text: "withDown"
).end (res) ->
).end (err, res) ->
expectCode res, 200
request.get(baseURL + "/user/tasks/withDown").end (res) ->
request.get(baseURL + "/user/tasks/withDown").end (err, res) ->
downTodo = res.body
expect(downTodo.completed).to.equal false
done()
@@ -34,9 +34,9 @@ describe "Score", ->
type: "todo"
text: "withUpWithComplete"
completed: false
).end (res) ->
).end (err, res) ->
expectCode res, 200
request.get(baseURL + "/user/tasks/withUpWithComplete").end (res) ->
request.get(baseURL + "/user/tasks/withUpWithComplete").end (err, res) ->
upTodo = res.body
expect(upTodo.completed).to.equal true
done()
@@ -46,9 +46,9 @@ describe "Score", ->
type: "todo"
text: "withDownWithUncomplete"
completed: true
).end (res) ->
).end (err, res) ->
expectCode res, 200
request.get(baseURL + "/user/tasks/withDownWithUncomplete").end (res) ->
request.get(baseURL + "/user/tasks/withDownWithUncomplete").end (err, res) ->
downTodo = res.body
expect(downTodo.completed).to.equal false
done()
@@ -58,13 +58,13 @@ describe "Score", ->
request.post(baseURL + "/user/tasks").send(
type: "todo"
text: "Sample Todo"
).end (res) ->
).end (err, res) ->
expectCode res, 200
unCompletedTodo = res.body
expect(unCompletedTodo.completed).to.equal false
request.post(baseURL + "/user/tasks/"+unCompletedTodo._id+"/up").end (res) ->
request.post(baseURL + "/user/tasks/"+unCompletedTodo._id+"/up").end (err, res) ->
expectCode res, 200
request.get(baseURL + "/user/tasks/"+unCompletedTodo._id).end (res) ->
request.get(baseURL + "/user/tasks/"+unCompletedTodo._id).end (err, res) ->
unCompletedTodo = res.body
expect(unCompletedTodo.completed).to.equal true
done()
@@ -74,13 +74,13 @@ describe "Score", ->
type: "todo"
text: "Sample Todo"
completed: true
).end (res) ->
).end (err, res) ->
expectCode res, 200
completedTodo = res.body
expect(completedTodo.completed).to.equal true
request.post(baseURL + "/user/tasks/"+completedTodo._id+"/down").end (res) ->
request.post(baseURL + "/user/tasks/"+completedTodo._id+"/down").end (err, res) ->
expectCode res, 200
request.get(baseURL + "/user/tasks/"+completedTodo._id).end (res) ->
request.get(baseURL + "/user/tasks/"+completedTodo._id).end (err, res) ->
completedTodo = res.body
expect(completedTodo.completed).to.equal false
done()
@@ -91,9 +91,9 @@ describe "Score", ->
type: "habit"
text: "testTitle"
completed: true
).end (res) ->
).end (err, res) ->
expectCode res, 200
request.get(baseURL + "/user/tasks/habitWithUp").end (res) ->
request.get(baseURL + "/user/tasks/habitWithUp").end (err, res) ->
upHabit = res.body
expect(upHabit.value).to.be.at.least(1)
expect(upHabit.type).to.equal("habit")
@@ -105,9 +105,9 @@ describe "Score", ->
type: "habit"
text: "testTitle"
completed: true
).end (res) ->
).end (err, res) ->
expectCode res, 200
request.get(baseURL + "/user/tasks/habitWithDown").end (res) ->
request.get(baseURL + "/user/tasks/habitWithDown").end (err, res) ->
downHabit = res.body
expect(downHabit.value).to.have.at.most(-1)
expect(downHabit.type).to.equal("habit")
@@ -31,7 +31,7 @@ describe "Todos", ->
body:
type: "todo"
}
]).end (res) ->
]).end (err, res) ->
expectCode res, 200
# Expect number of todos to be 3 greater than the number the user started with
expect(_.size(res.body.todos)).to.equal numTasks + 3
@@ -56,7 +56,7 @@ describe "Todos", ->
direction: "up"
id: res.body.todos[2].id
}
]).end (res) ->
]).end (err, res) ->
expectCode res, 200
expect(_.size(res.body.todos)).to.equal numTasks
request.post(baseURL + "/user/batch-update?_v=997").send([
@@ -76,7 +76,7 @@ describe "Todos", ->
body:
dateCompleted: moment().subtract(4, "days")
}
]).end (res) ->
]).end (err, res) ->
# Expect todos to be 2 less than the total count
expect(_.size(res.body.todos)).to.equal numTasks - 2
done()
@@ -89,7 +89,7 @@ describe "Todos", ->
request.post(baseURL + "/user/tasks").send(
type: "todo"
text: "Sample Todo"
).end (res) ->
).end (err, res) ->
expectCode res, 200
todo = res.body
expect(todo.text).to.equal "Sample Todo"
@@ -110,10 +110,10 @@ describe "Todos", ->
}
request.post(baseURL + "/user/tasks").send(
original_todo
).end (res) ->
).end (err, res) ->
request.post(baseURL + "/user/tasks").send(
duplicate_id_todo
).end (res) ->
).end (err, res) ->
expectCode res, 409
expect(res.body.err).to.eql('A task with that ID already exists.')
done()
@@ -122,7 +122,7 @@ describe "Todos", ->
it "Does not update id of todo", (done) ->
request.put(baseURL + "/user/tasks/" + todo.id).send(
id: "a-new-id"
).end (res) ->
).end (err, res) ->
expectCode res, 200
updateTodo = res.body
expect(updateTodo.id).to.equal todo.id
@@ -131,7 +131,7 @@ describe "Todos", ->
it "Does not update type of todo", (done) ->
request.put(baseURL + "/user/tasks/" + todo.id).send(
type: "habit"
).end (res) ->
).end (err, res) ->
expectCode res, 200
updateTodo = res.body
expect(updateTodo.type).to.equal todo.type
@@ -144,7 +144,7 @@ describe "Todos", ->
priority: 1.5
value: 5
notes: "Some notes"
).end (res) ->
).end (err, res) ->
expectCode res, 200
todo = res.body
expect(todo.text).to.equal "Changed Title"
@@ -157,7 +157,7 @@ describe "Todos", ->
describe "Deleting todos", ->
it "Does delete todo", (done) ->
request.del(baseURL + "/user/tasks/" + todo.id).send(
).end (res) ->
).end (err, res) ->
expectCode res, 200
body = res.body
expect(body).to.be.empty
@@ -165,7 +165,7 @@ describe "Todos", ->
it "Does not delete already deleted todo", (done) ->
request.del(baseURL + "/user/tasks/" + todo.id).send(
).end (res) ->
).end (err, res) ->
expectCode res, 404
body = res.body
expect(body.err).to.equal "Task not found."
@@ -178,7 +178,7 @@ describe "Todos", ->
priority: 1
value: 4
notes: "Other notes"
).end (res) ->
).end (err, res) ->
expectCode res, 404
body = res.body
expect(body.err).to.equal "Task not found."
@@ -5,30 +5,6 @@ app = require("../../website/src/server")
describe "Users", ->
describe "GET /user without token or user id", ->
it "/api/v2/user", (done) ->
request
.get(baseURL + "/user")
.set("Accept", "application/json")
.set("X-API-User", '')
.set("X-API-Key", '')
.end (res) ->
expect(res.statusCode).to.equal 401
expect(res.body.err).to.equal "You must include a token and uid (user id) in your request"
done()
describe "GET /user with token or user id", ->
before (done) ->
registerNewUser(done, true)
it "/api/v2/user", (done) ->
request.get(baseURL + "/user").set("Accept", "application/json").end (res) ->
expect(res.statusCode).to.equal 200
expect(res.body._id).to.equal user._id
done()
describe "Deleting Users", ->
before (done) ->
@@ -46,10 +22,10 @@ describe "Users", ->
request.del(baseURL + "/user")
.set("X-API-User", userToDelete._id)
.set("X-API-Key", userToDelete.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
request.get(baseURL + "/user/" + userToDelete._id)
.end (res) ->
.end (err, res) ->
expectCode res, 404
cb()
@@ -80,7 +56,7 @@ describe "Users", ->
)
.set("X-API-User", userToDelete._id)
.set("X-API-Key", userToDelete.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
guildToHaveNewLeader = res.body
expect(guildToHaveNewLeader.leader).to.eql(userToDelete._id)
@@ -90,7 +66,7 @@ describe "Users", ->
inviteURL = baseURL + "/groups/" + guildToHaveNewLeader._id + "/invite"
request.post(inviteURL)
.send( uuids: [userToBecomeLeader._id])
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -98,7 +74,7 @@ describe "Users", ->
request.post(baseURL + "/groups/" + guildToHaveNewLeader._id + "/join")
.set("X-API-User", userToBecomeLeader._id)
.set("X-API-Key", userToBecomeLeader.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -106,7 +82,7 @@ describe "Users", ->
request.del(baseURL + "/user")
.set("X-API-User", userToDelete._id)
.set("X-API-Key", userToDelete.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -114,7 +90,7 @@ describe "Users", ->
request.get(baseURL + "/groups/" + guildToHaveNewLeader._id)
.set("X-API-User", userToBecomeLeader._id)
.set("X-API-Key", userToBecomeLeader.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
g = res.body
userInGroup = _.find(g.members, (member) -> return member._id == userToDelete._id; )
@@ -138,7 +114,7 @@ describe "Users", ->
name: "TestPrivateGroup"
type: "party"
)
.end (res) ->
.end (err, res) ->
expectCode res, 200
party = res.body
cb()
@@ -148,7 +124,7 @@ describe "Users", ->
name: "TestPrivateGroup"
type: "guild"
)
.end (res) ->
.end (err, res) ->
expectCode res, 200
guild = res.body
cb()
@@ -167,7 +143,7 @@ describe "Users", ->
inviteURL = baseURL + "/groups/" + party._id + "/invite"
request.post(inviteURL)
.send( uuids: [userToDelete._id])
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -175,7 +151,7 @@ describe "Users", ->
inviteURL = baseURL + "/groups/" + guild._id + "/invite"
request.post(inviteURL)
.send( uuids: [userToDelete._id])
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -183,7 +159,7 @@ describe "Users", ->
request.post(baseURL + "/groups/" + party._id + "/join")
.set("X-API-User", userToDelete._id)
.set("X-API-Key", userToDelete.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -191,7 +167,7 @@ describe "Users", ->
request.post(baseURL + "/groups/" + guild._id + "/join")
.set("X-API-User", userToDelete._id)
.set("X-API-Key", userToDelete.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -199,13 +175,13 @@ describe "Users", ->
request.del(baseURL + "/user")
.set("X-API-User", userToDelete._id)
.set("X-API-Key", userToDelete.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
(cb) ->
request.get(baseURL + "/groups/" + party._id)
.end (res) ->
.end (err, res) ->
expectCode res, 200
g = res.body
userInGroup = _.find(g.members, (member) -> return member._id == userToDelete._id; )
@@ -214,7 +190,7 @@ describe "Users", ->
(cb) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
.end (err, res) ->
expectCode res, 200
g = res.body
userInGroup = _.find(g.members, (member) -> return member._id == userToDelete._id; );
@@ -235,7 +211,7 @@ describe "Users", ->
inviteURL = baseURL + "/groups/" + party._id + "/invite"
request.post(inviteURL)
.send( uuids: [userToDeleteInvites._id])
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -243,7 +219,7 @@ describe "Users", ->
inviteURL = baseURL + "/groups/" + guild._id + "/invite"
request.post(inviteURL)
.send( uuids: [userToDeleteInvites._id])
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
@@ -251,13 +227,13 @@ describe "Users", ->
request.del(baseURL + "/user")
.set("X-API-User", userToDeleteInvites._id)
.set("X-API-Key", userToDeleteInvites.apiToken)
.end (res) ->
.end (err, res) ->
expectCode res, 200
cb()
(cb) ->
request.get(baseURL + "/groups/" + party._id)
.end (res) ->
.end (err, res) ->
expectCode res, 200
g = res.body
userInviteForGroup = _.find(g.invites, (invite) -> return invite._id == userToDeleteInvites._id; )
@@ -266,7 +242,7 @@ describe "Users", ->
(cb) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
.end (err, res) ->
expectCode res, 200
g = res.body
userInviteForGroup = _.find(g.invites, (invite) -> return invite._id == userToDeleteInvites._id; )
+92 -21
View File
@@ -1,32 +1,103 @@
# API Tests
# So you want to write API integration tests?
Our API tests are written in [coffeescript](http://coffeescript.org/) using the [Mocha testing framework](http://mochajs.org/).
That's great! This README will serve as a quick primer for style conventions and practices for these tests.
There's a variety of ways to run the tests:
## What is this?
These are integration tests for the Habitica API. They are performed by making REST requests to the API's endpoints and asserting on the data that is returned.
If the javascript looks weird to you, that's because it's written in [ES2015](http://www.ecma-international.org/ecma-262/6.0/) and transpiled by [Babel](https://babeljs.io/docs/learn-es2015/).
## How to run the tests
First, install gulp.
```bash
# Individually
mocha test/api/name_of_test.coffee
# The entire collection of api tests
mocha test/api
# As part of the whole test suite
npm test
$ npm install -g gulp
```
### Modules
To run the api tests, make sure the mongo db is up and running and then type this on the command line:
Some modules are declared in the [api-helper.coffee](api-helper.coffee) file for use in any of the api tests:
```bash
$ gulp test:api
```
* `moment` - time manipulation
* `async` - run async processes, good for before blocks
* `lodash (_)` - many utilities
* `shared` - generate uuids
* `expect` - making assertions
* `User` - look up a User in the db
It may take a little while to get going, since it requires the web server to start up before the tests can run.
### Helper Methods
You can also run a watch command for the api tests. This will allow the tests to re-run automatically when you make changes in the `/test/api/` or `/website/src/` directories.
There are helper methods declared in the [api-helper.coffee](api-helper.coffee) file. Some useful methods contained there:
```bash
$ gulp test:api:watch
```
* `registerNewUser(callback, main)` - Theres a global user variable that gets overwritten with the new user whenever you call `registerNewUser` unless you pass false in as the second argument.
* `registerManyUsers(number, callback)` - Good for testing things that require many users. The callback function returns new users as and array in the second argument.
One caveat. If you have a severe syntax error in your files, the tests may fail to run, but they won't alert you that they are not running. If you ever find your test hanging for a while, run this to get the stackstrace. Once you've fixed the problem, you can resume running the watch comand.
```bash
$ gulp test:api:safe
```
## Structure
Each top level route has it's own directory. So, all the routes that begin with `/groups/` live in `/test/api/groups/`.
Each test should:
* encompase a single route
* begin with the REST parameter for the route
* display the full name of the route, swapping out `/` for `_`
* end with test.js
So, for the `POST` route `/groups/:id/leave`, it would be
```bash
POST-groups_id_leave.test.js
```
## Promises
To mitigate [callback hell](http://callbackhell.com/) :imp:, we've written helper methods that [return promises](https://babeljs.io/docs/learn-es2015/#promises). This makes it very easy to chain together commands. All you need to do to make a subsequent request is return another promise and then call `.then((result) => {})` on the surrounding block, like so:
```js
it('does something', () => {
let request;
return generateUser().then((user) => { // We return the initial promise so this test can be run asyncronously
request = requester(user);
return request.post('/groups', {
type: 'party',
});
}).then((party) => { // the result of the promise above is the argument of the function
return request.put(`/groups/${party._id}`, {
name: 'My party',
});
}).then((result) => {
return request.get('/groups/party');
}).then((party) => {
expect(party.name).to.eql('My party');
});
});
```
If the test is simple, you can use the [chai-as-promised](http://chaijs.com/plugins/chai-as-promised) `return expect(somePromise).to.eventually` syntax to make your assertion.
```js
it('makes the party creator the leader automatically', () => {
let api = requester(user);
return expect(request.post('/groups', {
type: 'party',
})).to.eventually.have.deep.property('leader._id', user._id);
});
```
If the test is checking that the request returns an error, use the `rejectedWith` syntax.
```js
it('returns an error', () => {
let api = requester(user);
return expect(request.get('/groups/id-of-a-party-that-user-does-not-belong-to'))
.to.be.rejectedWith('Group not found or you don\'t have access.');
});
```
## Questions?
Ask in the [Aspiring Coder's Guild](https://habitica.com/#/options/groups/guilds/68d4a01e-db97-4786-8ee3-05d612c5af6f)!
-76
View File
@@ -1,76 +0,0 @@
##############################
# Global modules
##############################
superagentDefaults = require("superagent-defaults")
global.request = superagentDefaults()
global.mongoose = require("mongoose")
global.moment = require("moment")
global.async = require("async")
global._ = require("lodash")
global.shared = require("../../common")
global.User = require("../../website/src/models/user").model
global.chai = require("chai")
chai.use(require("sinon-chai"))
global.expect = chai.expect
##############################
# Nconf config
##############################
path = require("path")
global.conf = require("nconf")
conf.argv().env().file(file: path.join(__dirname, "../config.json")).defaults()
conf.set "PORT", "1337"
##############################
# Node ENV and global variables
##############################
process.env.NODE_DB_URI = "mongodb://localhost/habitrpg_test"
global.baseURL = "http://localhost:" + conf.get("PORT") + "/api/v2"
global.user = undefined
##############################
# Helper Methods
##############################
global.expectCode = (res, code) ->
expect(res.body.err).to.not.exist if code is 200
expect(res.statusCode).to.equal code
global.registerNewUser = (cb, main) ->
main = true unless main?
randomID = shared.uuid()
username = password = randomID if main
request
.post(baseURL + "/register")
.set("Accept", "application/json")
.set("X-API-User", null)
.set("X-API-Key", null)
.send
username: randomID
password: randomID
confirmPassword: randomID
email: randomID + "@gmail.com"
.end (res) ->
return cb(null, res.body) unless main
_id = res.body._id
apiToken = res.body.apiToken
User.findOne
_id: _id
apiToken: apiToken
, (err, _user) ->
expect(err).to.not.be.ok
global.user = _user
request
.set("Accept", "application/json")
.set("X-API-User", _id)
.set("X-API-Key", apiToken)
cb null, res.body
global.registerManyUsers = (number, callback) ->
async.times number, (n, next) ->
registerNewUser (err, user) ->
next(err, user)
, false
, (err, users) ->
callback(err, users)
-500
View File
@@ -1,500 +0,0 @@
'use strict'
diff = require("deep-diff")
Group = require("../../website/src/models/group").model
app = require("../../website/src/server")
describe "Guilds", ->
context "creating groups", ->
before (done) ->
registerNewUser ->
User.findByIdAndUpdate user._id,
$set:
"balance": 40
, (err, _user) ->
done()
, true
it "can create a public guild", (done) ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "guild",
privacy: "public"
).end (res) ->
expectCode res, 200
guild = res.body
expect(guild.members.length).to.equal 1
expect(guild.leader).to.equal user._id
done()
it "can create a private guild", (done) ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "guild",
privacy: "private"
).end (res) ->
expectCode res, 200
guild = res.body
expect(guild.members.length).to.equal 1
expect(guild.leader).to.equal user._id
done()
it "prevents user from creating a guild when the user has 0 gems", (done) ->
registerNewUser (err, user_with_0_gems) ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "guild",
)
.set("X-API-User", user_with_0_gems._id)
.set("X-API-Key", user_with_0_gems.apiToken)
.end (res) ->
expectCode res, 401
done()
, false
context "get guilds", ->
guild = undefined
beforeEach (done)->
request.post(baseURL + "/groups").send(
name: "TestGroup2"
type: "guild"
).end (res) ->
guild = res.body
done()
it "can find a guild", (done) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
expectCode res, 200
expect(res.body._id).to.equal res.body._id
done()
it "transforms members array to an arrray of user objects", (done) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
expectCode res, 200
members = res.body.members
# @TODO: would be more instructive if it had more members in guild :(
_(members).each (member) ->
expect(member).to.be.an 'object'
expect(member.profile.name).to.exist
done()
it "transforms leader id to a user object", (done) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
expectCode res, 200
leader = res.body.leader
expect(leader).to.be.an 'object'
expect(leader.profile.name).to.exist
done()
it "can list guilds", (done) ->
request.get(baseURL + "/groups").send()
.end (res) ->
expectCode res, 200
guild = res.body[0]
expect(guild).to.exist
done()
context "updating groups", ->
groupToUpdate = undefined
before (done) ->
request.post(baseURL + "/groups").send(
name: "TestGroup"
type: "guild"
description: "notUpdatedDesc"
).end (res) ->
groupToUpdate = res.body
done()
it "prevents user from updating a party when they aren't the leader", (done) ->
registerNewUser (err, tmpUser) ->
request.post(baseURL + "/groups/" + groupToUpdate._id).send(
name: "TestGroupName"
description: "updatedDesc"
)
.set("X-API-User", tmpUser._id)
.set("X-API-Key", tmpUser.apiToken)
.end (res) ->
expectCode res, 401
expect(res.body.err).to.equal "Only the group leader can update the group!"
done()
, false
it "allows user to update a group", (done) ->
request.post(baseURL + "/groups/" + groupToUpdate._id).send(
description: "updatedDesc"
)
.end (res) ->
expectCode res, 204
request.get(baseURL + "/groups/" + groupToUpdate._id).send()
.end (res) ->
updatedGroup = res.body
expect(updatedGroup.description).to.equal "updatedDesc"
done()
context "leaving groups", ->
it "can leave a guild", (done) ->
guildToLeave = undefined
request.post(baseURL + "/groups").send(
name: "TestGroupToLeave"
type: "guild"
).end (res) ->
guildToLeave = res.body
request.post(baseURL + "/groups/" + guildToLeave._id + "/leave")
.send()
.end (res) ->
expectCode res, 204
done()
it "deletes a group when the last member leaves", (done) ->
groupToDeleteAfterLeave = undefined
request.post(baseURL + "/groups").send(
name: "TestGroupToDeleteAfteLeave"
type: "guild"
privacy: "private"
).end (res) ->
groupToDeleteAfterLeave = res.body
async.waterfall [
(cb) ->
request.post(baseURL + "/groups/" + groupToDeleteAfterLeave._id + "/leave")
.end (res) ->
expectCode res, 204
cb()
(cb) ->
request.post(baseURL + "/groups/" + groupToDeleteAfterLeave._id)
.end (res) ->
expectCode res, 404
cb()
], done
it "deletes all invites to a group (guild) when the last member leaves", (done) ->
groupToDeleteAfterLeave = undefined
userToRemoveInvite = undefined
request.post(baseURL + "/groups").send(
name: "TestGroupToDeleteAfterLeave"
type: "guild"
privacy: "private"
).end (res) ->
groupToDeleteAfterLeave = res.body
async.waterfall [
(cb) ->
registerManyUsers 1, cb
(_members, cb) ->
userToRemoveInvite = _members[0]
inviteURL = baseURL + "/groups/" + groupToDeleteAfterLeave._id + "/invite"
request.post(inviteURL).send(
uuids: [userToRemoveInvite._id]
)
.end ->
cb()
(cb) ->
request.post(baseURL + "/groups/" + groupToDeleteAfterLeave._id + "/leave")
.end (res) ->
expectCode res, 204
cb()
(cb) ->
request.get(baseURL + '/user')
.set("X-API-User", userToRemoveInvite._id)
.set("X-API-Key", userToRemoveInvite.apiToken)
.end (err, res) ->
expectCode res, 200
groupInvitation = _.find(res.body.invitations.guilds, {id: groupToDeleteAfterLeave._id})
expect(groupInvitation).to.not.exist
cb()
(cb) ->
request.post(baseURL + "/groups/" + groupToDeleteAfterLeave._id)
.end (res) ->
expectCode res, 404
cb()
], done
it "deletes all invites to a group (party) when the last member leaves", (done) ->
partyToDeleteAfterLeave = undefined
userToRemovePartyInvite = undefined
request.post(baseURL + "/groups").send(
name: "TestPartyToDeleteAfterLeave"
type: "party"
).end (res) ->
partyToDeleteAfterLeave = res.body
async.waterfall [
(cb) ->
registerManyUsers 1, cb
(_members, cb) ->
userToRemovePartyInvite = _members[0]
inviteURL = baseURL + "/groups/" + partyToDeleteAfterLeave._id + "/invite"
request.post(inviteURL).send(
uuids: [userToRemovePartyInvite._id]
)
.end ->
cb()
(cb) ->
request.post(baseURL + "/groups/" + partyToDeleteAfterLeave._id + "/leave")
.end (res) ->
expectCode res, 204
cb()
(cb) ->
request.get(baseURL + '/user')
.set("X-API-User", userToRemovePartyInvite._id)
.set("X-API-Key", userToRemovePartyInvite.apiToken)
.end (err, res) ->
expectCode res, 200
party = partyToDeleteAfterLeave
partyInvitation = _.find(res.body.invitations.party, (invite) ->
return invite == party._id
)
expect(partyInvitation).to.not.exist
cb()
(cb) ->
request.post(baseURL + "/groups/" + partyToDeleteAfterLeave._id)
.end (res) ->
expectCode res, 404
cb()
], done
context "removing users groups", ->
it "allows guild leaders to remove a member (but not themselves)", (done) ->
guildToRemoveMember = undefined
members = undefined
userToRemove = undefined
request.post(baseURL + "/groups").send(
name: "TestGuildToRemoveMember"
type: "guild"
).end (res) ->
guildToRemoveMember = res.body
#Add members to guild
async.waterfall [
(cb) ->
registerManyUsers 1, cb
(_members, cb) ->
userToRemove = _members[0]
members = _members
inviteURL = baseURL + "/groups/" + guildToRemoveMember._id + "/invite"
request.post(inviteURL).send(
uuids: [userToRemove._id]
)
.end ->
cb()
(cb) ->
request.post(baseURL + "/groups/" + guildToRemoveMember._id + "/join")
.set("X-API-User", userToRemove._id)
.set("X-API-Key", userToRemove.apiToken)
.end (res) ->
cb()
(cb) ->
request.post(baseURL + "/groups/" + guildToRemoveMember._id + "/removeMember?uuid=" + guildToRemoveMember.leader)
.send().end (res) ->
expectCode res, 401
cb()
(cb) ->
request.post(baseURL + "/groups/" + guildToRemoveMember._id + "/removeMember?uuid=" + userToRemove._id)
.send().end (res) ->
expectCode res, 204
cb()
(cb) ->
request.get(baseURL + "/groups/" + guildToRemoveMember._id)
.send()
.end (res) ->
g = res.body
userInGroup = _.find g.members, (member) -> return member._id == userToRemove._id
expect(userInGroup).to.not.exist
cb()
], done
describe "Private Guilds", ->
guild = undefined
before (done) ->
request.post(baseURL + "/groups").send(
name: "TestPrivateGroup"
type: "guild"
privacy: "private"
).end (res) ->
expectCode res, 200
guild = res.body
expect(guild.members.length).to.equal 1
expect(guild.leader).to.equal user._id
#Add members to guild
async.waterfall [
(cb) ->
registerManyUsers 15, cb
(_members, cb) ->
members = _members
joinGuild = (member, callback) ->
request.post(baseURL + "/groups/" + guild._id + "/join")
.set("X-API-User", member._id)
.set("X-API-Key", member.apiToken)
.end ->
callback(null, null)
async.map members, joinGuild, (err, results) -> cb()
], done
it "includes user in private group member list when user is a member", (done) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
g = res.body
userInGroup = _.find g.members, (member) -> return member._id == user._id
expect(userInGroup).to.exist
done()
it "excludes user from viewing private group member list when user is not a member", (done) ->
request.post(baseURL + "/groups/" + guild._id + "/leave")
.end (res) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
expect res, 404
done()
describe "Public Guilds", ->
before (done) ->
async.waterfall [
(cb) ->
registerNewUser ->
User.findByIdAndUpdate user._id, {$set: { "balance": 10 } }, (err, _user) ->
cb()
, true
], done
context "joining groups", ->
it "makes user a group leader when group is empty", (done) ->
guildToEmptyAndAssignLeader = undefined
members = undefined
userToBecomeLeader = undefined
request.post(baseURL + "/groups").send(
name: "TestGuildToEmptyAndAssignLeader"
type: "guild",
privacy: "public"
).end (res) ->
guildToEmptyAndAssignLeader = res.body
#Add members to guild
async.waterfall [
(cb) ->
registerManyUsers 1, cb
(_members, cb) ->
userToBecomeLeader = _members[0]
members = _members
inviteURL = baseURL + "/groups/" + guildToEmptyAndAssignLeader._id + "/invite"
request.post(inviteURL).send(
uuids: [userToBecomeLeader._id]
)
.end ->
cb()
(cb) ->
request.post(baseURL + "/groups/" + guildToEmptyAndAssignLeader._id + "/leave")
.send()
.end (res) ->
expectCode res, 204
cb()
(cb) ->
request.post(baseURL + "/groups/" + guildToEmptyAndAssignLeader._id + "/join")
.set("X-API-User", userToBecomeLeader._id)
.set("X-API-Key", userToBecomeLeader.apiToken)
.end (res) ->
expectCode res, 200
cb()
(cb) ->
request.get(baseURL + "/groups/" + guildToEmptyAndAssignLeader._id)
.set("X-API-User", userToBecomeLeader._id)
.set("X-API-Key", userToBecomeLeader.apiToken)
.send()
.end (res) ->
expectCode res, 200
g = res.body
expect(g.leader._id).to.equal(userToBecomeLeader._id)
cb()
], done
context "viewing", ->
guild = undefined
before (done) ->
async.waterfall [
(cb) ->
registerNewUser ->
User.findByIdAndUpdate user._id, {$set: { "balance": 10 } }, (err, _user) ->
cb()
, true
(cb) ->
request.post(baseURL + "/groups").send(
name: "TestPublicGroup"
type: "guild"
privacy: "public"
).end (res) ->
guild = res.body
expect(guild.members.length).to.equal 1
expect(guild.leader).to.equal user._id
#Add members to guild
cb()
(cb) ->
registerManyUsers 15, cb
(_members, cb) ->
members = _members
joinGuild = (member, callback) ->
request.post(baseURL + "/groups/" + guild._id + "/join")
.set("X-API-User", member._id)
.set("X-API-Key", member.apiToken)
.end ->
callback(null, null)
async.map members, joinGuild, (err, results) -> cb()
], done
context "is a member", ->
before (done) ->
registerNewUser ->
request.post(baseURL + "/groups/" + guild._id + "/join")
.end (res)->
done()
, true
it "includes user in public group member list", (done) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
g = res.body
expect(g.members.length).to.equal 15
userInGroup = _.find g.members, (member) -> return member._id == user._id
expect(userInGroup).to.exist
done()
context "is not a member", ->
before (done) ->
registerNewUser done, true
it "excludes user in public group member list", (done) ->
request.get(baseURL + "/groups/" + guild._id)
.end (res) ->
g = res.body
expect(g.members.length).to.equal 15
userInGroup = _.find g.members, (member) -> return member._id == user._id
expect(userInGroup).to.not.exist
done()
+122
View File
@@ -0,0 +1,122 @@
import {
generateGroup,
generateUser,
resetHabiticaDB,
requester,
} from '../../helpers/api.helper';
describe('GET /groups', () => {
const NUMBER_OF_PUBLIC_GUILDS = 3;
const NUMBER_OF_USERS_GUILDS = 2;
let user, api;
before(() => {
let leader, createdGroup;
// Set up a world with a mixture of public and private guilds
// Invite user to a few of them
return resetHabiticaDB().then(() => {
return generateUser();
}).then((_user) => {
user = _user;
return generateUser({
balance: 10,
});
}).then((_user) => {
leader = _user;
api = requester(leader);
let publicGuildWithUserAsMember = generateGroup(leader, {
name: 'public guild - is member',
type: 'guild',
privacy: 'public',
members: [leader._id, user._id],
});
let publicGuildWithoutUserAsMember = generateGroup(leader, {
name: 'public guild - is not member',
type: 'guild',
privacy: 'public',
});
let privateGuildWithUserAsMember = generateGroup(leader, {
name: 'private guild - is member',
type: 'guild',
privacy: 'private',
members: [leader._id, user._id],
});
let privateGuildWithoutUserAsMember = generateGroup(leader, {
name: 'private guild - is not member',
type: 'guild',
privacy: 'private',
});
let partyWithoutUserAsMember = generateGroup(leader, {
name: 'party name',
type: 'party',
privacy: 'private',
});
let promises = [
publicGuildWithUserAsMember,
publicGuildWithoutUserAsMember,
privateGuildWithUserAsMember,
privateGuildWithoutUserAsMember,
partyWithoutUserAsMember,
];
return Promise.all(promises);
}).then((groups) => {
api = requester(user);
return api.post('/groups', {
type: 'party',
name: 'user\'s party',
privacy: 'private',
});
});
});
context('no query passed in', () => {
xit('lists all public guilds, the tavern, user\'s party, and any private guilds that user is a part of - TODO query includes duplicates - IE, tavern is included as tavern and part of public guilds. Refactor so this is not the case');
});
context('tavern passed in as query', () => {
it('returns only the tavern', () => {
return expect(api.get('/groups', null, {type: 'tavern'}))
.to.eventually.have.a.lengthOf(1)
.and.to.have.deep.property('[0]')
.and.to.have.property('_id', 'habitrpg');
});
});
context('party passed in as query', () => {
it('returns only the user\'s party', () => {
return expect(api.get('/groups', null, {type: 'party'}))
.to.eventually.have.a.lengthOf(1)
.and.to.have.deep.property('[0]')
.and.to.have.property('leader', user._id);
});
});
context('public passed in as query', () => {
it('returns all public guilds', () => {
return expect(api.get('/groups', null, {type: 'public'}))
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
});
});
context('guilds passed in as query', () => {
it('returns all guilds user is a part of ', () => {
return expect(api.get('/groups', null, {type: 'guilds'}))
.to.eventually.have.a.lengthOf(NUMBER_OF_USERS_GUILDS);
});
});
});
+234
View File
@@ -0,0 +1,234 @@
import {
createAndPopulateGroup,
generateUser,
requester,
} from '../../helpers/api.helper';
import {
find,
each
} from 'lodash';
describe('GET /groups/:id', () => {
let typesOfGroups = {};
typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' };
typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
typesOfGroups['party'] = { type: 'party', privacy: 'private' };
each(typesOfGroups, (groupData, groupType) => {
context(`Member of a ${groupType}`, () => {
let leader, member, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 30,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
leader = res.leader;
member = res.members[0];
createdGroup = res.group;
});
});
it('returns the group object', () => {
let api = requester(member);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
expect(group._id).to.eql(createdGroup._id);
expect(group.name).to.eql(createdGroup.name);
expect(group.type).to.eql(createdGroup.type);
expect(group.privacy).to.eql(createdGroup.privacy);
});
});
it('transforms members array to an array of user objects', () => {
let api = requester(member);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
let member = group.members[0];
expect(member._id).to.exist;
expect(member.profile.name).to.exist;
expect(member.contributor).to.exist;
expect(member.achievements).to.exist;
expect(member.items).to.exist;
});
});
it('transforms leader id to leader object', () => {
let api = requester(member);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
expect(group.leader._id).to.eql(leader._id);
expect(group.leader.profile.name).to.eql(leader.profile.name);
expect(group.leader.items).to.exist;
expect(group.leader.stats).to.exist;
expect(group.leader.achievements).to.exist;
expect(group.leader.contributor).to.exist;
});
});
it('includes the user in the members list', () => {
let api = requester(member);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
let members = group.members;
let userInGroup = find(members, (user) => {
return member._id === user._id;
});
expect(userInGroup).to.be.ok;
});
});
});
});
context('Non-member of a public guild', () => {
let leader, nonMember, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
leader = res.leader;
createdGroup = res.group;
return generateUser();
}).then((user) => {
nonMember = user;
});
});
it('returns the group object for a non-member', () => {
let api = requester(nonMember);
return api.get(`/groups/${createdGroup._id}`)
.then((group) => {
expect(group._id).to.eql(createdGroup._id);
expect(group.name).to.eql(createdGroup.name);
expect(group.type).to.eql(createdGroup.type);
expect(group.privacy).to.eql(createdGroup.privacy);
});
});
it('does not include user in members list', () => {
let api = requester(nonMember);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
let userInGroup = find(group.members, (user) => {
return nonMember._id === user._id;
});
expect(userInGroup).to.not.be.ok;
});
});
});
context('Private Guilds', () => {
let leader, nonMember, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'private',
},
}).then((res) => {
leader = res.leader;
createdGroup = res.group;
return generateUser();
}).then((user) => {
nonMember = user;
});
});
it('does not return the group object for a non-member', () => {
let api = requester(nonMember);
return expect(api.get(`/groups/${createdGroup._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
text: 'Group not found or you don\'t have access.',
});
});
});
context('Non-member of a party', () => {
let leader, nonMember, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test party',
type: 'party',
privacy: 'private',
},
}).then((res) => {
leader = res.leader;
createdGroup = res.group;
return generateUser();
}).then((user) => {
nonMember = user;
});
});
it('does not return the group object for a non-member', () => {
let api = requester(nonMember);
return expect(api.get(`/groups/${createdGroup._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
text: 'Group not found or you don\'t have access.',
});
});
});
context('Member of a party', () => {
let leader, member, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test party',
type: 'party',
privacy: 'private',
},
}).then((res) => {
leader = res.leader;
createdGroup = res.group;
member = res.members[0];
});
});
it('returns the user\'s party if an id of "party" is passed in', () => {
let api = requester(member);
return api.get('/groups/party')
.then((group) => {
expect(group._id).to.eql(createdGroup._id);
expect(group.name).to.eql(createdGroup.name);
expect(group.type).to.eql(createdGroup.type);
expect(group.privacy).to.eql(createdGroup.privacy);
});
});
});
context('Non-existent group', () => {
let user;
beforeEach(() => {
return generateUser().then((_user) => {
user = _user;
});
});
it('returns error if group does not exist', () => {
let api = requester(user);
return expect(api.get('/groups/group-that-does-not-exist'))
.to.eventually.be.rejected.and.eql({
code: 404,
text: 'Group not found or you don\'t have access.',
});
});
});
});
+151
View File
@@ -0,0 +1,151 @@
import {
generateGroup,
generateUser,
requester,
} from '../../helpers/api.helper';
describe('POST /groups', () => {
context('All groups', () => {
let api, leader;
beforeEach(() => {
return generateUser().then((user) => {
leader = user;
api = requester(user);
});
});
xit('returns defaults? (TODO: it\'s possible to create a group without a type. Should the group default to party? Should we require type to be set?', () => {
return api.post('/groups').then((group) => {
expect(group._id).to.exist;
expect(group.name).to.eql(`${leader.profile.name}'s group`);
expect(group.type).to.eql('party');
expect(group.privacy).to.eql('private');
});
});
it('returns a group object', () => {
let group = {
name: 'Test Group',
type: 'party',
leaderOnly: { challenges: true },
description: 'Test Group Description',
leaderMessage: 'Test Group Message',
};
return api.post('/groups', group).then((createdGroup) => {
expect(createdGroup._id).to.exist;
expect(createdGroup.leader).to.eql(leader._id);
expect(createdGroup.name).to.eql(group.name);
expect(createdGroup.description).to.eql(group.description);
expect(createdGroup.leaderMessage).to.eql(group.leaderMessage);
expect(createdGroup.leaderOnly).to.eql(group.leaderOnly);
expect(createdGroup.memberCount).to.eql(1);
});
});
it('returns a populated members array', () => {
return api.post('/groups', {
type: 'party',
}).then((party) => {
let member = party.members[0];
expect(member._id).to.eql(leader._id);
expect(member.profile).to.eql(leader.profile);
expect(member.contributor).to.eql(leader.contributor);
});
});
});
context('Parties', () => {
let api, leader;
beforeEach(() => {
return generateUser().then((user) => {
leader = user;
api = requester(user);
});
});
it('allows party creation without gems', () => {
return expect(api.post('/groups', {
type: 'party',
})).to.eventually.have.property('_id');
});
it('prevents party creation if user is already in party', () => {
return expect(generateGroup(leader, {
type: 'party',
}).then((group) => {
return api.post('/groups', {
type: 'party',
});
})).to.eventually.be.rejected.and.eql({
code: 400,
text: 'Already in a party, try refreshing.',
});
})
xit('prevents creating a public party. TODO: it is possible to create a public party. Should we send back an error? Automatically switch the privacy to private?', () => {
return expect(api.post('/groups', {
type: 'party',
privacy: 'public',
})).to.eventually.be.rejected.and.eql({
code: 400,
text: 'Parties must be private',
});
});
});
context('Guilds', () => {
let api, leader;
beforeEach(() => {
return generateUser({
balance: 2,
}).then((user) => {
leader = user;
api = requester(user);
});
});
it('prevents guild creation when user does not have enough gems', () => {
return expect(generateUser({
balance: 0.75,
}).then((user) => {
api = requester(user);
return api.post('/groups', {
type: 'guild',
});
})).to.eventually.be.rejected.and.eql({
code: 401,
text: 'Not enough gems!',
});
});
it('can create a public guild', () => {
return expect(api.post('/groups', {
type: 'guild',
privacy: 'public',
})).to.eventually.have.property('leader', leader._id);
});
it('can create a private guild', () => {
return expect(api.post('/groups', {
type: 'guild',
privacy: 'private',
})).to.eventually.have.property('leader', leader._id);
});
it('deducts gems from user and adds them to guild bank', () => {
return expect(api.post('/groups', {
type: 'guild',
privacy: 'private',
}).then((group) => {
expect(group.balance).to.eql(1);
return api.get('/user');
})).to.eventually.have.deep.property('balance', 1);
});
});
});
+74
View File
@@ -0,0 +1,74 @@
import {
generateGroup,
generateUser,
requester,
} from '../../helpers/api.helper';
describe('POST /groups/:id', () => {
context('user is not the leader of the group', () => {
let api, user, otherUser, groupUserDoesNotOwn;
beforeEach(() => {
return Promise.all([
generateUser({ balance: 10 }),
generateUser({ balance: 10 }),
]).then((users) => {
user = users[0];
otherUser = users[1];
api = requester(user);
return generateGroup(otherUser, {
name: 'Group not Owned By User',
type: 'guild',
privacy: 'public',
members: [user, otherUser],
});
}).then((group) => {
groupUserDoesNotOwn = group;
});
});
it('does not allow user to update group', () => {
return expect(api.post(`/groups/${groupUserDoesNotOwn._id}`, {
name: 'Change'
})).to.eventually.be.rejected.and.eql({
code: 401,
text: 'Only the group leader can update the group!',
});
});
});
context('user is the leader of the group', () => {
let api, user, usersGroup;
beforeEach(() => {
return generateUser({
balance: 10,
}).then((_user) => {
user = _user;
api = requester(user);
return generateGroup(user, {
name: 'Original Group Title',
type: 'guild',
privacy: 'public',
});
}).then((group) => {
usersGroup = group;
});
});
it('allows user to update group', () => {
return api.post(`/groups/${usersGroup._id}`, {
name: 'New Group Title',
description: 'New group description',
}).then((group) => {
return api.get(`/groups/${usersGroup._id}`);
}).then((group) => {
expect(group.name).to.eql('New Group Title');
expect(group.description).to.eql('New group description');
});
});
});
});
@@ -0,0 +1,40 @@
import {
createAndPopulateGroup,
generateUser,
requester,
} from '../../helpers/api.helper';
describe('POST /groups/:id/join', () => {
context('user is already a member of the group', () => {
it('returns an error');
});
context('public guild has no leader', () => {
let user, group;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
group = res.group;
return requester(res.leader).post(`/groups/${group._id}/leave`);
}).then((res) => {
return generateUser();
}).then((generatedUser) => {
user = generatedUser;
});
});
it('makes the joining user the leader', () => {
let api = requester(user);
return expect(api.post(`/groups/${group._id}/join`).then((result) => {
return api.get(`/groups/${group._id}`);
})).to.eventually.have.deep.property('leader._id', user._id);
});
});
});
@@ -0,0 +1,163 @@
import {
createAndPopulateGroup,
generateGroup,
generateUser,
requester,
} from '../../helpers/api.helper';
import { find } from 'lodash';
describe('POST /groups/:id/leave', () => {
context('user is not member of the group', () => {
it('returns an error');
});
context('user is a non-leader member of a guild', () => {
let api, user, group;
beforeEach(() => {
return createAndPopulateGroup({
members: 3,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
user = res.members[0];
api = requester(user);
group = res.group;
});
});
it('leaves the group', () => {
return api.post(`/groups/${group._id}/leave`).then((result) => {
return api.get(`/groups/${group._id}`);
}).then((group) => {
let userInGroup = find(group.members, (member) => {
return member._id === user._id;
});
expect(userInGroup).to.not.be.ok;
});
});
});
context('user is the last member of a public guild', () => {
let api, user, group;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
user = res.leader;
api = requester(user);
group = res.group;
});
});
it('leaves the group accessible', () => {
return expect(api.post(`/groups/${group._id}/leave`).then((result) => {
return api.get(`/groups/${group._id}`);
})).to.eventually.have.property('_id', group._id);
});
});
context('user is the last member of a private group', () => {
let api, user, group;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'private',
},
}).then((res) => {
user = res.leader;
api = requester(user);
group = res.group;
});
});
it('group is not accessible after leaving', () => {
return expect(api.post(`/groups/${group._id}/leave`).then((result) => {
return api.get(`/groups/${group._id}`);
})).to.eventually.be.rejected.and.eql({
code: 404,
text: 'Group not found or you don\'t have access.',
});
});
});
context('user is the last member of a private group with pending invites', () => {
let api, user, inviteeRequest1, inviteeRequest2, group;
beforeEach(() => {
return createAndPopulateGroup({
invites: 2,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'private',
},
}).then((res) => {
user = res.leader;
inviteeRequest1 = requester(res.invitees[0]);
inviteeRequest2 = requester(res.invitees[1]);
api = requester(user);
group = res.group;
});
});
it('deletes the group invitations from users', () => {
return api.post(`/groups/${group._id}/leave`).then((result) => {
return Promise.all([
expect(inviteeRequest1.get(`/user`))
.to.eventually.have.deep.property('invitations.guilds')
.and.to.be.empty,
expect(inviteeRequest2.get(`/user`))
.to.eventually.have.deep.property('invitations.guilds')
.and.to.be.empty,
]);
});
});
});
context('user is the last member of a party with pending invites', () => {
let api, user, inviteeRequest1, inviteeRequest2, group;
beforeEach(() => {
return createAndPopulateGroup({
invites: 2,
groupDetails: {
name: 'test party',
type: 'party',
privacy: 'private',
},
}).then((res) => {
user = res.leader;
inviteeRequest1 = requester(res.invitees[0]);
inviteeRequest2 = requester(res.invitees[1]);
api = requester(user);
group = res.group;
});
});
it('deletes the group invitations from users', () => {
return api.post(`/groups/${group._id}/leave`).then((result) => {
return Promise.all([
expect(inviteeRequest1.get(`/user`))
.to.eventually.have.deep.property('invitations.party')
.and.to.be.empty,
expect(inviteeRequest2.get(`/user`))
.to.eventually.have.deep.property('invitations.party')
.and.to.be.empty,
]);
});
});
});
});
@@ -0,0 +1,58 @@
import {
createAndPopulateGroup,
generateGroup,
generateUser,
requester,
} from '../../helpers/api.helper';
describe('POST /groups/:id/removeMember', () => {
context('user is not member of the group', () => {
it('returns an error');
});
context('user is a non-leader member of a guild', () => {
it('returns an error');
});
context('user is the leader of a guild', () => {
let api, leader, member, group;
beforeEach(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test group',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
leader = res.leader;
member = res.members[0];
group = res.group;
api = requester(leader);
});
});
it('does not allow leader to remove themselves', () => {
return expect(api.post(`/groups/${group._id}/removeMember`, null, {
uuid: leader._id,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: 'You cannot remove yourself!',
});
});
it('can remove other members of guild', () => {
return api.post(`/groups/${group._id}/removeMember`, null, {
uuid: member._id,
}).then((res) => {
return api.get(`/groups/${group._id}`);
}).then((guild) => {
expect(guild.members).to.have.a.lengthOf(1);
expect(guild.members[0]._id).to.not.eql(member._id);
});
});
});
});
+91
View File
@@ -0,0 +1,91 @@
import {
generateUser,
requester,
} from '../../helpers/api.helper';
import {v4 as generateRandomUserName} from 'uuid';
describe('POST /register', () => {
context('username and email are free', () => {
it('registers a new user', () => {
let api = requester();
let username = generateRandomUserName();
let email = `${username}@example.com`;
let password = 'password';
return api.post('/register', {
username: username,
email: email,
password: password,
confirmPassword: password,
}).then((user) => {
expect(user._id).to.exist;
expect(user.apiToken).to.exist;
expect(user.auth.local.username).to.eql(username);
});
});
it('requires password and confirmPassword to match', () => {
let api = requester();
let username = generateRandomUserName();
let email = `${username}@example.com`;
let password = 'password';
let confirmPassword = 'not password';
return expect(api.post('/register', {
username: username,
email: email,
password: password,
confirmPassword: confirmPassword,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: ':password and :confirmPassword don\'t match',
});
});
});
context('login is already taken', () => {
let username, email;
beforeEach(() => {
username = generateRandomUserName();
email = `${username}@example.com`;
return generateUser({
'auth.local.username': username,
'auth.local.lowerCaseUsername': username,
'auth.local.email': email
});
});
it('rejects if username is already taken', () => {
let api = requester();
let uniqueEmail = `${generateRandomUserName()}@exampe.com`;
let password = 'password';
return expect(api.post('/register', {
username: username,
email: uniqueEmail,
password: password,
confirmPassword: password,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: 'Username already taken',
});
});
it('rejects if email is already taken', () => {
let api = requester();
let uniqueUsername = generateRandomUserName();
let password = 'password';
return expect(api.post('/register', {
username: uniqueUsername,
email: email,
password: password,
confirmPassword: password,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: 'Email already taken',
});
});
});
});
-13
View File
@@ -1,13 +0,0 @@
'use strict'
app = require("../../website/src/server")
describe "Site Status", ->
describe "GET /status", ->
it "/api/v2/status", (done) ->
request.get(baseURL + "/status").set("Accept", "application/json").end (res) ->
expect(res.statusCode).to.equal 200
expect(res.body.status).to.equal "up"
done()
+10
View File
@@ -0,0 +1,10 @@
import {requester} from '../../helpers/api.helper';
describe('Status', () => {
it('returns a status of up when server is up', () => {
let api = requester();
return expect(api.get('/status'))
.to.eventually.eql({status: 'up'});
});
});
+39
View File
@@ -0,0 +1,39 @@
import {
generateUser,
requester,
} from '../../helpers/api.helper';
describe('DELETE /user', () => {
let api;
beforeEach(() => {
return generateUser().then((user) => {
api = requester(user);
});
});
it('deletes the user', () => {
return expect(api.del('/user').then((fetchedUser) => {
return api.get('/user');
})).to.eventually.be.rejected.and.eql({
code: 401,
text: 'No user found.',
});
});
context('user in solo group', () => {
it('deletes party when user is the only member');
it('deletes private guild when user is the only member');
});
context('user in group with members', () => {
it('removes user from all groups user was a part of');
it('chooses new group leader for any group user was the leader of');
it('removes invitations from groups');
});
});
+24
View File
@@ -0,0 +1,24 @@
import {
generateUser,
requester,
} from '../../helpers/api.helper';
describe('GET /user', () => {
let user;
beforeEach(() => {
return generateUser().then((usr) => {
user = usr;
});
});
it('gets the user object', () => {
let api = requester(user);
return api.get('/user').then((fetchedUser) => {
expect(fetchedUser._id).to.eql(user._id);
expect(fetchedUser.auth.local.username).to.eql(user.auth.local.username);
expect(fetchedUser.todos).to.eql(user.todos);
expect(fetchedUser.items).to.eql(user.items);
});
});
});
+75
View File
@@ -0,0 +1,75 @@
import {
generateUser,
requester,
} from '../../helpers/api.helper';
import { each } from 'lodash';
describe('PUT /user', () => {
let api, user;
beforeEach(() => {
return generateUser().then((usr) => {
user = usr;
api = requester(user);
});
});
context('allowed paths', () => {
it('updates the user', () => {
return api.put('/user', {
'profile.name' : 'Frodo',
'preferences.costume': true,
'stats.hp': 14,
}).then((updatedUser) => {
expect(updatedUser.profile.name).to.eql('Frodo');
expect(updatedUser.preferences.costume).to.eql(true);
expect(updatedUser.stats.hp).to.eql(14);
});
});
});
context('top level protected paths', () => {
let protectedPaths = {
'gem balance': {balance: 100},
'auth': {'auth.blocked': true, 'auth.timestamps.created': new Date()},
'contributor': {'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text'},
'backer': {'backer.tier': 10, 'backer.npc': 'Bilbo'},
'subscriptions': {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000},
'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true},
'tasks': {todos: [], habits: [], dailys: [], rewards: []},
};
each(protectedPaths, (data, testName) => {
it(`does not allow updating ${testName}`, () => {
let errorText = [];
each(data, (value, path) => {
errorText.push(`path \`${path}\` was not saved, as it's a protected path.`);
});
return expect(api.put('/user', data)).to.eventually.be.rejected.and.eql({
code: 401,
text: errorText,
});
});
});
});
context('sub-level protected paths', () => {
let protectedPaths = {
'class stat': {'stats.class': 'wizard'},
};
each(protectedPaths, (data, testName) => {
it(`does not allow updating ${testName}`, () => {
let errorText = [];
each(data, (value, path) => {
errorText.push(`path \`${path}\` was not saved, as it's a protected path.`);
});
return expect(api.put('/user', data)).to.eventually.be.rejected.and.eql({
code: 401,
text: errorText,
});
});
});
});
});
-7
View File
@@ -1,7 +0,0 @@
--colors
--reporter spec
--timeout 8000
--check-leaks
--growl
--compilers js:babel/register
--require ./test/helpers/content.helper.js
+217
View File
@@ -0,0 +1,217 @@
import {
assign,
each,
isEmpty,
times,
} from 'lodash';
import {MongoClient as mongo} from 'mongodb';
import {v4 as generateUUID} from 'uuid';
import superagent from 'superagent';
const API_TEST_SERVER_PORT = 3003;
// Sets up an abject that can make all REST requests
// If a user is passed in, the uuid and api token of
// the user are used to make the requests
export function requester(user={}) {
return {
get: _requestMaker(user, 'get'),
post: _requestMaker(user, 'post'),
put: _requestMaker(user, 'put'),
del: _requestMaker(user, 'del'),
}
};
// Creates a new user and returns it
// If you need the user to have specific requirements,
// such as a balance > 0, just pass in the adjustment
// to the update object. If you want to adjust a nested
// paramter, such as the number of wolf eggs the user has,
// , you can do so by passing in the full path as a string:
// { 'items.eggs.Wolf': 10 }
export function generateUser(update={}) {
let username = generateUUID();
let password = 'password'
let email = username + '@example.com';
let request = _requestMaker({}, 'post');
return new Promise((resolve, reject) => {
request('/register', {
username: username,
email: email,
password: password,
confirmPassword: password,
}).then((user) => {
_updateDocument('users', user, update, () => {
resolve(user);
});
});
});
};
// Generates a new group. Requires a user object, which
// will will become the groups leader. Takes an update
// argument which will update group
export function generateGroup(leader, update={}) {
let request = _requestMaker(leader, 'post');
return new Promise((resolve, reject) => {
request('/groups').then((group) => {
_updateDocument('groups', group, update, () => {
resolve(group);
});
});
});
};
// This is generate group + the ability to create
// real users to populate it. The settings object
// takes in:
// members: Number - the number of group members to create. Defaults to 0.
// inivtes: Number - the number of users to create and invite to the group. Defaults to 0.
// groupDetails: Object - how to initialize the group
// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user
// can create the group
//
// Returns an object with
// members: an array of user objects that correspond to the members of the group
// invitees: an array of user objects that correspond to the invitees of the group
// leader: the leader user object
// group: the group object
export function createAndPopulateGroup(settings={}) {
let request, leader, members, invitees, group;
let numberOfMembers = settings.members || 0;
let numberOfInvites = settings.invites || 0;
let groupDetails = settings.groupDetails;
let leaderDetails = settings.leaderDetails || { balance: 10 };
let leaderPromise = generateUser(leaderDetails);
let memberPromises = Promise.all(
times(numberOfMembers, () => {
return generateUser();
})
);
let invitePromises = Promise.all(
times(numberOfInvites, () => {
return generateUser();
})
);
return new Promise((resolve, reject) => {
return leaderPromise.then((user) => {
leader = user;
request = _requestMaker(leader, 'post');
return memberPromises;
}).then((users) => {
members = users;
groupDetails.members = groupDetails.members || [leader._id];
each(members, (member) => {
groupDetails.members.push(member._id);
});
return generateGroup(leader, groupDetails);
}).then((createdGroup) => {
group = createdGroup;
return invitePromises;
}).then((users) => {
invitees = users;
let invitePromises = [];
each(invitees, (invitee) => {
let invitePromise = request(`/groups/${group._id}/invite`, {
uuids: [invitee._id]
});
invitePromises.push(invitePromise);
});
return Promise.all(invitePromises);
}).then((inviteResults) => {
resolve({
leader: leader,
group: group,
members: members,
invitees: invitees,
});
}).catch(reject);
});
};
// Specifically helpful for the GET /groups tests,
// resets the db to an empty state and creates a tavern document
export function resetHabiticaDB() {
return new Promise((resolve, reject) => {
mongo.connect('mongodb://localhost/habitrpg_test', (err, db) => {
if (err) return reject(err);
db.dropDatabase((err) => {
if (err) return reject(err);
let groups = db.collection('groups');
groups.insertOne({
_id: 'habitrpg',
chat: [],
leader: '9',
name: 'HabitRPG',
type: 'guild',
privacy: 'public',
members: [],
}, (err) => {
if (err) return reject(err);
resolve();
});
});
});
});
}
function _requestMaker(user, method) {
return (route, send, query) => {
return new Promise((resolve, reject) => {
let request = superagent[method](`http://localhost:${API_TEST_SERVER_PORT}/api/v2${route}`)
.accept('application/json');
if (user._id && user.apiToken) {
request
.set('x-api-user', user._id)
.set('x-api-key', user.apiToken);
}
request
.query(query)
.send(send)
.end((err, response) => {
if (err) {
if (!err.response) return reject(err);
let errorString = JSON.parse(err.response.text).err;
return reject({
code: err.response.statusCode,
text: errorString,
});
}
resolve(response.body);
});
});
}
}
function _updateDocument(collectionName, doc, update, cb) {
if (isEmpty(update)) { return cb(); }
mongo.connect('mongodb://localhost/habitrpg_test', (err, db) => {
if (err) throw `Error connecting to database when updating ${collectionName} collection: ${err}`;
let collection = db.collection(collectionName);
collection.update({ _id: doc._id }, { $set: update }, (err, result) => {
if (err) throw `Error updating ${collectionName}: ${err}`;
assign(doc, update);
cb();
});
});
}
+1
View File
@@ -5,4 +5,5 @@
global._ = require("lodash")
global.chai = require("chai")
chai.use(require("sinon-chai"))
chai.use(require("chai-as-promised"));
global.expect = chai.expect
+2 -1
View File
@@ -6,4 +6,5 @@
--debug
--compilers coffee:coffee-script
--globals io
--require test/api/api-helper
--require test/api-legacy/api-helper
--require ./test/helpers/globals.helper
+4 -5
View File
@@ -298,10 +298,9 @@ acceptablePUTPaths = _.reduce(require('./../models/user').schema.paths, function
return m;
}, {})
//// Uncomment this if we we want to disable GP-restoring (eg, holiday events)
//_.each('stats.gp'.split(' '), function(removePath){
// delete acceptablePUTPaths[removePath];
//})
_.each('stats.class'.split(' '), function(removePath){
delete acceptablePUTPaths[removePath];
})
/**
* Update user
@@ -318,7 +317,7 @@ api.update = function(req, res, next) {
if (acceptablePUTPaths[k])
user.fns.dotSet(k, v);
else
errors.push("path `" + k + "` was not saved, as it's a protected path. See https://github.com/HabitRPG/habitrpg/blob/develop/API.md for PUT /api/v2/user.");
errors.push("path `" + k + "` was not saved, as it's a protected path.");
return true;
});
user.save(function(err) {
+1 -1
View File
@@ -9,7 +9,7 @@ script(type='text/ng-template', id='modals/reroll.html')
.modal-footer
button.btn.btn-default(ng-click='$close()')=env.t('neverMind')
span(ng-if='user.balance < 1')
a.btn.btn-success(ng-click='openModal("buyGems",{track:{"Gems > Reroll"}})')=env.t('buyMoreGems')
a.btn.btn-success(ng-click='openModal("buyGems",{track:"Gems > Reroll"})')=env.t('buyMoreGems')
span.gem-cost=env.t('notEnoughGems')
span(ng-if='user.balance >= 1', ng-controller='SettingsCtrl')
a.btn.btn-danger(ng-click='$close(); reroll()')=env.t('fortify')
+28 -21
View File
@@ -1,33 +1,40 @@
h2 10/21/2015 - FROG PET QUEST, TO-DO SORTING FIX, AND SECOND WORLD BOSS EXHAUST STRIKE
h2 10/26/2015 - OCTOBER SUBSCRIBER ITEM REVEALED
hr
tr
td
span.Mount_Body_Frog-Base.pull-right
span.Mount_Head_Frog-Base.pull-right(style='margin:0')
h3 Frog Pet Quest
p Deep in the Swamps of Stagnation, you find your path obstructed by debris... and an angry amphibian. Yuck! If you complete the <a href='/#/options/inventory/quests'>Clutter Frog quest</a>, you'll be rewarded with some princely Frog pets!
p.small.muted Art by starsystemic, RosemonkeyCT, Jon Arjinborn, and Breadstrings
p.small.muted Writing by Fluitare
tr
td
h3 To-Do Sorting Fix
p To-dos will now stay sorted when you drag and drop them! Often, To-Dos used to change their order unless you cleared your completed To-Dos on a regular basis. That should no longer be a problem.
p.small.muted by ikadzuchi and Alys
tr
td
.seasonalshop_broken.pull-right
h3 World Boss: Burnout Uses Exhaust Strike!
p Ahh!!! Our incomplete Dailies have fed the flames of Burnout, and now it has enough energy to strike again! It lets loose a gout of spectral flame that sears the Seasonal Shop. You're horrified to see that the cheery Seasonal Sorceress has been transformed into a sorrowful Exhaust Spirit!
br
p We have to rescue our NPCs! Hurry, Habiticans, complete your tasks and defeat Burnout before it strikes for a third time!
br
p Late to the fight? Learn about Burnout and how to defeat World Bosses <a href='http://habitica.wikia.com/wiki/Boss#World_Bosses' target='_blank'>here</a>.
.promo_mystery_201510.pull-right
h3 October Subscriber Item: Horned Goblin!
p The October Subscriber Item has been revealed: the Horned Goblin Set! All October subscribers will receive the Goblin Horns and the Goblin Tail. You still have four days to <a href='/#/options/settings/subscription'>subscribe</a> and receive the item set! Thank you so much for your support - we really do rely on you to keep Habitica free to use and running smoothly.
p.small.muted by Lemoness
if menuItem !== 'oldNews'
hr
a(href='/static/old-news', target='_blank') Read older news
mixin oldNews
h2 10/21/2015 - FROG PET QUEST, TO-DO SORTING FIX, AND SECOND WORLD BOSS EXHAUST STRIKE
tr
td
span.Mount_Body_Frog-Base.pull-right
span.Mount_Head_Frog-Base.pull-right(style='margin:0')
h3 Frog Pet Quest
p Deep in the Swamps of Stagnation, you find your path obstructed by debris... and an angry amphibian. Yuck! If you complete the <a href='/#/options/inventory/quests'>Clutter Frog quest</a>, you'll be rewarded with some princely Frog pets!
p.small.muted Art by starsystemic, RosemonkeyCT, Jon Arjinborn, and Breadstrings
p.small.muted Writing by Fluitare
tr
td
h3 To-Do Sorting Fix
p To-dos will now stay sorted when you drag and drop them! Often, To-Dos used to change their order unless you cleared your completed To-Dos on a regular basis. That should no longer be a problem.
p.small.muted by ikadzuchi and Alys
tr
td
.seasonalshop_broken.pull-right
h3 World Boss: Burnout Uses Exhaust Strike!
p Ahh!!! Our incomplete Dailies have fed the flames of Burnout, and now it has enough energy to strike again! It lets loose a gout of spectral flame that sears the Seasonal Shop. You're horrified to see that the cheery Seasonal Sorceress has been transformed into a sorrowful Exhaust Spirit!
br
p We have to rescue our NPCs! Hurry, Habiticans, complete your tasks and defeat Burnout before it strikes for a third time!
br
p Late to the fight? Learn about Burnout and how to defeat World Bosses <a href='http://habitica.wikia.com/wiki/Boss#World_Bosses' target='_blank'>here</a>.
h2 10/14/2015 - JACK O'LANTERN PETS AND MOUNTS! WORLD BOSS EXHAUST STRIKE!
hr
tr
@@ -21,7 +21,7 @@ div(ng-if='::profile.backer.npc')
hr
div(ng-if='::profile.contributor.level || user._id == profile._id')
.achievement.achievement-firefox(ng-if='::profile.contributor.level')
.achievement.achievement-boot(ng-if='::profile.contributor.level')
div(ng-class='::{muted: !profile.contributor.level}')
h5
span.label.label-default(ng-if='::profile.contributor.level', class='label-contributor-{{::profile.contributor.level}}') {{::contribText(profile.contributor, profile.backer)}}