diff --git a/package.json b/package.json index 5893a7d523..56fa6cdd6c 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js index 881d4559d9..8f8c3c82a8 100644 --- a/tasks/gulp-tests.js +++ b/tasks/gulp-tests.js @@ -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(); } }); diff --git a/test/api-legacy/README.md b/test/api-legacy/README.md new file mode 100644 index 0000000000..9157af554d --- /dev/null +++ b/test/api-legacy/README.md @@ -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. diff --git a/test/api/anonymized.coffee b/test/api-legacy/anonymized.coffee similarity index 98% rename from test/api/anonymized.coffee rename to test/api-legacy/anonymized.coffee index 5532a8c472..c570229069 100644 --- a/test/api/anonymized.coffee +++ b/test/api-legacy/anonymized.coffee @@ -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 diff --git a/test/api-legacy/api-helper.js b/test/api-legacy/api-helper.js new file mode 100644 index 0000000000..3dcaf5241c --- /dev/null +++ b/test/api-legacy/api-helper.js @@ -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); + }); +}; diff --git a/test/api/challenges.coffee b/test/api-legacy/challenges.coffee similarity index 93% rename from test/api/challenges.coffee rename to test/api-legacy/challenges.coffee index ad4172c30a..8fa3afe9ce 100644 --- a/test/api/challenges.coffee +++ b/test/api-legacy/challenges.coffee @@ -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() diff --git a/test/api/chat.coffee b/test/api-legacy/chat.coffee similarity index 92% rename from test/api/chat.coffee rename to test/api-legacy/chat.coffee index 38c1254a00..d480f75c6d 100644 --- a/test/api/chat.coffee +++ b/test/api-legacy/chat.coffee @@ -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 diff --git a/test/api/coupons.coffee b/test/api-legacy/coupons.coffee similarity index 95% rename from test/api/coupons.coffee rename to test/api-legacy/coupons.coffee index 4a0635aa9c..c6fb4f36d7 100644 --- a/test/api/coupons.coffee +++ b/test/api-legacy/coupons.coffee @@ -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) -> diff --git a/test/api/get_user.coffee b/test/api-legacy/get_user.coffee similarity index 100% rename from test/api/get_user.coffee rename to test/api-legacy/get_user.coffee diff --git a/test/api/inAppPurchases.coffee b/test/api-legacy/inAppPurchases.coffee similarity index 100% rename from test/api/inAppPurchases.coffee rename to test/api-legacy/inAppPurchases.coffee diff --git a/test/api/party.coffee b/test/api-legacy/party.coffee similarity index 94% rename from test/api/party.coffee rename to test/api-legacy/party.coffee index 2a8b54801a..3e46328991 100644 --- a/test/api/party.coffee +++ b/test/api-legacy/party.coffee @@ -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) -> diff --git a/test/api/pushNotifications.coffee b/test/api-legacy/pushNotifications.coffee similarity index 99% rename from test/api/pushNotifications.coffee rename to test/api-legacy/pushNotifications.coffee index d7c23ce47c..803e3fee0f 100644 --- a/test/api/pushNotifications.coffee +++ b/test/api-legacy/pushNotifications.coffee @@ -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 diff --git a/test/api/score.coffee b/test/api-legacy/score.coffee similarity index 85% rename from test/api/score.coffee rename to test/api-legacy/score.coffee index a3b1b41826..57e455f068 100644 --- a/test/api/score.coffee +++ b/test/api-legacy/score.coffee @@ -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") diff --git a/test/api/subscriptions.coffee b/test/api-legacy/subscriptions.coffee similarity index 100% rename from test/api/subscriptions.coffee rename to test/api-legacy/subscriptions.coffee diff --git a/test/api/todos.coffee b/test/api-legacy/todos.coffee similarity index 93% rename from test/api/todos.coffee rename to test/api-legacy/todos.coffee index 7b57378bf4..0485266cea 100644 --- a/test/api/todos.coffee +++ b/test/api-legacy/todos.coffee @@ -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." diff --git a/test/api/users.coffee b/test/api-legacy/users.coffee similarity index 85% rename from test/api/users.coffee rename to test/api-legacy/users.coffee index 87f7897dde..966a6134a2 100644 --- a/test/api/users.coffee +++ b/test/api-legacy/users.coffee @@ -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; ) diff --git a/test/api/README.md b/test/api/README.md index 2f3dc40681..df495489ee 100644 --- a/test/api/README.md +++ b/test/api/README.md @@ -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)! diff --git a/test/api/api-helper.coffee b/test/api/api-helper.coffee deleted file mode 100644 index 0c3ae7b580..0000000000 --- a/test/api/api-helper.coffee +++ /dev/null @@ -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) diff --git a/test/api/groups.coffee b/test/api/groups.coffee deleted file mode 100644 index b5a4fa2765..0000000000 --- a/test/api/groups.coffee +++ /dev/null @@ -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() diff --git a/test/api/groups/GET-groups.test.js b/test/api/groups/GET-groups.test.js new file mode 100644 index 0000000000..102efc41f1 --- /dev/null +++ b/test/api/groups/GET-groups.test.js @@ -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); + }); + }); +}); diff --git a/test/api/groups/GET-groups_id.test.js b/test/api/groups/GET-groups_id.test.js new file mode 100644 index 0000000000..c23f6de01b --- /dev/null +++ b/test/api/groups/GET-groups_id.test.js @@ -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() + }); + }); + }); +}); diff --git a/test/api/groups/POST-groups.test.js b/test/api/groups/POST-groups.test.js new file mode 100644 index 0000000000..e231e17812 --- /dev/null +++ b/test/api/groups/POST-groups.test.js @@ -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); + }); + }); + }); +}); diff --git a/test/api/groups/POST-groups_id.test.js b/test/api/groups/POST-groups_id.test.js new file mode 100644 index 0000000000..43659bae91 --- /dev/null +++ b/test/api/groups/POST-groups_id.test.js @@ -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); + }); + }); +}); diff --git a/test/api/groups/POST-groups_id_join.test.js b/test/api/groups/POST-groups_id_join.test.js new file mode 100644 index 0000000000..921165aa72 --- /dev/null +++ b/test/api/groups/POST-groups_id_join.test.js @@ -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); + }); + }); +}); diff --git a/test/api/groups/POST-groups_id_leave.test.js b/test/api/groups/POST-groups_id_leave.test.js new file mode 100644 index 0000000000..6a741206c0 --- /dev/null +++ b/test/api/groups/POST-groups_id_leave.test.js @@ -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); + }); + }); +}); diff --git a/test/api/groups/POST-groups_id_removeMember.test.js b/test/api/groups/POST-groups_id_removeMember.test.js new file mode 100644 index 0000000000..283fcff60e --- /dev/null +++ b/test/api/groups/POST-groups_id_removeMember.test.js @@ -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); + }); + }); +}); diff --git a/test/api/register/POST-register.test.js b/test/api/register/POST-register.test.js new file mode 100644 index 0000000000..e8e970cf44 --- /dev/null +++ b/test/api/register/POST-register.test.js @@ -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); + }); + }); +}); diff --git a/test/api/status.coffee b/test/api/status.coffee deleted file mode 100644 index b70dca0ea5..0000000000 --- a/test/api/status.coffee +++ /dev/null @@ -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() diff --git a/test/api/status/GET-status.test.js b/test/api/status/GET-status.test.js new file mode 100644 index 0000000000..f4687b64be --- /dev/null +++ b/test/api/status/GET-status.test.js @@ -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); + }); + }); +}); diff --git a/test/api/user/DELETE-user.test.js b/test/api/user/DELETE-user.test.js new file mode 100644 index 0000000000..ed97b2bdb7 --- /dev/null +++ b/test/api/user/DELETE-user.test.js @@ -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'); + }); +}); diff --git a/test/api/user/GET-user.test.js b/test/api/user/GET-user.test.js new file mode 100644 index 0000000000..20cfce2308 --- /dev/null +++ b/test/api/user/GET-user.test.js @@ -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); + }); + }); +}); diff --git a/test/content/mocha.content.opts b/test/content/mocha.content.opts deleted file mode 100644 index 610fc454d3..0000000000 --- a/test/content/mocha.content.opts +++ /dev/null @@ -1,7 +0,0 @@ ---colors ---reporter spec ---timeout 8000 ---check-leaks ---growl ---compilers js:babel/register ---require ./test/helpers/content.helper.js diff --git a/test/helpers/api.helper.js b/test/helpers/api.helper.js new file mode 100644 index 0000000000..01afef66f1 --- /dev/null +++ b/test/helpers/api.helper.js @@ -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(); + }); + }); +} diff --git a/test/mocha.opts b/test/mocha.opts index 3f0bf9cd67..01d9dcfcd7 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -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