Merge pull request #6062 from crookedneighbor/new_api_test_infrastructure

New api test infrastructure
This commit is contained in:
Blade Barringer 2015-10-23 18:06:39 -05:00
commit ff64523ae4
34 changed files with 1679 additions and 794 deletions

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",
@ -121,13 +121,15 @@
"karma-script-launcher": "~0.1.0",
"lcov-result-merger": "^1.0.2",
"mocha": "~1.12.1",
"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",
"superagent-defaults": "^0.1.13",
"uuid": "^2.0.1",
"vinyl-source-stream": "^1.0.0",
"vinyl-transform": "^1.0.0"
}

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 E2E_TEST_SERVER_PORT = 3001
const API_TEST_SERVER_PORT = 3003
const TEST_DB = 'habitrpg_test'
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,7 +37,8 @@ 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();
@ -149,9 +155,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 +165,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 +181,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) => {
@ -225,7 +231,7 @@ gulp.task('test:karma:safe', ['test:prepare:build'], (cb) => {
gulp.task('test:e2e', ['test:prepare'], (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_DB_URI="${TEST_DB_URI}" PORT="${E2E_TEST_SERVER_PORT}" node ./website/src/server.js`,
'./node_modules/protractor/bin/webdriver-manager start',
].map(exec);
@ -250,7 +256,7 @@ gulp.task('test:e2e', ['test:prepare'], (cb) => {
gulp.task('test:e2e:safe', ['test:prepare'], (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_DB_URI="${TEST_DB_URI}" PORT="${E2E_TEST_SERVER_PORT}" node ./website/src/server.js`,
'./node_modules/protractor/bin/webdriver-manager start',
].map(exec);
@ -279,13 +285,64 @@ gulp.task('test:e2e:safe', ['test:prepare'], (cb) => {
});
});
gulp.task('test:api', ['test:prepare:mongo'], (done) => {
require('../test/helpers/globals.helper');
let server = exec(`NODE_DB_URI="${TEST_DB_URI}" PORT="${API_TEST_SERVER_PORT}" node ./website/src/server.js`);
awaitPort(API_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', () => {
process.env.RUN_INTEGRATION_TEST_FOREVER = true;
gulp.watch(['website/src/**', 'test/api/**'], ['test:api']);
});
gulp.task('test:api:safe', ['test:prepare:mongo'], (done) => {
let server = exec(`NODE_DB_URI="${TEST_DB_URI}" PORT="${API_TEST_SERVER_PORT}" node ./website/src/server.js`);
awaitPort(API_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/)
});
kill(server);
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];
@ -312,5 +369,6 @@ gulp.task('test', [
if (totals[1] > 0) throw "ERROR: There are failing tests!"
else {
console.log('\n\x1b[36mThanks for helping keep Habitica clean!\x1b[0m');
process.exit();
}
});

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.

View file

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

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

View file

@ -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()

View file

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

View file

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

View file

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

View file

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

View file

@ -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")

View file

@ -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."

View file

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

View file

@ -1,32 +1,83 @@
# 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', (done) => { // Since it's an asynronous test, we need to pass the done callback
let request;
generateUser().then((user) => {
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');
done(); // Call this function to end the test
}).catch(done); // If there is an error, it'll call the function done with that error, which will fail the test
});
```
## Questions?
Ask in the [Aspiring Coder's Guild](https://habitica.com/#/options/groups/guilds/68d4a01e-db97-4786-8ee3-05d612c5af6f)!

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)

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()

View file

@ -0,0 +1,143 @@
import {
generateGroup,
generateUser,
resetHabiticaDB,
requester,
} from '../../helpers/api.helper';
describe('GET /groups', () => {
let user, api;
before((done) => {
let leader, createdGroup;
// Set up a world with a mixture of public and private guilds
// Invite user to a few of them
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',
});
})
.then((party) => {
done();
}).catch(done);
});
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', (done) => {
api.get('/groups').then((groups) => {
expect(groups.length).to.eql(4);
done();
}).catch(done);
});
});
context('tavern passed in as query', () => {
it('returns only the tavern', (done) => {
api.get('/groups', null, {type: 'tavern'}).then((groups) => {
expect(groups).to.have.a.lengthOf(1);
let tavern = groups[0];
expect(tavern._id).to.eql('habitrpg');
done();
}).catch((err) => {
console.log(err);
done(err);
});
});
});
context('party passed in as query', () => {
it('returns only the user\'s party', (done) => {
api.get('/groups', null, {type: 'party'}).then((groups) => {
expect(groups).to.have.a.lengthOf(1);
let party = groups[0];
expect(party.leader).to.eql(user._id);
done();
}).catch(done);
});
});
context('public passed in as query', () => {
it('returns all public guilds', (done) => {
api.get('/groups', null, {type: 'public'}).then((groups) => {
expect(groups).to.have.a.lengthOf(3);
done();
}).catch(done);
});
});
context('guilds passed in as query', () => {
it('returns all guilds user is a part of ', (done) => {
api.get('/groups', null, {type: 'guilds'}).then((groups) => {
expect(groups).to.have.a.lengthOf(2);
done();
}).catch(done);
});
});
});

View file

@ -0,0 +1,253 @@
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((done) => {
createAndPopulateGroup({
members: 30,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
leader = res.leader;
member = res.members[0];
createdGroup = res.group;
done();
}).catch(done);
});
it('returns the group object', (done) => {
let api = requester(member);
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);
done();
}).catch(done);
});
it('transforms members array to an array of user objects', (done) => {
let api = requester(member);
api.get(`/groups/${createdGroup._id}`).then((group) => {
let foundMember = group.members[0];
expect(foundMember._id).to.exist;
expect(foundMember.profile.name).to.exist;
expect(foundMember.items).to.exist;
expect(foundMember.stats).to.exist;
expect(foundMember.achievements).to.exist;
expect(foundMember.contributor).to.exist;
done();
}).catch(done);
});
it('transforms leader id to leader object', (done) => {
let api = requester(member);
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;
done();
}).catch(done);
});
it('includes the user in the members list', (done) => {
let api = requester(member);
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;
done();
}).catch(done);
});
});
});
context('Non-member of a public guild', () => {
let leader, nonMember, createdGroup;
before((done) => {
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;
done();
}).catch(done);
});
it('returns the group object for a non-member', (done) => {
let api = requester(nonMember);
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);
done();
}).catch(done);
});
it('does not include user in members list', (done) => {
let api = requester(nonMember);
api.get(`/groups/${createdGroup._id}`)
.then((group) => {
let userInGroup = find(group.members, (user) => {
return nonMember._id === user._id;
});
expect(userInGroup).to.not.be.ok;
done();
}).catch(done);
});
});
context('Private Guilds', () => {
let leader, nonMember, createdGroup;
before((done) => {
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;
done();
}).catch(done);
});
it('does not return the group object for a non-member', (done) => {
let api = requester(nonMember);
api.get(`/groups/${createdGroup._id}`)
.then((done)).catch((err) => {
expect(err.code).to.eql(404);
expect(err.text).to.eql('Group not found or you don\'t have access.');
done()
});
});
});
context('Non-member of a party', () => {
let leader, nonMember, createdGroup;
before((done) => {
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;
done();
}).catch(done);
});
it('does not return the group object for a non-member', (done) => {
let api = requester(nonMember);
api.get(`/groups/${createdGroup._id}`)
.then((done)).catch((err) => {
expect(err.code).to.eql(404);
expect(err.text).to.eql('Group not found or you don\'t have access.');
done()
});
});
});
context('Member of a party', () => {
let leader, member, createdGroup;
before((done) => {
createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test party',
type: 'party',
privacy: 'private',
},
}).then((res) => {
leader = res.leader;
createdGroup = res.group;
member = res.members[0];
done();
}).catch(done);
});
it('returns the user\'s party if an id of "party" is passed in', (done) => {
let api = requester(member);
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);
done();
}).catch(done);
});
});
context('Non-existent group', () => {
let user;
beforeEach((done) => {
generateUser().then((_user) => {
user = _user;
done();
}).catch(done);
});
it('returns error if group does not exist', (done) => {
let api = requester(user);
api.get('/groups/group-that-does-not-exist')
.then((done)).catch((err) => {
expect(err.code).to.eql(404);
expect(err.text).to.eql('Group not found or you don\'t have access.');
done()
});
});
});
});

View file

@ -0,0 +1,209 @@
import {
generateGroup,
generateUser,
requester,
} from '../../helpers/api.helper';
describe('POST /groups', () => {
context('All groups', () => {
let api, leader;
beforeEach((done) => {
generateUser().then((user) => {
leader = user;
api = requester(user);
done();
}).catch(done);
});
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?', (done) => {
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');
done();
}).catch((err) => {
done(err);
});
});
it('returns a group object', (done) => {
let group = {
name: 'Test Group',
type: 'party',
leaderOnly: { challenges: true },
description: 'Test Group Description',
leaderMessage: 'Test Group Message',
};
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.challenges).to.eql(group.leaderOnly.challenges);
expect(createdGroup.memberCount).to.eql(1);
expect(createdGroup.members).to.have.a.lengthOf(1);
done();
})
.catch((err) => {
done(err);
});
});
it('returns a populated members array', (done) => {
api.post('/groups', {
type: 'party',
}).then((createdGroup) => {
let member = createdGroup.members[0];
expect(member._id).to.eql(leader._id);
expect(member.profile.name).to.eql(leader.profile.name);
expect(member.items).to.exist;
expect(member.stats).to.exist;
expect(member.achievements).to.exist;
expect(member.contributor).to.exist;
done();
})
.catch((err) => {
done(err);
});
});
});
context('Parties', () => {
let api, leader;
beforeEach((done) => {
generateUser().then((user) => {
leader = user;
api = requester(user);
done();
}).catch(done);
});
it('allows party creation without gems', (done) => {
api.post('/groups', {
type: 'party',
})
.then((group) => {
expect(group._id).to.exist;
done();
})
.catch((err) => {
done(err);
});
});
it('prevents party creation if user is already in party', (done) => {
generateGroup(leader, {
type: 'party',
}).then((group) => {
return api.post('/groups', {
type: 'party',
});
}).then((group) => {
done('Unexpected success');
})
.catch((err) => {
expect(err.code).to.eql(400);
expect(err.text).to.eql('Already in a party, try refreshing.');
done();
});
})
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?', (done) => {
api.post('/groups', {
type: 'party',
privacy: 'public',
})
.then((group) => {
done('Unexpected success');
})
.catch((err) => {
expect(err.code).to.eql(400);
expect(err.text).to.eql('Parties must be private');
done();
});
});
});
context('Guilds', () => {
let api, leader;
beforeEach((done) => {
generateUser({
balance: 2,
}).then((user) => {
leader = user;
api = requester(user);
done();
}).catch(done);
});
it('prevents guild creation when user does not have enough gems', (done) => {
generateUser({
balance: 0.75,
}).then((user) => {
api = requester(user);
return api.post('/groups', {
type: 'guild',
});
}).then((group) => {
done('Unexpected success');
}).catch((err) => {
expect(err.code).to.eql(401);
expect(err.text).to.eql('Not enough gems!');
done();
});
});
it('can create a public guild', (done) => {
api.post('/groups', {
type: 'guild',
privacy: 'public',
}).then((group) => {
expect(group.leader).to.eql(leader._id);
done();
})
.catch((err) => {
done(err);
});
});
it('can create a private guild', (done) => {
api.post('/groups', {
type: 'guild',
privacy: 'private',
}).then((group) => {
expect(group.leader).to.eql(leader._id);
done();
})
.catch((err) => {
done(err);
});
});
it('deducts gems from user and adds them to guild bank', (done) => {
api.post('/groups', {
type: 'guild',
privacy: 'private',
}).then((group) => {
expect(group.balance).to.eql(1);
return api.get('/user');
}).then((user) => {
expect(user.balance).to.eql(1);
done();
})
.catch((err) => {
done(err);
});
});
});
});

View file

@ -0,0 +1,78 @@
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((done) => {
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;
done();
}).catch(done);
});
it('does not allow user to update group', (done) => {
api.post(`/groups/${groupUserDoesNotOwn._id}`, {
name: 'Change'
}).then(done).catch((err) => {
expect(err.code).to.eql(401);
expect(err.text).to.eql('Only the group leader can update the group!');
done();
});
});
});
context('user is the leader of the group', () => {
let api, user, usersGroup;
beforeEach((done) => {
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;
done();
}).catch(done);
});
it('allows user to update group', (done) => {
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');
done();
}).catch(done);
});
});
});

View file

@ -0,0 +1,44 @@
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((done) => {
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;
done();
}).catch(done);
});
it('makes the joining user the leader', (done) => {
let api = requester(user);
api.post(`/groups/${group._id}/join`).then((result) => {
return api.get(`/groups/${group._id}`);
}).then((group) => {
expect(group.leader._id).to.eql(user._id);
done();
}).catch(done);
});
});
});

View file

@ -0,0 +1,169 @@
import {
createAndPopulateGroup,
generateGroup,
generateUser,
requester,
} from '../../helpers/api.helper';
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((done) => {
createAndPopulateGroup({
members: 3,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
user = res.members[0];
api = requester(user);
group = res.group;
done();
}).catch(done);
});
it('leaves the group', (done) => {
api.post(`/groups/${group._id}/leave`).then((result) => {
return api.get(`/groups/${group._id}`);
}).then((group) => {
expect(group.members).to.not.include(user._id);
done();
}).catch(done);
});
});
context('user is the last member of a public guild', () => {
let api, user, group;
beforeEach((done) => {
createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
user = res.leader;
api = requester(user);
group = res.group;
done();
}).catch(done);
});
it('leaves the group accessible', (done) => {
api.post(`/groups/${group._id}/leave`).then((result) => {
return api.get(`/groups/${group._id}`);
}).then((group) => {
expect(group._id).to.eql(group._id);
done();
}).catch(done);
});
});
context('user is the last member of a private group', () => {
let api, user, group;
beforeEach((done) => {
createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'private',
},
}).then((res) => {
user = res.leader;
api = requester(user);
group = res.group;
done();
}).catch(done);
});
it('group is not accessible after leaving', (done) => {
api.post(`/groups/${group._id}/leave`).then((result) => {
return api.get(`/groups/${group._id}`);
}).then(done).catch((err) => {
expect(err.code).to.eql(404);
expect(err.text).to.eql('Group not found or you don\'t have access.');
done();
});
});
});
context('user is the last member of a private group with pending invites', () => {
let api, user, inviteeRequest1, inviteeRequest2, group;
beforeEach((done) => {
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;
done();
}).catch(done);
});
it('deletes the group invitations from users', (done) => {
api.post(`/groups/${group._id}/leave`).then((result) => {
return Promise.all([
inviteeRequest1.get(`/user`),
inviteeRequest2.get(`/user`),
]);
}).then((users) => {
expect(users[0].invitations.guilds[0]).to.not.exist;
expect(users[1].invitations.guilds[0]).to.not.exist;
done();
}).catch(done);
});
});
context('user is the last member of a party with pending invites', () => {
let api, user, inviteeRequest1, inviteeRequest2, group;
beforeEach((done) => {
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;
done();
}).catch(done);
});
it('deletes the group invitations from users', (done) => {
api.post(`/groups/${group._id}/leave`).then((result) => {
return Promise.all([
inviteeRequest1.get(`/user`),
inviteeRequest2.get(`/user`),
]);
}).then((users) => {
expect(users[0].invitations.party).to.be.empty;
expect(users[1].invitations.party).to.be.empty;
done();
}).catch(done);
});
});
});

View file

@ -0,0 +1,61 @@
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((done) => {
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);
done();
}).catch(done);
});
it('does not allow leader to remove themselves', (done) => {
api.post(`/groups/${group._id}/removeMember`, null, {
uuid: leader._id,
}).then(done).catch((err) => {
expect(err.code).to.eql(401);
expect(err.text).to.eql('You cannot remove yourself!');
done();
});
});
it('can remove other members of guild', (done) => {
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);
done();
}).catch(done);
});
});
});

View file

@ -0,0 +1,31 @@
import {requester} from '../../helpers/api.helper';
import {v4 as generateRandomUserName} from 'uuid';
describe('POST /register', () => {
let api;
beforeEach(() => {
api = requester();
});
it('registers a new user', (done) => {
let username = generateRandomUserName();
let email = `${username}@example.com`;
let password = 'password';
api.post('/register', {
username: username,
email: email,
password: password,
confirmPassword: password,
}).then((user) => {
expect(user._id).to.exist;
expect(user.auth.local.username).to.eql(username);
expect(user.auth.local.email).to.eql(`${username}@example.com`);
done();
})
.catch((err) => {
done(err);
});
});
});

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()

View file

@ -0,0 +1,20 @@
import {requester} from '../../helpers/api.helper';
describe('Status', () => {
let api;
beforeEach(() => {
api = requester();
});
it('returns a status of up when server is up', (done) => {
api.get('/status')
.then((res) => {
expect(res.status).to.eql('up');
done();
})
.catch((err) => {
done(err);
});
});
});

View file

@ -0,0 +1,45 @@
import {
generateUser,
requester,
} from '../../helpers/api.helper';
describe('DELETE /user', () => {
let api;
beforeEach((done) => {
generateUser().then((usr) => {
api = requester(usr);
done();
}).catch(done);
});
it('deletes the user', (done) => {
api.del('/user')
.then((fetchedUser) => {
return api.get('/user')
})
.then((deletedUser) => {
done('Unexpected user');
})
.catch((err) => {
expect(err.code).to.eql(401);
done();
});
});
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');
});
});

View file

@ -0,0 +1,28 @@
import {
generateUser,
requester,
} from '../../helpers/api.helper';
describe('GET /user', () => {
let api, user;
beforeEach((done) => {
generateUser().then((usr) => {
user = usr;
api = requester(usr);
done();
}).catch(done);
});
it('gets the user object', (done) => {
api.get('/user')
.then((fetchedUser) => {
expect(fetchedUser._id).to.eql(user._id);
expect(fetchedUser.todos).to.eql(user.todos);
done();
})
.catch((err) => {
done(err);
});
});
});

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
test/helpers/api.helper.js Normal file
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();
});
});
}

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