mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 14:17:22 +01:00
Revert "Deprecate API v2" (#7801)
This commit is contained in:
@@ -13,6 +13,13 @@ website/client/
|
||||
|
||||
# Temporarilly disabled. These should be removed when the linting errors are fixed
|
||||
common/script/content/index.js
|
||||
common/script/public/**/*.js
|
||||
|
||||
website/server/**/api-v2/**/*.js
|
||||
website/server/routes/payments.js
|
||||
website/server/routes/pages.js
|
||||
website/server/middlewares/apiThrottle.js
|
||||
website/server/middlewares/forceRefresh.js
|
||||
|
||||
debug-scripts/*
|
||||
scripts/*
|
||||
|
||||
@@ -131,7 +131,7 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('build:test', ['test:prepare:translations', 'build:dev']);
|
||||
|
||||
grunt.registerTask('test:prepare:translations', function() {
|
||||
var i18n = require('./website/server/libs/i18n'),
|
||||
var i18n = require('./website/server/libs/api-v3/i18n'),
|
||||
fs = require('fs');
|
||||
fs.writeFileSync('test/spec/mocks/translations.js',
|
||||
"if(!window.env) window.env = {};\n" +
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"deleteLocalAccountText": "Are you sure? This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type your password into the text box below.",
|
||||
"API": "API",
|
||||
"APIv3": "API v3",
|
||||
"APIv2": "API v2 - Deprecated",
|
||||
"APIText": "Copy these for use in third party applications. However, think of your API Token like a password, and do not share it publicly. You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.",
|
||||
"APIToken": "API Token (this is a password - see warning above!)",
|
||||
"thirdPartyApps": "Third Party Apps",
|
||||
|
||||
@@ -102,10 +102,13 @@
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "npm run lint && gulp test",
|
||||
"test:api-v2:unit": "mocha test/server_side",
|
||||
"test:api-v2:integration": "mocha test/api/v2 --recursive",
|
||||
"test:api-v3": "gulp test:api-v3",
|
||||
"test:api-v3:unit": "gulp test:api-v3:unit",
|
||||
"test:api-v3:integration": "gulp test:api-v3:integration",
|
||||
"test:api-v3:integration:separate-server": "NODE_ENV=test gulp test:api-v3:integration:separate-server",
|
||||
"test:api-v3:integration:separate-server": "gulp test:api-v3:integration:separate-server",
|
||||
"test:api-legacy": "istanbul cover -i \"website/server/**\" --dir coverage/api ./node_modules/mocha/bin/_mocha test/api-legacy",
|
||||
"test:common": "mocha test/common --recursive",
|
||||
"test:content": "mocha test/content --recursive",
|
||||
"test:karma": "karma start --single-run",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import mongoose from 'mongoose';
|
||||
import autoinc from 'mongoose-id-autoinc';
|
||||
import logger from '../website/server/libs/logger';
|
||||
import logger from '../website/server/libs/api-v3/logger';
|
||||
import nconf from 'nconf';
|
||||
import repl from 'repl';
|
||||
import gulp from 'gulp';
|
||||
|
||||
@@ -21,11 +21,15 @@ let server;
|
||||
|
||||
const TEST_DB_URI = nconf.get('TEST_DB_URI');
|
||||
|
||||
const API_V2_TEST_COMMAND = 'npm run test:api-v2:integration';
|
||||
const API_V3_TEST_COMMAND = 'npm run test:api-v3';
|
||||
const LEGACY_API_TEST_COMMAND = 'npm run test:api-legacy';
|
||||
const COMMON_TEST_COMMAND = 'npm run test:common';
|
||||
const CONTENT_TEST_COMMAND = 'npm run test:content';
|
||||
const CONTENT_OPTIONS = {maxBuffer: 1024 * 500};
|
||||
const KARMA_TEST_COMMAND = 'npm run test:karma';
|
||||
const SERVER_SIDE_TEST_COMMAND = 'npm run test:api-v2:unit';
|
||||
const ISTANBUL_TEST_COMMAND = 'npm run test:api-legacy';
|
||||
|
||||
/* Helper methods for reporting test summary */
|
||||
let testResults = [];
|
||||
@@ -181,6 +185,43 @@ gulp.task('test:server_side:safe', ['test:prepare:build'], (cb) => {
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-legacy', ['test:prepare:mongo'], (cb) => {
|
||||
let runner = exec(
|
||||
testBin(ISTANBUL_TEST_COMMAND),
|
||||
(err, stdout, stderr) => {
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-legacy:safe', ['test:prepare:mongo'], (cb) => {
|
||||
let runner = exec(
|
||||
testBin(ISTANBUL_TEST_COMMAND),
|
||||
(err, stdout, stderr) => {
|
||||
testResults.push({
|
||||
suite: 'API (legacy) Specs',
|
||||
pass: testCount(stdout, /(\d+) passing/),
|
||||
fail: testCount(stdout, /(\d+) failing/),
|
||||
pend: testCount(stdout, /(\d+) pending/)
|
||||
});
|
||||
cb();
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-legacy:clean', (cb) => {
|
||||
pipe(exec(testBin(LEGACY_API_TEST_COMMAND), () => cb()));
|
||||
});
|
||||
|
||||
gulp.task('test:api-legacy:watch', [
|
||||
'test:prepare:mongo',
|
||||
'test:api-legacy:clean'
|
||||
], () => {
|
||||
gulp.watch(['website/server/**', 'test/api-legacy/**'], ['test:api-legacy:clean']);
|
||||
});
|
||||
|
||||
gulp.task('test:karma', ['test:prepare:build'], (cb) => {
|
||||
let runner = exec(
|
||||
testBin(KARMA_TEST_COMMAND),
|
||||
@@ -268,6 +309,46 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
|
||||
});
|
||||
});
|
||||
|
||||
/*gulp.task('test:api-v2', ['test:prepare:server'], (done) => {
|
||||
process.env.API_VERSION = 'v2';
|
||||
awaitPort(TEST_SERVER_PORT).then(() => {
|
||||
runMochaTests('./test/api/v2/**//*.js', server, done)
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('test:api-v2:watch', ['test:prepare:server'], () => {
|
||||
process.env.RUN_INTEGRATION_TEST_FOREVER = true;
|
||||
gulp.watch(['website/server/**', 'test/api/v2/**'], ['test:api-v2']);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => {
|
||||
awaitPort(TEST_SERVER_PORT).then(() => {
|
||||
let runner = exec(
|
||||
testBin(API_V2_TEST_COMMAND),
|
||||
(err, stdout, stderr) => {
|
||||
testResults.push({
|
||||
suite: 'API V2 Specs\t',
|
||||
pass: testCount(stdout, /(\d+) passing/),
|
||||
fail: testCount(stderr, /(\d+) failing/),
|
||||
pend: testCount(stdout, /(\d+) pending/)
|
||||
});
|
||||
done();
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
});*/
|
||||
|
||||
gulp.task('test:api-v2:integration', (done) => {
|
||||
let runner = exec(
|
||||
testBin('mocha test/api/v2 --recursive'),
|
||||
{maxBuffer: 500*1024},
|
||||
(err, stdout, stderr) => done(err)
|
||||
)
|
||||
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:unit', (done) => {
|
||||
let runner = exec(
|
||||
testBin('mocha test/api/v3/unit --recursive'),
|
||||
@@ -278,7 +359,7 @@ gulp.task('test:api-v3:unit', (done) => {
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:unit:watch', () => {
|
||||
gulp.watch(['website/server/libs/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], ['test:api-v3:unit']);
|
||||
gulp.watch(['website/server/libs/api-v3/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], ['test:api-v3:unit']);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration', (done) => {
|
||||
@@ -292,7 +373,7 @@ gulp.task('test:api-v3:integration', (done) => {
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration:watch', () => {
|
||||
gulp.watch(['website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
|
||||
gulp.watch(['website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/api-v3/*.js',
|
||||
'test/api/v3/integration/**/*'], ['test:api-v3:integration']);
|
||||
});
|
||||
|
||||
@@ -312,6 +393,7 @@ gulp.task('test', (done) => {
|
||||
'test:karma',
|
||||
'test:api-v3:unit',
|
||||
'test:api-v3:integration',
|
||||
'test:api-v2:integration',
|
||||
done
|
||||
);
|
||||
});
|
||||
@@ -322,4 +404,100 @@ gulp.task('test:api-v3', (done) => {
|
||||
'test:api-v3:integration',
|
||||
done
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Old tests tasks
|
||||
/*
|
||||
gulp.task('test:api-v3', ['test:api-v3:unit', 'test:api-v3:integration']);
|
||||
|
||||
gulp.task('test:api-v3:watch', ['test:api-v3:unit:watch', 'test:api-v3:integration:watch']);
|
||||
|
||||
gulp.task('test:api-v3:unit', (done) => {*/
|
||||
// runMochaTests('./test/api/v3/unit/**/*.js', null, done)
|
||||
/*});
|
||||
|
||||
gulp.task('test:api-v3:unit:watch', () => {
|
||||
gulp.watch(['website/server/**', 'test/api/v3/unit/**'], ['test:api-v3:unit']);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration', ['test:prepare:server'], (done) => {
|
||||
process.env.API_VERSION = 'v3';
|
||||
awaitPort(TEST_SERVER_PORT).then(() => {*/
|
||||
// runMochaTests('./test/api/v3/integration/**/*.js', server, done)
|
||||
/* });
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration:watch', ['test:prepare:server'], () => {
|
||||
process.env.RUN_INTEGRATION_TEST_FOREVER = true;
|
||||
gulp.watch(['website/server/**', 'test/api/v3/integration/**'], ['test:api-v3:integration']);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:safe', ['test:prepare:server'], (done) => {
|
||||
awaitPort(TEST_SERVER_PORT).then(() => {
|
||||
let runner = exec(
|
||||
testBin(API_V3_TEST_COMMAND),
|
||||
(err, stdout, stderr) => {
|
||||
testResults.push({
|
||||
suite: 'API V3 Specs\t',
|
||||
pass: testCount(stdout, /(\d+) passing/),
|
||||
fail: testCount(stdout, /(\d+) failing/),
|
||||
pend: testCount(stdout, /(\d+) pending/)
|
||||
});
|
||||
done();
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('test:all', (done) => {
|
||||
runSequence(
|
||||
//'test:e2e:safe',
|
||||
//'test:common:safe',
|
||||
//'test:content:safe',
|
||||
// 'test:server_side:safe',
|
||||
//'test:karma:safe',
|
||||
//'test:api-legacy:safe',
|
||||
//'test:api-v2:safe',
|
||||
'test:api-v3:safe',
|
||||
done);
|
||||
});
|
||||
|
||||
gulp.task('test', ['test:all'], () => {
|
||||
let totals = [0,0,0];
|
||||
|
||||
console.log('\n\x1b[36m\x1b[4mHabitica Test Summary\x1b[0m\n');
|
||||
testResults.forEach((s) => {
|
||||
totals[0] = totals[0] + s.pass;
|
||||
totals[1] = totals[1] + s.fail;
|
||||
totals[2] = totals[2] + s.pend;
|
||||
console.log(
|
||||
`\x1b[33m\x1b[4m${s.suite}\x1b[0m\t`,
|
||||
`\x1b[32mPassing: ${s.pass},\t`,
|
||||
`\x1b[31mFailed: ${s.fail},\t`,
|
||||
`\x1b[36mPending: ${s.pend}\t`
|
||||
);
|
||||
|
||||
if (s.pass === 0) {
|
||||
console.error('ERROR: Detected a test suite with 0 passing tests. Something may be wrong causing the build to error.');
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(
|
||||
'\n\x1b[33m\x1b[4mTotal:\x1b[0m\t\t\t',
|
||||
`\x1b[32mPassing: ${totals[0]},\t`,
|
||||
`\x1b[31mFailed: ${totals[1]},\t`,
|
||||
`\x1b[36mPending: ${totals[2]}\t`
|
||||
);
|
||||
|
||||
kill(server);
|
||||
|
||||
if (totals[1] > 0) {
|
||||
console.error('ERROR: There are failing tests!');
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('\n\x1b[36mThanks for helping keep Habitica clean!\x1b[0m');
|
||||
process.exit();
|
||||
}
|
||||
});*/
|
||||
|
||||
5
test/api-legacy/README.md
Normal file
5
test/api-legacy/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Legacy API Tests
|
||||
|
||||
These tests have been deprecated. Any tests that you see in this folder should be converted to ES2015 and ported to the `/test/api` directory.
|
||||
|
||||
Once all tests have been ported, this directory will be removed.
|
||||
92
test/api-legacy/api-helper.js
Normal file
92
test/api-legacy/api-helper.js
Normal file
@@ -0,0 +1,92 @@
|
||||
require('babel-core/register');
|
||||
var path, superagentDefaults;
|
||||
|
||||
superagentDefaults = require("superagent-defaults");
|
||||
|
||||
global.request = superagentDefaults();
|
||||
|
||||
global.mongoose = require("mongoose");
|
||||
var Bluebird = require('bluebird');
|
||||
mongoose.Promise = Bluebird;
|
||||
|
||||
global.moment = require("moment");
|
||||
|
||||
global.async = require("async");
|
||||
|
||||
global._ = require("lodash");
|
||||
|
||||
global.shared = require("../../common");
|
||||
|
||||
global.User = require("../../website/server/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);
|
||||
});
|
||||
};
|
||||
412
test/api-legacy/challenges.js
Normal file
412
test/api-legacy/challenges.js
Normal file
@@ -0,0 +1,412 @@
|
||||
var Challenge, Group, app;
|
||||
|
||||
app = require("../../website/server/server");
|
||||
|
||||
Group = require("../../website/server/models/group").model;
|
||||
|
||||
Challenge = require("../../website/server/models/challenge").model;
|
||||
|
||||
describe("Challenges", function() {
|
||||
var challenge, group, updateTodo;
|
||||
challenge = void 0;
|
||||
updateTodo = void 0;
|
||||
group = void 0;
|
||||
beforeEach(function(done) {
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return registerNewUser(cb, true);
|
||||
}, function(user, cb) {
|
||||
return request.post(baseURL + "/groups").send({
|
||||
name: "TestGroup",
|
||||
type: "party"
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
group = res.body;
|
||||
expect(group.members.length).to.equal(1);
|
||||
expect(group.leader).to.equal(user._id);
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return request.post(baseURL + "/challenges").send({
|
||||
group: group._id,
|
||||
dailys: [
|
||||
{
|
||||
type: "daily",
|
||||
text: "Challenge Daily"
|
||||
}
|
||||
],
|
||||
todos: [
|
||||
{
|
||||
type: "todo",
|
||||
text: "Challenge Todo 1",
|
||||
notes: "Challenge Notes"
|
||||
}
|
||||
],
|
||||
rewards: [],
|
||||
habits: []
|
||||
}).end(function(err, res) {
|
||||
challenge = res.body;
|
||||
return done();
|
||||
});
|
||||
}
|
||||
]);
|
||||
});
|
||||
describe('POST /challenge', function() {
|
||||
return it("Creates a challenge", function(done) {
|
||||
return request.post(baseURL + "/challenges").send({
|
||||
group: group._id,
|
||||
dailys: [
|
||||
{
|
||||
type: "daily",
|
||||
text: "Challenge Daily"
|
||||
}
|
||||
],
|
||||
todos: [
|
||||
{
|
||||
type: "todo",
|
||||
text: "Challenge Todo 1",
|
||||
notes: "Challenge Notes"
|
||||
}, {
|
||||
type: "todo",
|
||||
text: "Challenge Todo 2",
|
||||
notes: "Challenge Notes"
|
||||
}
|
||||
],
|
||||
rewards: [],
|
||||
habits: [],
|
||||
official: true
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
return async.parallel([
|
||||
function(cb) {
|
||||
return User.findById(user._id, cb);
|
||||
}, function(cb) {
|
||||
return Challenge.findById(res.body._id, cb);
|
||||
}
|
||||
], function(err, results) {
|
||||
var user;
|
||||
user = results[0];
|
||||
challenge = results[1];
|
||||
expect(user.dailys[user.dailys.length - 1].text).to.equal("Challenge Daily");
|
||||
expect(challenge.official).to.equal(false);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('POST /challenge/:cid', function() {
|
||||
it("updates the notes on user's version of a challenge task's note without updating the challenge", function(done) {
|
||||
updateTodo = challenge.todos[0];
|
||||
updateTodo.notes = "User overriden notes";
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return Challenge.findById(challenge._id, cb);
|
||||
}, function(chal, cb) {
|
||||
expect(chal.todos[0].notes).to.eql("Challenge Notes");
|
||||
return cb();
|
||||
}, function(cb) {
|
||||
return request.get(baseURL + "/user/tasks/" + updateTodo.id).end(function(err, res) {
|
||||
expect(res.body.notes).to.eql("User overriden notes");
|
||||
return done();
|
||||
});
|
||||
}
|
||||
]);
|
||||
});
|
||||
it("changes user's copy of challenge tasks when the challenge is updated", function(done) {
|
||||
challenge.dailys[0].text = "Updated Daily";
|
||||
return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) {
|
||||
challenge = res.body;
|
||||
expect(challenge.dailys[0].text).to.equal("Updated Daily");
|
||||
return User.findById(user._id, function(err, _user) {
|
||||
expectCode(res, 200);
|
||||
expect(_user.dailys[_user.dailys.length - 1].text).to.equal("Updated Daily");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it("does not changes user's notes on tasks when challenge task notes are updated", function(done) {
|
||||
challenge.todos[0].notes = "Challenge Updated Todo Notes";
|
||||
return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) {
|
||||
challenge = res.body;
|
||||
expect(challenge.todos[0].notes).to.equal("Challenge Updated Todo Notes");
|
||||
return User.findById(user._id, function(err, _user) {
|
||||
expectCode(res, 200);
|
||||
expect(_user.todos[_user.todos.length - 1].notes).to.equal("Challenge Notes");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
return it("shows user notes on challenge page", function(done) {
|
||||
updateTodo = challenge.todos[0];
|
||||
updateTodo.notes = "User overriden notes";
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return Challenge.findById(challenge._id, cb);
|
||||
}, function(chal, cb) {
|
||||
expect(chal.todos[0].notes).to.eql("Challenge Notes");
|
||||
return cb();
|
||||
}, function(cb) {
|
||||
return request.get(baseURL + "/challenges/" + challenge._id + "/member/" + user._id).end(function(err, res) {
|
||||
expect(res.body.todos[res.body.todos.length - 1].notes).to.equal("User overriden notes");
|
||||
return done();
|
||||
});
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
it("Complete To-Dos", function(done) {
|
||||
return User.findById(user._id, function(err, _user) {
|
||||
var numTasks, u;
|
||||
u = _user;
|
||||
numTasks = _.size(u.todos);
|
||||
return request.post(baseURL + "/user/tasks/" + u.todos[0].id + "/up").end(function(err, res) {
|
||||
return request.post(baseURL + "/user/tasks/clear-completed").end(function(err, res) {
|
||||
expect(_.size(res.body)).to.equal(numTasks - 1);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it("Challenge deleted, breaks task link", function(done) {
|
||||
var itThis;
|
||||
itThis = this;
|
||||
return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) {
|
||||
return User.findById(user._id, function(err, user) {
|
||||
var daily, len, unset;
|
||||
len = user.dailys.length - 1;
|
||||
daily = user.dailys[user.dailys.length - 1];
|
||||
expect(daily.challenge.broken).to.equal("CHALLENGE_DELETED");
|
||||
unset = {
|
||||
$unset: {}
|
||||
};
|
||||
unset["$unset"]["dailys." + len + ".challenge.broken"] = 1;
|
||||
return User.findByIdAndUpdate(user._id, unset, {
|
||||
"new": true
|
||||
}, function(err, user) {
|
||||
expect(err).to.not.exist;
|
||||
expect(user.dailys[len].challenge.broken).to.not.exist;
|
||||
return request.post(baseURL + "/user/tasks/" + daily.id + "/up").end(function(err, res) {
|
||||
return setTimeout((function() {
|
||||
return User.findById(user._id, function(err, user) {
|
||||
expect(user.dailys[len].challenge.broken).to.equal("CHALLENGE_DELETED");
|
||||
return done();
|
||||
});
|
||||
}), 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it("admin creates a challenge", function(done) {
|
||||
return User.findByIdAndUpdate(user._id, {
|
||||
$set: {
|
||||
"contributor.admin": true
|
||||
}
|
||||
}, {
|
||||
"new": true
|
||||
}, function(err, _user) {
|
||||
expect(err).to.not.exist;
|
||||
return async.parallel([
|
||||
function(cb) {
|
||||
return request.post(baseURL + "/challenges").send({
|
||||
group: group._id,
|
||||
dailys: [],
|
||||
todos: [],
|
||||
rewards: [],
|
||||
habits: [],
|
||||
official: false
|
||||
}).end(function(err, res) {
|
||||
expect(res.body.official).to.equal(false);
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return request.post(baseURL + "/challenges").send({
|
||||
group: group._id,
|
||||
dailys: [],
|
||||
todos: [],
|
||||
rewards: [],
|
||||
habits: [],
|
||||
official: true
|
||||
}).end(function(err, res) {
|
||||
expect(res.body.official).to.equal(true);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
});
|
||||
it("User creates a non-tavern challenge with prize, deletes it, gets refund", function(done) {
|
||||
return User.findByIdAndUpdate(user._id, {
|
||||
$set: {
|
||||
"balance": 8
|
||||
}
|
||||
}, {
|
||||
"new": true
|
||||
}, function(err, user) {
|
||||
expect(err).to.not.be.ok;
|
||||
return request.post(baseURL + "/challenges").send({
|
||||
group: group._id,
|
||||
dailys: [],
|
||||
todos: [],
|
||||
rewards: [],
|
||||
habits: [],
|
||||
prize: 10
|
||||
}).end(function(err, res) {
|
||||
expect(res.body.prize).to.equal(10);
|
||||
return async.parallel([
|
||||
function(cb) {
|
||||
return User.findById(user._id, cb);
|
||||
}, function(cb) {
|
||||
return Challenge.findById(res.body._id, cb);
|
||||
}
|
||||
], function(err, results) {
|
||||
user = results[0];
|
||||
challenge = results[1];
|
||||
expect(user.balance).to.equal(5.5);
|
||||
return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) {
|
||||
return User.findById(user._id, function(err, _user) {
|
||||
expect(_user.balance).to.equal(8);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it("User creates a tavern challenge with prize, deletes it, and does not get refund", function(done) {
|
||||
return User.findByIdAndUpdate(user._id, {
|
||||
$set: {
|
||||
"balance": 8
|
||||
}
|
||||
}, {
|
||||
"new": true
|
||||
}, function(err, user) {
|
||||
expect(err).to.not.be.ok;
|
||||
return request.post(baseURL + "/challenges").send({
|
||||
group: 'habitrpg',
|
||||
dailys: [],
|
||||
todos: [],
|
||||
rewards: [],
|
||||
habits: [],
|
||||
prize: 10
|
||||
}).end(function(err, res) {
|
||||
expect(res.body.prize).to.equal(10);
|
||||
return async.parallel([
|
||||
function(cb) {
|
||||
return User.findById(user._id, cb);
|
||||
}, function(cb) {
|
||||
return Challenge.findById(res.body._id, cb);
|
||||
}
|
||||
], function(err, results) {
|
||||
user = results[0];
|
||||
challenge = results[1];
|
||||
expect(user.balance).to.equal(5.5);
|
||||
return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) {
|
||||
return User.findById(user._id, function(err, _user) {
|
||||
expect(_user.balance).to.equal(5.5);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return describe("non-owner permissions", function() {
|
||||
challenge = void 0;
|
||||
beforeEach(function(done) {
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return request.post(baseURL + "/challenges").send({
|
||||
group: group._id,
|
||||
name: 'challenge name',
|
||||
dailys: [
|
||||
{
|
||||
type: "daily",
|
||||
text: "Challenge Daily"
|
||||
}
|
||||
]
|
||||
}).end(function(err, res) {
|
||||
challenge = res.body;
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return registerNewUser(done, true);
|
||||
}
|
||||
]);
|
||||
});
|
||||
context("non-owner", function() {
|
||||
it('can not edit challenge', function(done) {
|
||||
challenge.name = 'foobar';
|
||||
return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) {
|
||||
var error;
|
||||
error = res.body.err;
|
||||
expect(error).to.eql("You don't have permissions to edit this challenge");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
it('can not close challenge', function(done) {
|
||||
return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) {
|
||||
var error;
|
||||
error = res.body.err;
|
||||
expect(error).to.eql("You don't have permissions to close this challenge");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return it('can not delete challenge', function(done) {
|
||||
return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) {
|
||||
var error;
|
||||
error = res.body.err;
|
||||
expect(error).to.eql("You don't have permissions to delete this challenge");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
return context("non-owner that is an admin", function() {
|
||||
beforeEach(function(done) {
|
||||
return User.findByIdAndUpdate(user._id, {
|
||||
'contributor.admin': true
|
||||
}, {
|
||||
"new": true
|
||||
}, done);
|
||||
});
|
||||
it('can edit challenge', function(done) {
|
||||
challenge.name = 'foobar';
|
||||
return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) {
|
||||
expect(res.body.err).to.not.exist;
|
||||
return Challenge.findById(challenge._id, function(err, chal) {
|
||||
expect(chal.name).to.eql('foobar');
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('can close challenge', function(done) {
|
||||
return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) {
|
||||
expect(res.body.err).to.not.exist;
|
||||
return User.findById(user._id, function(err, usr) {
|
||||
expect(usr.achievements.challenges[0]).to.eql(challenge.name);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('can delete challenge', function(done) {
|
||||
return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) {
|
||||
expect(res.body.err).to.not.exist;
|
||||
return request.get(baseURL + "/challenges/" + challenge._id).end(function(err, res) {
|
||||
var error;
|
||||
error = res.body.err;
|
||||
expect(error).to.eql("Challenge " + challenge._id + " not found");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
71
test/api-legacy/chat.js
Normal file
71
test/api-legacy/chat.js
Normal file
@@ -0,0 +1,71 @@
|
||||
var Group, app, diff;
|
||||
|
||||
diff = require("deep-diff");
|
||||
|
||||
Group = require("../../website/server/models/group").model;
|
||||
|
||||
app = require("../../website/server/server");
|
||||
|
||||
describe("Chat", function() {
|
||||
var chat, group;
|
||||
group = void 0;
|
||||
before(function(done) {
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return registerNewUser(cb, true);
|
||||
}, function(user, cb) {
|
||||
return request.post(baseURL + "/groups").send({
|
||||
name: "TestGroup",
|
||||
type: "party"
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
group = res.body;
|
||||
expect(group.members.length).to.equal(1);
|
||||
expect(group.leader).to.equal(user._id);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
chat = void 0;
|
||||
return it("removes a user's chat notifications when user is kicked", function(done) {
|
||||
var userToRemove;
|
||||
userToRemove = null;
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return registerManyUsers(1, cb);
|
||||
}, function(members, cb) {
|
||||
userToRemove = members[0];
|
||||
return request.post(baseURL + "/groups/" + group._id + "/invite").send({
|
||||
uuids: [userToRemove._id]
|
||||
}).end(function() {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
var msg;
|
||||
msg = "TestMsg";
|
||||
return request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg).end(function(err, res) {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) {
|
||||
expect(res.body.newMessages[group._id]).to.exist;
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return request.post(baseURL + "/groups/" + group._id + "/removeMember?uuid=" + userToRemove._id).end(function(err, res) {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) {
|
||||
expect(res.body.newMessages[group._id]).to.not.exist;
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
});
|
||||
221
test/api-legacy/coupons.js
Normal file
221
test/api-legacy/coupons.js
Normal file
@@ -0,0 +1,221 @@
|
||||
var Coupon, app, makeSudoUser;
|
||||
|
||||
app = require("../../website/server/server");
|
||||
|
||||
Coupon = require("../../website/server/models/coupon").model;
|
||||
|
||||
makeSudoUser = function(usr, cb) {
|
||||
return registerNewUser(function() {
|
||||
var sudoUpdate;
|
||||
sudoUpdate = {
|
||||
"$set": {
|
||||
"contributor.sudo": true
|
||||
}
|
||||
};
|
||||
return User.findByIdAndUpdate(user._id, sudoUpdate, {
|
||||
"new": true
|
||||
}, function(err, _user) {
|
||||
usr = _user;
|
||||
return cb();
|
||||
});
|
||||
}, true);
|
||||
};
|
||||
|
||||
describe("Coupons", function() {
|
||||
var coupons;
|
||||
before(function(done) {
|
||||
return async.parallel([
|
||||
function(cb) {
|
||||
return mongoose.connection.collections['coupons'].drop(function(err) {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return mongoose.connection.collections['users'].drop(function(err) {
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
coupons = null;
|
||||
describe("POST /api/v2/coupons/generate/:event", function() {
|
||||
context("while sudo user", function() {
|
||||
before(function(done) {
|
||||
makeSudoUser(user, done);
|
||||
});
|
||||
|
||||
it("generates coupons", function(done) {
|
||||
var queries;
|
||||
queries = '?count=10';
|
||||
|
||||
request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
Coupon.find({
|
||||
event: 'wondercon'
|
||||
}, function(err, _coupons) {
|
||||
coupons = _coupons;
|
||||
expect(coupons.length).to.equal(10);
|
||||
_(coupons).each(function(c) {
|
||||
expect(c.event).to.equal('wondercon');
|
||||
}).value();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return context("while regular user", function() {
|
||||
before(function(done) {
|
||||
return registerNewUser(done, true);
|
||||
});
|
||||
return it("does not generate coupons", function(done) {
|
||||
var queries;
|
||||
queries = '?count=10';
|
||||
return request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) {
|
||||
expectCode(res, 401);
|
||||
expect(res.body.err).to.equal('You don\'t have admin access');
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("GET /api/v2/coupons", function() {
|
||||
context("while sudo user", function() {
|
||||
before(function(done) {
|
||||
return makeSudoUser(user, done);
|
||||
});
|
||||
it("gets coupons", function(done) {
|
||||
var queries;
|
||||
queries = '?_id=' + user._id + '&apiToken=' + user.apiToken;
|
||||
return request.get(baseURL + '/coupons' + queries).end(function(err, res) {
|
||||
var codes;
|
||||
expectCode(res, 200);
|
||||
codes = res.text;
|
||||
expect(codes).to.contain('code');
|
||||
_(coupons).each(function(c) {
|
||||
return expect(codes).to.contain(c._id);
|
||||
}).value();
|
||||
return done();
|
||||
});
|
||||
});
|
||||
it("gets first 5 coupons out of 10 when a limit of 5 is set", function(done) {
|
||||
var queries;
|
||||
queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&limit=5';
|
||||
return request.get(baseURL + '/coupons' + queries).end(function(err, res) {
|
||||
var codes, firstHalf, secondHalf, sortedCoupons;
|
||||
expectCode(res, 200);
|
||||
codes = res.text;
|
||||
sortedCoupons = _.sortBy(coupons, 'seq');
|
||||
firstHalf = sortedCoupons.slice(0, 5);
|
||||
secondHalf = sortedCoupons.slice(5, 10);
|
||||
_(firstHalf).each(function(c) {
|
||||
return expect(codes).to.contain(c._id);
|
||||
}).value();
|
||||
_(secondHalf).each(function(c) {
|
||||
return expect(codes).to.not.contain(c._id);
|
||||
}).value();
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return it("gets last 5 coupons out of 10 when a limit of 5 is set", function(done) {
|
||||
var queries;
|
||||
queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&skip=5';
|
||||
return request.get(baseURL + '/coupons' + queries).end(function(err, res) {
|
||||
var codes, firstHalf, secondHalf, sortedCoupons;
|
||||
expectCode(res, 200);
|
||||
codes = res.text;
|
||||
sortedCoupons = _.sortBy(coupons, 'seq');
|
||||
firstHalf = sortedCoupons.slice(0, 5);
|
||||
secondHalf = sortedCoupons.slice(5, 10);
|
||||
_(firstHalf).each(function(c) {
|
||||
return expect(codes).to.not.contain(c._id);
|
||||
}).value();
|
||||
_(secondHalf).each(function(c) {
|
||||
return expect(codes).to.contain(c._id);
|
||||
}).value();
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
return context("while regular user", function() {
|
||||
before(function(done) {
|
||||
return registerNewUser(done, true);
|
||||
});
|
||||
return it("does not get coupons", function(done) {
|
||||
var queries;
|
||||
queries = '?_id=' + user._id + '&apiToken=' + user.apiToken;
|
||||
return request.get(baseURL + '/coupons' + queries).end(function(err, res) {
|
||||
expectCode(res, 401);
|
||||
expect(res.body.err).to.equal('You don\'t have admin access');
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return describe("POST /api/v2/user/coupon/:code", function() {
|
||||
var specialGear;
|
||||
specialGear = function(gear, has) {
|
||||
var items;
|
||||
items = ['body_special_wondercon_gold', 'body_special_wondercon_black', 'body_special_wondercon_red', 'back_special_wondercon_red', 'back_special_wondercon_black', 'back_special_wondercon_red', 'eyewear_special_wondercon_black', 'eyewear_special_wondercon_red'];
|
||||
return _(items).each(function(i) {
|
||||
if (has) {
|
||||
return expect(gear[i]).to.exist;
|
||||
} else {
|
||||
return expect(gear[i]).to.not.exist;
|
||||
}
|
||||
}).value();
|
||||
};
|
||||
beforeEach(function(done) {
|
||||
return registerNewUser(function() {
|
||||
var gear;
|
||||
gear = user.items.gear.owned;
|
||||
specialGear(gear, false);
|
||||
return done();
|
||||
}, true);
|
||||
});
|
||||
context("unused coupon", function() {
|
||||
return it("applies coupon and awards equipment", function(done) {
|
||||
var code;
|
||||
code = coupons[0]._id;
|
||||
return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) {
|
||||
var gear;
|
||||
expectCode(res, 200);
|
||||
gear = res.body.items.gear.owned;
|
||||
specialGear(gear, true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
context("already used coupon", function() {
|
||||
return it("does not apply coupon and does not award equipment", function(done) {
|
||||
var code;
|
||||
code = coupons[0]._id;
|
||||
return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) {
|
||||
expectCode(res, 400);
|
||||
expect(res.body.err).to.equal("Coupon already used");
|
||||
return User.findById(user._id, function(err, _user) {
|
||||
var gear;
|
||||
gear = _user.items.gear.owned;
|
||||
specialGear(gear, false);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return context("invalid coupon", function() {
|
||||
return it("does not apply coupon and does not award equipment", function(done) {
|
||||
var code;
|
||||
code = "not-a-real-coupon";
|
||||
return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) {
|
||||
expectCode(res, 400);
|
||||
expect(res.body.err).to.equal("Invalid coupon code");
|
||||
return User.findById(user._id, function(err, _user) {
|
||||
var gear;
|
||||
gear = _user.items.gear.owned;
|
||||
specialGear(gear, false);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
367
test/api-legacy/inAppPurchases.js
Normal file
367
test/api-legacy/inAppPurchases.js
Normal file
@@ -0,0 +1,367 @@
|
||||
var app, iapMock, inApp, rewire, sinon;
|
||||
|
||||
app = require('../../website/server/server');
|
||||
|
||||
rewire = require('rewire');
|
||||
|
||||
sinon = require('sinon');
|
||||
|
||||
inApp = rewire('../../website/server/controllers/payments/iap');
|
||||
|
||||
iapMock = {};
|
||||
|
||||
inApp.__set__('iap', iapMock);
|
||||
|
||||
describe('In-App Purchases', function() {
|
||||
describe('Android', function() {
|
||||
var next, paymentSpy, req, res;
|
||||
req = {
|
||||
body: {
|
||||
transaction: {
|
||||
reciept: 'foo',
|
||||
signature: 'sig'
|
||||
}
|
||||
}
|
||||
};
|
||||
res = {
|
||||
locals: {
|
||||
user: {
|
||||
_id: 'user'
|
||||
}
|
||||
},
|
||||
json: sinon.spy()
|
||||
};
|
||||
next = function() {
|
||||
return true;
|
||||
};
|
||||
paymentSpy = sinon.spy();
|
||||
before(function() {
|
||||
return inApp.__set__('payments.buyGems', paymentSpy);
|
||||
});
|
||||
afterEach(function() {
|
||||
paymentSpy.reset();
|
||||
return res.json.reset();
|
||||
});
|
||||
context('successful app purchase', function() {
|
||||
before(function() {
|
||||
iapMock.setup = function(cb) {
|
||||
return cb(null);
|
||||
};
|
||||
iapMock.validate = function(iapGoogle, iapBodyReciept, cb) {
|
||||
return cb(null, true);
|
||||
};
|
||||
iapMock.isValidated = function(googleRes) {
|
||||
return googleRes;
|
||||
};
|
||||
return iapMock.GOOGLE = 'google';
|
||||
});
|
||||
it('calls res.json with succesful result object', function() {
|
||||
var expectedResObj;
|
||||
expectedResObj = {
|
||||
ok: true,
|
||||
data: true
|
||||
};
|
||||
inApp.androidVerify(req, res, next);
|
||||
expect(res.json).to.be.calledOnce;
|
||||
return expect(res.json).to.be.calledWith(expectedResObj);
|
||||
});
|
||||
return it('calls payments.buyGems function', function() {
|
||||
inApp.androidVerify(req, res, next);
|
||||
expect(paymentSpy).to.be.calledOnce;
|
||||
return expect(paymentSpy).to.be.calledWith({
|
||||
user: res.locals.user,
|
||||
paymentMethod: 'IAP GooglePlay',
|
||||
amount: 5.25
|
||||
});
|
||||
});
|
||||
});
|
||||
context('error in setup', function() {
|
||||
before(function() {
|
||||
return iapMock.setup = function(cb) {
|
||||
return cb("error in setup");
|
||||
};
|
||||
});
|
||||
it('calls res.json with setup error object', function() {
|
||||
var expectedResObj;
|
||||
expectedResObj = {
|
||||
ok: false,
|
||||
data: 'IAP Error'
|
||||
};
|
||||
inApp.androidVerify(req, res, next);
|
||||
expect(res.json).to.be.calledOnce;
|
||||
return expect(res.json).to.be.calledWith(expectedResObj);
|
||||
});
|
||||
return it('does not calls payments.buyGems function', function() {
|
||||
inApp.androidVerify(req, res, next);
|
||||
return expect(paymentSpy).to.not.be.called;
|
||||
});
|
||||
});
|
||||
context('error in validation', function() {
|
||||
before(function() {
|
||||
iapMock.setup = function(cb) {
|
||||
return cb(null);
|
||||
};
|
||||
return iapMock.validate = function(iapGoogle, iapBodyReciept, cb) {
|
||||
return cb('error in validation', true);
|
||||
};
|
||||
});
|
||||
it('calls res.json with validation error object', function() {
|
||||
var expectedResObj;
|
||||
expectedResObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: 6778001,
|
||||
message: 'error in validation'
|
||||
}
|
||||
};
|
||||
inApp.androidVerify(req, res, next);
|
||||
expect(res.json).to.be.calledOnce;
|
||||
return expect(res.json).to.be.calledWith(expectedResObj);
|
||||
});
|
||||
return it('does not calls payments.buyGems function', function() {
|
||||
inApp.androidVerify(req, res, next);
|
||||
return expect(paymentSpy).to.not.be.called;
|
||||
});
|
||||
});
|
||||
return context('iap is not valid', function() {
|
||||
before(function() {
|
||||
iapMock.setup = function(cb) {
|
||||
return cb(null);
|
||||
};
|
||||
iapMock.validate = function(iapGoogle, iapBodyReciept, cb) {
|
||||
return cb(null, false);
|
||||
};
|
||||
return iapMock.isValidated = function(googleRes) {
|
||||
return googleRes;
|
||||
};
|
||||
});
|
||||
it('does not call res.json', function() {
|
||||
inApp.androidVerify(req, res, next);
|
||||
return expect(res.json).to.not.be.called;
|
||||
});
|
||||
return it('does not calls payments.buyGems function', function() {
|
||||
inApp.androidVerify(req, res, next);
|
||||
return expect(paymentSpy).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
return describe('iOS', function() {
|
||||
var next, paymentSpy, req, res;
|
||||
req = {
|
||||
body: {
|
||||
transaction: {
|
||||
reciept: 'foo'
|
||||
}
|
||||
}
|
||||
};
|
||||
res = {
|
||||
locals: {
|
||||
user: {
|
||||
_id: 'user'
|
||||
}
|
||||
},
|
||||
json: sinon.spy()
|
||||
};
|
||||
next = function() {
|
||||
return true;
|
||||
};
|
||||
paymentSpy = sinon.spy();
|
||||
before(function() {
|
||||
return inApp.__set__('payments.buyGems', paymentSpy);
|
||||
});
|
||||
afterEach(function() {
|
||||
paymentSpy.reset();
|
||||
return res.json.reset();
|
||||
});
|
||||
context('successful app purchase', function() {
|
||||
before(function() {
|
||||
iapMock.setup = function(cb) {
|
||||
return cb(null);
|
||||
};
|
||||
iapMock.validate = function(iapApple, iapBodyReciept, cb) {
|
||||
return cb(null, true);
|
||||
};
|
||||
iapMock.isValidated = function(appleRes) {
|
||||
return appleRes;
|
||||
};
|
||||
iapMock.getPurchaseData = function(appleRes) {
|
||||
return [
|
||||
{
|
||||
productId: 'com.habitrpg.ios.Habitica.20gems'
|
||||
}
|
||||
];
|
||||
};
|
||||
return iapMock.APPLE = 'apple';
|
||||
});
|
||||
it('calls res.json with succesful result object', function() {
|
||||
var expectedResObj;
|
||||
expectedResObj = {
|
||||
ok: true,
|
||||
data: true
|
||||
};
|
||||
inApp.iosVerify(req, res, next);
|
||||
expect(res.json).to.be.calledOnce;
|
||||
return expect(res.json).to.be.calledWith(expectedResObj);
|
||||
});
|
||||
return it('calls payments.buyGems function', function() {
|
||||
inApp.iosVerify(req, res, next);
|
||||
expect(paymentSpy).to.be.calledOnce;
|
||||
return expect(paymentSpy).to.be.calledWith({
|
||||
user: res.locals.user,
|
||||
paymentMethod: 'IAP AppleStore',
|
||||
amount: 5.25
|
||||
});
|
||||
});
|
||||
});
|
||||
context('error in setup', function() {
|
||||
before(function() {
|
||||
return iapMock.setup = function(cb) {
|
||||
return cb("error in setup");
|
||||
};
|
||||
});
|
||||
it('calls res.json with setup error object', function() {
|
||||
var expectedResObj;
|
||||
expectedResObj = {
|
||||
ok: false,
|
||||
data: 'IAP Error'
|
||||
};
|
||||
inApp.iosVerify(req, res, next);
|
||||
expect(res.json).to.be.calledOnce;
|
||||
return expect(res.json).to.be.calledWith(expectedResObj);
|
||||
});
|
||||
return it('does not calls payments.buyGems function', function() {
|
||||
inApp.iosVerify(req, res, next);
|
||||
return expect(paymentSpy).to.not.be.called;
|
||||
});
|
||||
});
|
||||
context('error in validation', function() {
|
||||
before(function() {
|
||||
iapMock.setup = function(cb) {
|
||||
return cb(null);
|
||||
};
|
||||
return iapMock.validate = function(iapApple, iapBodyReciept, cb) {
|
||||
return cb('error in validation', true);
|
||||
};
|
||||
});
|
||||
it('calls res.json with validation error object', function() {
|
||||
var expectedResObj;
|
||||
expectedResObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: 6778001,
|
||||
message: 'error in validation'
|
||||
}
|
||||
};
|
||||
inApp.iosVerify(req, res, next);
|
||||
expect(res.json).to.be.calledOnce;
|
||||
return expect(res.json).to.be.calledWith(expectedResObj);
|
||||
});
|
||||
return it('does not calls payments.buyGems function', function() {
|
||||
inApp.iosVerify(req, res, next);
|
||||
return expect(paymentSpy).to.not.be.called;
|
||||
});
|
||||
});
|
||||
context('iap is not valid', function() {
|
||||
before(function() {
|
||||
iapMock.setup = function(cb) {
|
||||
return cb(null);
|
||||
};
|
||||
iapMock.validate = function(iapApple, iapBodyReciept, cb) {
|
||||
return cb(null, false);
|
||||
};
|
||||
return iapMock.isValidated = function(appleRes) {
|
||||
return appleRes;
|
||||
};
|
||||
});
|
||||
it('does not call res.json', function() {
|
||||
var expectedResObj;
|
||||
inApp.iosVerify(req, res, next);
|
||||
expectedResObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: 6778001,
|
||||
message: 'Invalid receipt'
|
||||
}
|
||||
};
|
||||
expect(res.json).to.be.calledOnce;
|
||||
return expect(res.json).to.be.calledWith(expectedResObj);
|
||||
});
|
||||
return it('does not calls payments.buyGems function', function() {
|
||||
inApp.iosVerify(req, res, next);
|
||||
return expect(paymentSpy).to.not.be.called;
|
||||
});
|
||||
});
|
||||
context('iap is valid but has no purchaseDataList', function() {
|
||||
before(function() {
|
||||
iapMock.setup = function(cb) {
|
||||
return cb(null);
|
||||
};
|
||||
iapMock.validate = function(iapApple, iapBodyReciept, cb) {
|
||||
return cb(null, true);
|
||||
};
|
||||
iapMock.isValidated = function(appleRes) {
|
||||
return appleRes;
|
||||
};
|
||||
iapMock.getPurchaseData = function(appleRes) {
|
||||
return [];
|
||||
};
|
||||
return iapMock.APPLE = 'apple';
|
||||
});
|
||||
it('calls res.json with succesful result object', function() {
|
||||
var expectedResObj;
|
||||
expectedResObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: 6778001,
|
||||
message: 'Incorrect receipt content'
|
||||
}
|
||||
};
|
||||
inApp.iosVerify(req, res, next);
|
||||
expect(res.json).to.be.calledOnce;
|
||||
return expect(res.json).to.be.calledWith(expectedResObj);
|
||||
});
|
||||
return it('does not calls payments.buyGems function', function() {
|
||||
inApp.iosVerify(req, res, next);
|
||||
return expect(paymentSpy).to.not.be.called;
|
||||
});
|
||||
});
|
||||
return context('iap is valid, has purchaseDataList, but productId does not match', function() {
|
||||
before(function() {
|
||||
iapMock.setup = function(cb) {
|
||||
return cb(null);
|
||||
};
|
||||
iapMock.validate = function(iapApple, iapBodyReciept, cb) {
|
||||
return cb(null, true);
|
||||
};
|
||||
iapMock.isValidated = function(appleRes) {
|
||||
return appleRes;
|
||||
};
|
||||
iapMock.getPurchaseData = function(appleRes) {
|
||||
return [
|
||||
{
|
||||
productId: 'com.another.company'
|
||||
}
|
||||
];
|
||||
};
|
||||
return iapMock.APPLE = 'apple';
|
||||
});
|
||||
it('calls res.json with incorrect reciept obj', function() {
|
||||
var expectedResObj;
|
||||
expectedResObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: 6778001,
|
||||
message: 'Incorrect receipt content'
|
||||
}
|
||||
};
|
||||
inApp.iosVerify(req, res, next);
|
||||
expect(res.json).to.be.calledOnce;
|
||||
return expect(res.json).to.be.calledWith(expectedResObj);
|
||||
});
|
||||
return it('does not calls payments.buyGems function', function() {
|
||||
inApp.iosVerify(req, res, next);
|
||||
return expect(paymentSpy).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
372
test/api-legacy/party.js
Normal file
372
test/api-legacy/party.js
Normal file
@@ -0,0 +1,372 @@
|
||||
var Group, app, diff;
|
||||
|
||||
diff = require("deep-diff");
|
||||
|
||||
Group = require("../../website/server/models/group").model;
|
||||
|
||||
app = require("../../website/server/server");
|
||||
|
||||
describe("Party", function() {
|
||||
return context("Quests", function() {
|
||||
var group, notParticipating, participating, party;
|
||||
party = void 0;
|
||||
group = void 0;
|
||||
participating = [];
|
||||
notParticipating = [];
|
||||
beforeEach(function(done) {
|
||||
Group.update({
|
||||
_id: "habitrpg"
|
||||
}, {
|
||||
$set: {
|
||||
quest: {
|
||||
key: "dilatory",
|
||||
active: true,
|
||||
progress: {
|
||||
hp: shared.content.quests.dilatory.boss.hp,
|
||||
rage: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}).exec();
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return registerNewUser(cb, true);
|
||||
}, function(user, cb) {
|
||||
return request.post(baseURL + "/groups").send({
|
||||
name: "TestGroup",
|
||||
type: "party"
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
group = res.body;
|
||||
expect(group.members.length).to.equal(1);
|
||||
expect(group.leader).to.equal(user._id);
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return request.post(baseURL + '/user/tasks').send({
|
||||
type: 'daily',
|
||||
text: 'daily one'
|
||||
}).end(function(err, res) {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return request.post(baseURL + '/user/tasks').send({
|
||||
type: 'daily',
|
||||
text: 'daily two'
|
||||
}).end(function(err, res) {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return User.findByIdAndUpdate(user._id, {
|
||||
$set: {
|
||||
"stats.lvl": 50
|
||||
}
|
||||
}, {
|
||||
"new": true
|
||||
}, function(err, _user) {
|
||||
return cb(null, _user);
|
||||
});
|
||||
}, function(_user, cb) {
|
||||
var user;
|
||||
user = _user;
|
||||
return request.post(baseURL + "/user/batch-update").send([
|
||||
{
|
||||
op: "score",
|
||||
params: {
|
||||
direction: "up",
|
||||
id: user.dailys[0].id
|
||||
}
|
||||
}, {
|
||||
op: "score",
|
||||
params: {
|
||||
direction: "up",
|
||||
id: user.dailys[0].id
|
||||
}
|
||||
}, {
|
||||
op: "update",
|
||||
body: {
|
||||
"stats.lvl": 50
|
||||
}
|
||||
}
|
||||
]).end(function(err, res) {
|
||||
user = res.body;
|
||||
expect(user.party.quest.progress.up).to.be.above(0);
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return registerManyUsers(3, cb);
|
||||
}, function(_party, cb) {
|
||||
var inviteURL;
|
||||
party = _party;
|
||||
inviteURL = baseURL + "/groups/" + group._id + "/invite";
|
||||
return async.parallel([
|
||||
function(cb2) {
|
||||
return request.post(inviteURL).send({
|
||||
uuids: [party[0]._id]
|
||||
}).end(function() {
|
||||
return cb2();
|
||||
});
|
||||
}, function(cb2) {
|
||||
return request.post(inviteURL).send({
|
||||
uuids: [party[1]._id]
|
||||
}).end(function() {
|
||||
return cb2();
|
||||
});
|
||||
}, function(cb2) {
|
||||
return request.post(inviteURL).send({
|
||||
uuids: [party[2]._id]
|
||||
}).end(function(err, res) {
|
||||
return cb2();
|
||||
});
|
||||
}
|
||||
], cb);
|
||||
}, function(results, cb) {
|
||||
var series;
|
||||
series = _.reduce(party, function(m, v, i) {
|
||||
m.push(function(cb2) {
|
||||
return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", party[i]._id).set("X-API-Key", party[i].apiToken).end(function() {
|
||||
return cb2();
|
||||
});
|
||||
});
|
||||
return m;
|
||||
}, []);
|
||||
return async.series(series, cb);
|
||||
}, function(whatever, cb) {
|
||||
return Group.findById(group._id, function(err, g) {
|
||||
group = g;
|
||||
expect(g.members.length).to.equal(4);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], function() {
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) {
|
||||
expectCode(res, 400);
|
||||
return User.findByIdAndUpdate(user._id, {
|
||||
$set: {
|
||||
"items.quests.vice3": 1
|
||||
}
|
||||
}, {
|
||||
"new": true
|
||||
}, cb);
|
||||
});
|
||||
}, function(_user, cb) {
|
||||
return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
return Group.findById(group._id, cb);
|
||||
});
|
||||
}, function(_group, cb) {
|
||||
expect(_group.quest.key).to.equal("vice3");
|
||||
expect(_group.quest.active).to.equal(false);
|
||||
return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[0]._id).set("X-API-Key", party[0].apiToken).end(function() {
|
||||
return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[1]._id).set("X-API-Key", party[1].apiToken).end(function(err, res) {
|
||||
return request.post(baseURL + "/groups/" + group._id + "/questReject").set("X-API-User", party[2]._id).set("X-API-Key", party[2].apiToken).end(function(err, res) {
|
||||
group = res.body;
|
||||
expect(group.quest.active).to.equal(true);
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
});
|
||||
}
|
||||
]);
|
||||
});
|
||||
it("Casts a spell", function(done) {
|
||||
var mp;
|
||||
mp = user.stats.mp;
|
||||
return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) {
|
||||
party[0] = res.body;
|
||||
return request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end(function(err, res) {
|
||||
return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) {
|
||||
var difference, member;
|
||||
member = res.body;
|
||||
expect(member.achievements.snowball).to.equal(1);
|
||||
expect(member.stats.buffs.snowball).to.exist;
|
||||
difference = diff(member, party[0]);
|
||||
expect(_.size(difference)).to.equal(2);
|
||||
return request.put(baseURL + "/user").send({
|
||||
"stats.lvl": 5
|
||||
}).end(function(err, res) {
|
||||
return request.put(baseURL + "/user").send({
|
||||
"stats.mp": 100
|
||||
}).end(function(err, res) {
|
||||
return request.post(baseURL + "/user/class/cast/valorousPresence?targetType=party").end(function(err, res) {
|
||||
return request.get(baseURL + "/members/" + member._id).end(function(err, res) {
|
||||
expect(res.body.stats.buffs.str).to.be.above(0);
|
||||
expect(diff(res.body, member).length).to.equal(1);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it("Doesn't include people who aren't participating", function(done) {
|
||||
return request.get(baseURL + "/groups/" + group._id).end(function(err, res) {
|
||||
expect(_.size(res.body.quest.members)).to.equal(3);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
it("allows quest participants to leave quest", function(done) {
|
||||
var leavingMember;
|
||||
leavingMember = party[1];
|
||||
expect(group.quest.members[leavingMember._id]).to.eql(true);
|
||||
return request.post(baseURL + "/groups/" + group._id + "/questLeave").set("X-API-User", leavingMember._id).set("X-API-Key", leavingMember.apiToken).end(function(err, res) {
|
||||
expectCode(res, 204);
|
||||
return request.get(baseURL + '/groups/party').end(function(err, res) {
|
||||
expect(res.body.quest.members[leavingMember._id]).to.not.be.ok;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
return xit("Hurts the boss", function(done) {
|
||||
return request.post(baseURL + "/user/batch-update").end(function(err, res) {
|
||||
var up, user;
|
||||
user = res.body;
|
||||
up = user.party.quest.progress.up;
|
||||
expect(up).to.be.above(0);
|
||||
return request.post(baseURL + "/user/batch-update").send([
|
||||
{
|
||||
op: "score",
|
||||
params: {
|
||||
direction: "up",
|
||||
id: user.dailys[0].id
|
||||
}
|
||||
}, {
|
||||
op: "update",
|
||||
body: {
|
||||
lastCron: moment().subtract(1, "days")
|
||||
}
|
||||
}
|
||||
]).end(function(err, res) {
|
||||
expect(res.body.party.quest.progress.up).to.be.above(up);
|
||||
return request.post(baseURL + "/user/batch-update").end(function() {
|
||||
return request.get(baseURL + "/groups/party").end(function(err, res) {
|
||||
return async.waterfall([
|
||||
function(cb) {
|
||||
return async.parallel([
|
||||
function(cb2) {
|
||||
return Group.findById("habitrpg", {
|
||||
quest: 1
|
||||
}, function(err, tavern) {
|
||||
expect(tavern.quest.progress.hp).to.be.below(shared.content.quests.dilatory.boss.hp);
|
||||
expect(tavern.quest.progress.rage).to.be.above(0);
|
||||
return cb2();
|
||||
});
|
||||
}, function(cb2) {
|
||||
var _party;
|
||||
expect(res.body.quest.progress.hp).to.be.below(shared.content.quests.vice3.boss.hp);
|
||||
_party = res.body.members;
|
||||
expect(_.find(_party, {
|
||||
_id: party[0]._id
|
||||
}).stats.hp).to.be.below(50);
|
||||
expect(_.find(_party, {
|
||||
_id: party[1]._id
|
||||
}).stats.hp).to.be.below(50);
|
||||
expect(_.find(_party, {
|
||||
_id: party[2]._id
|
||||
}).stats.hp).to.be(50);
|
||||
return cb2();
|
||||
}
|
||||
], cb);
|
||||
}, function(whatever, cb) {
|
||||
return async.waterfall([
|
||||
function(cb2) {
|
||||
expect(user.items.pets["MantisShrimp-Base"]).to.not.be.ok();
|
||||
return Group.update({
|
||||
_id: "habitrpg"
|
||||
}, {
|
||||
$set: {
|
||||
"quest.progress.hp": 0
|
||||
}
|
||||
}, cb2);
|
||||
}, function(arg1, arg2, cb2) {
|
||||
expect(user.items.gear.owned.weapon_special_2).to.not.be.ok();
|
||||
return Group.findByIdAndUpdate(group._id, {
|
||||
$set: {
|
||||
"quest.progress.hp": 0
|
||||
}
|
||||
}, {
|
||||
"new": true
|
||||
}, cb2);
|
||||
}
|
||||
], cb);
|
||||
}, function(_group, cb) {
|
||||
return request.post(baseURL + "/user/batch-update").send([
|
||||
{
|
||||
op: "score",
|
||||
params: {
|
||||
direction: "up",
|
||||
id: user.dailys[1].id
|
||||
}
|
||||
}, {
|
||||
op: "update",
|
||||
body: {
|
||||
lastCron: moment().subtract(1, "days")
|
||||
}
|
||||
}
|
||||
]).end(function() {
|
||||
return cb();
|
||||
});
|
||||
}, function(cb) {
|
||||
return request.post(baseURL + "/user/batch-update").end(function(err, res) {
|
||||
return cb(null, res.body);
|
||||
});
|
||||
}, function(_user, cb) {
|
||||
return User.findById(_user._id, cb);
|
||||
}, function(_user, cb) {
|
||||
user = _user;
|
||||
return Group.findById(group._id, cb);
|
||||
}, function(_group, cb) {
|
||||
var cummExp, cummGp;
|
||||
cummExp = shared.content.quests.vice3.drop.exp + shared.content.quests.dilatory.drop.exp;
|
||||
cummGp = shared.content.quests.vice3.drop.gp + shared.content.quests.dilatory.drop.gp;
|
||||
return async.parallel([
|
||||
function(cb2) {
|
||||
return Group.findById("habitrpg", function(err, tavern) {
|
||||
expect(_.isEmpty(tavern.get("quest"))).to.equal(true);
|
||||
expect(user.items.pets["MantisShrimp-Base"]).to.equal(5);
|
||||
expect(user.items.mounts["MantisShrimp-Base"]).to.equal(true);
|
||||
expect(user.items.eggs.Dragon).to.equal(2);
|
||||
expect(user.items.hatchingPotions.Shade).to.equal(2);
|
||||
return cb2();
|
||||
});
|
||||
}, function(cb2) {
|
||||
expect(_.isEmpty(_group.get("quest"))).to.equal(true);
|
||||
expect(user.items.gear.owned.weapon_special_2).to.equal(true);
|
||||
expect(user.items.eggs.Dragon).to.equal(2);
|
||||
expect(user.items.hatchingPotions.Shade).to.equal(2);
|
||||
return async.parallel([
|
||||
function(cb3) {
|
||||
return User.findById(party[0].id, function(err, mbr) {
|
||||
expect(mbr.items.gear.owned.weapon_special_2).to.equal(true);
|
||||
return cb3();
|
||||
});
|
||||
}, function(cb3) {
|
||||
return User.findById(party[1].id, function(err, mbr) {
|
||||
expect(mbr.items.gear.owned.weapon_special_2).to.equal(true);
|
||||
return cb3();
|
||||
});
|
||||
}, function(cb3) {
|
||||
return User.findById(party[2].id, function(err, mbr) {
|
||||
expect(mbr.items.gear.owned.weapon_special_2).to.not.be.ok();
|
||||
return cb3();
|
||||
});
|
||||
}
|
||||
], cb2);
|
||||
}
|
||||
], cb);
|
||||
}
|
||||
], done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
430
test/api-legacy/pushNotifications.js
Normal file
430
test/api-legacy/pushNotifications.js
Normal file
@@ -0,0 +1,430 @@
|
||||
var app, rewire, sinon;
|
||||
|
||||
app = require("../../website/server/server");
|
||||
|
||||
rewire = require('rewire');
|
||||
|
||||
sinon = require('sinon');
|
||||
|
||||
describe("Push-Notifications", function() {
|
||||
before(function(done) {
|
||||
return registerNewUser(done, true);
|
||||
});
|
||||
return describe("Events that send push notifications", function() {
|
||||
var pushSpy;
|
||||
pushSpy = {
|
||||
sendNotify: sinon.spy()
|
||||
};
|
||||
afterEach(function(done) {
|
||||
pushSpy.sendNotify.reset();
|
||||
return done();
|
||||
});
|
||||
context("Challenges", function() {
|
||||
var challengeMock, challenges, userMock;
|
||||
challenges = rewire("../../website/server/controllers/api-v2/challenges");
|
||||
challenges.__set__('pushNotify', pushSpy);
|
||||
challengeMock = {
|
||||
findById: function(arg, cb) {
|
||||
return cb(null, {
|
||||
leader: user._id,
|
||||
name: 'challenge-name'
|
||||
});
|
||||
}
|
||||
};
|
||||
userMock = {
|
||||
findById: function(arg, cb) {
|
||||
return cb(null, user);
|
||||
}
|
||||
};
|
||||
challenges.__set__('Challenge', challengeMock);
|
||||
challenges.__set__('User', userMock);
|
||||
challenges.__set__('closeChal', function() {
|
||||
return true;
|
||||
});
|
||||
beforeEach(function(done) {
|
||||
return registerNewUser(function() {
|
||||
user.preferences.emailNotifications.wonChallenge = false;
|
||||
user.save = function(cb) {
|
||||
return cb(null, user);
|
||||
};
|
||||
return done();
|
||||
}, true);
|
||||
});
|
||||
return it("sends a push notification when you win a challenge", function(done) {
|
||||
var req, res;
|
||||
req = {
|
||||
params: {
|
||||
cid: 'challenge-id'
|
||||
},
|
||||
query: {
|
||||
uid: 'user-id'
|
||||
}
|
||||
};
|
||||
res = {
|
||||
locals: {
|
||||
user: user
|
||||
}
|
||||
};
|
||||
challenges.selectWinner(req, res);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.have.been.calledOnce;
|
||||
expect(pushSpy.sendNotify).to.have.been.calledWith(user, 'You won a Challenge!', 'challenge-name');
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
context("Groups", function() {
|
||||
var groups, recipient;
|
||||
recipient = null;
|
||||
groups = rewire("../../website/server/controllers/api-v2/groups");
|
||||
groups.__set__('pushNotify', pushSpy);
|
||||
before(function(done) {
|
||||
return registerNewUser(function(err, _user) {
|
||||
var userMock;
|
||||
recipient = _user;
|
||||
recipient.invitations.guilds = [];
|
||||
recipient.save = function(cb) {
|
||||
return cb(null, recipient);
|
||||
};
|
||||
recipient.preferences.emailNotifications.invitedGuild = false;
|
||||
recipient.preferences.emailNotifications.invitedParty = false;
|
||||
recipient.preferences.emailNotifications.invitedQuest = false;
|
||||
userMock = {
|
||||
findById: function(arg, cb) {
|
||||
return cb(null, recipient);
|
||||
},
|
||||
find: function(arg, arg2, cb) {
|
||||
return cb(null, [recipient]);
|
||||
},
|
||||
update: function(arg, arg2) {
|
||||
return {
|
||||
exec: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
groups.__set__('User', userMock);
|
||||
return done();
|
||||
}, false);
|
||||
});
|
||||
it("sends a push notification when invited to a guild", function(done) {
|
||||
var group, req, res;
|
||||
group = {
|
||||
_id: 'guild-id',
|
||||
name: 'guild-name',
|
||||
type: 'guild',
|
||||
members: [user._id],
|
||||
invites: []
|
||||
};
|
||||
group.save = function(cb) {
|
||||
return cb(null, group);
|
||||
};
|
||||
req = {
|
||||
body: {
|
||||
uuids: [recipient._id]
|
||||
}
|
||||
};
|
||||
res = {
|
||||
locals: {
|
||||
group: group,
|
||||
user: user
|
||||
},
|
||||
json: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
groups.invite(req, res);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.have.been.calledOnce;
|
||||
expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Guild', group.name);
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
it("sends a push notification when invited to a party", function(done) {
|
||||
var group, req, res;
|
||||
group = {
|
||||
_id: 'party-id',
|
||||
name: 'party-name',
|
||||
type: 'party',
|
||||
members: [user._id],
|
||||
invites: []
|
||||
};
|
||||
group.save = function(cb) {
|
||||
return cb(null, group);
|
||||
};
|
||||
req = {
|
||||
body: {
|
||||
uuids: [recipient._id]
|
||||
}
|
||||
};
|
||||
res = {
|
||||
locals: {
|
||||
group: group,
|
||||
user: user
|
||||
},
|
||||
json: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
groups.invite(req, res);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.have.been.calledOnce;
|
||||
expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Party', group.name);
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
it("sends a push notification when invited to a quest", function(done) {
|
||||
var group, req, res;
|
||||
group = {
|
||||
_id: 'party-id',
|
||||
name: 'party-name',
|
||||
type: 'party',
|
||||
members: [user._id, recipient._id],
|
||||
invites: [],
|
||||
quest: {}
|
||||
};
|
||||
user.items.quests.hedgehog = 5;
|
||||
group.save = function(cb) {
|
||||
return cb(null, group);
|
||||
};
|
||||
group.markModified = function() {
|
||||
return true;
|
||||
};
|
||||
req = {
|
||||
body: {
|
||||
uuids: [recipient._id]
|
||||
},
|
||||
query: {
|
||||
key: 'hedgehog'
|
||||
}
|
||||
};
|
||||
res = {
|
||||
locals: {
|
||||
group: group,
|
||||
user: user
|
||||
},
|
||||
json: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
groups.questAccept(req, res);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.have.been.calledOnce;
|
||||
expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Quest Invitation', 'Invitation for the Quest The Hedgebeast');
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
return it("sends a push notification to participating members when quest starts", function(done) {
|
||||
var group, req, res, userMock;
|
||||
group = {
|
||||
_id: 'party-id',
|
||||
name: 'party-name',
|
||||
type: 'party',
|
||||
members: [user._id, recipient._id],
|
||||
invites: []
|
||||
};
|
||||
group.quest = {
|
||||
key: 'hedgehog',
|
||||
progress: {
|
||||
hp: 100
|
||||
},
|
||||
members: {}
|
||||
};
|
||||
group.quest.members[recipient._id] = true;
|
||||
group.save = function(cb) {
|
||||
return cb(null, group);
|
||||
};
|
||||
group.markModified = function() {
|
||||
return true;
|
||||
};
|
||||
req = {
|
||||
body: {
|
||||
uuids: [recipient._id]
|
||||
},
|
||||
query: {}
|
||||
};
|
||||
res = {
|
||||
locals: {
|
||||
group: group,
|
||||
user: user
|
||||
},
|
||||
json: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
userMock = {
|
||||
findOne: function(arg, arg2, cb) {
|
||||
return cb(null, recipient);
|
||||
},
|
||||
update: function(arg, arg2, cb) {
|
||||
if (cb) {
|
||||
return cb(null, user);
|
||||
} else {
|
||||
return {
|
||||
exec: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
groups.__set__('User', userMock);
|
||||
groups.__set__('populateQuery', function(arg, arg2, arg3) {
|
||||
return {
|
||||
exec: function() {
|
||||
return group.members;
|
||||
}
|
||||
};
|
||||
});
|
||||
groups.questAccept(req, res);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.have.been.calledTwice;
|
||||
expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'HabitRPG', 'Your Quest has Begun: The Hedgebeast');
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
return describe("Gifts", function() {
|
||||
var recipient;
|
||||
recipient = null;
|
||||
before(function(done) {
|
||||
return registerNewUser(function(err, _user) {
|
||||
recipient = _user;
|
||||
recipient.preferences.emailNotifications.giftedGems = false;
|
||||
user.balance = 4;
|
||||
user.save = function() {
|
||||
return true;
|
||||
};
|
||||
recipient.save = function() {
|
||||
return true;
|
||||
};
|
||||
return done();
|
||||
}, false);
|
||||
});
|
||||
context("sending gems from balance", function() {
|
||||
var members;
|
||||
members = rewire("../../website/server/controllers/api-v2/members");
|
||||
members.sendMessage = function() {
|
||||
return true;
|
||||
};
|
||||
members.__set__('pushNotify', pushSpy);
|
||||
members.__set__('fetchMember', function(id) {
|
||||
return function(cb) {
|
||||
return cb(null, recipient);
|
||||
};
|
||||
});
|
||||
return it("sends a push notification", function(done) {
|
||||
var req, res;
|
||||
req = {
|
||||
params: {
|
||||
uuid: "uuid"
|
||||
},
|
||||
body: {
|
||||
type: 'gems',
|
||||
gems: {
|
||||
amount: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
res = {
|
||||
locals: {
|
||||
user: user
|
||||
}
|
||||
};
|
||||
members.sendGift(req, res);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.have.been.calledOnce;
|
||||
expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Gems', '1 Gems - by ' + user.profile.name);
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
return describe("Purchases", function() {
|
||||
var membersMock, payments;
|
||||
payments = rewire("../../website/server/controllers/payments");
|
||||
payments.__set__('pushNotify', pushSpy);
|
||||
membersMock = {
|
||||
sendMessage: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
payments.__set__('members', membersMock);
|
||||
context("buying gems as a purchased gift", function() {
|
||||
it("sends a push notification", function(done) {
|
||||
var data;
|
||||
data = {
|
||||
user: user,
|
||||
gift: {
|
||||
member: recipient,
|
||||
gems: {
|
||||
amount: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
payments.buyGems(data);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.have.been.calledOnce;
|
||||
expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Gems', '1 Gems - by ' + user.profile.name);
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
return it("does not send a push notification if buying gems for self", function(done) {
|
||||
var data;
|
||||
data = {
|
||||
user: user,
|
||||
gift: {
|
||||
member: user,
|
||||
gems: {
|
||||
amount: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
payments.buyGems(data);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.not.have.been.called;
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
return context("sending a subscription as a purchased gift", function() {
|
||||
it("sends a push notification", function(done) {
|
||||
var data;
|
||||
data = {
|
||||
user: user,
|
||||
gift: {
|
||||
member: recipient,
|
||||
subscription: {
|
||||
key: 'basic_6mo'
|
||||
}
|
||||
}
|
||||
};
|
||||
payments.createSubscription(data);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.have.been.calledOnce;
|
||||
expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Subscription', '6 months - by ' + user.profile.name);
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
return it("does not send a push notification if buying subscription for self", function(done) {
|
||||
var data;
|
||||
data = {
|
||||
user: user,
|
||||
gift: {
|
||||
member: user,
|
||||
subscription: {
|
||||
key: 'basic_6mo'
|
||||
}
|
||||
}
|
||||
};
|
||||
payments.createSubscription(data);
|
||||
return setTimeout(function() {
|
||||
expect(pushSpy.sendNotify).to.not.have.been.called;
|
||||
return done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
142
test/api-legacy/score.js
Normal file
142
test/api-legacy/score.js
Normal file
@@ -0,0 +1,142 @@
|
||||
require("../../website/server/server");
|
||||
|
||||
describe("Score", function() {
|
||||
before(function(done) {
|
||||
return registerNewUser(done, true);
|
||||
});
|
||||
context("Todos that did not previously exist", function() {
|
||||
it("creates a completed a todo when using up url", function(done) {
|
||||
return request.post(baseURL + "/user/tasks/withUp/up").send({
|
||||
type: "todo",
|
||||
text: "withUp"
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
request.get(baseURL + "/user/tasks/withUp").end(function(err, res) {
|
||||
var upTodo;
|
||||
upTodo = res.body;
|
||||
return expect(upTodo.completed).to.equal(true);
|
||||
});
|
||||
return done();
|
||||
});
|
||||
});
|
||||
it("creates an uncompleted todo when using down url", function(done) {
|
||||
return request.post(baseURL + "/user/tasks/withDown/down").send({
|
||||
type: "todo",
|
||||
text: "withDown"
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
return request.get(baseURL + "/user/tasks/withDown").end(function(err, res) {
|
||||
var downTodo;
|
||||
downTodo = res.body;
|
||||
expect(downTodo.completed).to.equal(false);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it("creates a completed a todo overriding the complete parameter when using up url", function(done) {
|
||||
return request.post(baseURL + "/user/tasks/withUpWithComplete/up").send({
|
||||
type: "todo",
|
||||
text: "withUpWithComplete",
|
||||
completed: false
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
return request.get(baseURL + "/user/tasks/withUpWithComplete").end(function(err, res) {
|
||||
var upTodo;
|
||||
upTodo = res.body;
|
||||
expect(upTodo.completed).to.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
return it("Creates an uncompleted todo overriding the completed parameter when using down url", function(done) {
|
||||
return request.post(baseURL + "/user/tasks/withDownWithUncomplete/down").send({
|
||||
type: "todo",
|
||||
text: "withDownWithUncomplete",
|
||||
completed: true
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
return request.get(baseURL + "/user/tasks/withDownWithUncomplete").end(function(err, res) {
|
||||
var downTodo;
|
||||
downTodo = res.body;
|
||||
expect(downTodo.completed).to.equal(false);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
context("Todo that already exists", function() {
|
||||
it("It completes a todo when using up url", function(done) {
|
||||
return request.post(baseURL + "/user/tasks").send({
|
||||
type: "todo",
|
||||
text: "Sample Todo"
|
||||
}).end(function(err, res) {
|
||||
var unCompletedTodo;
|
||||
expectCode(res, 200);
|
||||
unCompletedTodo = res.body;
|
||||
expect(unCompletedTodo.completed).to.equal(false);
|
||||
return request.post(baseURL + "/user/tasks/" + unCompletedTodo._id + "/up").end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
return request.get(baseURL + "/user/tasks/" + unCompletedTodo._id).end(function(err, res) {
|
||||
unCompletedTodo = res.body;
|
||||
expect(unCompletedTodo.completed).to.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return it("It uncompletes a todo when using down url", function(done) {
|
||||
return request.post(baseURL + "/user/tasks").send({
|
||||
type: "todo",
|
||||
text: "Sample Todo",
|
||||
completed: true
|
||||
}).end(function(err, res) {
|
||||
var completedTodo;
|
||||
expectCode(res, 200);
|
||||
completedTodo = res.body;
|
||||
expect(completedTodo.completed).to.equal(true);
|
||||
return request.post(baseURL + "/user/tasks/" + completedTodo._id + "/down").end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
return request.get(baseURL + "/user/tasks/" + completedTodo._id).end(function(err, res) {
|
||||
completedTodo = res.body;
|
||||
expect(completedTodo.completed).to.equal(false);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it("Creates and scores up a habit when using up url", function(done) {
|
||||
var upHabit;
|
||||
upHabit = void 0;
|
||||
return request.post(baseURL + "/user/tasks/habitWithUp/up").send({
|
||||
type: "habit",
|
||||
text: "testTitle",
|
||||
completed: true
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
return request.get(baseURL + "/user/tasks/habitWithUp").end(function(err, res) {
|
||||
upHabit = res.body;
|
||||
expect(upHabit.value).to.be.at.least(1);
|
||||
expect(upHabit.type).to.equal("habit");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
return it("Creates and scores down a habit when using down url", function(done) {
|
||||
var downHabit;
|
||||
downHabit = void 0;
|
||||
return request.post(baseURL + "/user/tasks/habitWithDown/down").send({
|
||||
type: "habit",
|
||||
text: "testTitle",
|
||||
completed: true
|
||||
}).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
return request.get(baseURL + "/user/tasks/habitWithDown").end(function(err, res) {
|
||||
downHabit = res.body;
|
||||
expect(downHabit.value).to.have.at.most(-1);
|
||||
expect(downHabit.type).to.equal("habit");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
50
test/api-legacy/subscriptions.js
Normal file
50
test/api-legacy/subscriptions.js
Normal file
@@ -0,0 +1,50 @@
|
||||
var app, payments;
|
||||
|
||||
payments = require("../../website/server/controllers/payments");
|
||||
|
||||
app = require("../../website/server/server");
|
||||
|
||||
describe("Subscriptions", function() {
|
||||
before(function(done) {
|
||||
return registerNewUser(done, true);
|
||||
});
|
||||
return it("Handles unsubscription", function(done) {
|
||||
var cron;
|
||||
cron = function() {
|
||||
user.lastCron = moment().subtract(1, "d");
|
||||
return user.fns.cron();
|
||||
};
|
||||
expect(user.purchased.plan.customerId).to.not.exist;
|
||||
payments.createSubscription({
|
||||
user: user,
|
||||
customerId: "123",
|
||||
paymentMethod: "Stripe",
|
||||
sub: {
|
||||
key: 'basic_6mo'
|
||||
}
|
||||
});
|
||||
expect(user.purchased.plan.customerId).to.exist;
|
||||
shared.wrap(user);
|
||||
cron();
|
||||
expect(user.purchased.plan.customerId).to.exist;
|
||||
payments.cancelSubscription({
|
||||
user: user
|
||||
});
|
||||
cron();
|
||||
expect(user.purchased.plan.customerId).to.exist;
|
||||
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||
user.purchased.plan.dateTerminated = moment().subtract(2, "d");
|
||||
cron();
|
||||
expect(user.purchased.plan.customerId).to.not.exist;
|
||||
payments.createSubscription({
|
||||
user: user,
|
||||
customerId: "123",
|
||||
paymentMethod: "Stripe",
|
||||
sub: {
|
||||
key: 'basic_6mo'
|
||||
}
|
||||
});
|
||||
expect(user.purchased.plan.dateTerminated).to.not.exist;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
87
test/api-legacy/todos.js
Normal file
87
test/api-legacy/todos.js
Normal file
@@ -0,0 +1,87 @@
|
||||
require("../../website/server/server");
|
||||
|
||||
describe("Todos", function() {
|
||||
before(function(done) {
|
||||
return registerNewUser(done, true);
|
||||
});
|
||||
beforeEach(function(done) {
|
||||
return User.findById(user._id, function(err, _user) {
|
||||
var user;
|
||||
user = _user;
|
||||
shared.wrap(user);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return it("Archives old todos", function(done) {
|
||||
var numTasks;
|
||||
numTasks = _.size(user.todos);
|
||||
return request.post(baseURL + "/user/batch-update?_v=999").send([
|
||||
{
|
||||
op: "addTask",
|
||||
body: {
|
||||
type: "todo"
|
||||
}
|
||||
}, {
|
||||
op: "addTask",
|
||||
body: {
|
||||
type: "todo"
|
||||
}
|
||||
}, {
|
||||
op: "addTask",
|
||||
body: {
|
||||
type: "todo"
|
||||
}
|
||||
}
|
||||
]).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
expect(_.size(res.body.todos)).to.equal(numTasks + 3);
|
||||
numTasks += 3;
|
||||
return request.post(baseURL + "/user/batch-update?_v=998").send([
|
||||
{
|
||||
op: "score",
|
||||
params: {
|
||||
direction: "up",
|
||||
id: res.body.todos[0].id
|
||||
}
|
||||
}, {
|
||||
op: "score",
|
||||
params: {
|
||||
direction: "up",
|
||||
id: res.body.todos[1].id
|
||||
}
|
||||
}, {
|
||||
op: "score",
|
||||
params: {
|
||||
direction: "up",
|
||||
id: res.body.todos[2].id
|
||||
}
|
||||
}
|
||||
]).end(function(err, res) {
|
||||
expectCode(res, 200);
|
||||
expect(_.size(res.body.todos)).to.equal(numTasks);
|
||||
return request.post(baseURL + "/user/batch-update?_v=997").send([
|
||||
{
|
||||
op: "updateTask",
|
||||
params: {
|
||||
id: res.body.todos[0].id
|
||||
},
|
||||
body: {
|
||||
dateCompleted: moment().subtract(4, "days")
|
||||
}
|
||||
}, {
|
||||
op: "updateTask",
|
||||
params: {
|
||||
id: res.body.todos[1].id
|
||||
},
|
||||
body: {
|
||||
dateCompleted: moment().subtract(4, "days")
|
||||
}
|
||||
}
|
||||
]).end(function(err, res) {
|
||||
expect(_.size(res.body.todos)).to.equal(numTasks - 2);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
34
test/api/v2/challenges/GET-challenges_id.test.js
Normal file
34
test/api/v2/challenges/GET-challenges_id.test.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateChallenge,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /challenges/:id', () => {
|
||||
context('Member of a challenge', () => {
|
||||
let leader, party, challenge;
|
||||
|
||||
before(async () => {
|
||||
let {
|
||||
group,
|
||||
groupLeader,
|
||||
} = await createAndPopulateGroup();
|
||||
|
||||
party = group;
|
||||
leader = groupLeader;
|
||||
challenge = await generateChallenge(leader, party, {
|
||||
name: 'a created challenge',
|
||||
shortName: 'aCreatedChallenge',
|
||||
description: 'a description for the challenge',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the challenge object', async () => {
|
||||
let fetchedChallenge = await leader.get(`/challenges/${challenge._id}`);
|
||||
|
||||
expect(fetchedChallenge.name).to.eql(challenge.name);
|
||||
expect(fetchedChallenge.shortName).to.eql(challenge.shortName);
|
||||
expect(fetchedChallenge.description).to.eql(challenge.description);
|
||||
expect(fetchedChallenge.members).to.have.a.lengthOf(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
100
test/api/v2/groups/GET-groups.test.js
Normal file
100
test/api/v2/groups/GET-groups.test.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import {
|
||||
generateGroup,
|
||||
generateUser,
|
||||
resetHabiticaDB,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
import {
|
||||
TAVERN_ID,
|
||||
} from '../../../../website/server/models/group';
|
||||
|
||||
describe('GET /groups', () => {
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 3;
|
||||
|
||||
let user;
|
||||
let leader;
|
||||
|
||||
before(async () => {
|
||||
// Set up a world with a mixture of public and private guilds
|
||||
// Invite user to a few of them
|
||||
await resetHabiticaDB();
|
||||
|
||||
user = await generateUser();
|
||||
leader = await generateUser({ balance: 10 });
|
||||
|
||||
await generateGroup(leader, {
|
||||
name: 'public guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}, {
|
||||
members: [leader._id, user._id],
|
||||
});
|
||||
|
||||
await generateGroup(leader, {
|
||||
name: 'public guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
await generateGroup(leader, {
|
||||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
}, {
|
||||
members: [leader._id, user._id],
|
||||
});
|
||||
|
||||
await generateGroup(leader, {
|
||||
name: 'private guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
await generateGroup(leader, {
|
||||
name: 'party - is not member',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
await user.post('/groups', {
|
||||
name: 'party - is member',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
});
|
||||
});
|
||||
|
||||
context('no query passed in', () => {
|
||||
xit('lists all public guilds, the tavern, user\'s party, and any private guilds that user is a part of - TODO query includes duplicates - IE, tavern is included as tavern and part of public guilds. Refactor so this is not the case');
|
||||
});
|
||||
|
||||
context('tavern passed in as query', () => {
|
||||
it('returns only the tavern', async () => {
|
||||
await expect(user.get('/groups', null, {type: 'tavern'}))
|
||||
.to.eventually.have.a.lengthOf(1)
|
||||
.and.to.have.deep.property('[0]')
|
||||
.and.to.have.property('_id', TAVERN_ID);
|
||||
});
|
||||
});
|
||||
|
||||
context('party passed in as query', () => {
|
||||
it('returns only the user\'s party', async () => {
|
||||
await expect(user.get('/groups', null, {type: 'party'}))
|
||||
.to.eventually.have.a.lengthOf(1)
|
||||
.and.to.have.deep.property('[0]')
|
||||
.and.to.have.property('leader', user._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('public passed in as query', () => {
|
||||
it('returns all public guilds', async () => {
|
||||
await expect(user.get('/groups', null, {type: 'public'}))
|
||||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
|
||||
});
|
||||
});
|
||||
|
||||
context('guilds passed in as query', () => {
|
||||
it('returns all guilds user is a part of ', async () => {
|
||||
await expect(leader.get('/groups', null, {type: 'guilds'}))
|
||||
.to.eventually.have.a.lengthOf(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
322
test/api/v2/groups/GET-groups_id.test.js
Normal file
322
test/api/v2/groups/GET-groups_id.test.js
Normal file
@@ -0,0 +1,322 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
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, (groupDetails, groupType) => {
|
||||
context(`Member of a ${groupType}`, () => {
|
||||
let leader, member, createdGroup;
|
||||
|
||||
before(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
members: 30,
|
||||
groupDetails,
|
||||
});
|
||||
|
||||
leader = groupData.groupLeader;
|
||||
member = groupData.members[0];
|
||||
createdGroup = groupData.group;
|
||||
});
|
||||
|
||||
it('returns the group object', async () => {
|
||||
let group = await member.get(`/groups/${createdGroup._id}`);
|
||||
|
||||
expect(group._id).to.eql(createdGroup._id);
|
||||
expect(group.name).to.eql(createdGroup.name);
|
||||
expect(group.type).to.eql(createdGroup.type);
|
||||
expect(group.privacy).to.eql(createdGroup.privacy);
|
||||
});
|
||||
|
||||
it('transforms members array to an array of user objects', async () => {
|
||||
let group = await member.get(`/groups/${createdGroup._id}`);
|
||||
let someMember = group.members[0];
|
||||
|
||||
expect(someMember._id).to.exist;
|
||||
expect(someMember.profile.name).to.exist;
|
||||
expect(someMember.contributor).to.exist;
|
||||
expect(someMember.achievements).to.exist;
|
||||
expect(someMember.items).to.exist;
|
||||
});
|
||||
|
||||
it('transforms leader id to leader object', async () => {
|
||||
let group = await member.get(`/groups/${createdGroup._id}`);
|
||||
|
||||
expect(group.leader._id).to.eql(leader._id);
|
||||
expect(group.leader.profile.name).to.eql(leader.profile.name);
|
||||
expect(group.leader.items).to.exist;
|
||||
expect(group.leader.stats).to.exist;
|
||||
expect(group.leader.achievements).to.exist;
|
||||
expect(group.leader.contributor).to.exist;
|
||||
});
|
||||
|
||||
it('includes the user in the members list', async () => {
|
||||
let group = await member.get(`/groups/${createdGroup._id}`);
|
||||
let userInGroup = find(group.members, '_id', member._id);
|
||||
|
||||
expect(userInGroup).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('flagged messages', () => {
|
||||
let group;
|
||||
|
||||
let chat1 = {
|
||||
id: 'chat1',
|
||||
text: 'chat 1',
|
||||
flags: {},
|
||||
};
|
||||
|
||||
let chat2 = {
|
||||
id: 'chat2',
|
||||
text: 'chat 2',
|
||||
flags: {},
|
||||
flagCount: 0,
|
||||
};
|
||||
|
||||
let chat3 = {
|
||||
id: 'chat3',
|
||||
text: 'chat 3',
|
||||
flags: {
|
||||
'user-id': true,
|
||||
},
|
||||
flagCount: 1,
|
||||
};
|
||||
|
||||
let chat4 = {
|
||||
id: 'chat4',
|
||||
text: 'chat 4',
|
||||
flags: {
|
||||
'user-id': true,
|
||||
'other-user-id': true,
|
||||
},
|
||||
flagCount: 2,
|
||||
};
|
||||
|
||||
let chat5 = {
|
||||
id: 'chat5',
|
||||
text: 'chat 5',
|
||||
flags: {
|
||||
'user-id': true,
|
||||
'other-user-id': true,
|
||||
'yet-another-user-id': true,
|
||||
},
|
||||
flagCount: 3,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
chat: [
|
||||
chat1,
|
||||
chat2,
|
||||
chat3,
|
||||
chat4,
|
||||
chat5,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
group = groupData.group;
|
||||
});
|
||||
|
||||
context('non-admin', () => {
|
||||
let nonAdmin;
|
||||
|
||||
beforeEach(async () => {
|
||||
nonAdmin = await generateUser();
|
||||
});
|
||||
|
||||
it('does not include messages with a flag count of 2 or greater', async () => {
|
||||
let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`);
|
||||
|
||||
expect(fetchedGroup.chat).to.have.lengthOf(3);
|
||||
expect(fetchedGroup.chat[0].id).to.eql(chat1.id);
|
||||
expect(fetchedGroup.chat[1].id).to.eql(chat2.id);
|
||||
expect(fetchedGroup.chat[2].id).to.eql(chat3.id);
|
||||
});
|
||||
|
||||
it('does not include user ids in flags object', async () => {
|
||||
let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`);
|
||||
let chatWithOneFlag = fetchedGroup.chat[2];
|
||||
|
||||
expect(chatWithOneFlag.id).to.eql(chat3.id);
|
||||
expect(chat3.flags).to.eql({ 'user-id': true });
|
||||
expect(chatWithOneFlag.flags).to.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
context('admin', () => {
|
||||
let admin;
|
||||
|
||||
beforeEach(async () => {
|
||||
admin = await generateUser({
|
||||
'contributor.admin': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('includes all messages', async () => {
|
||||
let fetchedGroup = await admin.get(`/groups/${group._id}`);
|
||||
|
||||
expect(fetchedGroup.chat).to.have.lengthOf(5);
|
||||
expect(fetchedGroup.chat[0].id).to.eql(chat1.id);
|
||||
expect(fetchedGroup.chat[1].id).to.eql(chat2.id);
|
||||
expect(fetchedGroup.chat[2].id).to.eql(chat3.id);
|
||||
expect(fetchedGroup.chat[3].id).to.eql(chat4.id);
|
||||
expect(fetchedGroup.chat[4].id).to.eql(chat5.id);
|
||||
});
|
||||
|
||||
it('includes user ids in flags object', async () => {
|
||||
let fetchedGroup = await admin.get(`/groups/${group._id}`);
|
||||
let chatWithOneFlag = fetchedGroup.chat[2];
|
||||
|
||||
expect(chatWithOneFlag.id).to.eql(chat3.id);
|
||||
expect(chat3.flags).to.eql({ 'user-id': true });
|
||||
expect(chatWithOneFlag.flags).to.eql(chat3.flags);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-member of a public guild', () => {
|
||||
let nonMember, createdGroup;
|
||||
|
||||
before(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
nonMember = await generateUser();
|
||||
});
|
||||
|
||||
it('returns the group object for a non-member', async () => {
|
||||
let group = await nonMember.get(`/groups/${createdGroup._id}`);
|
||||
|
||||
expect(group._id).to.eql(createdGroup._id);
|
||||
expect(group.name).to.eql(createdGroup.name);
|
||||
expect(group.type).to.eql(createdGroup.type);
|
||||
expect(group.privacy).to.eql(createdGroup.privacy);
|
||||
});
|
||||
|
||||
it('does not include user in members list', async () => {
|
||||
let group = await nonMember.get(`/groups/${createdGroup._id}`);
|
||||
let userInGroup = find(group.members, '_id', nonMember._id);
|
||||
|
||||
expect(userInGroup).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
context('Private Guilds', () => {
|
||||
let nonMember, createdGroup;
|
||||
|
||||
before(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
nonMember = await generateUser();
|
||||
});
|
||||
|
||||
it('does not return the group object for a non-member', async () => {
|
||||
await expect(nonMember.get(`/groups/${createdGroup._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageGroupNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-member of a party', () => {
|
||||
let nonMember, createdGroup;
|
||||
|
||||
before(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
groupDetails: {
|
||||
name: 'test party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
nonMember = await generateUser();
|
||||
});
|
||||
|
||||
it('does not return the group object for a non-member', async () => {
|
||||
await expect(nonMember.get(`/groups/${createdGroup._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageGroupNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Member of a party', () => {
|
||||
let member, createdGroup;
|
||||
|
||||
before(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
groupDetails: {
|
||||
name: 'test party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
createdGroup = groupData.group;
|
||||
member = groupData.members[0];
|
||||
});
|
||||
|
||||
it('returns the user\'s party if an id of "party" is passed in', async () => {
|
||||
let group = await member.get('/groups/party');
|
||||
|
||||
expect(group._id).to.eql(createdGroup._id);
|
||||
expect(group.name).to.eql(createdGroup.name);
|
||||
expect(group.type).to.eql(createdGroup.type);
|
||||
expect(group.privacy).to.eql(createdGroup.privacy);
|
||||
});
|
||||
});
|
||||
|
||||
context('Non-existent group', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns error if group does not exist', async () => {
|
||||
await expect(user.get('/groups/group-that-does-not-exist'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageGroupNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
144
test/api/v2/groups/POST-groups.test.js
Normal file
144
test/api/v2/groups/POST-groups.test.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
generateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups', () => {
|
||||
context('All groups', () => {
|
||||
let leader;
|
||||
|
||||
beforeEach(async () => {
|
||||
leader = await generateUser();
|
||||
});
|
||||
|
||||
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?', async () => {
|
||||
return leader.post('/groups').then((group) => {
|
||||
expect(group._id).to.exist;
|
||||
expect(group.name).to.eql(`${leader.profile.name}'s group`);
|
||||
expect(group.type).to.eql('party');
|
||||
expect(group.privacy).to.eql('private');
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a group object', async () => {
|
||||
let group = await leader.post('/groups', {
|
||||
name: 'Test Group',
|
||||
type: 'party',
|
||||
leaderOnly: { challenges: true },
|
||||
description: 'Test Group Description',
|
||||
leaderMessage: 'Test Group Message',
|
||||
});
|
||||
|
||||
expect(group._id).to.exist;
|
||||
expect(group.leader).to.eql(leader._id);
|
||||
expect(group.name).to.eql(group.name);
|
||||
expect(group.description).to.eql(group.description);
|
||||
expect(group.leaderMessage).to.eql(group.leaderMessage);
|
||||
expect(group.leaderOnly).to.eql(group.leaderOnly);
|
||||
expect(group.memberCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('returns a populated members array', async () => {
|
||||
let party = await leader.post('/groups', {
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
let member = party.members[0];
|
||||
|
||||
expect(member._id).to.eql(leader._id);
|
||||
expect(member.profile).to.eql(leader.profile);
|
||||
expect(member.contributor).to.eql(leader.contributor);
|
||||
});
|
||||
});
|
||||
|
||||
context('Parties', () => {
|
||||
let leader;
|
||||
|
||||
beforeEach(async () => {
|
||||
leader = await generateUser();
|
||||
});
|
||||
|
||||
it('allows party creation without gems', async () => {
|
||||
let party = await leader.post('/groups', {
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
expect(party._id).to.exist;
|
||||
});
|
||||
|
||||
it('prevents party creation if user is already in party', async () => {
|
||||
await generateGroup(leader, {
|
||||
name: 'first party that user attempts to create',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
await expect(leader.post('/groups', { type: 'party' })).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
text: t('messageGroupAlreadyInParty'),
|
||||
});
|
||||
});
|
||||
|
||||
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?', async () => {
|
||||
return expect(leader.post('/groups', {
|
||||
type: 'party',
|
||||
privacy: 'public',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
text: 'Parties must be private',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Guilds', () => {
|
||||
let leader;
|
||||
|
||||
beforeEach(async () => {
|
||||
leader = await generateUser({
|
||||
balance: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents guild creation when user does not have enough gems', async () => {
|
||||
let userWithoutGems = await generateUser({
|
||||
balance: 0.75,
|
||||
});
|
||||
|
||||
await expect(userWithoutGems.post('/groups', { type: 'guild' })).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageInsufficientGems'),
|
||||
});
|
||||
});
|
||||
|
||||
it('can create a public guild', async () => {
|
||||
let guild = await leader.post('/groups', {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
expect(guild.leader).to.eql(leader._id);
|
||||
});
|
||||
|
||||
it('can create a private guild', async () => {
|
||||
let privateGuild = await leader.post('/groups', {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
expect(privateGuild.leader).to.eql(leader._id);
|
||||
});
|
||||
|
||||
it('deducts gems from user and adds them to guild bank', async () => {
|
||||
let guild = await leader.post('/groups', {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
expect(guild.balance).to.eql(1);
|
||||
|
||||
let updatedUser = await leader.get('/user');
|
||||
|
||||
expect(updatedUser.balance).to.eql(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
67
test/api/v2/groups/POST-groups_id.test.js
Normal file
67
test/api/v2/groups/POST-groups_id.test.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
generateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id', () => {
|
||||
context('user is not the leader of the group', () => {
|
||||
let user, otherUser, groupUserDoesNotOwn;
|
||||
|
||||
beforeEach(async () => {
|
||||
return Promise.all([
|
||||
generateUser({ balance: 10 }),
|
||||
generateUser({ balance: 10 }),
|
||||
]).then((users) => {
|
||||
user = users[0];
|
||||
otherUser = users[1];
|
||||
|
||||
return generateGroup(otherUser, {
|
||||
name: 'Group not Owned By User',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
members: [user, otherUser],
|
||||
});
|
||||
}).then((group) => {
|
||||
groupUserDoesNotOwn = group;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not allow user to update group', async () => {
|
||||
return expect(user.post(`/groups/${groupUserDoesNotOwn._id}`, {
|
||||
name: 'Change',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageGroupOnlyLeaderCanUpdate'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('user is the leader of the group', () => {
|
||||
let user, usersGroup;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
balance: 10,
|
||||
});
|
||||
|
||||
usersGroup = await generateGroup(user, {
|
||||
name: 'Original Group Title',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows user to update group', async () => {
|
||||
await user.post(`/groups/${usersGroup._id}`, {
|
||||
name: 'New Group Title',
|
||||
description: 'New group description',
|
||||
});
|
||||
|
||||
await usersGroup.sync();
|
||||
|
||||
expect(usersGroup.name).to.eql('New Group Title');
|
||||
expect(usersGroup.description).to.eql('New group description');
|
||||
});
|
||||
});
|
||||
});
|
||||
89
test/api/v2/groups/POST-groups_id_invite.test.js
Normal file
89
test/api/v2/groups/POST-groups_id_invite.test.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
import { each } from 'lodash';
|
||||
|
||||
describe('POST /groups/:id/invite', () => {
|
||||
context('user is a member of the group', () => {
|
||||
each({
|
||||
'public guild': {type: 'guild', privacy: 'public'},
|
||||
'private guild': {type: 'guild', privacy: 'private'},
|
||||
party: {type: 'party', privacy: 'private'},
|
||||
}, (groupDetails, groupType) => {
|
||||
let group, invitee, inviter;
|
||||
|
||||
beforeEach(async () => {
|
||||
invitee = await generateUser();
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
members: 1,
|
||||
});
|
||||
group = groupData.group;
|
||||
inviter = groupData.members[0];
|
||||
});
|
||||
|
||||
it(`allows user to send an invitation for a ${groupType}`, async () => {
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [invitee._id],
|
||||
});
|
||||
group = await inviter.get(`/groups/${group._id}`);
|
||||
expect(_.find(group.invites, {_id: invitee._id})._id).to.exists;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('user is a not member of the group', () => {
|
||||
each({
|
||||
'public guild': {type: 'guild', privacy: 'public'},
|
||||
}, (groupDetails, groupType) => {
|
||||
context(`the group is a ${groupType}`, () => {
|
||||
let group, invitee, inviter;
|
||||
|
||||
beforeEach(async () => {
|
||||
invitee = await generateUser();
|
||||
inviter = await generateUser();
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
});
|
||||
group = groupData.group;
|
||||
});
|
||||
|
||||
it(`allows user to send an invitation for a ${groupType}`, async () => {
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [invitee._id],
|
||||
});
|
||||
group = await inviter.get(`/groups/${group._id}`);
|
||||
expect(_.find(group.invites, {_id: invitee._id})._id).to.exists;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
each({
|
||||
'private guild': {type: 'guild', privacy: 'private'},
|
||||
party: {type: 'party', privacy: 'private'},
|
||||
}, (groupDetails, groupType) => {
|
||||
context(`the group is a ${groupType}`, () => {
|
||||
let group, invitee, inviter;
|
||||
|
||||
beforeEach(async () => {
|
||||
invitee = await generateUser();
|
||||
inviter = await generateUser();
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
});
|
||||
group = groupData.group;
|
||||
});
|
||||
|
||||
it(`does not allows user to send an invitation for a ${groupType}`, async () => {
|
||||
return expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [invitee._id],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: 'Only a member can invite new members!',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
110
test/api/v2/groups/POST-groups_id_join.test.js
Normal file
110
test/api/v2/groups/POST-groups_id_join.test.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
import { each } from 'lodash';
|
||||
|
||||
describe('POST /groups/:id/join', () => {
|
||||
context('user is already a member of the group', () => {
|
||||
it('returns an error');
|
||||
});
|
||||
|
||||
each({
|
||||
'public guild': {type: 'guild', privacy: 'public'},
|
||||
'private guild': {type: 'guild', privacy: 'private'},
|
||||
party: {type: 'party', privacy: 'private'},
|
||||
}, (groupDetails, groupType) => {
|
||||
context(`user has invitation to a ${groupType}`, () => {
|
||||
let group, invitee;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
invites: 1,
|
||||
});
|
||||
group = groupData.group;
|
||||
invitee = groupData.invitees[0];
|
||||
});
|
||||
|
||||
it(`allows user to join a ${groupType}`, async () => {
|
||||
await invitee.post(`/groups/${group._id}/join`);
|
||||
|
||||
group = await invitee.get(`/groups/${group._id}`);
|
||||
expect(_.find(group.members, {_id: invitee._id})._id).to.exists;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
each({
|
||||
'private guild': {type: 'guild', privacy: 'private'},
|
||||
party: {type: 'party', privacy: 'private'},
|
||||
}, (groupDetails, groupType) => {
|
||||
context(`user does not have an invitation to a ${groupType}`, () => {
|
||||
let group, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails,
|
||||
});
|
||||
group = groupData.group;
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it(`does not allow user to join a ${groupType}`, async () => {
|
||||
await expect(user.post(`/groups/${group._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageGroupRequiresInvite'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('user does not have an invitation to a public group', () => {
|
||||
let group, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
group = groupData.group;
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('allows user to join a public guild', async () => {
|
||||
await user.post(`/groups/${group._id}/join`);
|
||||
|
||||
group = await user.get(`/groups/${group._id}`);
|
||||
|
||||
expect(_.find(group.members, {_id: user._id})._id).to.exists;
|
||||
});
|
||||
});
|
||||
|
||||
context('public guild has no leader', () => {
|
||||
let user, group;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
group = groupData.group;
|
||||
await groupData.groupLeader.post(`/groups/${group._id}/leave`);
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('makes the joining user the leader', async () => {
|
||||
await user.post(`/groups/${group._id}/join`);
|
||||
|
||||
group = await user.get(`/groups/${group._id}`);
|
||||
|
||||
expect(group.leader._id).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
136
test/api/v2/groups/POST-groups_id_leave.test.js
Normal file
136
test/api/v2/groups/POST-groups_id_leave.test.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
checkExistence,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
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 user, group;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
members: 3,
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
user = groupData.members[0];
|
||||
group = groupData.group;
|
||||
});
|
||||
|
||||
it('leaves the group', async () => {
|
||||
await user.post(`/groups/${group._id}/leave`);
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.guilds).to.not.include(group._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('user is the last member of a public guild', () => {
|
||||
let user, group;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
user = groupData.groupLeader;
|
||||
group = groupData.group;
|
||||
});
|
||||
|
||||
it('leaves the group accessible', async () => {
|
||||
await user.post(`/groups/${group._id}/leave`);
|
||||
|
||||
await expect(user.get(`/groups/${group._id}`)).to.eventually.have.property('_id', group._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('user is the last member of a private group', () => {
|
||||
let user, group;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
user = groupData.groupLeader;
|
||||
group = groupData.group;
|
||||
});
|
||||
|
||||
it('group is deleted', async () => {
|
||||
await user.post(`/groups/${group._id}/leave`);
|
||||
|
||||
await expect(checkExistence('groups', group._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('user is the last member of a private group with pending invites', () => {
|
||||
let user, invitee1, invitee2, group;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
invites: 2,
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
user = groupData.groupLeader;
|
||||
group = groupData.group;
|
||||
invitee1 = groupData.invitees[0];
|
||||
invitee2 = groupData.invitees[1];
|
||||
});
|
||||
|
||||
it('deletes the group invitations from users', async () => {
|
||||
await user.post(`/groups/${group._id}/leave`);
|
||||
|
||||
await expect(invitee1.get('/user')).to.eventually.have.deep.property('invitations.guilds').and.to.be.empty;
|
||||
await expect(invitee2.get('/user')).to.eventually.have.deep.property('invitations.guilds').and.to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
context('user is the last member of a party with pending invites', () => {
|
||||
let user, invitee1, invitee2, group;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
invites: 2,
|
||||
groupDetails: {
|
||||
name: 'test guild',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
user = groupData.groupLeader;
|
||||
group = groupData.group;
|
||||
invitee1 = groupData.invitees[0];
|
||||
invitee2 = groupData.invitees[1];
|
||||
});
|
||||
|
||||
it('deletes the group invitations from users', async () => {
|
||||
await user.post(`/groups/${group._id}/leave`);
|
||||
|
||||
await expect(invitee1.get('/user')).to.eventually.have.deep.property('invitations.party').and.to.be.empty;
|
||||
await expect(invitee2.get('/user')).to.eventually.have.deep.property('invitations.party').and.to.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
53
test/api/v2/groups/POST-groups_id_removeMember.test.js
Normal file
53
test/api/v2/groups/POST-groups_id_removeMember.test.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
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 leader, member, group;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
members: 1,
|
||||
groupDetails: {
|
||||
name: 'test group',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
}).then((res) => {
|
||||
leader = res.groupLeader;
|
||||
member = res.members[0];
|
||||
group = res.group;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not allow leader to remove themselves', async () => {
|
||||
return expect(leader.post(`/groups/${group._id}/removeMember`, null, {
|
||||
uuid: leader._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageGroupCannotRemoveSelf'),
|
||||
});
|
||||
});
|
||||
|
||||
it('can remove other members of guild', async () => {
|
||||
return leader.post(`/groups/${group._id}/removeMember`, null, {
|
||||
uuid: member._id,
|
||||
}).then(() => {
|
||||
return leader.get(`/groups/${group._id}`);
|
||||
}).then((guild) => {
|
||||
expect(guild.members).to.have.a.lengthOf(1);
|
||||
expect(guild.members[0]._id).to.not.eql(member._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
39
test/api/v2/groups/chat/DELETE-groups_id_chat.test.js
Normal file
39
test/api/v2/groups/chat/DELETE-groups_id_chat.test.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('DELETE /groups/:id/chat', () => {
|
||||
let group, message, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.groupLeader;
|
||||
|
||||
return user.post(`/groups/${group._id}/chat`, null, { message: 'Some message' });
|
||||
}).then((res) => {
|
||||
message = res.message;
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes a message', async () => {
|
||||
return user.del(`/groups/${group._id}/chat/${message.id}`).then(() => {
|
||||
return user.get(`/groups/${group._id}/chat/`);
|
||||
}).then((messages) => {
|
||||
expect(messages).to.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error is message does not exist', async () => {
|
||||
return expect(user.del(`/groups/${group._id}/chat/some-fake-id`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageGroupChatNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
38
test/api/v2/groups/chat/GET-groups_id_chat.test.js
Normal file
38
test/api/v2/groups/chat/GET-groups_id_chat.test.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /groups/:id/chat', () => {
|
||||
context('group with multiple messages', () => {
|
||||
let group, member, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
group = groupData.group;
|
||||
user = groupData.groupLeader;
|
||||
member = groupData.members[0];
|
||||
|
||||
await member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' });
|
||||
await user.post(`/groups/${group._id}/chat`, null, { message: 'User message' });
|
||||
});
|
||||
|
||||
it('gets messages', async () => {
|
||||
let messages = await user.get(`/groups/${group._id}/chat`);
|
||||
|
||||
expect(messages).to.have.length(2);
|
||||
|
||||
let message = messages[0];
|
||||
|
||||
expect(message.id).to.exist;
|
||||
expect(message.text).to.exist;
|
||||
expect(message.uuid).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
42
test/api/v2/groups/chat/POST-groups_id_chat.test.js
Normal file
42
test/api/v2/groups/chat/POST-groups_id_chat.test.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/chat', () => {
|
||||
let group, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.groupLeader;
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a chat message', async () => {
|
||||
return user.post(`/groups/${group._id}/chat`, null, {
|
||||
message: 'Test Message',
|
||||
}).then((res) => {
|
||||
let message = res.message;
|
||||
|
||||
expect(message.id).to.exist;
|
||||
expect(message.timestamp).to.exist;
|
||||
expect(message.text).to.eql('Test Message');
|
||||
expect(message.uuid).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not post an empty message', async () => {
|
||||
return expect(user.post(`/groups/${group._id}/chat`, null, {
|
||||
message: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
text: t('messageGroupChatBlankMessage'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
let guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
let { group } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
members: 1,
|
||||
chat: [{
|
||||
id: 'message-to-clear',
|
||||
flagCount: 1,
|
||||
flags: { 'some-id': true },
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
guild = group;
|
||||
});
|
||||
|
||||
context('non admin', () => {
|
||||
let nonadmin;
|
||||
|
||||
beforeEach(async () => {
|
||||
nonadmin = await generateUser();
|
||||
});
|
||||
|
||||
it('cannot clear flags', async () => {
|
||||
return expect(nonadmin.post(`/groups/${guild._id}/chat/message-to-clear/clearflags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageGroupChatAdminClearFlagCount'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('admin', () => {
|
||||
let admin;
|
||||
|
||||
beforeEach(async () => {
|
||||
return generateUser({
|
||||
'contributor.admin': true,
|
||||
}).then((user) => {
|
||||
admin = user;
|
||||
});
|
||||
});
|
||||
|
||||
it('clears flags', async () => {
|
||||
return admin.post(`/groups/${guild._id}/chat/message-to-clear/clearflags`).then(() => {
|
||||
return admin.get(`/groups/${guild._id}/chat`);
|
||||
}).then((messages) => {
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('leaves old flags on the flag object', async () => {
|
||||
return admin.post(`/groups/${guild._id}/chat/message-to-clear/clearflags`).then(() => {
|
||||
return admin.get(`/groups/${guild._id}/chat`);
|
||||
}).then((messages) => {
|
||||
expect(messages[0].flags).to.have.property('some-id', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error if message does not exist', async () => {
|
||||
return expect(admin.post(`/groups/${guild._id}/chat/non-existant-message/clearflags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageGroupChatNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('admin user, group with multiple messages', () => {
|
||||
let admin, author, groupWithMessages;
|
||||
|
||||
beforeEach(async () => {
|
||||
author = await generateUser();
|
||||
admin = await generateUser({
|
||||
'contributor.admin': true,
|
||||
});
|
||||
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
chat: [
|
||||
{ id: 'message-to-unflag', uuid: author._id, flagCount: 1, flags: {'some-user': true} },
|
||||
{ id: '1-flag-message', uuid: author._id, flagCount: 1, flags: { id1: true } },
|
||||
{ id: '2-flag-message', uuid: author._id, flagCount: 2, flags: { id1: true, id2: true } },
|
||||
{ id: 'no-flags', uuid: author._id, flagCount: 0, flags: {} },
|
||||
],
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
groupWithMessages = groupData.group;
|
||||
});
|
||||
|
||||
it('changes only the message that is flagged', async () => {
|
||||
return admin.post(`/groups/${groupWithMessages._id}/chat/message-to-unflag/clearflags`).then(() => {
|
||||
return admin.get(`/groups/${groupWithMessages._id}/chat`);
|
||||
}).then((messages) => {
|
||||
expect(messages).to.have.lengthOf(4);
|
||||
|
||||
let messageThatWasUnflagged = messages[0];
|
||||
let messageWith1Flag = messages[1];
|
||||
let messageWith2Flag = messages[2];
|
||||
let messageWithoutFlags = messages[3];
|
||||
|
||||
expect(messageThatWasUnflagged.flagCount).to.eql(0);
|
||||
expect(messageThatWasUnflagged.flags).to.have.property('some-user', true);
|
||||
|
||||
expect(messageWith1Flag.flagCount).to.eql(1);
|
||||
expect(messageWith1Flag.flags).to.have.property('id1', true);
|
||||
|
||||
expect(messageWith2Flag.flagCount).to.eql(2);
|
||||
expect(messageWith2Flag.flags).to.have.property('id1', true);
|
||||
|
||||
expect(messageWithoutFlags.flagCount).to.eql(0);
|
||||
expect(messageWithoutFlags.flags).to.eql({});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
186
test/api/v2/groups/chat/POST-groups_id_chat_id_flag.test.js
Normal file
186
test/api/v2/groups/chat/POST-groups_id_chat_id_flag.test.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/chat/:id/flag', () => {
|
||||
context('another member\'s message', () => {
|
||||
let group, member, message, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.groupLeader;
|
||||
member = res.members[0];
|
||||
|
||||
return member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' });
|
||||
}).then((res) => {
|
||||
message = res.message;
|
||||
});
|
||||
});
|
||||
|
||||
it('flags message', async () => {
|
||||
return user.post(`/groups/${group._id}/chat/${message.id}/flag`).then(() => {
|
||||
return user.get(`/groups/${group._id}/chat`);
|
||||
}).then((messages) => {
|
||||
expect(messages[0].flagCount).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot flag the same message twice', async () => {
|
||||
return expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`).then(() => {
|
||||
return user.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageGroupChatFlagAlreadyReported'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('own message', () => {
|
||||
let group, message, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
members: 1,
|
||||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.groupLeader;
|
||||
|
||||
return user.post(`/groups/${group._id}/chat`, null, { message: 'User\'s own message' });
|
||||
}).then((res) => {
|
||||
message = res.message;
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot flag message', async () => {
|
||||
return expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageGroupChatFlagOwnMessage'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('nonexistant message', () => {
|
||||
let group, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.groupLeader;
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error', async () => {
|
||||
return expect(user.post(`/groups/${group._id}/chat/non-existant-message/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageGroupChatNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('group with multiple messages', () => {
|
||||
let admin, author, group, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
author = await generateUser();
|
||||
admin = await generateUser({
|
||||
'contributor.admin': true,
|
||||
});
|
||||
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
chat: [
|
||||
{ id: 'message-to-be-flagged', uuid: author._id, flagCount: 0, flags: {} },
|
||||
{ id: '1-flag-message', uuid: author._id, flagCount: 1, flags: { id1: true } },
|
||||
{ id: '2-flag-message', uuid: author._id, flagCount: 2, flags: { id1: true, id2: true } },
|
||||
{ id: 'no-flags', uuid: author._id, flagCount: 0, flags: {} },
|
||||
],
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
group = groupData.group;
|
||||
user = groupData.groupLeader;
|
||||
});
|
||||
|
||||
it('changes only the message that is flagged', async () => {
|
||||
return user.post(`/groups/${group._id}/chat/message-to-be-flagged/flag`).then(() => {
|
||||
return admin.get(`/groups/${group._id}/chat`);
|
||||
}).then((messages) => {
|
||||
expect(messages).to.have.lengthOf(4);
|
||||
|
||||
let messageThatWasFlagged = messages[0];
|
||||
let messageWith1Flag = messages[1];
|
||||
let messageWith2Flag = messages[2];
|
||||
let messageWithoutFlags = messages[3];
|
||||
|
||||
expect(messageThatWasFlagged.flagCount).to.eql(1);
|
||||
expect(messageThatWasFlagged.flags).to.have.property(user._id, true);
|
||||
|
||||
expect(messageWith1Flag.flagCount).to.eql(1);
|
||||
expect(messageWith1Flag.flags).to.have.property('id1', true);
|
||||
|
||||
expect(messageWith2Flag.flagCount).to.eql(2);
|
||||
expect(messageWith2Flag.flags).to.have.property('id1', true);
|
||||
|
||||
expect(messageWithoutFlags.flagCount).to.eql(0);
|
||||
expect(messageWithoutFlags.flags).to.eql({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('admin flagging a message', () => {
|
||||
let group, member, message, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
leaderDetails: {
|
||||
'contributor.admin': true,
|
||||
balance: 10,
|
||||
},
|
||||
members: 1,
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.groupLeader;
|
||||
member = res.members[0];
|
||||
|
||||
return member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' });
|
||||
}).then((res) => {
|
||||
message = res.message;
|
||||
});
|
||||
});
|
||||
|
||||
it('sets flagCount to 5', async () => {
|
||||
return user.post(`/groups/${group._id}/chat/${message.id}/flag`).then(() => {
|
||||
return user.get(`/groups/${group._id}/chat`);
|
||||
}).then((messages) => {
|
||||
expect(messages[0].flagCount).to.eql(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
149
test/api/v2/groups/chat/POST-groups_id_chat_id_like.test.js
Normal file
149
test/api/v2/groups/chat/POST-groups_id_chat_id_like.test.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/chat/:id/like', () => {
|
||||
context('another member\'s message', () => {
|
||||
let group, member, message, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.groupLeader;
|
||||
member = res.members[0];
|
||||
|
||||
return member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' });
|
||||
}).then((res) => {
|
||||
message = res.message;
|
||||
});
|
||||
});
|
||||
|
||||
it('likes message', async () => {
|
||||
return user.post(`/groups/${group._id}/chat/${message.id}/like`).then((messages) => {
|
||||
expect(messages[0].likes[user._id]).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the message object', async () => {
|
||||
return user.post(`/groups/${group._id}/chat/${message.id}/like`).then((messages) => {
|
||||
expect(messages[0].text).to.eql('Group member message');
|
||||
expect(messages[0].uuid).to.eql(member._id);
|
||||
expect(messages[0].user).to.eql(member.profile.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('own message', () => {
|
||||
let group, message, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
members: 1,
|
||||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.groupLeader;
|
||||
|
||||
return user.post(`/groups/${group._id}/chat`, null, { message: 'User\'s own message' });
|
||||
}).then((res) => {
|
||||
message = res.message;
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot like message', async () => {
|
||||
return expect(user.post(`/groups/${group._id}/chat/${message.id}/like`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageGroupChatLikeOwnMessage'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('group with multiple messages', () => {
|
||||
let admin, author, group, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
author = await generateUser();
|
||||
admin = await generateUser({
|
||||
'contributor.admin': true,
|
||||
});
|
||||
|
||||
let groupData = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
chat: [
|
||||
{ id: 'message-to-be-liked', likes: {}, uuid: author._id, flagCount: 0, flags: {} },
|
||||
{ id: '1-like-message', likes: { id: true }, uuid: author._id, flagCount: 1, flags: { id1: true } },
|
||||
{ id: '2-like-message', likes: { id: true, id2: true }, uuid: author._id, flagCount: 2, flags: { id1: true, id2: true } },
|
||||
{ id: 'no-likes', likes: {}, uuid: author._id, flagCount: 0, flags: {} },
|
||||
],
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
group = groupData.group;
|
||||
user = groupData.groupLeader;
|
||||
});
|
||||
|
||||
it('changes only the message that is liked', async () => {
|
||||
return user.post(`/groups/${group._id}/chat/message-to-be-liked/like`).then(() => {
|
||||
return admin.get(`/groups/${group._id}/chat`);
|
||||
}).then((messages) => {
|
||||
expect(messages).to.have.lengthOf(4);
|
||||
|
||||
let messageThatWasLiked = messages[0];
|
||||
let messageWith1Like = messages[1];
|
||||
let messageWith2Like = messages[2];
|
||||
let messageWithoutLike = messages[3];
|
||||
|
||||
expect(messageThatWasLiked.likes).to.have.property(user._id, true);
|
||||
|
||||
expect(messageWith1Like.flagCount).to.eql(1);
|
||||
expect(messageWith1Like.flags).to.have.property('id1', true);
|
||||
|
||||
expect(messageWith2Like.flagCount).to.eql(2);
|
||||
expect(messageWith2Like.flags).to.have.property('id1', true);
|
||||
expect(messageWith2Like.flags).to.have.property('id2', true);
|
||||
|
||||
expect(messageWithoutLike.flagCount).to.eql(0);
|
||||
expect(messageWithoutLike.flags).to.eql({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('nonexistant message', () => {
|
||||
let group, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.groupLeader;
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error', async () => {
|
||||
return expect(user.post(`/groups/${group._id}/chat/non-existant-message/like`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageGroupChatNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
61
test/api/v2/members/POST-members_id_gift.test.js
Normal file
61
test/api/v2/members/POST-members_id_gift.test.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /members/id/gift', () => {
|
||||
let userWithBalance, userWithoutBalance;
|
||||
|
||||
beforeEach(async () => {
|
||||
userWithBalance = await generateUser({ balance: 10 });
|
||||
userWithoutBalance = await generateUser({ balance: 0 });
|
||||
});
|
||||
|
||||
context('send gems from balance', () => {
|
||||
it('subtracts gems from sender\'s balance and adds it to recipient\'s balance', async () => {
|
||||
await userWithBalance.post(`/members/${userWithoutBalance._id}/gift`, {
|
||||
type: 'gems',
|
||||
gems: {
|
||||
amount: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
userWithoutBalance.sync(),
|
||||
userWithBalance.sync(),
|
||||
]);
|
||||
|
||||
expect(userWithBalance.balance).to.eql(9.75);
|
||||
expect(userWithoutBalance.balance).to.eql(0.25);
|
||||
});
|
||||
|
||||
it('adds a message to sender\'s inbox', async () => {
|
||||
expect(userWithBalance.inbox.messages).to.be.empty;
|
||||
|
||||
await userWithBalance.post(`/members/${userWithoutBalance._id}/gift`, {
|
||||
type: 'gems',
|
||||
gems: {
|
||||
amount: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await userWithBalance.sync();
|
||||
|
||||
expect(userWithBalance.inbox.messages).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('adds a message to recipients\'s inbox', async () => {
|
||||
expect(userWithoutBalance.inbox.messages).to.be.empty;
|
||||
|
||||
await userWithBalance.post(`/members/${userWithoutBalance._id}/gift`, {
|
||||
type: 'gems',
|
||||
gems: {
|
||||
amount: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await userWithoutBalance.sync();
|
||||
|
||||
expect(userWithoutBalance.inbox.messages).to.not.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
70
test/api/v2/members/POST-members_id_message.test.js
Normal file
70
test/api/v2/members/POST-members_id_message.test.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /members/id/message', () => {
|
||||
let sender, recipient;
|
||||
|
||||
beforeEach(async () => {
|
||||
sender = await generateUser();
|
||||
recipient = await generateUser();
|
||||
});
|
||||
|
||||
it('adds the sent message to sender\'s inbox', async () => {
|
||||
expect(sender.inbox.messages).to.be.empty;
|
||||
|
||||
await sender.post(`/members/${recipient._id}/message`, {
|
||||
message: 'hello frodo',
|
||||
});
|
||||
|
||||
await sender.sync();
|
||||
|
||||
expect(sender.inbox.messages).to.not.be.empty;
|
||||
|
||||
let messageKey = Object.keys(sender.inbox.messages)[0];
|
||||
let message = sender.inbox.messages[messageKey];
|
||||
|
||||
expect(message.text).to.eql('hello frodo');
|
||||
});
|
||||
|
||||
it('adds a message to recipients\'s inbox', async () => {
|
||||
expect(recipient.inbox.messages).to.be.empty;
|
||||
|
||||
await sender.post(`/members/${recipient._id}/message`, {
|
||||
message: 'hello frodo',
|
||||
});
|
||||
|
||||
await recipient.sync();
|
||||
|
||||
expect(recipient.inbox.messages).to.not.be.empty;
|
||||
|
||||
let messageKey = Object.keys(recipient.inbox.messages)[0];
|
||||
let message = recipient.inbox.messages[messageKey];
|
||||
|
||||
expect(message.text).to.eql('hello frodo');
|
||||
});
|
||||
|
||||
it('does not increment the sender\'s new messages field', async () => {
|
||||
expect(sender.inbox.messages).to.be.empty;
|
||||
|
||||
await sender.post(`/members/${recipient._id}/message`, {
|
||||
message: 'hello frodo',
|
||||
});
|
||||
|
||||
await sender.sync();
|
||||
|
||||
expect(sender.inbox.newMessages).to.eql(0);
|
||||
});
|
||||
|
||||
it('increments the recipient\'s new messages field', async () => {
|
||||
expect(recipient.inbox.messages).to.be.empty;
|
||||
|
||||
await sender.post(`/members/${recipient._id}/message`, {
|
||||
message: 'hello frodo',
|
||||
});
|
||||
|
||||
await recipient.sync();
|
||||
|
||||
expect(recipient.inbox.newMessages).to.eql(1);
|
||||
});
|
||||
});
|
||||
293
test/api/v2/register/POST-register.test.js
Normal file
293
test/api/v2/register/POST-register.test.js
Normal file
@@ -0,0 +1,293 @@
|
||||
import {
|
||||
generateUser,
|
||||
requester,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
import { v4 as generateRandomUserName } from 'uuid';
|
||||
import { each } from 'lodash';
|
||||
|
||||
describe('POST /register', () => {
|
||||
context('username and email are free', () => {
|
||||
it('registers a new user', async () => {
|
||||
let api = requester();
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user._id).to.exist;
|
||||
expect(user.apiToken).to.exist;
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
});
|
||||
});
|
||||
|
||||
it('requires password and confirmPassword to match', async () => {
|
||||
let api = requester();
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let password = 'password';
|
||||
let confirmPassword = 'not password';
|
||||
|
||||
return expect(api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageAuthPasswordMustMatch'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a username', async () => {
|
||||
let api = requester();
|
||||
let email = `${generateRandomUserName()}@example.com`;
|
||||
let password = 'password';
|
||||
let confirmPassword = 'password';
|
||||
|
||||
return expect(api.post('/register', {
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageAuthCredentialsRequired'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires an email', async () => {
|
||||
let api = requester();
|
||||
let username = generateRandomUserName();
|
||||
let password = 'password';
|
||||
let confirmPassword = 'password';
|
||||
|
||||
return expect(api.post('/register', {
|
||||
username,
|
||||
password,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageAuthCredentialsRequired'),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a password', async () => {
|
||||
let api = requester();
|
||||
let username = generateRandomUserName();
|
||||
let email = `${username}@example.com`;
|
||||
let confirmPassword = 'password';
|
||||
|
||||
return expect(api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
confirmPassword,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageAuthCredentialsRequired'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('login is already taken', () => {
|
||||
let username, email;
|
||||
beforeEach(async () => {
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
return generateUser({
|
||||
'auth.local.username': username,
|
||||
'auth.local.lowerCaseUsername': username,
|
||||
'auth.local.email': email,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects if username is already taken', async () => {
|
||||
let api = requester();
|
||||
let uniqueEmail = `${generateRandomUserName()}@exampe.com`;
|
||||
let password = 'password';
|
||||
|
||||
return expect(api.post('/register', {
|
||||
username,
|
||||
email: uniqueEmail,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageAuthUsernameTaken'),
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects if email is already taken', async () => {
|
||||
let api = requester();
|
||||
let uniqueUsername = generateRandomUserName();
|
||||
let password = 'password';
|
||||
|
||||
return expect(api.post('/register', {
|
||||
username: uniqueUsername,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageAuthEmailTaken'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('successful login via api', () => {
|
||||
let api, username, email, password;
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester();
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
password = 'password';
|
||||
});
|
||||
|
||||
it('sets all site tour values to -2 (already seen)', async () => {
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user.flags.tour).to.not.be.empty;
|
||||
|
||||
each(user.flags.tour, (value) => {
|
||||
expect(value).to.eql(-2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default todos, not no other task types', async () => {
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user.todos).to.not.be.empty;
|
||||
expect(user.dailys).to.be.empty;
|
||||
expect(user.habits).to.be.empty;
|
||||
expect(user.rewards).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default tags', async () => {
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user.tags).to.not.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('successful login with habitica-web header', () => {
|
||||
let api, username, email, password;
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester({}, {'x-client': 'habitica-web'});
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
password = 'password';
|
||||
});
|
||||
|
||||
it('sets all common tutorial flags to true', async () => {
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user.flags.tour).to.not.be.empty;
|
||||
|
||||
each(user.flags.tutorial.common, (value) => {
|
||||
expect(value).to.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default todos, habits, and rewards', async () => {
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user.todos).to.not.be.empty;
|
||||
expect(user.dailys).to.be.empty;
|
||||
expect(user.habits).to.not.be.empty;
|
||||
expect(user.rewards).to.not.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default tags', async () => {
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user.tags).to.not.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('successful login with habitica-android header', () => {
|
||||
let api, username, email, password;
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester({}, {'x-client': 'habitica-android'});
|
||||
username = generateRandomUserName();
|
||||
email = `${username}@example.com`;
|
||||
password = 'password';
|
||||
});
|
||||
|
||||
it('sets all common tutorial flags to true', async () => {
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user.flags.tour).to.not.be.empty;
|
||||
|
||||
each(user.flags.tutorial.common, (value) => {
|
||||
expect(value).to.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default todos, habits, and rewards', async () => {
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user.todos).to.not.be.empty;
|
||||
expect(user.dailys).to.be.empty;
|
||||
expect(user.habits).to.not.be.empty;
|
||||
expect(user.rewards).to.not.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
it('populates user with default tags', async () => {
|
||||
return api.post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
}).then((user) => {
|
||||
expect(user.tags).to.not.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
9
test/api/v2/status/GET-status.test.js
Normal file
9
test/api/v2/status/GET-status.test.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import {requester} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('Status', () => {
|
||||
it('returns a status of up when server is up', async () => {
|
||||
let api = requester();
|
||||
|
||||
await expect(api.get('/status')).to.eventually.eql({status: 'up'});
|
||||
});
|
||||
});
|
||||
186
test/api/v2/user/DELETE-user.test.js
Normal file
186
test/api/v2/user/DELETE-user.test.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import {
|
||||
checkExistence,
|
||||
createAndPopulateGroup,
|
||||
generateGroup,
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
import {
|
||||
find,
|
||||
map,
|
||||
} from 'lodash';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
describe('DELETE /user', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('deletes the user', async () => {
|
||||
return expect(user.del('/user').then(() => {
|
||||
return checkExistence('users', user._id);
|
||||
})).to.eventually.eql(false);
|
||||
});
|
||||
|
||||
it('deletes the user\'s tasks', async () => {
|
||||
// gets the user's todos ids
|
||||
let ids = user.todos.map(todo => todo._id);
|
||||
expect(ids.length).to.be.above(0); // make sure the user has some task to delete
|
||||
|
||||
await user.del('/user');
|
||||
|
||||
await Bluebird.all(map(ids, id => {
|
||||
return expect(checkExistence('tasks', id)).to.eventually.eql(false);
|
||||
}));
|
||||
});
|
||||
|
||||
context('user has active subscription', () => {
|
||||
it('does not delete account');
|
||||
});
|
||||
|
||||
context('last member of a party', () => {
|
||||
let party;
|
||||
|
||||
beforeEach(async () => {
|
||||
return generateGroup(user, {
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
}).then((group) => {
|
||||
party = group;
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes party when user is the only member', async () => {
|
||||
await user.del('/user');
|
||||
await expect(checkExistence('groups', party._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('last member of a private guild', () => {
|
||||
let guild, lastMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
let {
|
||||
groupLeader,
|
||||
group,
|
||||
} = await createAndPopulateGroup({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
guild = group;
|
||||
lastMember = groupLeader;
|
||||
});
|
||||
|
||||
it('deletes guild when user is the only member', async () => {
|
||||
await lastMember.del('/user');
|
||||
await expect(checkExistence('groups', guild._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is leader of', () => {
|
||||
let group, oldLeader, newLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
newLeader = res.members[0];
|
||||
oldLeader = res.groupLeader;
|
||||
});
|
||||
});
|
||||
|
||||
it('chooses new group leader for any group user was the leader of', async () => {
|
||||
return oldLeader.del('/user').then(() => {
|
||||
return newLeader.get(`/groups/${group._id}`);
|
||||
}).then((guild) => {
|
||||
expect(guild.leader).to.exist;
|
||||
expect(guild.leader._id).to.not.eql(oldLeader._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is a part of', () => {
|
||||
let group1, group2, userToDelete, otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
return generateUser({
|
||||
balance: 10,
|
||||
}).then((_user) => {
|
||||
userToDelete = _user;
|
||||
|
||||
return generateGroup(userToDelete, {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
}).then((newGroup) => {
|
||||
group1 = newGroup;
|
||||
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
});
|
||||
}).then((res) => {
|
||||
group2 = res.group;
|
||||
otherUser = res.members[0];
|
||||
|
||||
return userToDelete.post(`/groups/${group2._id}/join`);
|
||||
});
|
||||
});
|
||||
|
||||
it('removes user from all groups user was a part of', async () => {
|
||||
return userToDelete.del('/user').then(() => {
|
||||
return otherUser.get(`/groups/${group1._id}`);
|
||||
}).then((fetchedGroup1) => {
|
||||
expect(fetchedGroup1.members).to.be.empty;
|
||||
|
||||
return otherUser.get(`/groups/${group2._id}`);
|
||||
}).then((fetchedGroup2) => {
|
||||
expect(fetchedGroup2.members).to.not.be.empty;
|
||||
|
||||
let userInGroup = find(fetchedGroup2.members, (member) => {
|
||||
return member._id === userToDelete._id;
|
||||
});
|
||||
|
||||
expect(userInGroup).to.not.be.ok;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('pending invitation to group', () => {
|
||||
let group, userToDelete, otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
invites: 2,
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
otherUser = res.members[0];
|
||||
userToDelete = res.invitees[0];
|
||||
});
|
||||
});
|
||||
|
||||
it('removes invitations from groups', async () => {
|
||||
return userToDelete.del('/user').then(() => {
|
||||
return otherUser.get(`/groups/${group._id}`);
|
||||
}).then((fetchedGroup) => {
|
||||
expect(fetchedGroup.invites).to.have.a.lengthOf(1);
|
||||
expect(fetchedGroup.invites[0]._id).to.not.eql(userToDelete._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
28
test/api/v2/user/GET-user.test.js
Normal file
28
test/api/v2/user/GET-user.test.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /user', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
let usr = await generateUser();
|
||||
user = await usr.get('/user');
|
||||
});
|
||||
|
||||
it('gets the user object', async () => {
|
||||
expect(user._id).to.eql(user._id);
|
||||
expect(user.auth.local.username).to.eql(user.auth.local.username);
|
||||
expect(user.todos).to.eql(user.todos);
|
||||
expect(user.items).to.eql(user.items);
|
||||
});
|
||||
|
||||
it('does not include password information', async () => {
|
||||
expect(user.auth.local.hashed_password).to.not.exist;
|
||||
expect(user.auth.local.salt).to.not.exist;
|
||||
});
|
||||
|
||||
it('does not include api token', async () => {
|
||||
expect(user.apiToken).to.not.exist;
|
||||
});
|
||||
});
|
||||
16
test/api/v2/user/GET-user_tags.test.js
Normal file
16
test/api/v2/user/GET-user_tags.test.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /user/tags', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('gets the user\'s tags', async () => {
|
||||
return expect(user.get('/user/tags'))
|
||||
.to.eventually.eql(user.tags);
|
||||
});
|
||||
});
|
||||
25
test/api/v2/user/GET-user_tags_id.test.js
Normal file
25
test/api/v2/user/GET-user_tags_id.test.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /user/tags/id', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('gets a user\'s tag by id', async () => {
|
||||
return expect(user.get(`/user/tags/${user.tags[0].id}`))
|
||||
.to.eventually.eql(user.tags[0]);
|
||||
});
|
||||
|
||||
it('fails for non-existent tags', async () => {
|
||||
return expect(user.get('/user/tags/not-an-id'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageTagNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
201
test/api/v2/user/PUT-user.test.js
Normal file
201
test/api/v2/user/PUT-user.test.js
Normal file
@@ -0,0 +1,201 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
import { each, get } from 'lodash';
|
||||
|
||||
describe('PUT /user', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
context('Allowed Operations', () => {
|
||||
it('updates the user', async () => {
|
||||
await user.put('/user', {
|
||||
'profile.name': 'Frodo',
|
||||
'preferences.costume': true,
|
||||
'stats.hp': 14,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.profile.name).to.eql('Frodo');
|
||||
expect(user.preferences.costume).to.eql(true);
|
||||
expect(user.stats.hp).to.eql(14);
|
||||
});
|
||||
});
|
||||
|
||||
context('Top Level Protected Operations', () => {
|
||||
let protectedOperations = {
|
||||
'gem balance': {balance: 100},
|
||||
auth: {'auth.blocked': true, 'auth.timestamps.created': new Date()},
|
||||
contributor: {'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text'},
|
||||
backer: {'backer.tier': 10, 'backer.npc': 'Bilbo'},
|
||||
subscriptions: {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000},
|
||||
'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true},
|
||||
tasks: {todos: [], habits: [], dailys: [], rewards: []},
|
||||
};
|
||||
|
||||
each(protectedOperations, (data, testName) => {
|
||||
it(`does not allow updating ${testName}`, async () => {
|
||||
let errorText = [];
|
||||
each(data, (value, operation) => {
|
||||
errorText.push(t('messageUserOperationProtected', { operation }));
|
||||
});
|
||||
|
||||
await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: errorText,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Sub-Level Protected Operations', () => {
|
||||
let protectedOperations = {
|
||||
'class stat': {'stats.class': 'wizard'},
|
||||
};
|
||||
|
||||
each(protectedOperations, (data, testName) => {
|
||||
it(`does not allow updating ${testName}`, async () => {
|
||||
let errorText = [];
|
||||
each(data, (value, operation) => {
|
||||
errorText.push(t('messageUserOperationProtected', { operation }));
|
||||
});
|
||||
|
||||
await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: errorText,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Default Appearance Preferences', () => {
|
||||
let testCases = {
|
||||
shirt: 'yellow',
|
||||
skin: 'ddc994',
|
||||
'hair.color': 'blond',
|
||||
'hair.bangs': 2,
|
||||
'hair.base': 1,
|
||||
'hair.flower': 4,
|
||||
size: 'broad',
|
||||
};
|
||||
|
||||
each(testCases, (item, type) => {
|
||||
const update = {};
|
||||
update[`preferences.${type}`] = item;
|
||||
|
||||
it(`updates user with ${type} that is a default`, async () => {
|
||||
let dbUpdate = {};
|
||||
dbUpdate[`purchased.${type}.${item}`] = true;
|
||||
await user.update(dbUpdate);
|
||||
|
||||
// Sanity checks to make sure user is not already equipped with item
|
||||
expect(get(user.preferences, type)).to.not.eql(item);
|
||||
|
||||
let updatedUser = await user.put('/user', update);
|
||||
|
||||
expect(get(updatedUser.preferences, type)).to.eql(item);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user tries to update body size with invalid type', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'preferences.size': 'round',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: ['Must purchase round to set it on preferences.size'],
|
||||
});
|
||||
});
|
||||
|
||||
it('can set beard to default', async () => {
|
||||
await user.update({
|
||||
'purchased.hair.beard': 3,
|
||||
'preferences.hair.beard': 3,
|
||||
});
|
||||
|
||||
let updatedUser = await user.put('/user', {
|
||||
'preferences.hair.beard': 0,
|
||||
});
|
||||
|
||||
expect(updatedUser.preferences.hair.beard).to.eql(0);
|
||||
});
|
||||
|
||||
it('can set mustache to default', async () => {
|
||||
await user.update({
|
||||
'purchased.hair.mustache': 2,
|
||||
'preferences.hair.mustache': 2,
|
||||
});
|
||||
|
||||
let updatedUser = await user.put('/user', {
|
||||
'preferences.hair.mustache': 0,
|
||||
});
|
||||
|
||||
expect(updatedUser.preferences.hair.mustache).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('Purchasable Appearance Preferences', () => {
|
||||
let testCases = {
|
||||
background: 'volcano',
|
||||
shirt: 'convict',
|
||||
skin: 'cactus',
|
||||
'hair.base': 7,
|
||||
'hair.beard': 2,
|
||||
'hair.color': 'rainbow',
|
||||
'hair.mustache': 2,
|
||||
};
|
||||
|
||||
each(testCases, (item, type) => {
|
||||
const update = {};
|
||||
update[`preferences.${type}`] = item;
|
||||
|
||||
it(`returns an error if user tries to update ${type} with ${type} the user does not own`, async () => {
|
||||
await expect(user.put('/user', update)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: [`Must purchase ${item} to set it on preferences.${type}`],
|
||||
});
|
||||
});
|
||||
|
||||
it(`updates user with ${type} user does own`, async () => {
|
||||
let dbUpdate = {};
|
||||
dbUpdate[`purchased.${type}.${item}`] = true;
|
||||
await user.update(dbUpdate);
|
||||
|
||||
// Sanity check to make sure user is not already equipped with item
|
||||
expect(get(user.preferences, type)).to.not.eql(item);
|
||||
|
||||
let updatedUser = await user.put('/user', update);
|
||||
|
||||
expect(get(updatedUser.preferences, type)).to.eql(item);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Improvement Categories', () => {
|
||||
it('sets valid categories', async () => {
|
||||
await user.put('/user', {
|
||||
'preferences.improvementCategories': ['work', 'school'],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.preferences.improvementCategories).to.eql(['work', 'school']);
|
||||
});
|
||||
|
||||
it('discards invalid categories', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'preferences.improvementCategories': ['work', 'procrastination', 'school'],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
text: [
|
||||
'Validator failed for path `preferences.improvementCategories` with value `work,procrastination,school`',
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
98
test/api/v2/user/anonymized/GET-user_anonymized.test.js
Normal file
98
test/api/v2/user/anonymized/GET-user_anonymized.test.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
import { each } from 'lodash';
|
||||
|
||||
describe('GET /user/anonymized', () => {
|
||||
let user, anonymizedUser;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser({
|
||||
'inbox.messages': {
|
||||
'the-message-id': {
|
||||
sort: 214,
|
||||
user: 'Some user',
|
||||
backer: {},
|
||||
contributor: {
|
||||
text: 'Blacksmith',
|
||||
level: 2,
|
||||
contributions: 'Made some contributions',
|
||||
admin: false,
|
||||
},
|
||||
uuid: 'some-users-uuid',
|
||||
flagCount: 0,
|
||||
flags: {},
|
||||
likes: {},
|
||||
timestamp: 1444154258699.0000000000000000,
|
||||
text: 'Lorem ipsum',
|
||||
id: 'the-messages-id',
|
||||
sent: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await user.post('/user/tasks', {
|
||||
text: 'some private text',
|
||||
notes: 'some private notes',
|
||||
checklist: [
|
||||
{text: 'a private checklist'},
|
||||
{text: 'another private checklist'},
|
||||
],
|
||||
type: 'daily',
|
||||
});
|
||||
|
||||
anonymizedUser = await user.get('/user/anonymized');
|
||||
});
|
||||
|
||||
it('retains user id', async () => {
|
||||
expect(anonymizedUser._id).to.eql(user._id);
|
||||
});
|
||||
|
||||
it('removes credentials and financial information', async () => {
|
||||
expect(anonymizedUser.apiToken).to.not.exist;
|
||||
expect(anonymizedUser.auth.local).to.not.exist;
|
||||
expect(anonymizedUser.auth.facebook).to.not.exist;
|
||||
expect(anonymizedUser.purchased.plan).to.not.exist;
|
||||
});
|
||||
|
||||
it('removes profile information', async () => {
|
||||
expect(anonymizedUser.profile).to.not.exist;
|
||||
expect(anonymizedUser.contributor).to.not.exist;
|
||||
expect(anonymizedUser.achievements.challenges).to.not.exist;
|
||||
});
|
||||
|
||||
it('removes social information', async () => {
|
||||
expect(anonymizedUser.newMessages).to.not.exist;
|
||||
expect(anonymizedUser.invitations).to.not.exist;
|
||||
expect(anonymizedUser.items.special.nyeReceived).to.not.exist;
|
||||
expect(anonymizedUser.items.special.valentineReceived).to.not.exist;
|
||||
|
||||
each(anonymizedUser.inbox.messages, (msg) => {
|
||||
expect(msg.text).to.eql('inbox message text');
|
||||
});
|
||||
});
|
||||
|
||||
it('anonymizes task info', async () => {
|
||||
each(['habits', 'todos', 'dailys', 'rewards'], (tasks) => {
|
||||
each(anonymizedUser[tasks], (task) => {
|
||||
expect(task.text).to.eql('task text');
|
||||
expect(task.notes).to.eql('task notes');
|
||||
|
||||
each(task.checklist, (box) => {
|
||||
expect(box.text).to.match(/item\d*/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('anonymizes tags', async () => {
|
||||
each(anonymizedUser.tags, (tag) => {
|
||||
expect(tag.name).to.eql('tag');
|
||||
expect(tag.challenge).to.eql('challenge');
|
||||
});
|
||||
});
|
||||
|
||||
it('removes webhooks', async () => {
|
||||
expect(anonymizedUser.webhooks).to.not.exist;
|
||||
});
|
||||
});
|
||||
62
test/api/v2/user/batch-update/POST-user_batch-update.test.js
Normal file
62
test/api/v2/user/batch-update/POST-user_batch-update.test.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
import { each } from 'lodash';
|
||||
|
||||
describe('POST /user/batch-update', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
context('allowed operations', () => {
|
||||
it('makes batch operations', async () => {
|
||||
let task = (await user.get('/user/tasks'))[0];
|
||||
|
||||
let updatedUser = await user.post('/user/batch-update', [
|
||||
{op: 'update', body: {'stats.hp': 30}},
|
||||
{op: 'update', body: {'profile.name': 'Samwise'}},
|
||||
{op: 'score', params: { direction: 'up', id: task.id }},
|
||||
]);
|
||||
|
||||
expect(updatedUser.stats.hp).to.eql(30);
|
||||
expect(updatedUser.profile.name).to.eql('Samwise');
|
||||
|
||||
let fetchedTask = await user.get(`/user/tasks/${task.id}`);
|
||||
|
||||
expect(fetchedTask.value).to.be.greaterThan(task.value);
|
||||
});
|
||||
});
|
||||
|
||||
xcontext('development only operations', () => { // These tests will fail if your NODE_ENV is set to 'development' instead of 'testing'
|
||||
let protectedOperations = {
|
||||
'Add Ten Gems': 'addTenGems',
|
||||
'Add Hourglass': 'addHourglass',
|
||||
};
|
||||
|
||||
each(protectedOperations, (operation, description) => {
|
||||
it(`it sends back a 500 error for ${description} operation`, async () => {
|
||||
return expect(user.post('/user/batch-update', [
|
||||
{ op: operation },
|
||||
])).to.eventually.be.rejected.and.eql({
|
||||
code: 500,
|
||||
text: t('messageUserOperationNotFound', { operation }),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('unknown operations', () => {
|
||||
it('sends back a 500 error', async () => {
|
||||
return expect(user.post('/user/batch-update', [
|
||||
{op: 'aNotRealOperation'},
|
||||
])).to.eventually.be.rejected.and.eql({
|
||||
code: 500,
|
||||
text: t('messageUserOperationNotFound', { operation: 'aNotRealOperation' }),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
24
test/api/v2/user/pushDevice/POST-pushDevice.test.js
Normal file
24
test/api/v2/user/pushDevice/POST-pushDevice.test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
xdescribe('POST /user/pushDevice', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('registers a device id', async () => {
|
||||
return user.post('/user/pushDevice', {
|
||||
regId: '123123',
|
||||
type: 'android',
|
||||
}).then((devices) => {
|
||||
let device = devices[0];
|
||||
|
||||
expect(device._id).to.exist;
|
||||
expect(device.regId).to.eql('123123');
|
||||
expect(device.type).to.eql('android');
|
||||
});
|
||||
});
|
||||
});
|
||||
40
test/api/v2/user/tasks/DELETE-tasks_id.test.js
Normal file
40
test/api/v2/user/tasks/DELETE-tasks_id.test.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('DELETE /user/tasks/:id', () => {
|
||||
let user, task;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
task = user.todos[0];
|
||||
});
|
||||
|
||||
it('deletes a task', async () => {
|
||||
await user.del(`/user/tasks/${task.id}`);
|
||||
|
||||
await expect(user.get(`/user/tasks/${task.id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageTaskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the task does not exist', async () => {
|
||||
return expect(user.del('/user/tasks/task-that-does-not-exist'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageTaskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not delete another user\'s task', async () => {
|
||||
return expect(generateUser().then((otherUser) => {
|
||||
let otherUsersTask = otherUser.todos[0];
|
||||
return user.del(`/user/tasks/${otherUsersTask.id}`);
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: 'Task not found.',
|
||||
});
|
||||
});
|
||||
});
|
||||
25
test/api/v2/user/tasks/GET-tasks.test.js
Normal file
25
test/api/v2/user/tasks/GET-tasks.test.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /user/tasks/', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return generateUser().then((_user) => {
|
||||
user = _user;
|
||||
});
|
||||
});
|
||||
|
||||
it('gets all tasks', async () => {
|
||||
return user.get('/user/tasks/').then((tasks) => {
|
||||
expect(tasks).to.be.an('array');
|
||||
expect(tasks.length).to.equal(1);
|
||||
|
||||
let task = tasks[0];
|
||||
expect(task.id).to.exist;
|
||||
expect(task.type).to.exist;
|
||||
expect(task.text).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
42
test/api/v2/user/tasks/GET-tasks_id.test.js
Normal file
42
test/api/v2/user/tasks/GET-tasks_id.test.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /user/tasks/:id', () => {
|
||||
let user, task;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
task = user.todos[0];
|
||||
});
|
||||
|
||||
it('gets a task', async () => {
|
||||
return user.get(`/user/tasks/${task.id}`).then((foundTask) => {
|
||||
expect(foundTask.id).to.eql(task.id);
|
||||
expect(foundTask.text).to.eql(task.text);
|
||||
expect(foundTask.notes).to.eql(task.notes);
|
||||
expect(foundTask.value).to.eql(task.value);
|
||||
expect(foundTask.type).to.eql(task.type);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the task does not exist', async () => {
|
||||
return expect(user.get('/user/tasks/task-that-does-not-exist'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageTaskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not get another user\'s task', async () => {
|
||||
return expect(generateUser().then((otherUser) => {
|
||||
let otherUsersTask = otherUser.todos[0];
|
||||
|
||||
return user.get(`/user/tasks/${otherUsersTask.id}`);
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageTaskNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
26
test/api/v2/user/tasks/POST-clear-completed.test.js
Normal file
26
test/api/v2/user/tasks/POST-clear-completed.test.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /user/tasks/clear-completed', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
return generateUser().then((_user) => {
|
||||
user = _user;
|
||||
});
|
||||
});
|
||||
|
||||
it('removes all completed todos', async () => {
|
||||
let toComplete = await user.post('/user/tasks', {
|
||||
type: 'todo',
|
||||
text: 'done',
|
||||
});
|
||||
|
||||
await user.post(`/user/tasks/${toComplete._id}/up`);
|
||||
|
||||
let todos = await user.get('/user/tasks?type=todo');
|
||||
let uncomplete = await user.post('/user/tasks/clear-completed');
|
||||
expect(todos.length).to.equal(uncomplete.length + 1);
|
||||
});
|
||||
});
|
||||
66
test/api/v2/user/tasks/POST-tasks.test.js
Normal file
66
test/api/v2/user/tasks/POST-tasks.test.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /user/tasks', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('creates a task', async () => {
|
||||
return user.post('/user/tasks').then((task) => {
|
||||
expect(task.id).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a habit by default', async () => {
|
||||
return expect(user.post('/user/tasks'))
|
||||
.to.eventually.have.property('type', 'habit');
|
||||
});
|
||||
|
||||
it('creates a task with specified values', async () => {
|
||||
return user.post('/user/tasks', {
|
||||
type: 'daily',
|
||||
text: 'My task',
|
||||
notes: 'My notes',
|
||||
frequency: 'daily',
|
||||
}).then((task) => {
|
||||
expect(task.type).to.eql('daily');
|
||||
expect(task.text).to.eql('My task');
|
||||
expect(task.notes).to.eql('My notes');
|
||||
expect(task.frequency).to.eql('daily');
|
||||
});
|
||||
});
|
||||
|
||||
xit('does not create a task with an id that already exists', async () => {
|
||||
let todo = user.todos[0];
|
||||
|
||||
return expect(user.post('/user/tasks', {
|
||||
id: todo.id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 409,
|
||||
text: t('messageDuplicateTaskID'),
|
||||
});
|
||||
});
|
||||
|
||||
xit('TODO: no error is thrown - throws a 500 validation error if invalid type is posted', async () => {
|
||||
return expect(user.post('/user/tasks', {
|
||||
type: 'not-valid',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 500,
|
||||
text: 'Cannot call method \'indexOf\' of undefined',
|
||||
});
|
||||
});
|
||||
|
||||
xit('TODO: no error is thrown - throws a 500 validation error if invalid data is posted', async () => {
|
||||
return expect(user.post('/user/tasks', {
|
||||
frequency: 'not-valid',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 500,
|
||||
text: 'Task validation failed',
|
||||
});
|
||||
});
|
||||
});
|
||||
64
test/api/v2/user/tasks/PUT-tasks_id.test.js
Normal file
64
test/api/v2/user/tasks/PUT-tasks_id.test.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('PUT /user/tasks/:id', () => {
|
||||
let user, task;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
task = user.todos[0];
|
||||
});
|
||||
|
||||
it('does not update the id of the task', async () => {
|
||||
return user.put(`/user/tasks/${task.id}`, {
|
||||
id: 'some-thing',
|
||||
}).then((updatedTask) => {
|
||||
expect(updatedTask.id).to.eql(task.id);
|
||||
expect(updatedTask.id).to.not.eql('some-thing');
|
||||
});
|
||||
});
|
||||
|
||||
it('does not update the type of the task', async () => {
|
||||
return user.put(`/user/tasks/${task.id}`, {
|
||||
type: 'habit',
|
||||
}).then((updatedTask) => {
|
||||
expect(updatedTask.type).to.eql(task.type);
|
||||
expect(updatedTask.type).to.not.eql('habit');
|
||||
});
|
||||
});
|
||||
|
||||
it('updates text, attribute, priority and notes', async () => {
|
||||
return user.put(`/user/tasks/${task.id}`, {
|
||||
text: 'new text',
|
||||
notes: 'new notes',
|
||||
priority: 0.1,
|
||||
attribute: 'str',
|
||||
}).then((updatedTask) => {
|
||||
expect(updatedTask.text).to.eql('new text');
|
||||
expect(updatedTask.notes).to.eql('new notes');
|
||||
expect(updatedTask.priority).to.eql(0.1);
|
||||
expect(updatedTask.attribute).to.eql('str');
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the task does not exist', async () => {
|
||||
return expect(user.put('/user/tasks/task-id-that-does-not-exist'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: 'Task not found.',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not update another user\'s task', async () => {
|
||||
return expect(generateUser().then((otherUser) => {
|
||||
let otherUsersTask = otherUser.todos[0];
|
||||
return user.put(`/user/tasks/${otherUsersTask._id}`, {
|
||||
name: 'some name',
|
||||
});
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: 'Task not found.',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { encrypt } from '../../../../../website/server/libs/encryption';
|
||||
import { encrypt } from '../../../../../website/server/libs/api-v3/encryption';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('GET /email/unsubscribe', () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateRandomUserName } from 'uuid';
|
||||
import { each } from 'lodash';
|
||||
import { encrypt } from '../../../../../../website/server/libs/encryption';
|
||||
import { encrypt } from '../../../../../../website/server/libs/api-v3/encryption';
|
||||
|
||||
describe('POST /user/auth/local/register', () => {
|
||||
context('username and email are free', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// TODO These tests are pretty brittle
|
||||
// rewrite them to not depend on nock
|
||||
// Trust that the amplitude module works as intended and sends the requests
|
||||
import analyticsService from '../../../../../website/server/libs/analyticsService';
|
||||
import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService';
|
||||
|
||||
import nock from 'nock';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import baseModel from '../../../../../website/server/libs/baseModel';
|
||||
import baseModel from '../../../../../website/server/libs/api-v3/baseModel';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
describe('Base model plugin', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
getManifestFiles,
|
||||
} from '../../../../../website/server/libs/buildManifest';
|
||||
} from '../../../../../website/server/libs/api-v3/buildManifest';
|
||||
|
||||
describe('Build Manifest', () => {
|
||||
describe('getManifestFiles', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
removeFromArray,
|
||||
} from '../../../../../website/server/libs/collectionManipulators';
|
||||
} from '../../../../../website/server/libs/api-v3/collectionManipulators';
|
||||
|
||||
describe('Collection Manipulators', () => {
|
||||
describe('removeFromArray', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable global-require */
|
||||
import moment from 'moment';
|
||||
import Bluebird from 'bluebird';
|
||||
import { recoverCron, cron } from '../../../../../website/server/libs/cron';
|
||||
import { recoverCron, cron } from '../../../../../website/server/libs/api-v3/cron';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../../website/server/models/task';
|
||||
import { clone } from 'lodash';
|
||||
|
||||
@@ -4,7 +4,7 @@ import nconf from 'nconf';
|
||||
import nodemailer from 'nodemailer';
|
||||
import Bluebird from 'bluebird';
|
||||
import requireAgain from 'require-again';
|
||||
import logger from '../../../../../website/server/libs/logger';
|
||||
import logger from '../../../../../website/server/libs/api-v3/logger';
|
||||
import { TAVERN_ID } from '../../../../../website/server/models/group';
|
||||
|
||||
function defer () {
|
||||
@@ -50,7 +50,7 @@ function getUser () {
|
||||
}
|
||||
|
||||
describe('emails', () => {
|
||||
let pathToEmailLib = '../../../../../website/server/libs/email';
|
||||
let pathToEmailLib = '../../../../../website/server/libs/api-v3/email';
|
||||
|
||||
describe('sendEmail', () => {
|
||||
it('can send an email using the default transport', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
encrypt,
|
||||
decrypt,
|
||||
} from '../../../../../website/server/libs/encryption';
|
||||
} from '../../../../../website/server/libs/api-v3/encryption';
|
||||
|
||||
describe('encryption', () => {
|
||||
it('can encrypt and decrypt', () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
BadRequest,
|
||||
InternalServerError,
|
||||
NotFound,
|
||||
} from '../../../../../website/server/libs/errors';
|
||||
} from '../../../../../website/server/libs/api-v3/errors';
|
||||
|
||||
describe('Custom Errors', () => {
|
||||
describe('CustomError', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
translations,
|
||||
localePath,
|
||||
langCodes,
|
||||
} from '../../../../../website/server/libs/i18n';
|
||||
} from '../../../../../website/server/libs/api-v3/i18n';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import requireAgain from 'require-again';
|
||||
|
||||
/* eslint-disable global-require */
|
||||
describe('logger', () => {
|
||||
let pathToLoggerLib = '../../../../../website/server/libs/logger';
|
||||
let pathToLoggerLib = '../../../../../website/server/libs/api-v3/logger';
|
||||
let infoSpy;
|
||||
let errorSpy;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
encrypt as encryptPassword,
|
||||
makeSalt,
|
||||
} from '../../../../../website/server/libs/password';
|
||||
} from '../../../../../website/server/libs/api-v3/password';
|
||||
|
||||
describe('Password Utilities', () => {
|
||||
describe('Encrypt', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as sender from '../../../../../website/server/libs/email';
|
||||
import * as api from '../../../../../website/server/libs/payments';
|
||||
import analytics from '../../../../../website/server/libs/analyticsService';
|
||||
import * as sender from '../../../../../website/server/libs/api-v3/email';
|
||||
import * as api from '../../../../../website/server/libs/api-v3/payments';
|
||||
import analytics from '../../../../../website/server/libs/api-v3/analyticsService';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import moment from 'moment';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { preenHistory } from '../../../../../website/server/libs/preening';
|
||||
import { preenHistory } from '../../../../../website/server/libs/api-v3/preening';
|
||||
import moment from 'moment';
|
||||
import sinon from 'sinon'; // eslint-disable-line no-shadow
|
||||
import { generateHistory } from '../../../../helpers/api-unit.helper.js';
|
||||
|
||||
@@ -6,7 +6,7 @@ import nconf from 'nconf';
|
||||
describe('pushNotifications', () => {
|
||||
let user;
|
||||
let sendPushNotification;
|
||||
let pathToPushNotifications = '../../../../../website/server/libs/pushNotifications';
|
||||
let pathToPushNotifications = '../../../../../website/server/libs/api-v3/pushNotifications';
|
||||
let gcmSendSpy;
|
||||
let apnSendSpy;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import setupNconf from '../../../../../website/server/libs/setupNconf';
|
||||
import setupNconf from '../../../../../website/server/libs/api-v3/setupNconf';
|
||||
|
||||
import path from 'path';
|
||||
import nconf from 'nconf';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import request from 'request';
|
||||
import { sendTaskWebhook } from '../../../../../website/server/libs/webhook';
|
||||
import { sendTaskWebhook } from '../../../../../website/server/libs/api-v3/webhook';
|
||||
|
||||
describe('webhooks', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -4,13 +4,13 @@ import {
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
import analyticsService from '../../../../../website/server/libs/analyticsService';
|
||||
import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService';
|
||||
import nconf from 'nconf';
|
||||
import requireAgain from 'require-again';
|
||||
|
||||
describe('analytics middleware', () => {
|
||||
let res, req, next;
|
||||
let pathToAnalyticsMiddleware = '../../../../../website/server/middlewares/analytics';
|
||||
let pathToAnalyticsMiddleware = '../../../../../website/server/middlewares/api-v3/analytics';
|
||||
|
||||
beforeEach(() => {
|
||||
res = generateRes();
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
import cors from '../../../../../website/server/middlewares/cors';
|
||||
import cors from '../../../../../website/server/middlewares/api-v3/cors';
|
||||
|
||||
describe('cors middleware', () => {
|
||||
let res, req, next;
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
generateDaily,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cronMiddleware from '../../../../../website/server/middlewares/cron';
|
||||
import cronMiddleware from '../../../../../website/server/middlewares/api-v3/cron';
|
||||
import moment from 'moment';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import * as Tasks from '../../../../../website/server/models/task';
|
||||
import analyticsService from '../../../../../website/server/libs/analyticsService';
|
||||
import * as cronLib from '../../../../../website/server/libs/cron';
|
||||
import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService';
|
||||
import * as cronLib from '../../../../../website/server/libs/api-v3/cron';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('cron middleware', () => {
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
generateNext,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
import i18n from '../../../../../common/script/i18n';
|
||||
import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/ensureAccessRight';
|
||||
import { NotAuthorized } from '../../../../../website/server/libs/errors';
|
||||
import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/api-v3/ensureAccessRight';
|
||||
import { NotAuthorized } from '../../../../../website/server/libs/api-v3/errors';
|
||||
|
||||
describe('ensure access middlewares', () => {
|
||||
let res, req, next;
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
import ensureDevelpmentMode from '../../../../../website/server/middlewares/ensureDevelpmentMode';
|
||||
import { NotFound } from '../../../../../website/server/libs/errors';
|
||||
import ensureDevelpmentMode from '../../../../../website/server/middlewares/api-v3/ensureDevelpmentMode';
|
||||
import { NotFound } from '../../../../../website/server/libs/api-v3/errors';
|
||||
import nconf from 'nconf';
|
||||
|
||||
describe('developmentMode middleware', () => {
|
||||
|
||||
@@ -4,15 +4,15 @@ import {
|
||||
generateNext,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
|
||||
import errorHandler from '../../../../../website/server/middlewares/errorHandler';
|
||||
import responseMiddleware from '../../../../../website/server/middlewares/response';
|
||||
import errorHandler from '../../../../../website/server/middlewares/api-v3/errorHandler';
|
||||
import responseMiddleware from '../../../../../website/server/middlewares/api-v3/response';
|
||||
import {
|
||||
getUserLanguage,
|
||||
attachTranslateFunction,
|
||||
} from '../../../../../website/server/middlewares/language';
|
||||
} from '../../../../../website/server/middlewares/api-v3/language';
|
||||
|
||||
import { BadRequest } from '../../../../../website/server/libs/errors';
|
||||
import logger from '../../../../../website/server/libs/logger';
|
||||
import { BadRequest } from '../../../../../website/server/libs/api-v3/errors';
|
||||
import logger from '../../../../../website/server/libs/api-v3/logger';
|
||||
|
||||
describe('errorHandler', () => {
|
||||
let res, req, next;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import {
|
||||
getUserLanguage,
|
||||
attachTranslateFunction,
|
||||
} from '../../../../../website/server/middlewares/language';
|
||||
} from '../../../../../website/server/middlewares/api-v3/language';
|
||||
import common from '../../../../../common';
|
||||
import Bluebird from 'bluebird';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
|
||||
@@ -8,7 +8,7 @@ import requireAgain from 'require-again';
|
||||
|
||||
describe('maintenance mode middleware', () => {
|
||||
let res, req, next;
|
||||
let pathToMaintenanceModeMiddleware = '../../../../../website/server/middlewares/maintenanceMode';
|
||||
let pathToMaintenanceModeMiddleware = '../../../../../website/server/middlewares/api-v3/maintenanceMode';
|
||||
|
||||
beforeEach(() => {
|
||||
res = generateRes();
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
import responseMiddleware from '../../../../../website/server/middlewares/response';
|
||||
import responseMiddleware from '../../../../../website/server/middlewares/api-v3/response';
|
||||
|
||||
describe('response middleware', () => {
|
||||
let res, req, next;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { sleep } from '../../../../helpers/api-unit.helper';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { quests as questScrolls } from '../../../../../common/script/content';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
import * as email from '../../../../../website/server/libs/api-v3/email';
|
||||
import validator from 'validator';
|
||||
import { TAVERN_ID } from '../../../../../common/script/';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { model as Challenge } from '../../../../../website/server/models/challen
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../../website/server/models/task';
|
||||
import { InternalServerError } from '../../../../../website/server/libs/errors';
|
||||
import { InternalServerError } from '../../../../../website/server/libs/api-v3/errors';
|
||||
import { each } from 'lodash';
|
||||
import { generateHistory } from '../../../../helpers/api-unit.helper.js';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import i18n from '../../../common/script/i18n';
|
||||
i18n.translations = require('../../../website/server/libs/i18n').translations;
|
||||
i18n.translations = require('../../../website/server/libs/api-v3/i18n').translations;
|
||||
|
||||
// Use this to verify error messages returned by the server
|
||||
// That way, if the translated string changes, the test
|
||||
|
||||
8
test/helpers/api-integration/v2/index.js
Normal file
8
test/helpers/api-integration/v2/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Import requester function, set it up for v2, export it
|
||||
import { requester } from '../requester';
|
||||
requester.setApiVersion('v2');
|
||||
export { requester };
|
||||
|
||||
export { translate } from '../translate';
|
||||
export { checkExistence, resetHabiticaDB } from '../../mongo';
|
||||
export * from './object-generators';
|
||||
146
test/helpers/api-integration/v2/object-generators.js
Normal file
146
test/helpers/api-integration/v2/object-generators.js
Normal file
@@ -0,0 +1,146 @@
|
||||
import {
|
||||
times,
|
||||
map,
|
||||
} from 'lodash';
|
||||
import Bluebird from 'bluebird';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes';
|
||||
import { requester } from '../requester';
|
||||
|
||||
// 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 async function generateUser (update = {}) {
|
||||
let username = generateUUID();
|
||||
let password = 'password';
|
||||
let email = `${username}@example.com`;
|
||||
|
||||
let user = await requester().post('/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
let apiUser = new ApiUser(user);
|
||||
|
||||
await apiUser.update(update);
|
||||
|
||||
return apiUser;
|
||||
}
|
||||
|
||||
// Generates a new group. Requires a user object, which
|
||||
// will will become the groups leader. Takes a details argument
|
||||
// for the initial group creation and an update argument which
|
||||
// will update the group via the db
|
||||
export async function generateGroup (leader, details = {}, update = {}) {
|
||||
details.type = details.type || 'party';
|
||||
details.privacy = details.privacy || 'private';
|
||||
details.name = details.name || 'test group';
|
||||
|
||||
let members;
|
||||
|
||||
if (details.members) {
|
||||
members = details.members;
|
||||
delete details.members;
|
||||
}
|
||||
|
||||
let group = await leader.post('/groups', details);
|
||||
let apiGroup = new ApiGroup(group);
|
||||
|
||||
const groupMembershipTypes = {
|
||||
party: { 'party._id': group._id},
|
||||
guild: { guilds: [group._id] },
|
||||
};
|
||||
|
||||
await Bluebird.all(
|
||||
map(members, (member) => {
|
||||
return member.update(groupMembershipTypes[group.type]);
|
||||
})
|
||||
);
|
||||
|
||||
await apiGroup.update(update);
|
||||
await apiGroup.sync();
|
||||
return apiGroup;
|
||||
}
|
||||
|
||||
// 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 async function createAndPopulateGroup (settings = {}) {
|
||||
let numberOfMembers = settings.members || 0;
|
||||
let numberOfInvites = settings.invites || 0;
|
||||
let groupDetails = settings.groupDetails;
|
||||
let leaderDetails = settings.leaderDetails || { balance: 10 };
|
||||
|
||||
let groupLeader = await generateUser(leaderDetails);
|
||||
let group = await generateGroup(groupLeader, groupDetails);
|
||||
|
||||
const groupMembershipTypes = {
|
||||
party: { 'party._id': group._id},
|
||||
guild: { guilds: [group._id] },
|
||||
};
|
||||
|
||||
let members = await Bluebird.all(
|
||||
times(numberOfMembers, () => {
|
||||
return generateUser(groupMembershipTypes[group.type]);
|
||||
})
|
||||
);
|
||||
|
||||
await group.update({ memberCount: numberOfMembers + 1});
|
||||
|
||||
let invitees = await Bluebird.all(
|
||||
times(numberOfInvites, () => {
|
||||
return generateUser();
|
||||
})
|
||||
);
|
||||
|
||||
let invitationPromises = invitees.map((invitee) => {
|
||||
return groupLeader.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [invitee._id],
|
||||
});
|
||||
});
|
||||
|
||||
await Bluebird.all(invitationPromises);
|
||||
|
||||
return {
|
||||
groupLeader,
|
||||
group,
|
||||
members,
|
||||
invitees,
|
||||
};
|
||||
}
|
||||
|
||||
// Generates a new challenge. Requires an ApiGroup object with a
|
||||
// _leader attribute (given with generateGroup method). The group
|
||||
// will will become the group that owns the challenge. The group's
|
||||
// leader will be the one to create the challenge. It takes a details
|
||||
// argument for the initial challenge creation and an update argument
|
||||
// which will update the challenge via the db
|
||||
export async function generateChallenge (challengeCreator, group, details = {}, update = {}) {
|
||||
details.group = group._id;
|
||||
details.prize = details.prize || 0;
|
||||
details.official = details.official || false;
|
||||
|
||||
let challenge = await challengeCreator.post('/challenges', details);
|
||||
let apiChallenge = new ApiChallenge(challenge);
|
||||
|
||||
await apiChallenge.update(update);
|
||||
|
||||
return apiChallenge;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import '../../website/server/libs/i18n';
|
||||
import '../../website/server/libs/api-v3/i18n';
|
||||
import mongoose from 'mongoose';
|
||||
import { defaultsDeep as defaults } from 'lodash';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require('./globals.helper');
|
||||
import i18n from '../../common/script/i18n';
|
||||
i18n.translations = require('../../website/server/libs/i18n').translations;
|
||||
i18n.translations = require('../../website/server/libs/api-v3/i18n').translations;
|
||||
|
||||
export const STRING_ERROR_MSG = 'Error processing the string. Please see Help > Report a Bug.';
|
||||
export const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/;
|
||||
|
||||
@@ -23,12 +23,12 @@ import mongoose from 'mongoose';
|
||||
// Load nconf for unit tests
|
||||
//------------------------------
|
||||
if (process.env.LOAD_SERVER === '0') { // when the server is in a different process we simply connect to mongoose
|
||||
require('../../website/server/libs/setupNconf')('./config.json');
|
||||
require('../../website/server/libs/api-v3/setupNconf')('./config.json');
|
||||
// Use Q promises instead of mpromise in mongoose
|
||||
mongoose.Promise = Bluebird;
|
||||
mongoose.connect(nconf.get('TEST_DB_URI'));
|
||||
} else { // When running tests and the server in the same process
|
||||
require('../../website/server/libs/setupNconf')('./config.json.example');
|
||||
require('../../website/server/libs/api-v3/setupNconf')('./config.json.example');
|
||||
nconf.set('NODE_DB_URI', nconf.get('TEST_DB_URI'));
|
||||
nconf.set('NODE_ENV', 'test');
|
||||
nconf.set('IS_TEST', true);
|
||||
|
||||
362
website/server/controllers/api-v2/auth.js
Normal file
362
website/server/controllers/api-v2/auth.js
Normal file
@@ -0,0 +1,362 @@
|
||||
var _ = require('lodash');
|
||||
var validator = require('validator');
|
||||
var passport = require('passport');
|
||||
var shared = require('../../../../common');
|
||||
var async = require('async');
|
||||
var utils = require('../../libs/api-v2/utils');
|
||||
var nconf = require('nconf');
|
||||
var request = require('request');
|
||||
import {
|
||||
model as User,
|
||||
} from '../../models/user';
|
||||
import {
|
||||
model as EmailUnsubscription,
|
||||
} from '../../models/emailUnsubscription';
|
||||
|
||||
var analytics = utils.analytics;
|
||||
var i18n = require('./../../libs/api-v2/i18n');
|
||||
|
||||
var isProd = nconf.get('NODE_ENV') === 'production';
|
||||
|
||||
var api = module.exports;
|
||||
|
||||
var NO_TOKEN_OR_UID = { err: shared.i18n.t('messageAuthMustIncludeTokens') };
|
||||
var NO_USER_FOUND = {err: shared.i18n.t('messageAuthNoUserFound') };
|
||||
var NO_SESSION_FOUND = { err: shared.i18n.t('messageAuthMustBeLoggedIn') };
|
||||
var accountSuspended = function(uuid){
|
||||
return {
|
||||
err: 'Account has been suspended, please contact leslie@habitica.com with your UUID ('+uuid+') for assistance.',
|
||||
code: 'ACCOUNT_SUSPENDED'
|
||||
};
|
||||
}
|
||||
|
||||
api.auth = function(req, res, next) {
|
||||
var uid = req.headers['x-api-user'];
|
||||
var token = req.headers['x-api-key'];
|
||||
if (!(uid && token)) return res.status(401).json(NO_TOKEN_OR_UID);
|
||||
User.findOne({_id: uid, apiToken: token}, function(err, user) {
|
||||
if (err) return next(err);
|
||||
if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND);
|
||||
if (user.auth.blocked) return res.status(401).json(accountSuspended(user._id));
|
||||
|
||||
res.locals.wasModified = req.query._v ? +user._v !== +req.query._v : true;
|
||||
res.locals.user = user;
|
||||
req.session.userId = user._id;
|
||||
return next();
|
||||
});
|
||||
};
|
||||
|
||||
api.authWithSession = function(req, res, next) { //[todo] there is probably a more elegant way of doing this...
|
||||
if (!(req.session && req.session.userId))
|
||||
return res.status(401).json(NO_SESSION_FOUND);
|
||||
User.findOne({_id: req.session.userId}, function(err, user) {
|
||||
if (err) return next(err);
|
||||
if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND);
|
||||
res.locals.user = user;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
// TODO passing auth params as query params is not safe as they are logged by browser history, ...
|
||||
api.authWithUrl = function(req, res, next) {
|
||||
User.findOne({_id:req.query._id, apiToken:req.query.apiToken}, function(err,user){
|
||||
if (err) return next(err);
|
||||
if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND);
|
||||
res.locals.user = user;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
api.registerUser = function(req, res, next) {
|
||||
var email = req.body.email && req.body.email.toLowerCase();
|
||||
var username = req.body.username;
|
||||
// Get the lowercase version of username to check that we do not have duplicates
|
||||
// So we can search for it in the database and then reject the choosen username if 1 or more results are found
|
||||
var lowerCaseUsername = username && username.toLowerCase();
|
||||
|
||||
async.auto({
|
||||
validate: function(cb) {
|
||||
if (!(username && req.body.password && email))
|
||||
return cb({code:401, err: shared.i18n.t('messageAuthCredentialsRequired')});
|
||||
if (req.body.password !== req.body.confirmPassword)
|
||||
return cb({code:401, err: shared.i18n.t('messageAuthPasswordMustMatch')});
|
||||
if (!validator.isEmail(email))
|
||||
return cb({code:401, err: ":email invalid"});
|
||||
cb();
|
||||
},
|
||||
findReg: function(cb) {
|
||||
// Search for duplicates using lowercase version of username
|
||||
User.findOne({$or:[{'auth.local.email': email}, {'auth.local.lowerCaseUsername': lowerCaseUsername}]}, {'auth.local':1}, cb);
|
||||
},
|
||||
findFacebook: function(cb){
|
||||
User.findOne({_id: req.headers['x-api-user'], apiToken: req.headers['x-api-key']}, {auth:1}, cb);
|
||||
},
|
||||
register: ['validate', 'findReg', 'findFacebook', function(cb, data) {
|
||||
if (data.findReg) {
|
||||
if (email === data.findReg.auth.local.email) return cb({code:401, err:"Email already taken"});
|
||||
// Check that the lowercase username isn't already used
|
||||
if (lowerCaseUsername === data.findReg.auth.local.lowerCaseUsername) return cb({code:401, err: shared.i18n.t('messageAuthUsernameTaken')});
|
||||
}
|
||||
var salt = utils.makeSalt();
|
||||
var newUser = {
|
||||
auth: {
|
||||
local: {
|
||||
username: username,
|
||||
lowerCaseUsername: lowerCaseUsername, // Store the lowercase version of the username
|
||||
email: email, // Store email as lowercase
|
||||
salt: salt,
|
||||
hashed_password: utils.encryptPassword(req.body.password, salt)
|
||||
},
|
||||
timestamps: {created: +new Date(), loggedIn: +new Date()}
|
||||
}
|
||||
};
|
||||
// existing user, allow them to add local authentication
|
||||
if (data.findFacebook) {
|
||||
data.findFacebook.auth.local = newUser.auth.local;
|
||||
data.findFacebook.registeredThrough = newUser.registeredThrough;
|
||||
data.findFacebook.save(cb);
|
||||
// new user, register them
|
||||
} else {
|
||||
newUser.preferences = newUser.preferences || {};
|
||||
newUser.preferences.language = req.language; // User language detected from browser, not saved
|
||||
var user = new User(newUser);
|
||||
|
||||
user.registeredThrough = req.headers['x-client'];
|
||||
var analyticsData = {
|
||||
category: 'acquisition',
|
||||
type: 'local',
|
||||
gaLabel: 'local',
|
||||
uuid: user._id,
|
||||
};
|
||||
analytics.track('register', analyticsData)
|
||||
|
||||
user.save(function(err, savedUser){
|
||||
if (err) return cb(err);
|
||||
// Clean previous email preferences
|
||||
EmailUnsubscription.remove({email: savedUser.auth.local.email}, function(){
|
||||
utils.txnEmail(savedUser, 'welcome');
|
||||
});
|
||||
cb.apply(cb, arguments);
|
||||
});
|
||||
}
|
||||
}]
|
||||
}, function(err, data) {
|
||||
if (err) return err.code ? res.status(err.code).json(err) : next(err);
|
||||
data.register[0].getTransformedData(function(err, userTransformed){
|
||||
if(err) return next(err);
|
||||
res.status(200).json(userTransformed);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
api.loginLocal = function(req, res, next) {
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
if (!(username && password)) return res.status(401).json({err:'Missing :username or :password in request body, please provide both'});
|
||||
var login = validator.isEmail(username) ?
|
||||
{'auth.local.email':username.toLowerCase()} : // Emails are all lowercase
|
||||
{'auth.local.username':username}; // Use the username as the user typed it
|
||||
|
||||
User.findOne(login, {auth:1}, function(err, user){
|
||||
if (err) return next(err);
|
||||
if (!user) return res.status(401).json({err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\" on the habitica.com website's login form."});
|
||||
if (user.auth.blocked) return res.status(401).json(accountSuspended(user._id));
|
||||
// We needed the whole user object first so we can get his salt to encrypt password comparison
|
||||
User.findOne(
|
||||
{$and: [login, {'auth.local.hashed_password': utils.encryptPassword(password, user.auth.local.salt)}]}
|
||||
, {_id:1, apiToken:1}
|
||||
, function(err, user){
|
||||
if (err) return next(err);
|
||||
if (!user) return res.status(401).json({err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\" on the habitica.com website's login form."});
|
||||
res.json({id: user._id,token: user.apiToken});
|
||||
password = null;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
POST /user/auth/social
|
||||
*/
|
||||
api.loginSocial = function(req, res, next) {
|
||||
var access_token = req.body.authResponse.access_token,
|
||||
network = req.body.network;
|
||||
if (network!=='facebook')
|
||||
return res.status(401).json({err:"Only Facebook supported currently."});
|
||||
async.auto({
|
||||
profile: function (cb) {
|
||||
passport._strategies[network].userProfile(access_token, cb);
|
||||
},
|
||||
user: ['profile', function (cb, results) {
|
||||
var q = {};
|
||||
q['auth.' + network + '.id'] = results.profile.id;
|
||||
User.findOne(q, {_id: 1, apiToken: 1, auth: 1}, cb);
|
||||
}],
|
||||
register: ['profile', 'user', function (cb, results) {
|
||||
if (results.user) return cb(null, results.user);
|
||||
// Create new user
|
||||
var prof = results.profile;
|
||||
var user = {
|
||||
preferences: {
|
||||
language: req.language // User language detected from browser, not saved
|
||||
},
|
||||
auth: {
|
||||
timestamps: {created: +new Date(), loggedIn: +new Date()}
|
||||
}
|
||||
};
|
||||
user.auth[network] = prof;
|
||||
user = new User(user);
|
||||
user.registeredThrough = req.headers['x-client'];
|
||||
|
||||
user.save(function(err, savedUser){
|
||||
// Clean previous email preferences
|
||||
if(savedUser.auth.facebook.emails && savedUser.auth.facebook.emails[0] && savedUser.auth.facebook.emails[0].value){
|
||||
EmailUnsubscription.remove({email: savedUser.auth.facebook.emails[0].value}, function(){
|
||||
utils.txnEmail(savedUser, 'welcome');
|
||||
});
|
||||
}
|
||||
cb.apply(cb, arguments);
|
||||
});
|
||||
|
||||
var analyticsData = {
|
||||
category: 'acquisition',
|
||||
type: network,
|
||||
gaLabel: network,
|
||||
uuid: user._id,
|
||||
};
|
||||
analytics.track('register', analyticsData)
|
||||
}]
|
||||
}, function(err, results){
|
||||
if (err) return res.status(401).json({err: err.toString ? err.toString() : err});
|
||||
var acct = results.register[0] ? results.register[0] : results.register;
|
||||
if (acct.auth.blocked) return res.status(401).json(accountSuspended(acct._id));
|
||||
return res.status(200).json({id:acct._id, token:acct.apiToken});
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* DELETE /user/auth/social
|
||||
*/
|
||||
api.deleteSocial = function(req,res,next){
|
||||
if (!res.locals.user.auth.local.username)
|
||||
return res.status(401).json({err:"Account lacks another authentication method, can't detach Facebook"});
|
||||
//TODO for some reason, the following gives https://gist.github.com/lefnire/f93eb306069b9089d123
|
||||
//res.locals.user.auth.facebook = null;
|
||||
//res.locals.user.auth.save(function(err, saved){
|
||||
User.update({_id:res.locals.user._id}, {$unset:{'auth.facebook':1}}, function(err){
|
||||
if (err) return next(err);
|
||||
res.sendStatus(200);
|
||||
})
|
||||
}
|
||||
|
||||
api.resetPassword = function(req, res, next){
|
||||
var email = req.body.email && req.body.email.toLowerCase(), // Emails are all lowercase
|
||||
salt = utils.makeSalt(),
|
||||
newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later)
|
||||
hashed_password = utils.encryptPassword(newPassword, salt);
|
||||
|
||||
if(!email) return res.status(400).json({err: "Email not provided"});
|
||||
|
||||
User.findOne({'auth.local.email': email}, function(err, user){
|
||||
if (err) return next(err);
|
||||
if (!user) return res.status(401).json({err:"Sorry, we can't find a user registered with email " + email + "\n- Make sure your email address is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login."});
|
||||
user.auth.local.salt = salt;
|
||||
user.auth.local.hashed_password = hashed_password;
|
||||
utils.sendEmail({
|
||||
from: "Habitica <admin@habitica.com>",
|
||||
to: email,
|
||||
subject: "Password Reset for Habitica",
|
||||
text: "Password for " + user.auth.local.username + " has been reset to " + newPassword + " Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at " + nconf.get('BASE_URL') + ". After you've logged in, head to " + nconf.get('BASE_URL') + "/#/options/settings/settings and change your password.",
|
||||
html: "Password for <strong>" + user.auth.local.username + "</strong> has been reset to <strong>" + newPassword + "</strong><br /><br />Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.<br /><br />Log in at " + nconf.get('BASE_URL') + ". After you've logged in, head to " + nconf.get('BASE_URL') + "/#/options/settings/settings and change your password."
|
||||
});
|
||||
user.save(function(err){
|
||||
if(err) return next(err);
|
||||
res.send('New password sent to '+ email);
|
||||
email = salt = newPassword = hashed_password = null;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var invalidPassword = function(user, password){
|
||||
var hashed_password = utils.encryptPassword(password, user.auth.local.salt);
|
||||
if (hashed_password !== user.auth.local.hashed_password)
|
||||
return {code:401, err:"Incorrect password"};
|
||||
return false;
|
||||
}
|
||||
|
||||
api.changeUsername = function(req, res, next) {
|
||||
var user = res.locals.user;
|
||||
var username = req.body.username;
|
||||
var lowerCaseUsername = username && username.toLowerCase(); // we search for the lowercased version to intercept duplicates
|
||||
|
||||
if(!username) return res.status(400).json({err: "Username not provided"});
|
||||
async.waterfall([
|
||||
function(cb){
|
||||
User.findOne({'auth.local.lowerCaseUsername': lowerCaseUsername}, {auth:1}, cb);
|
||||
},
|
||||
function(found, cb){
|
||||
if (found) return cb({code:401, err: "Username already taken"});
|
||||
if (invalidPassword(user, req.body.password)) return cb(invalidPassword(user, req.body.password));
|
||||
user.auth.local.username = username;
|
||||
user.auth.local.lowerCaseUsername = lowerCaseUsername;
|
||||
|
||||
user.save(cb);
|
||||
}
|
||||
], function(err){
|
||||
if (err) return err.code ? res.status(err.code).json(err) : next(err);
|
||||
res.sendStatus(200);
|
||||
})
|
||||
}
|
||||
|
||||
api.changeEmail = function(req, res, next){
|
||||
var email = req.body.email && req.body.email.toLowerCase(); // emails are all lowercase
|
||||
if(!email) return res.status(400).json({err: "Email not provided"});
|
||||
|
||||
async.waterfall([
|
||||
function(cb){
|
||||
User.findOne({'auth.local.email': email}, {auth:1}, cb);
|
||||
},
|
||||
function(found, cb){
|
||||
if(found) return cb({code:401, err: shared.i18n.t('messageAuthEmailTaken')});
|
||||
if (invalidPassword(res.locals.user, req.body.password)) return cb(invalidPassword(res.locals.user, req.body.password));
|
||||
res.locals.user.auth.local.email = email;
|
||||
res.locals.user.save(cb);
|
||||
}
|
||||
], function(err){
|
||||
if (err) return err.code ? res.status(err.code).json(err) : next(err);
|
||||
res.sendStatus(200);
|
||||
})
|
||||
}
|
||||
|
||||
api.changePassword = function(req, res, next) {
|
||||
var user = res.locals.user,
|
||||
oldPassword = req.body.oldPassword,
|
||||
newPassword = req.body.newPassword,
|
||||
confirmNewPassword = req.body.confirmNewPassword;
|
||||
|
||||
if (newPassword != confirmNewPassword)
|
||||
return res.status(401).json({err: "Password & Confirm don't match"});
|
||||
|
||||
var salt = user.auth.local.salt,
|
||||
hashed_old_password = utils.encryptPassword(oldPassword, salt),
|
||||
hashed_new_password = utils.encryptPassword(newPassword, salt);
|
||||
|
||||
if (hashed_old_password !== user.auth.local.hashed_password)
|
||||
return res.status(401).json({err:"Old password doesn't match"});
|
||||
|
||||
user.auth.local.hashed_password = hashed_new_password;
|
||||
user.save(function(err, saved){
|
||||
if (err) next(err);
|
||||
res.sendStatus(200);
|
||||
})
|
||||
};
|
||||
|
||||
// DISABLED FOR API v2
|
||||
/*api.setupPassport = function(router) {
|
||||
|
||||
router.get('/logout', i18n.getUserLanguage, function(req, res) {
|
||||
req.logout();
|
||||
delete req.session.userId;
|
||||
res.redirect('/');
|
||||
})
|
||||
|
||||
};*/
|
||||
428
website/server/controllers/api-v2/challenges.js
Normal file
428
website/server/controllers/api-v2/challenges.js
Normal file
@@ -0,0 +1,428 @@
|
||||
// @see ../routes for routing
|
||||
|
||||
var _ = require('lodash');
|
||||
var nconf = require('nconf');
|
||||
var async = require('async');
|
||||
var shared = require('../../../../common');
|
||||
import {
|
||||
model as User,
|
||||
} from '../../models/user';
|
||||
import {
|
||||
model as Group,
|
||||
basicFields as basicGroupFields,
|
||||
TAVERN_ID,
|
||||
} from '../../models/group';
|
||||
import {
|
||||
model as Challenge,
|
||||
} from '../../models/challenge';
|
||||
import * as Tasks from '../../models/task';
|
||||
var logging = require('./../../libs/api-v2/logging');
|
||||
var csvStringify = require('csv-stringify');
|
||||
var utils = require('../../libs/api-v2/utils');
|
||||
var api = module.exports;
|
||||
var pushNotify = require('./pushNotifications');
|
||||
import Bluebird from 'bluebird';
|
||||
import v3MembersController from '../api-v3/members';
|
||||
/*
|
||||
------------------------------------------------------------------------
|
||||
Challenges
|
||||
------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
var nameFields = 'profile.name';
|
||||
|
||||
api.list = async function(req, res, next) {
|
||||
try {
|
||||
var user = res.locals.user;
|
||||
|
||||
let challenges = await Challenge.find({
|
||||
$or: [
|
||||
{_id: {$in: user.challenges}}, // Challenges where the user is participating
|
||||
{group: {$in: user.getGroups()}}, // Challenges in groups where I'm a member
|
||||
{leader: user._id}, // Challenges where I'm the leader
|
||||
],
|
||||
_id: {$ne: '95533e05-1ff9-4e46-970b-d77219f199e9'}, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug TODO revisit
|
||||
})
|
||||
.sort('-official -timestamp')
|
||||
// .populate('group', basicGroupFields)
|
||||
// .populate('leader', nameFields)
|
||||
.exec();
|
||||
|
||||
let resChals = challenges.map(challenge => {
|
||||
let obj = challenge.toJSON();
|
||||
|
||||
obj._isMember = user.challenges.indexOf(challenge._id) !== -1;
|
||||
return obj;
|
||||
});
|
||||
|
||||
// Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
|
||||
await Bluebird.all(resChals.map((chal, index) => {
|
||||
return Bluebird.all([
|
||||
User.findById(chal.leader).select(nameFields).exec(),
|
||||
Group.findById(chal.group).select(basicGroupFields).exec(),
|
||||
]).then(populatedData => {
|
||||
resChals[index].leader = populatedData[0] ? populatedData[0].toJSON({minimize: true}) : null;
|
||||
resChals[index].group = populatedData[1] ? populatedData[1].toJSON({minimize: true}) : null;
|
||||
});
|
||||
}));
|
||||
|
||||
res.json(resChals);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
// GET
|
||||
api.get = async function(req, res, next) {
|
||||
try {
|
||||
let user = res.locals.user;
|
||||
let challengeId = req.params.cid;
|
||||
|
||||
let challenge = await Challenge.findById(challengeId)
|
||||
// Don't populate the group as we'll fetch it manually later
|
||||
// .populate('leader', nameFields)
|
||||
.exec();
|
||||
if (!challenge) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'});
|
||||
|
||||
// Fetching basic group data
|
||||
let group = await Group.getGroup({user, groupId: challenge.group, optionalMembership: true});
|
||||
if (!group || !challenge.canView(user, group)) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'});
|
||||
|
||||
let leaderRes = await User.findById(challenge.leader).select('profile.name').exec();
|
||||
leaderRes = leaderRes ? leaderRes.toJSON({minimize: true}) : null;
|
||||
|
||||
challenge.getTransformedData({
|
||||
populateMembers: 'profile.name',
|
||||
cb (err, transformedChal) {
|
||||
transformedChal.group = group.toJSON({minimize: true});
|
||||
transformedChal.leader = leaderRes;
|
||||
transformedChal._isMember = user.challenges.indexOf(transformedChal._id) !== -1;
|
||||
res.json(transformedChal);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
api.csv = function(req, res, next) {
|
||||
var cid = req.params.cid;
|
||||
req.params.challengeId = cid;
|
||||
v3MembersController.exportChallengeCsv.handler(req, res, next).catch(next);
|
||||
}
|
||||
|
||||
api.getMember = function(req, res, next) {
|
||||
var cid = req.params.cid;
|
||||
var uid = req.params.uid;
|
||||
|
||||
req.params.memberId = uid;
|
||||
req.params.challengeId = cid;
|
||||
v3MembersController.getChallengeMemberProgress.handler(req, res, next)
|
||||
.then(result => {
|
||||
let newResult = {
|
||||
profile: {
|
||||
name: result.profile.name,
|
||||
},
|
||||
habits: [],
|
||||
dailys: [],
|
||||
todos: [],
|
||||
rewards: [],
|
||||
};
|
||||
|
||||
let tasks = result.tasks;
|
||||
tasks.forEach(task => {
|
||||
let taskObj = task.toJSONV2();
|
||||
newResult[taskObj.type + 's'].push(taskObj);
|
||||
});
|
||||
|
||||
res.json(newResult);
|
||||
})
|
||||
.catch(next);
|
||||
}
|
||||
|
||||
// CREATE
|
||||
api.create = async function(req, res, next){
|
||||
try {
|
||||
var user = res.locals.user;
|
||||
|
||||
let groupId = req.body.group;
|
||||
let prize = req.body.prize;
|
||||
|
||||
let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true});
|
||||
if (!group) return res.status(404).json({err:"Group." + req.body.group + " not found"});
|
||||
if (!group.isMember(user)) return res.status(404).json({err:"Group." + req.body.group + " not found"});
|
||||
|
||||
if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) {
|
||||
return res.status(401).json({err:"Only the group leader can create challenges"});
|
||||
}
|
||||
|
||||
if (group._id === TAVERN_ID && prize < 1) {
|
||||
return res.status(401).json({err: 'Prize must be at least 1 Gem for public challenges.'})
|
||||
}
|
||||
|
||||
if (prize > 0) {
|
||||
let groupBalance = group.balance && group.leader === user._id ? group.balance : 0;
|
||||
let prizeCost = prize / 4;
|
||||
|
||||
if (prizeCost > user.balance + groupBalance) {
|
||||
return res.status(401).json({err: 'You can\'t afford this prize. Purchase more gems or lower the prize amount.'});
|
||||
}
|
||||
|
||||
if (groupBalance >= prizeCost) {
|
||||
// Group pays for all of prize
|
||||
group.balance -= prizeCost;
|
||||
} else if (groupBalance > 0) {
|
||||
// User pays remainder of prize cost after group
|
||||
let remainder = prizeCost - group.balance;
|
||||
group.balance = 0;
|
||||
user.balance -= remainder;
|
||||
} else {
|
||||
// User pays for all of prize
|
||||
user.balance -= prizeCost;
|
||||
}
|
||||
}
|
||||
|
||||
group.challengeCount += 1;
|
||||
|
||||
req.body.leader = user._id;
|
||||
req.body.official = user.contributor.admin && req.body.official;
|
||||
let challenge = new Challenge(Challenge.sanitize(req.body));
|
||||
|
||||
// First validate challenge so we don't save group if it's invalid (only runs sync validators)
|
||||
let challengeValidationErrors = challenge.validateSync();
|
||||
if (challengeValidationErrors) throw challengeValidationErrors;
|
||||
|
||||
req.body.habits = req.body.habits || [];
|
||||
req.body.todos = req.body.todos || [];
|
||||
req.body.dailys = req.body.dailys || [];
|
||||
req.body.rewards = req.body.rewards || [];
|
||||
|
||||
var chalTasks = req.body.habits.concat(req.body.rewards)
|
||||
.concat(req.body.dailys).concat(req.body.todos)
|
||||
.map(v2Task => Tasks.Task.fromJSONV2(v2Task));
|
||||
|
||||
chalTasks = chalTasks.map(function(task) {
|
||||
var newTask = new Tasks[task.type](Tasks.Task.sanitize(task));
|
||||
newTask.challenge.id = challenge._id;
|
||||
return newTask.save();
|
||||
});
|
||||
|
||||
let results = await Bluebird.all([challenge.save({
|
||||
validateBeforeSave: false, // already validated
|
||||
}), group.save()].concat(chalTasks));
|
||||
let savedChal = results[0];
|
||||
|
||||
await savedChal.syncToUser(user); // (it also saves the user)
|
||||
|
||||
savedChal.getTransformedData({
|
||||
cb (err, transformedChal) {
|
||||
res.status(201).json(transformedChal);
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
api.update = function(req, res, next){
|
||||
var cid = req.params.cid;
|
||||
var user = res.locals.user;
|
||||
var before;
|
||||
var updatedTasks;
|
||||
|
||||
async.waterfall([
|
||||
function(cb){
|
||||
// We first need the original challenge data, since we're going to compare against new & decide to sync users
|
||||
Challenge.findById(cid, cb);
|
||||
},
|
||||
function(chal, cb){
|
||||
if(!chal) return cb({chal: null});
|
||||
|
||||
chal.getTasks(function(err, tasks){
|
||||
cb(err, {
|
||||
chal: chal,
|
||||
tasks: tasks
|
||||
});
|
||||
});
|
||||
},
|
||||
function(_before, cb) {
|
||||
if (!_before.chal) return cb('Challenge ' + cid + ' not found');
|
||||
if (_before.chal.leader != user._id && !user.contributor.admin) return cb({code: 401, err: shared.i18n.t('noPermissionEditChallenge', req.language)});
|
||||
// Update the challenge, since syncing will need the updated challenge. But store `before` we're going to do some
|
||||
// before-save / after-save comparison to determine if we need to sync to users
|
||||
before = {chal: _before.chal, tasks: _before.tasks};
|
||||
var chalAttrs = _.pick(req.body, 'name shortName description date'.split(' '));
|
||||
async.parallel({
|
||||
chal: function(cb1){
|
||||
Challenge.findByIdAndUpdate(cid, {$set:chalAttrs}, {new: true}, cb1);
|
||||
},
|
||||
tasks: function(cb1) {
|
||||
// Convert to map of {id: task} so we can easily match them
|
||||
var _beforeClonedTasks = _before.tasks;
|
||||
updatedTasks = _.object(_.pluck(_beforeClonedTasks, '_id'), _beforeClonedTasks);
|
||||
var newTasks = req.body.habits.concat(req.body.dailys)
|
||||
.concat(req.body.todos).concat(req.body.rewards);
|
||||
|
||||
var newTasksObj = _.object(_.pluck(newTasks, '_id'), newTasks);
|
||||
async.forEachOf(newTasksObj, function(newTask, taskId, cb2){
|
||||
// some properties can't be changed
|
||||
newTask = Tasks.Task.sanitize(newTask);
|
||||
// we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances?
|
||||
_.assign(updatedTasks[taskId], shared.ops.updateTask(updatedTasks[taskId].toObject(), {body: newTask}));
|
||||
_before.chal.updateTask(updatedTasks[taskId]).then(cb2).catch(cb2);
|
||||
}, cb1);
|
||||
}
|
||||
}, cb);
|
||||
},
|
||||
], function(err, saved){
|
||||
if(err) {
|
||||
return err.code ? res.json(err.code, err) : next(err);
|
||||
}
|
||||
|
||||
saved.chal.getTransformedData({cb: function(err, newChal){
|
||||
if(err) return next(err);
|
||||
res.json(newChal);
|
||||
}})
|
||||
cid = user = before = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete & close
|
||||
*/
|
||||
api.delete = async function(req, res, next){
|
||||
try {
|
||||
var user = res.locals.user;
|
||||
var cid = req.params.cid;
|
||||
|
||||
let challenge = await Challenge.findOne({_id: req.params.cid}).exec();
|
||||
if (!challenge) return next('Challenge ' + cid + ' not found');
|
||||
if (!challenge.canModify(user)) return next(shared.i18n.t('noPermissionCloseChallenge'));
|
||||
|
||||
// Close channel in background, some ops are run in the background without `await`ing
|
||||
await challenge.closeChal({broken: 'CHALLENGE_DELETED'});
|
||||
res.sendStatus(200);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select Winner & Close
|
||||
*/
|
||||
api.selectWinner = async function(req, res, next) {
|
||||
try {
|
||||
if (!req.query.uid) return res.status(401).json({err: 'Must select a winner'});
|
||||
|
||||
let challenge = await Challenge.findOne({_id: req.params.cid}).exec();
|
||||
if (!challenge) return next('Challenge ' + req.params.cid + ' not found');
|
||||
if (!challenge.canModify(res.locals.user)) return next(shared.i18n.t('noPermissionCloseChallenge'));
|
||||
|
||||
let winner = await User.findOne({_id: req.query.uid}).exec();
|
||||
if (!winner || winner.challenges.indexOf(challenge._id) === -1) return next('Winner ' + req.query.uid + ' not found.');
|
||||
|
||||
// Close channel in background, some ops are run in the background without `await`ing
|
||||
await challenge.closeChal({broken: 'CHALLENGE_CLOSED', winner});
|
||||
res.respond(200, {});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
api.join = async function(req, res, next){
|
||||
try {
|
||||
var user = res.locals.user;
|
||||
var cid = req.params.cid;
|
||||
|
||||
let challenge = await Challenge.findOne({ _id: cid });
|
||||
if (!challenge) return next(shared.i18n.t('challengeNotFound'));
|
||||
if (challenge.isMember(user)) return next(shared.i18n.t('userAlreadyInChallenge'));
|
||||
|
||||
let group = await Group.getGroup({user, groupId: challenge.group, optionalMembership: true});
|
||||
if (!group || !challenge.hasAccess(user, group)) return next(shared.i18n.t('challengeNotFound'));
|
||||
|
||||
challenge.memberCount += 1;
|
||||
|
||||
// Add all challenge's tasks to user's tasks and save the challenge
|
||||
await Bluebird.all([challenge.syncToUser(user), challenge.save()]);
|
||||
|
||||
challenge.getTransformedData({
|
||||
cb (err, transformedChal) {
|
||||
transformedChal._isMember = true;
|
||||
res.json(transformedChal);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
api.leave = async function(req, res, next){
|
||||
try {
|
||||
var user = res.locals.user;
|
||||
var cid = req.params.cid;
|
||||
// whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided
|
||||
var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all';
|
||||
|
||||
let challenge = await Challenge.findOne({ _id: cid });
|
||||
if (!challenge) return next(shared.i18n.t('challengeNotFound'));
|
||||
|
||||
let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy'});
|
||||
if (!group || !challenge.canView(user, group)) return next(shared.i18n.t('challengeNotFound'));
|
||||
|
||||
if (!challenge.isMember(user)) return next(shared.i18n.t('challengeMemberNotFound'));
|
||||
|
||||
challenge.memberCount -= 1;
|
||||
|
||||
// Unlink challenge's tasks from user's tasks and save the challenge
|
||||
await Bluebird.all([challenge.unlinkTasks(user, keep), challenge.save()]);
|
||||
|
||||
challenge.getTransformedData({
|
||||
cb (err, transformedChal) {
|
||||
transformedChal._isMember = false;
|
||||
res.json(transformedChal);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
import { removeFromArray } from '../../libs/api-v3/collectionManipulators';
|
||||
|
||||
api.unlink = async function(req, res, next) {
|
||||
try {
|
||||
var user = res.locals.user;
|
||||
var tid = req.params.id;
|
||||
var cid;
|
||||
if (!req.query.keep)
|
||||
return res.status(400).json({err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'});
|
||||
|
||||
let keep = req.query.keep;
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: tid,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
|
||||
if (!task) return next(shared.i18n.t('taskNotFound'));
|
||||
if (!task.challenge.id) return next(shared.i18n.t('cantOnlyUnlinkChalTask'));
|
||||
|
||||
cid = task.challenge.id;
|
||||
if (keep === 'keep') {
|
||||
task.challenge = {};
|
||||
await task.save();
|
||||
} else { // remove
|
||||
if (task.type !== 'todo' || !task.completed) { // eslint-disable-line no-lonely-if
|
||||
removeFromArray(user.tasksOrder[`${task.type}s`], tid);
|
||||
await Bluebird.all([user.save(), task.remove()]);
|
||||
} else {
|
||||
await task.remove();
|
||||
}
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
47
website/server/controllers/api-v2/coupon.js
Normal file
47
website/server/controllers/api-v2/coupon.js
Normal file
@@ -0,0 +1,47 @@
|
||||
var _ = require('lodash');
|
||||
import {
|
||||
model as Coupon,
|
||||
} from '../../models/coupon';
|
||||
var api = module.exports;
|
||||
var csvStringify = require('csv-stringify');
|
||||
var async = require('async');
|
||||
|
||||
api.ensureAdmin = function(req, res, next) {
|
||||
if (!res.locals.user.contributor.sudo) return res.status(401).json({err:"You don't have admin access"});
|
||||
next();
|
||||
}
|
||||
|
||||
api.generateCoupons = function(req,res,next) {
|
||||
let count = Number(req.query.count);
|
||||
Coupon.generate(req.params.event, count, function(err){
|
||||
if(err) return next(err);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
}
|
||||
|
||||
api.getCoupons = function(req,res,next) {
|
||||
var options = {sort:'seq'};
|
||||
if (req.query.limit) options.limit = req.query.limit;
|
||||
if (req.query.skip) options.skip = req.query.skip;
|
||||
Coupon.find({},{}, options, function(err,coupons){
|
||||
let output = [['code']].concat(_.map(coupons, function(c){
|
||||
return [c._id];
|
||||
}))
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'text/csv',
|
||||
'Content-disposition': 'attachment; filename=habitica-coupons.csv',
|
||||
});
|
||||
csvStringify(output, (err, csv) => {
|
||||
if (err) return next(err);
|
||||
res.status(200).send(csv);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
api.enterCode = function(req,res,next) {
|
||||
Coupon.apply(res.locals.user,req.params.code,function(err,user){
|
||||
if (err) return res.status(400).json({err:err});
|
||||
res.json(user);
|
||||
});
|
||||
}
|
||||
153
website/server/controllers/api-v2/dataexport.js
Normal file
153
website/server/controllers/api-v2/dataexport.js
Normal file
@@ -0,0 +1,153 @@
|
||||
var _ = require('lodash');
|
||||
var express = require('express');
|
||||
var csvStringify = require('csv-stringify');
|
||||
var nconf = require('nconf');
|
||||
var moment = require('moment');
|
||||
var js2xmlparser = require("js2xmlparser");
|
||||
var pd = require('pretty-data').pd;
|
||||
import {
|
||||
model as User,
|
||||
} from '../../models/user';
|
||||
|
||||
// Avatar screenshot/static-page includes
|
||||
//var Pageres = require('pageres'); //https://github.com/sindresorhus/pageres
|
||||
//var AWS = require('aws-sdk');
|
||||
//AWS.config.update({accessKeyId: nconf.get("S3:accessKeyId"), secretAccessKey: nconf.get("S3:secretAccessKey")});
|
||||
//var s3Stream = require('s3-upload-stream')(new AWS.S3()); //https://github.com/nathanpeck/s3-upload-stream
|
||||
//var bucket = nconf.get("S3:bucket");
|
||||
//var request = require('request');
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------
|
||||
Data export
|
||||
------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
var dataexport = module.exports;
|
||||
|
||||
dataexport.history = function(req, res) {
|
||||
var user = res.locals.user;
|
||||
var output = [
|
||||
["Task Name", "Task ID", "Task Type", "Date", "Value"]
|
||||
];
|
||||
_.each(user.tasks, function(task) {
|
||||
_.each(task.history, function(history) {
|
||||
output.push([
|
||||
task.text,
|
||||
task.id,
|
||||
task.type,
|
||||
moment(history.date).format("MM-DD-YYYY HH:mm:ss"),
|
||||
history.value
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'text/csv',
|
||||
'Content-disposition': 'attachment; filename=habitica-tasks-history.csv',
|
||||
});
|
||||
|
||||
csvStringify(output, (err, csv) => {
|
||||
if (err) return next(err);
|
||||
res.status(200).send(csv);
|
||||
});
|
||||
};
|
||||
|
||||
var userdata = function(user) {
|
||||
if(user.auth && user.auth.local) {
|
||||
delete user.auth.local.salt;
|
||||
delete user.auth.local.hashed_password;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
dataexport.leanuser = function(req, res, next) {
|
||||
User.findOne({_id: res.locals.user._id}).lean().exec(function(err, user) {
|
||||
if (err) return res.status(500).json({err: err});
|
||||
if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND);
|
||||
res.locals.user = user;
|
||||
return next();
|
||||
});
|
||||
};
|
||||
|
||||
dataexport.userdata = {
|
||||
xml: function(req, res) {
|
||||
var user = userdata(res.locals.user);
|
||||
return res.xml({data: JSON.stringify(user), rootname: 'user'});
|
||||
},
|
||||
json: function(req, res) {
|
||||
var user = userdata(res.locals.user);
|
||||
return res.jsonstring(user);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------
|
||||
Express Extensions (should be refactored into a module)
|
||||
------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
var expressres = express.response || http.ServerResponse.prototype;
|
||||
|
||||
expressres.xml = function(obj, headers, status) {
|
||||
var body = '';
|
||||
this.charset = this.charset || 'utf-8';
|
||||
this.header('Content-Type', 'text/xml');
|
||||
this.header('Content-Disposition', 'attachment');
|
||||
body = pd.xml(js2xmlparser(obj.rootname,obj.data));
|
||||
return this.send(body, headers, status);
|
||||
};
|
||||
|
||||
expressres.jsonstring = function(obj, headers, status) {
|
||||
var body = '';
|
||||
this.charset = this.charset || 'utf-8';
|
||||
this.header('Content-Type', 'application/json');
|
||||
this.header('Content-Disposition', 'attachment');
|
||||
body = pd.json(JSON.stringify(obj));
|
||||
return this.send(body, headers, status);
|
||||
};
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------
|
||||
Static page and image screenshot of avatar
|
||||
------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
dataexport.avatarPage = function(req, res) {
|
||||
User.findById(req.params.uuid).select('stats profile items achievements preferences backer contributor').exec(function(err, user){
|
||||
res.render('avatar-static', {
|
||||
title: user.profile.name,
|
||||
env: _.defaults({user:user}, res.locals.habitrpg)
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
dataexport.avatarImage = function(req, res, next) {
|
||||
var filename = 'avatars/'+req.params.uuid+'.png';
|
||||
request.head('https://'+bucket+'.s3.amazonaws.com/'+filename, function(err,response,body) {
|
||||
// cache images for 10 minutes on aws, else upload a new one
|
||||
if (response.statusCode==200 && moment().diff(response.headers['last-modified'], 'minutes') < 10)
|
||||
return res.redirect(301, 'https://' + bucket + '.s3.amazonaws.com/' + filename);
|
||||
new Pageres()//{delay:1}
|
||||
.src(nconf.get('BASE_URL') + '/export/avatar-' + req.params.uuid + '.html', ['140x147'], {crop: true, filename: filename.replace('.png', '')})
|
||||
.run()
|
||||
.then(function (file) {
|
||||
var upload = s3Stream.upload({
|
||||
Bucket: bucket,
|
||||
Key: filename,
|
||||
ACL: "public-read",
|
||||
StorageClass: "REDUCED_REDUNDANCY",
|
||||
ContentType: "image/png",
|
||||
Expires: +moment().add({minutes: 3})
|
||||
});
|
||||
upload.on('error', function (err) {
|
||||
next(err);
|
||||
});
|
||||
upload.on('uploaded', function (details) {
|
||||
res.redirect(details.Location);
|
||||
});
|
||||
file[0].pipe(upload);
|
||||
}).catch(next);
|
||||
})
|
||||
};
|
||||
1222
website/server/controllers/api-v2/groups.js
Normal file
1222
website/server/controllers/api-v2/groups.js
Normal file
File diff suppressed because it is too large
Load Diff
89
website/server/controllers/api-v2/hall.js
Normal file
89
website/server/controllers/api-v2/hall.js
Normal file
@@ -0,0 +1,89 @@
|
||||
var _ = require('lodash');
|
||||
var nconf = require('nconf');
|
||||
var async = require('async');
|
||||
var shared = require('../../../../common');
|
||||
import {
|
||||
model as User,
|
||||
} from '../../models/user';
|
||||
import {
|
||||
model as Group,
|
||||
} from '../../models/group';
|
||||
var api = module.exports;
|
||||
|
||||
api.ensureAdmin = function(req, res, next) {
|
||||
var user = res.locals.user;
|
||||
if (!(user.contributor && user.contributor.admin)) return res.status(401).json({err:"You don't have admin access"});
|
||||
next();
|
||||
}
|
||||
|
||||
api.getHeroes = function(req,res,next) {
|
||||
User.find({'contributor.level':{$gt:0}})
|
||||
.select('contributor backer balance profile.name')
|
||||
.sort('-contributor.level')
|
||||
.exec(function(err, users){
|
||||
if (err) return next(err);
|
||||
res.json(users);
|
||||
});
|
||||
}
|
||||
|
||||
api.getPatrons = function(req,res,next){
|
||||
var page = req.query.page || 0,
|
||||
perPage = 50;
|
||||
User.find({'backer.tier':{$gt:0}})
|
||||
.select('contributor backer profile.name')
|
||||
.sort('-backer.tier')
|
||||
.skip(page*perPage)
|
||||
.limit(perPage)
|
||||
.exec(function(err, users){
|
||||
if (err) return next(err);
|
||||
res.json(users);
|
||||
});
|
||||
}
|
||||
|
||||
api.getHero = function(req,res,next) {
|
||||
User.findById(req.params.uid)
|
||||
.select('contributor balance profile.name purchased items')
|
||||
.select('auth.local.username auth.local.email auth.facebook auth.blocked')
|
||||
.exec(function(err, user){
|
||||
if (err) return next(err)
|
||||
if (!user) return res.status(400).json({err:'User not found'});
|
||||
res.json(user);
|
||||
});
|
||||
}
|
||||
|
||||
api.updateHero = function(req,res,next) {
|
||||
async.waterfall([
|
||||
function(cb){
|
||||
User.findById(req.params.uid, cb);
|
||||
},
|
||||
function(member, cb){
|
||||
if (!member) return res.status(404).json({err: "User not found"});
|
||||
member.balance = req.body.balance || 0;
|
||||
var newTier = req.body.contributor.level; // tier = level in this context
|
||||
var oldTier = member.contributor && member.contributor.level || 0;
|
||||
if (newTier > oldTier) {
|
||||
member.flags.contributor = true;
|
||||
var gemsPerTier = {1:3, 2:3, 3:3, 4:4, 5:4, 6:4, 7:4, 8:0, 9:0}; // e.g., tier 5 gives 4 gems. Tier 8 = moderator. Tier 9 = staff
|
||||
var tierDiff = newTier - oldTier; // can be 2+ tier increases at once
|
||||
while (tierDiff) {
|
||||
member.balance += gemsPerTier[newTier] / 4; // balance is in $
|
||||
tierDiff--;
|
||||
newTier--; // give them gems for the next tier down if they weren't aready that tier
|
||||
}
|
||||
}
|
||||
member.contributor = req.body.contributor;
|
||||
member.purchased.ads = req.body.purchased.ads;
|
||||
if (member.contributor.level >= 6) member.items.pets['Dragon-Hydra'] = 5;
|
||||
if (req.body.itemPath && req.body.itemVal
|
||||
&& req.body.itemPath.indexOf('items.') === 0
|
||||
&& User.schema.paths[req.body.itemPath]) {
|
||||
shared.dotSet(member, req.body.itemPath, req.body.itemVal); // Sanitization at 5c30944 (deemed unnecessary)
|
||||
}
|
||||
if (_.isBoolean(req.body.auth.blocked)) member.auth.blocked = req.body.auth.blocked;
|
||||
member.save(cb);
|
||||
}
|
||||
], function(err, saved){
|
||||
if (err) return next(err);
|
||||
res.status(204).json({});
|
||||
})
|
||||
}
|
||||
139
website/server/controllers/api-v2/members.js
Normal file
139
website/server/controllers/api-v2/members.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import {
|
||||
model as groups,
|
||||
chatDefaults,
|
||||
} from '../../models/group';
|
||||
import {
|
||||
model as User,
|
||||
} from '../../models/user';
|
||||
let partyFields = require('./groups').partyFields;
|
||||
var api = module.exports;
|
||||
var async = require('async');
|
||||
var _ = require('lodash');
|
||||
var shared = require('../../../../common');
|
||||
var utils = require('../../libs/api-v2/utils');
|
||||
var nconf = require('nconf');
|
||||
var pushNotify = require('./pushNotifications');
|
||||
|
||||
var fetchMember = function(uuid, restrict){
|
||||
return function(cb){
|
||||
var q = User.findById(uuid);
|
||||
if (restrict) q.select(partyFields);
|
||||
q.exec(function(err, member){
|
||||
if (err) return cb(err);
|
||||
if (!member) return cb({code:404, err: 'User not found'});
|
||||
return cb(null, member);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var sendErr = function(err, res, next){
|
||||
err.code ? res.status(err.code).json({err: err.err}) : next(err);
|
||||
}
|
||||
|
||||
api.getMember = function(req, res, next) {
|
||||
fetchMember(req.params.uuid, true)(function(err, member){
|
||||
if (err) return sendErr(err, res, next);
|
||||
res.json(member);
|
||||
})
|
||||
}
|
||||
|
||||
api.sendMessage = function(user, member, data){
|
||||
var msg;
|
||||
if (!data.type) {
|
||||
msg = data.message
|
||||
} else {
|
||||
msg = "`Hello " + member.profile.name + ", " + user.profile.name + " has sent you ";
|
||||
if (data.type == 'gems') {
|
||||
var gemAmount = data.gems.amount;
|
||||
var gemLabel = gemAmount > 1 ? "gems" : "gem";
|
||||
msg += gemAmount + " " + gemLabel + "!`";
|
||||
} else {
|
||||
var monthAmount = shared.content.subscriptionBlocks[data.subscription.key].months;
|
||||
var monthLabel = monthAmount > 1 ? "months" : "month";
|
||||
msg += monthAmount + " " + monthLabel + " of subscription!`";
|
||||
}
|
||||
msg += data.message ? data.message : '';
|
||||
}
|
||||
shared.refPush(member.inbox.messages, chatDefaults(msg, user));
|
||||
member.inbox.newMessages++;
|
||||
member._v++;
|
||||
member.markModified('inbox.messages');
|
||||
|
||||
shared.refPush(user.inbox.messages, _.defaults({sent:true}, chatDefaults(msg, member)));
|
||||
user.markModified('inbox.messages');
|
||||
}
|
||||
|
||||
api.sendPrivateMessage = function(req, res, next){
|
||||
var fetchedMember;
|
||||
async.waterfall([
|
||||
fetchMember(req.params.uuid),
|
||||
function(member, cb) {
|
||||
fetchedMember = member;
|
||||
if (~member.inbox.blocks.indexOf(res.locals.user._id) // can't send message if that user blocked me
|
||||
|| ~res.locals.user.inbox.blocks.indexOf(member._id) // or if I blocked them
|
||||
|| member.inbox.optOut) { // or if they've opted out of messaging
|
||||
return cb({code: 401, err: "Can't send message to this user."});
|
||||
}
|
||||
api.sendMessage(res.locals.user, member, {message:req.body.message});
|
||||
async.parallel([
|
||||
function (cb2) { member.save(cb2) },
|
||||
function (cb2) { res.locals.user.save(cb2) }
|
||||
], cb);
|
||||
}
|
||||
], function(err){
|
||||
if (err) return sendErr(err, res, next);
|
||||
|
||||
if(fetchedMember.preferences.emailNotifications.newPM !== false){
|
||||
utils.txnEmail(fetchedMember, 'new-pm', [
|
||||
{name: 'SENDER', content: utils.getUserInfo(res.locals.user, ['name']).name},
|
||||
{name: 'PMS_INBOX_URL', content: '/#/options/groups/inbox'}
|
||||
]);
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
})
|
||||
}
|
||||
|
||||
api.sendGift = function(req, res, next){
|
||||
async.waterfall([
|
||||
fetchMember(req.params.uuid),
|
||||
function(member, cb) {
|
||||
// Gems
|
||||
switch (req.body.type) {
|
||||
case "gems":
|
||||
var amt = req.body.gems.amount / 4,
|
||||
user = res.locals.user;
|
||||
if (member.id == user.id)
|
||||
return cb({code: 401, err: "Cannot send gems to yourself. Try a subscription instead."});
|
||||
if (!amt || amt <=0 || user.balance < amt)
|
||||
return cb({code: 401, err: "Amount must be within 0 and your current number of gems."});
|
||||
member.balance += amt;
|
||||
user.balance -= amt;
|
||||
api.sendMessage(user, member, req.body);
|
||||
|
||||
var byUsername = utils.getUserInfo(user, ['name']).name;
|
||||
|
||||
if(member.preferences.emailNotifications.giftedGems !== false){
|
||||
utils.txnEmail(member, 'gifted-gems', [
|
||||
{name: 'GIFTER', content: byUsername},
|
||||
{name: 'X_GEMS_GIFTED', content: req.body.gems.amount}
|
||||
]);
|
||||
}
|
||||
|
||||
pushNotify.sendNotify(member, shared.i18n.t('giftedGems'), shared.i18n.t('giftedGemsInfo', { amount: req.body.gems.amount, name: byUsername }));
|
||||
|
||||
return async.parallel([
|
||||
function (cb2) { member.save(cb2) },
|
||||
function (cb2) { user.save(cb2) }
|
||||
], cb);
|
||||
case "subscription":
|
||||
return cb();
|
||||
default:
|
||||
return cb({code:400, err:"Body must contain a gems:{amount,fromBalance} or subscription:{months} object"});
|
||||
}
|
||||
}
|
||||
], function(err) {
|
||||
if (err) return sendErr(err, res, next);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
}
|
||||
6
website/server/controllers/api-v2/pushNotifications.js
Normal file
6
website/server/controllers/api-v2/pushNotifications.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// TODO move to /api-v2
|
||||
var api = module.exports;
|
||||
|
||||
api.sendNotify = function(user, title, msg, timeToLive){
|
||||
// Disabled, see libs/api-v3/pushNotifications
|
||||
};
|
||||
40
website/server/controllers/api-v2/unsubscription.js
Normal file
40
website/server/controllers/api-v2/unsubscription.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
model as User,
|
||||
} from '../../models/user';
|
||||
import {
|
||||
model as EmailUnsubscription,
|
||||
} from '../../models/emailUnsubscription';
|
||||
var utils = require('../../libs/api-v2/utils');
|
||||
var i18n = require('../../../../common').i18n;
|
||||
|
||||
var api = module.exports = {};
|
||||
|
||||
api.unsubscribe = function(req, res, next){
|
||||
if(!req.query.code) return res.status(500).json({err: 'Missing unsubscription code.'});
|
||||
|
||||
var data = JSON.parse(utils.decrypt(req.query.code));
|
||||
|
||||
if(data._id){
|
||||
User.update({_id: data._id}, {
|
||||
$set: {'preferences.emailNotifications.unsubscribeFromAll': true}
|
||||
}, {multi: false}, function(err, updateRes){
|
||||
if(err) return next(err);
|
||||
if(updateRes.n !== 1) return res.json(404, {err: 'User not found'});
|
||||
|
||||
res.send('<h1>' + i18n.t('unsubscribedSuccessfully', null, req.language) + '</h1>' + i18n.t('unsubscribedTextUsers', null, req.language));
|
||||
});
|
||||
}else{
|
||||
EmailUnsubscription.findOne({email: data.email}, function(err, doc){
|
||||
if(err) return next(err);
|
||||
var okRes = '<h1>' + i18n.t('unsubscribedSuccessfully', null, req.language) + '</h1>' + i18n.t('unsubscribedTextOthers', null, req.language);
|
||||
|
||||
if(doc) return res.send(okRes);
|
||||
|
||||
EmailUnsubscription.create({email: data.email}, function(err, doc){
|
||||
if(err) return next(err);
|
||||
|
||||
res.send(okRes);
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
1066
website/server/controllers/api-v2/user.js
Normal file
1066
website/server/controllers/api-v2/user.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,22 +4,22 @@ import passport from 'passport';
|
||||
import nconf from 'nconf';
|
||||
import {
|
||||
authWithHeaders,
|
||||
} from '../../middlewares/auth';
|
||||
} from '../../middlewares/api-v3/auth';
|
||||
import {
|
||||
NotAuthorized,
|
||||
BadRequest,
|
||||
NotFound,
|
||||
} from '../../libs/errors';
|
||||
} from '../../libs/api-v3/errors';
|
||||
import Bluebird from 'bluebird';
|
||||
import * as passwordUtils from '../../libs/password';
|
||||
import logger from '../../libs/logger';
|
||||
import * as passwordUtils from '../../libs/api-v3/password';
|
||||
import logger from '../../libs/api-v3/logger';
|
||||
import { model as User } from '../../models/user';
|
||||
import { model as Group } from '../../models/group';
|
||||
import { model as EmailUnsubscription } from '../../models/emailUnsubscription';
|
||||
import { sendTxn as sendTxnEmail } from '../../libs/email';
|
||||
import { decrypt } from '../../libs/encryption';
|
||||
import { send as sendEmail } from '../../libs/email';
|
||||
import pusher from '../../libs/pusher';
|
||||
import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email';
|
||||
import { decrypt } from '../../libs/api-v3/encryption';
|
||||
import { send as sendEmail } from '../../libs/api-v3/email';
|
||||
import pusher from '../../libs/api-v3/pusher';
|
||||
|
||||
let api = {};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
|
||||
import { authWithHeaders, authWithSession } from '../../middlewares/api-v3/auth';
|
||||
import _ from 'lodash';
|
||||
import { model as Challenge } from '../../models/challenge';
|
||||
import {
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
import {
|
||||
NotFound,
|
||||
NotAuthorized,
|
||||
} from '../../libs/errors';
|
||||
} from '../../libs/api-v3/errors';
|
||||
import * as Tasks from '../../models/task';
|
||||
import Bluebird from 'bluebird';
|
||||
import csvStringify from '../../libs/csvStringify';
|
||||
import csvStringify from '../../libs/api-v3/csvStringify';
|
||||
|
||||
let api = {};
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { authWithHeaders } from '../../middlewares/api-v3/auth';
|
||||
import { model as Group } from '../../models/group';
|
||||
import { model as User } from '../../models/user';
|
||||
import {
|
||||
NotFound,
|
||||
NotAuthorized,
|
||||
} from '../../libs/errors';
|
||||
} from '../../libs/api-v3/errors';
|
||||
import _ from 'lodash';
|
||||
import { removeFromArray } from '../../libs/collectionManipulators';
|
||||
import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email';
|
||||
import pusher from '../../libs/pusher';
|
||||
import { removeFromArray } from '../../libs/api-v3/collectionManipulators';
|
||||
import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/api-v3/email';
|
||||
import pusher from '../../libs/api-v3/pusher';
|
||||
import nconf from 'nconf';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import common from '../../../../common';
|
||||
import _ from 'lodash';
|
||||
import { langCodes } from '../../libs/i18n';
|
||||
import { langCodes } from '../../libs/api-v3/i18n';
|
||||
import Bluebird from 'bluebird';
|
||||
import fsCallback from 'fs';
|
||||
import path from 'path';
|
||||
import logger from '../../libs/logger';
|
||||
import logger from '../../libs/api-v3/logger';
|
||||
|
||||
// Transform fs methods that accept callbacks in ones that return promises
|
||||
const fs = {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import csvStringify from '../../libs/csvStringify';
|
||||
import csvStringify from '../../libs/api-v3/csvStringify';
|
||||
import {
|
||||
authWithHeaders,
|
||||
authWithSession,
|
||||
} from '../../middlewares/auth';
|
||||
import { ensureSudo } from '../../middlewares/ensureAccessRight';
|
||||
} from '../../middlewares/api-v3/auth';
|
||||
import { ensureSudo } from '../../middlewares/api-v3/ensureAccessRight';
|
||||
import { model as Coupon } from '../../models/coupon';
|
||||
import _ from 'lodash';
|
||||
import couponCode from 'coupon-code';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user