diff --git a/karma.conf.js b/karma.conf.js index a97bbe6c48..38f0411d5e 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -11,10 +11,22 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ + 'public/bower_components/jquery/jquery.js', 'public/bower_components/angular/angular.js', 'public/bower_components/angular-loading-bar/build/loading-bar.min.js', 'public/bower_components/angular-resource/angular-resource.min.js', + 'public/bower_components/angular-sanitize/angular-sanitize.js', + 'public/bower_components/bootstrap/docs/assets/js/bootstrap.js', + 'public/bower_components/angular-bootstrap/ui-bootstrap.js', + 'public/bower_components/angular-bootstrap/ui-bootstrap-tpls.js', + 'public/bower_components/angular-ui-router/release/angular-ui-router.js', + 'public/bower_components/angular-ui/build/angular-ui.js', + 'public/bower_components/angular-ui-utils/ui-utils.min.js', + 'public/bower_components/Angular-At-Directive/src/at.js', + 'public/bower_components/Angular-At-Directive/src/caret.js', 'public/bower_components/angular-mocks/angular-mocks.js', + 'public/bower_components/angular-bindonce/bindonce.js', + 'public/bower_components/ngInfiniteScroll/ng-infinite-scroll.js', 'public/bower_components/marked/lib/marked.js', 'public/bower_components/js-emoji/emoji.js', 'public/bower_components/habitrpg-shared/dist/habitrpg-shared.js', diff --git a/public/js/app.js b/public/js/app.js index f65ace1d79..ed3d79b8e8 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -17,7 +17,7 @@ window.env.t = function(string){ window.habitrpg = angular.module('habitrpg', ['ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'challengeServices', - 'authServices', 'notificationServices', 'guideServices', + 'authServices', 'notificationServices', 'guideServices', 'authCtrl', 'ui.bootstrap', 'ui.keypress', 'ui.router', 'chieffancypants.loadingBar', 'At', 'pasvaz.bindonce', 'infinite-scroll']) // @see https://github.com/angular-ui/ui-router/issues/110 and https://github.com/HabitRPG/habitrpg/issues/1705 @@ -231,4 +231,4 @@ window.habitrpg = angular.module('habitrpg', } }]; $httpProvider.responseInterceptors.push(interceptor); - }]) \ No newline at end of file + }]) diff --git a/public/js/controllers/authCtrl.js b/public/js/controllers/authCtrl.js index b4e195d893..58ae4bc23c 100644 --- a/public/js/controllers/authCtrl.js +++ b/public/js/controllers/authCtrl.js @@ -4,93 +4,94 @@ The authentication controller (login & facebook) */ -habitrpg.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','API_URL', - function($scope, $rootScope, User, $http, $location, $window, API_URL) { - var runAuth; - var showedFacebookMessage; +angular.module('authCtrl', []) + .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','API_URL', + function($scope, $rootScope, User, $http, $location, $window, API_URL) { + var runAuth; + var showedFacebookMessage; - $scope.useUUID = false; - $scope.toggleUUID = function() { - if (showedFacebookMessage === false) { - alert("Until we add Facebook, use your UUID and API Token to log in (found at https://habitrpg.com > Options > Settings)."); - showedFacebookMessage = true; - } - $scope.useUUID = !$scope.useUUID; - }; - - $scope.logout = function() { - localStorage.clear(); - window.location.href = '/logout'; - }; - - runAuth = function(id, token) { - User.authenticate(id, token, function(err) { - $window.location.href = '/'; - //$rootScope.modals.login = false; - }); - }; - - $scope.register = function() { - /*TODO highlight invalid inputs - we have this as a workaround for https://github.com/HabitRPG/habitrpg-mobile/issues/64 - */ - if ($scope.registrationForm.$invalid) { - return; - } - $http.post(API_URL + "/api/v2/register", $scope.registerVals).success(function(data, status, headers, config) { - runAuth(data.id, data.apiToken); - }).error(function(data, status, headers, config) { - if (status === 0) { - $window.alert("Server not currently reachable, try again later"); - } else if (!!data && !!data.err) { - $window.alert(data.err); - } else { - $window.alert("ERROR: " + status); - } - }); - }; - - function errorAlert(data, status, headers, config) { - if (status === 0) { - $window.alert("Server not currently reachable, try again later"); - } else if (!!data && !!data.err) { - $window.alert(data.err); - } else { - $window.alert("ERROR: " + status); - } - } - - $scope.auth = function() { - var data = { - username: $scope.loginUsername, - password: $scope.loginPassword + $scope.useUUID = false; + $scope.toggleUUID = function() { + if (showedFacebookMessage === false) { + alert("Until we add Facebook, use your UUID and API Token to log in (found at https://habitrpg.com > Options > Settings)."); + showedFacebookMessage = true; + } + $scope.useUUID = !$scope.useUUID; }; - if ($scope.useUUID) { - runAuth($scope.loginUsername, $scope.loginPassword); - } else { - $http.post(API_URL + "/api/v2/user/auth/local", data) - .success(function(data, status, headers, config) { - runAuth(data.id, data.token); - }).error(errorAlert); - } - }; - $scope.playButtonClick = function(){ - if (User.authenticated()) { - window.location.href = '/#/tasks'; - } else { - $('#login-modal').modal('show'); - } - } + $scope.logout = function() { + localStorage.clear(); + window.location.href = '/logout'; + }; - $scope.passwordReset = function(email){ - $http.post(API_URL + '/api/v2/user/reset-password', {email:email}) - .success(function(){ - alert('New password sent.'); - }) - .error(function(data){ - alert(data.err); + runAuth = function(id, token) { + User.authenticate(id, token, function(err) { + $window.location.href = '/'; + //$rootScope.modals.login = false; }); + }; + + $scope.register = function() { + /*TODO highlight invalid inputs + we have this as a workaround for https://github.com/HabitRPG/habitrpg-mobile/issues/64 + */ + if ($scope.registrationForm.$invalid) { + return; + } + $http.post(API_URL + "/api/v2/register", $scope.registerVals).success(function(data, status, headers, config) { + runAuth(data.id, data.apiToken); + }).error(function(data, status, headers, config) { + if (status === 0) { + $window.alert("Server not currently reachable, try again later"); + } else if (!!data && !!data.err) { + $window.alert(data.err); + } else { + $window.alert("ERROR: " + status); + } + }); + }; + + function errorAlert(data, status, headers, config) { + if (status === 0) { + $window.alert("Server not currently reachable, try again later"); + } else if (!!data && !!data.err) { + $window.alert(data.err); + } else { + $window.alert("ERROR: " + status); + } + } + + $scope.auth = function() { + var data = { + username: $scope.loginUsername, + password: $scope.loginPassword + }; + if ($scope.useUUID) { + runAuth($scope.loginUsername, $scope.loginPassword); + } else { + $http.post(API_URL + "/api/v2/user/auth/local", data) + .success(function(data, status, headers, config) { + runAuth(data.id, data.token); + }).error(errorAlert); + } + }; + + $scope.playButtonClick = function(){ + if (User.authenticated()) { + window.location.href = '/#/tasks'; + } else { + $('#login-modal').modal('show'); + } + } + + $scope.passwordReset = function(email){ + $http.post(API_URL + '/api/v2/user/reset-password', {email:email}) + .success(function(){ + alert('New password sent.'); + }) + .error(function(data){ + alert(data.err); + }); + } } - } ]); diff --git a/public/js/controllers/challengesCtrl.js b/public/js/controllers/challengesCtrl.js index 636ef0cedb..8c1cd05a57 100644 --- a/public/js/controllers/challengesCtrl.js +++ b/public/js/controllers/challengesCtrl.js @@ -1,7 +1,7 @@ "use strict"; -habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'User', 'Challenges', 'Notification', '$compile', 'Groups', '$state', - function($rootScope, $scope, User, Challenges, Notification, $compile, Groups, $state) { +habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', 'Challenges', 'Notification', '$compile', 'Groups', '$state', + function($rootScope, $scope, Shared, User, Challenges, Notification, $compile, Groups, $state) { // FIXME $scope.challenges needs to be resolved first (see app.js) $scope.groups = Groups.Group.query({type:'party,guilds,tavern'}); @@ -133,7 +133,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'User', 'Challenge //------------------------------------------------------------ $scope.addTask = function(addTo, listDef) { - var task = $rootScope.Shared.taskDefaults({text: listDef.newTask, type: listDef.type}); + var task = Shared.taskDefaults({text: listDef.newTask, type: listDef.type}); addTo.unshift(task); //User.log({op: "addTask", data: task}); //TODO persist delete listDef.newTask; @@ -229,4 +229,4 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'User', 'Challenge $scope.search.group = _.transform($scope.groups, function(m,g){m[g._id] = false}); } -}]); \ No newline at end of file +}]); diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index 23a08bb0dd..d17beab766 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -1,7 +1,7 @@ "use strict"; -habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'API_URL', '$q', 'User', 'Members', '$state', - function($scope, $rootScope, Groups, $http, API_URL, $q, User, Members, $state) { +habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '$http', 'API_URL', '$q', 'User', 'Members', '$state', + function($scope, $rootScope, Shared, Groups, $http, API_URL, $q, User, Members, $state) { $scope.isMember = function(user, group){ return ~(group.members.indexOf(user._id)); @@ -66,7 +66,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A // We watch Members.selectedMember because it's asynchronously set, so would be a hassle to handle updates here $scope.$watch( function() { return Members.selectedMember; }, function (member) { if(member) - member.petCount = $rootScope.Shared.countPets(null, member.items.pets); + member.petCount = Shared.countPets(null, member.items.pets); $scope.profile = member; }); } diff --git a/public/js/controllers/inventoryCtrl.js b/public/js/controllers/inventoryCtrl.js index 212cf3513b..cc4ed11448 100644 --- a/public/js/controllers/inventoryCtrl.js +++ b/public/js/controllers/inventoryCtrl.js @@ -1,8 +1,7 @@ -habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User', - function($rootScope, $scope, $window, User) { +habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User', 'Content', + function($rootScope, $scope, $window, User, Content) { var user = User.user; - var Content = $rootScope.Content; // convenience vars since these are accessed frequently @@ -135,4 +134,4 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User', $scope.closeQuest(); } } -]); \ No newline at end of file +]); diff --git a/public/js/controllers/notificationCtrl.js b/public/js/controllers/notificationCtrl.js index 2ba427ab11..a93deb9b1a 100644 --- a/public/js/controllers/notificationCtrl.js +++ b/public/js/controllers/notificationCtrl.js @@ -1,7 +1,7 @@ 'use strict'; habitrpg.controller('NotificationCtrl', - ['$scope', '$rootScope', 'User', 'Guide', 'Notification', function ($scope, $rootScope, User, Guide, Notification) { + ['$scope', '$rootScope', 'Shared', 'User', 'Guide', 'Notification', function ($scope, $rootScope, Shared, User, Guide, Notification) { $rootScope.$watch('user.stats.hp', function(after, before) { if (after == before) return; @@ -71,7 +71,7 @@ habitrpg.controller('NotificationCtrl', $rootScope.$watch('user.items.pets', function(after, before){ if(_.size(after) === _.size(before) || - $rootScope.Shared.countPets(null, after) < 90) return; + Shared.countPets(null, after) < 90) return; User.user.achievements.beastMaster = true; $rootScope.modals.achievements.beastMaster = true; }, true); diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index dd652eee74..7b08e0aab8 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -3,8 +3,8 @@ /* Make user and settings available for everyone through root scope. */ -habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', - function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups) { +habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', + function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content) { var user = User.user; var initSticky = _.once(function(){ @@ -20,8 +20,8 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $rootScope.moment = window.moment; $rootScope._ = window._; $rootScope.settings = User.settings; - $rootScope.Shared = window.habitrpgShared; - $rootScope.Content = window.habitrpgShared.content; + $rootScope.Shared = Shared; + $rootScope.Content = Content; // Angular UI Router $rootScope.$state = $state; @@ -45,10 +45,10 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ // count pets, mounts collected totals, etc $rootScope.countExists = function(items) {return _.reduce(items,function(m,v){return m+(v?1:0)},0)} - $rootScope.petCount = $rootScope.Shared.countPets(null, User.user.items.pets); + $rootScope.petCount = Shared.countPets(null, User.user.items.pets); $rootScope.$watch('user.items.pets', function(pets){ - $rootScope.petCount = $rootScope.Shared.countPets($rootScope.countExists(pets), User.user.items.pets); + $rootScope.petCount = Shared.countPets($rootScope.countExists(pets), User.user.items.pets); }, true); $scope.safeApply = function(fn) { diff --git a/public/js/controllers/tasksCtrl.js b/public/js/controllers/tasksCtrl.js index cf234fbb3c..5dd8fc9369 100644 --- a/public/js/controllers/tasksCtrl.js +++ b/public/js/controllers/tasksCtrl.js @@ -1,7 +1,7 @@ "use strict"; -habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'API_URL', '$timeout', - function($scope, $rootScope, $location, User, Notification, $http, API_URL, $timeout) { +habitrpg.controller("TasksCtrl", ['$scope', '$location', 'User','Notification', '$http', 'API_URL', '$timeout', 'Shared', + function($scope, $location, User, Notification, $http, API_URL, $timeout, Shared) { $scope.obj = User.user; // used for task-lists $scope.user = User.user; @@ -138,7 +138,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N */ $scope.$watch('user.items.gear.equipped', function(){ - $scope.itemStore = $rootScope.Shared.updateStore(User.user); + $scope.itemStore = Shared.updateStore(User.user); },true); $scope.buy = function(item) { diff --git a/public/js/controllers/userCtrl.js b/public/js/controllers/userCtrl.js index 0f117ffaee..ce3c629d82 100644 --- a/public/js/controllers/userCtrl.js +++ b/public/js/controllers/userCtrl.js @@ -1,9 +1,9 @@ "use strict"; -habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$http', '$state', 'Guide', - function($rootScope, $scope, $location, User, $http, $state, Guide) { +habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$http', '$state', 'Guide', 'Shared', + function($rootScope, $scope, $location, User, $http, $state, Guide, Shared) { $scope.profile = User.user; - $scope.profile.petCount = $rootScope.Shared.countPets(null, $scope.profile.items.pets); + $scope.profile.petCount = Shared.countPets(null, $scope.profile.items.pets); $scope.hideUserAvatar = function() { $(".userAvatar").hide(); }; @@ -25,7 +25,7 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ User.user.ops.changeClass({query:{class:klass}}); $scope.selectedClass = undefined; - $rootScope.Shared.updateStore(User.user); + Shared.updateStore(User.user); $state.go('options.profile.stats'); window.setTimeout(Guide.classesTour, 10); } diff --git a/public/js/services/memberServices.js b/public/js/services/memberServices.js index 73a9c6f663..29384f4cab 100644 --- a/public/js/services/memberServices.js +++ b/public/js/services/memberServices.js @@ -4,9 +4,9 @@ * Services that persists and retrieves user from localStorage. */ -angular.module('memberServices', ['ngResource']). - factory('Members', ['$rootScope', 'API_URL', '$resource', - function($rootScope, API_URL, $resource) { +angular.module('memberServices', ['ngResource', 'sharedServices']). + factory('Members', ['$rootScope', 'Shared', 'API_URL', '$resource', + function($rootScope, Shared, API_URL, $resource) { var members = {}; var Member = $resource(API_URL + '/api/v2/members/:uid', {uid:'@_id'}); var memberServices = { @@ -65,12 +65,12 @@ angular.module('memberServices', ['ngResource']). // and then for guild) // and if not, fetch them if (members[uid] && members[uid].items && members[uid].items.weapon) { - $rootScope.Shared.wrap(members[uid],false); + Shared.wrap(members[uid],false); self.selectedMember = members[uid]; } else { Member.get({uid: uid}, function(member){ self.populate(member); // lazy load for later - $rootScope.Shared.wrap(member,false); + Shared.wrap(member,false); self.selectedMember = members[member._id]; }); } diff --git a/public/js/services/sharedServices.js b/public/js/services/sharedServices.js new file mode 100644 index 0000000000..31ef0fc120 --- /dev/null +++ b/public/js/services/sharedServices.js @@ -0,0 +1,15 @@ +'use strict'; + +/** + * Services that expose habitrpg-shared + */ + +angular.module('sharedServices', []). + factory('Shared', [function () { + return window.habitrpgShared; + } +]). + factory('Content', ['Shared', function (Shared) { + return Shared.content; + } +]); diff --git a/public/js/static.js b/public/js/static.js index fa38902ee6..ef7e61c195 100644 --- a/public/js/static.js +++ b/public/js/static.js @@ -1,7 +1,7 @@ "use strict"; -window.habitrpg = angular.module('habitrpg', ['notificationServices', 'userServices', 'chieffancypants.loadingBar']) +window.habitrpgStatic = angular.module('habitrpgStatic', ['notificationServices', 'userServices', 'chieffancypants.loadingBar', 'authCtrl']) .constant("API_URL", "") .constant("STORAGE_USER_ID", 'habitrpg-user') .constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings') - .constant("MOBILE_APP", false) \ No newline at end of file + .constant("MOBILE_APP", false) diff --git a/public/manifest.json b/public/manifest.json index 52f6a5951f..3a4025e4d0 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -32,6 +32,7 @@ "bower_components/habitrpg-shared/dist/habitrpg-shared.js", "js/app.js", + "js/services/sharedServices.js", "js/services/authServices.js", "js/services/notificationServices.js", "bower_components/habitrpg-shared/script/userServices.js", @@ -87,4 +88,4 @@ "static.css" ] } -} \ No newline at end of file +} diff --git a/test/README.md b/test/README.md index 7aea53a0db..8115753727 100644 --- a/test/README.md +++ b/test/README.md @@ -1,6 +1,5 @@ We need to clean up this directory. The *real* tests are in spec/ mock/ e2e/ and api.mocha.coffee. We want to: 1. Move all old / deprecated tests from casper, test2, etc into spec, mock, e2e -1. Move api.mocha.coffee to vanilla javascript 1. Remove dependency of api.mocha.coffee on Derby, port it to Mongoose -1. Add better test-coverage \ No newline at end of file +1. Add better test-coverage diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee deleted file mode 100644 index fa966a9e48..0000000000 --- a/test/api.mocha.coffee +++ /dev/null @@ -1,656 +0,0 @@ -_ = require 'lodash' -expect = require 'expect.js' -require 'coffee-script' -async = require 'async' -superagentDefaults = require 'superagent-defaults' - -request = superagentDefaults() - -conf = require("nconf") -conf.argv().env().file({file: __dirname + '../config.json'}).defaults -conf.set('port','1337') - -# Override normal ENV values with nconf ENV values (ENV values are used the same way without nconf) -#FIXME can't get nconf file above to load... -process.env.BASE_URL = conf.get("BASE_URL") -process.env.FACEBOOK_KEY = conf.get("FACEBOOK_KEY") -process.env.FACEBOOK_SECRET = conf.get("FACEBOOK_SECRET") -process.env.NODE_DB_URI = 'mongodb://localhost/habitrpg' - -User = require('../src/models/user').model -Group = require('../src/models/group').model -Challenge = require('../src/models/challenge').model - -app = require '../src/server' - -## monkey-patch expect.js for better diffs on mocha -## see: https://github.com/LearnBoost/expect.js/pull/34 -#origBe = expect.Assertion::be -#expect.Assertion::be = expect.Assertion::equal = (obj) -> -# @_expected = obj -# origBe.call this, obj - -# Custom modules -shared = require 'habitrpg-shared' - -###### Helpers & Variables ###### - -model = null -uuid = null -taskPath = null -baseURL = 'http://localhost:3000/api/v2' - -### - expect().eql expects object keys to be in the correct order, this sorts that out -### - -expectUserEqual = (u1, u2) -> - [u1, u2] = _.map [u1, u2], (obj) -> - 'update__ stats.toNextLevel stats.maxHealth __v'.split(' ').forEach (path) -> - helpers.dotSet path, null, obj - sorted = {} - _.each _.keys(obj).sort(), (k) -> sorted[k] = obj[k] - sorted.tasks = _.sortBy sorted.tasks, 'id' - sorted -# console.log {u1, u2} - expect(u1).to.eql(u2) - -expectSameValues = (obj1, obj2, paths) -> - _.each paths, (k) -> - expect(helpers.dotGet(k,obj1)).to.eql helpers.dotGet(k,obj2) - -expectCode = (res, code) -> - expect(res.body.err).to.be undefined if code is 200 - expect(res.statusCode).to.be code - -###### Specs ###### - -describe 'API', -> - user = null - _id = null - apiToken = null - username = null - password = null - - registerNewUser = (cb, main=true)-> - randomID = shared.uuid() - [username,password] = [randomID,randomID] if main - request.post("#{baseURL}/register") - .set('Accept', 'application/json') - .send({ - username: randomID - password: randomID - confirmPassword: randomID - email: "#{randomID}@gmail.com" - }) - .end (res) -> - return cb(null,res.body) unless main - {_id,apiToken} = res.body - console.log {_id,apiToken} - User.findOne {_id, apiToken}, (err, _user) -> - expect(err).to.not.be.ok - user = _user - request - .set('Accept', 'application/json') - .set('X-API-User', _id) - .set('X-API-Key', apiToken) - cb null, res.body - - before (done)-> - require '../src/server' #start the server - # then wait for it to do it's thing. TODO make a cb-compatible export of server - setTimeout done, 2000 - - describe 'Without token or user id', -> - it '/api/v2/status', (done) -> - request.get("#{baseURL}/status") - .set('Accept', 'application/json') - .end (res) -> - expect(res.statusCode).to.be 200 - expect(res.body.status).to.be 'up' - done() - - it '/api/v2/user', (done) -> - request.get("#{baseURL}/user") - .set('Accept', 'application/json') - .end (res) -> - expect(res.statusCode).to.be 401 - expect(res.body.err).to.be 'You must include a token and uid (user id) in your request' - done() - - describe 'With token and user id', -> - currentUser = null - - before (done) -> - registerNewUser(done,true) - - beforeEach (done) -> - User.findById _id, (err,_user) -> - currentUser = _user - done() - - ############ - # Groups - ############ - - describe 'Groups', -> - group = undefined - - before (done) -> - request.post("#{baseURL}/groups") - .send({name:"TestGroup", type:"party"}) - .end (res) -> - expectCode res, 200 - group = res.body - expect(group.members.length).to.be 1 - expect(group.leader).to.be user._id - done() - - describe 'Challenges', -> - challenge = undefined - updateTodo = undefined - - it 'Creates a challenge', (done) -> - request.post("#{baseURL}/challenges") - .send({ - group:group._id - dailys: [{type:'daily',text:'Challenge Daily'}] - todos: [{type:'todo', text:'Challenge Todo', notes:'Challenge Notes'}] - rewards: [] - habits: [] - official: true - }) - .end (res) -> - expectCode res, 200 - async.parallel [ - (cb) -> User.findById _id, cb - (cb) -> Challenge.findById res.body._id, cb - ], (err, results) -> - [_user,challenge] = [results[0],results[1]] - expect(_user.dailys[_user.dailys.length-1].text).to.be('Challenge Daily') - updateTodo = _user.todos[_user.todos.length-1] - expect(updateTodo.text).to.be('Challenge Todo') - expect(challenge.official).to.be false - done() - - it 'User updates challenge notes', (done) -> - updateTodo.notes = "User overriden notes" - request.put("#{baseURL}/user/tasks/#{updateTodo.id}") - .send(updateTodo) - .end (res) -> - done() #we'll do the check down below - - it 'Change challenge daily', (done) -> - challenge.dailys[0].text = 'Updated Daily' - challenge.todos[0].notes = 'Challenge Updated Todo Notes' - request.post("#{baseURL}/challenges/#{challenge._id}") - .send(challenge) - .end (res) -> - setTimeout -> - User.findById _id, (err,_user) -> - expectCode res, 200 - expect(_user.dailys[_user.dailys.length-1].text).to.be('Updated Daily') - expect(res.body.todos[0].notes).to.be('Challenge Updated Todo Notes') - expect(_user.todos[_user.todos.length-1].notes).to.be('User overriden notes') - currentUser = _user - done() - , 500 # we have to wait a while for users' tasks to be updated, called async on server - - it 'Shows user notes on challenge page', (done) -> - request.get("#{baseURL}/challenges/#{challenge._id}/member/#{_id}") - .end (res) -> - expect(res.body.todos[res.body.todos.length-1].notes).to.be('User overriden notes') - done() - - it 'Complete To-Dos', (done) -> - u = currentUser - request.post("#{baseURL}/user/tasks/#{u.todos[0].id}/up").end (res) -> - request.post("#{baseURL}/user/tasks/#{u.todos[1].id}/up").end (res) -> - request.post("#{baseURL}/user/tasks/").send({type:'todo'}).end (res) -> - request.post("#{baseURL}/user/tasks/clear-completed").end (res) -> - expect(_.size res.body).to.be 2 - done() - - it 'Admin creates a challenge', (done) -> - User.findByIdAndUpdate _id, {$set:{'contributor.admin':true}}, (err,_user) -> - expect(err).to.not.be.ok - - async.parallel [ - (cb)-> - request.post("#{baseURL}/challenges") - .send({group:group._id, dailys: [], todos: [], rewards: [], habits: [], official: false}).end (res) -> - expect(res.body.official).to.be false - cb() - (cb)-> - request.post("#{baseURL}/challenges") - .send({group:group._id, dailys: [], todos: [], rewards: [], habits: [], official: true}).end (res) -> - expect(res.body.official).to.be true - cb() - ], done - - - describe 'Quests', -> - party = undefined - participating = [] - notParticipating = [] - - it 'Invites some members', (done) -> - async.waterfall [ - - # Register new users - (cb) -> - async.parallel [ - (cb2) -> registerNewUser(cb2,false) - (cb2) -> registerNewUser(cb2,false) - (cb2) -> registerNewUser(cb2,false) - ], cb - - # Send them invitations - (_party, cb) -> - party = _party - async.parallel [ - (cb2) -> request.post("#{baseURL}/groups/#{group._id}/invite?uuid=#{party[0]._id}").end (-> cb2()) - (cb2) -> request.post("#{baseURL}/groups/#{group._id}/invite?uuid=#{party[1]._id}").end (-> cb2()) - (cb2) -> request.post("#{baseURL}/groups/#{group._id}/invite?uuid=#{party[2]._id}").end (-> cb2()) - ], cb - - # Accept / Reject - (results, cb) -> - #series since they'll be modifying the same group record - async.series (_.reduce party, (m,v,i) -> - m.push (cb2) -> - request.post("#{baseURL}/groups/#{group._id}/join") - .set('X-API-User', party[i]._id) - .set('X-API-Key', party[i].apiToken) - .end (res) -> cb2() - m - , []), cb - - # Make sure the invites stuck - (whatever, cb) -> - Group.findById group._id, (err, g) -> - expect(g.members.length).to.be 4 - cb() - - ], (err, results) -> - expect(err).to.be.ok - done() - - it 'Starts a quest', (done) -> - async.waterfall [ - (cb)-> - request.post("#{baseURL}/groups/#{group._id}/questAccept?key=evilsanta") - .end (res) -> - expectCode(res, 401) - User.findByIdAndUpdate _id, {$set:'items.quests.evilsanta':1}, cb - (_user,cb)-> - request.post("#{baseURL}/groups/#{group._id}/questAccept?key=evilsanta") - .end (res) -> - expectCode(res, 200) - Group.findById group._id,cb - (_group,cb)-> - group = _group #refresh local group - expect(group.quest.key).to.be 'evilsanta' - - async.series (_.reduce party, (m,v,i) -> - m.push (cb2) -> - request.post("#{baseURL}/groups/#{group._id}/questAccept") - .set('X-API-User', party[i]._id) - .set('X-API-Key', party[i].apiToken) - .end (res) -> cb2() - m - , []), cb - - ], done - - it "Doesn't include people who aren't participating" - - -# ############ -# # Batch Update -# ############ -# -# describe 'Batch Update', -> -# -# it 'POST /api/v1/batch-update', (done) -> -# userBefore = _.cloneDeep(currentUser) -# -# ops = [ -# # Good scores -# op: 'score', params: {id:user.habits[0].id, direction: 'up'} -# op: 'score', params: {id:user.habits[1].id, direction: 'down'} -# op: 'score', params: {id:user.dailys[0].id, direction: 'up'} -# op: 'score', params: {id:user.todos[0].id, direction: 'up'} -# ] -# -# request.post("#{baseURL}/user/batch-update") -# .send(ops) -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# #expectUserEqual(userBefore, res.body) -# done() -# -# -# ############ -# # To Be Updated (these are old v1 tests which haven't been touched in over 6 months, need to be portd to new API tests or deleted) -# ############ -# -# it.skip 'POST /api/v2/batch-update (handles corrupt values)', (done) -> -# registerNewUser (_res) -> -# # corrupt the tasks, and let's see how the server handles this -# ids = _res.dailyIds -# _res.tasks[ids[0]].value = NaN -# _res.tasks[ids[1]].value = undefined -# _res.tasks[ids[2]] = {} -# _res.tasks["undefined"] = {} -# -# _res.stats.hp = _res.stats.gp = NaN -# -# _res.lastCron = +new Date('08/13/2013') -# -# ops = [ -# op: 'score', task: _res.tasks[ids[0]], dir: 'up' -# ] -# -# model.set "users.#{_res.id}", _res, -> -# request.post("#{baseURL}/user/batch-update") -# .set('Accept', 'application/json') -# .set('X-API-User', _res.id) -# .set('X-API-Key', _res.apiToken) -# .send(ops) -# .end (res) -> -# expect(res.statusCode).to.be 200 -# console.log {stats:res.body.stats, tasks:res.body.tasks} -# done() -# -# -# #FIXME figure out how to compare the objects -# it.skip 'GET /api/v1/user', (done) -> -# request.get("#{baseURL}/user") -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# expect(res.body.id).not.to.be.empty() -# self = _.clone(currentUser) -# delete self.apiToken -# self.stats.toNextLevel = 150 -# self.stats.maxHealth = 50 -# -# expectUserEqual(res.body, self) -# done() -# -# it.skip 'GET /api/v1/user/task/:id', (done) -> -# tid = _.pluck(currentUser.tasks, 'id')[0] -# request.get("#{baseURL}/user/task/#{tid}") -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# expect(res.body).to.eql currentUser.tasks[tid] -# done() -# -# it.skip 'POST /api/v1/user/task', (done) -> -# request.post("#{baseURL}/user/task") -# .send({title: 'Title', text: 'Text', type: 'habit'}) -# .end (res) -> -# query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) -# query.fetch (err, user) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 201 -# expect(res.body.id).not.to.be.empty() -# # Ensure that user owns the newly created object -# saved = user.get("tasks.#{res.body.id}") -# expect(saved).to.be.an('object') -# done() -# -# it.skip 'POST /api/v1/user/task (without type)', (done) -> -# request.post("#{baseURL}/user/task") -# .send({}) -# .end (res) -> -# expect(res.body.err).to.be 'type must be habit, todo, daily, or reward' -# expect(res.statusCode).to.be 400 -# done() -# -# it.skip 'POST /api/v1/user/task (only type)', (done) -> -# request.post("#{baseURL}/user/task") -# .send(type: 'habit') -# .end (res) -> -# query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) -# query.fetch (err, user) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 201 -# expect(res.body.id).not.to.be.empty() -# # Ensure that user owns the newly created object -# expect(user.get().tasks[res.body.id]).to.be.an('object') -# # Ensure that value gets set to 0 since not otherwise specified -# expect(user.get().tasks[res.body.id].value).to.be.equal(0) -# done() -# -# it.skip 'PUT /api/v1/user/task/:id', (done) -> -# tid = _.pluck(currentUser.tasks, 'id')[0] -# request.put("#{baseURL}/user/task/#{tid}") -# .send(text: 'bye') -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# currentUser.tasks[tid].text = 'bye' -# expectSameValues res.body, currentUser.tasks[tid], ['id','type','text'] -# #expect(res.body).to.eql currentUser.tasks[tid] -# done() -# -# it.skip 'PUT /api/v1/user/task/:id (shouldnt update type)', (done) -> -# tid = _.pluck(currentUser.tasks, 'id')[1] -# type = if currentUser.tasks[tid].type is 'habit' then 'daily' else 'habit' -# request.put("#{baseURL}/user/task/#{tid}") -# .send(type: type, text: 'fishman') -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# currentUser.tasks[tid].text = 'fishman' -# expect(res.body).to.eql currentUser.tasks[tid] -# done() -# -# it.skip 'PUT /api/v1/user/task/:id (update notes)', (done) -> -# tid = _.pluck(currentUser.tasks, 'id')[2] -# request.put("#{baseURL}/user/task/#{tid}") -# .send(text: 'hi',notes:'foobar matey') -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# currentUser.tasks[tid].text = 'hi' -# currentUser.tasks[tid].notes = 'foobar matey' -# expect(res.body).to.eql currentUser.tasks[tid] -# done() -# -# it.skip 'GET /api/v1/user/tasks', (done) -> -# request.get("#{baseURL}/user/tasks") -# .end (res) -> -# query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) -# query.fetch (err, user) -> -# expect(res.body.err).to.be undefined -# expect(user.get()).to.be.ok() -# expect(res.statusCode).to.be 200 -# model.ref '_user', user -# tasks = [] -# for type in ['habit','todo','daily','reward'] -# model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" -# tasks = tasks.concat model.get("_#{type}List") -# # Ensure that user owns the tasks -# expect(res.body.length).to.equal tasks.length -# # Ensure that the two sets are equal -# expect(_.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')).length).to.equal 0 -# done() -# -# it.skip 'GET /api/v1/user/tasks (todos)', (done) -> -# request.get("#{baseURL}/user/tasks") -# .query(type:'todo') -# .end (res) -> -# query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) -# query.fetch (err, user) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# model.ref '_user', user -# model.refList "_todoList", "_user.tasks", "_user.todoIds" -# tasks = model.get("_todoList") -# # Ensure that user owns the tasks -# expect(res.body.length).to.equal tasks.length -# # Ensure that the two sets are equal -# expect(_.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')).length).to.equal 0 -# done() -# -# it.skip 'DELETE /api/v1/user/task/:id', (done) -> -# tid = currentUser.habitIds[2] -# request.del("#{baseURL}/user/task/#{tid}") -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 204 -# query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) -# query.fetch (err, user) -> -# expect(user.get('habitIds').indexOf(tid)).to.be -1 -# expect(user.get("tasks.#{tid}")).to.be undefined -# done() -# -# it.skip 'DELETE /api/v1/user/task/:id (no task found)', (done) -> -# tid = "adsfasdfjunkshouldntbeatask" -# request.del("#{baseURL}/user/task/#{tid}") -# .end (res) -> -# expect(res.statusCode).to.be 400 -# expect(res.body.err).to.be 'No task found.' -# done() -# -# it.skip 'POST /api/v1/user/task/:id/up (habit)', (done) -> -# tid = currentUser.habitIds[0] -# request.post("#{baseURL}/user/task/#{tid}/up") -# .send({}) -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# expect(res.body).to.eql { gp: 1, exp: 7.5, lvl: 1, hp: 50, delta: 1 } -# done() -# -# it.skip 'POST /api/v1/user/task/:id/up (daily)', (done) -> -# tid = currentUser.dailyIds[0] -# request.post("#{baseURL}/user/task/#{tid}/up") -# .send({}) -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# expect(res.body).to.eql { gp: 2, exp: 15, lvl: 1, hp: 50, delta: 1 } -# query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) -# query.fetch (err, user) -> -# expect(user.get("tasks.#{tid}.completed")).to.be true -# done() -# -# it.skip 'POST /api/v1/user/task (array)', (done) -> -# habitId = currentUser.habitIds[0] -# dailyId = currentUser.dailyIds[0] -# arr = [{ -# id: habitId -# text: 'hello' -# notes: 'note' -# },{ -# text: 'new task' -# notes: 'notes!' -# },{ -# id: dailyId -# del: true -# }] -# -# request.post("#{baseURL}/user/tasks") -# .send(arr) -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 201 -# -# expectSameValues res.body[0], {id: habitId,text: 'hello',notes: 'note'}, ['id','text','notes'] -# expect(res.body[1].id).to.be.a 'string' -# expect(res.body[1].text).to.be 'new task' -# expect(res.body[1].notes).to.be 'notes!' -# expect(res.body[2]).to.eql deleted: true -# -# query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) -# query.fetch (err, user) -> -# expectSameValues user.get("tasks.#{habitId}"), {id: habitId,text: 'hello',notes: 'note'}, ['id','text','notes'] -# expect(user.get("tasks.#{dailyId}")).to.be undefined -# expectSameValues user.get("tasks.#{res.body[1].id}"), {id: res.body[1].id, text: 'new task', notes: 'notes!'}, ['id','text','notes'] -# done() -# -# it.skip 'PUT /api/v1/user (bad path)', (done) -> -# # These updates should not save, as per the API changes -# userUpdates = -# stats: hp: 30 -# flags: itemsEnabled: true -# tasks: [{ -# text: 'hello2' -# notes: 'note2' -# }] -# -# request.put("#{baseURL}/user") -# .send(userUpdates) -# .end (res) -> -# expect(res.body.err).to.be.ok() -# expect(res.statusCode).to.be 500 -# done() -# -# it.skip 'PUT /api/v1/user', (done) -> -# userBefore = {} -# query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) -# query.fetch (err, user) -> -# userBefore = user.get() -# -# habitId = currentUser.habitIds[0] -# dailyId = currentUser.dailyIds[0] -# updates = {} -# updates['stats.hp'] = 30 -# updates['flags.itemsEnabled'] = true -# updates["tasks.#{habitId}.text"] = 'hello2' -# updates["tasks.#{habitId}.notes"] = 'note2' -# -# request.put("#{baseURL}/user") -# .send(updates) -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# changesWereMade = (obj) -> -# expect(obj.stats.hp).to.be 30 -# expect(obj.flags.itemsEnabled).to.be true -# expectSameValues _.find(obj.tasks,{id:habitId}), {id: habitId,text: 'hello2',notes: 'note2'}, ['id','text','notes'] -# changesWereMade res.body -# query.fetch (err, user) -> -# changesWereMade user.get() -# done() -# -# it.skip 'POST /api/v1/user/auth/local', (done) -> -# userAuth = {username, password} -# request.post("#{baseURL}/user/auth/local") -# .set('Accept', 'application/json') -# .send(userAuth) -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# expect(res.body.id).to.be currentUser.id -# expect(res.body.token).to.be currentUser.apiToken -# done() -# -# it.skip 'POST /api/v1/user/auth/facebook', (done) -> -# id = shared.uuid() -# userAuth = facebook_id: 12345, name: 'Tyler Renelle', email: 'x@y.com' -# newUser = helpers.newUser(true) -# newUser.id = id -# newUser.auth = facebook: -# id: userAuth.facebook_id -# name: userAuth.name -# email: userAuth.email -# model.set "users.#{id}", newUser, -> -# -# request.post("#{baseURL}/user/auth/facebook") -# .set('Accept', 'application/json') -# .send(userAuth) -# .end (res) -> -# expect(res.body.err).to.be undefined -# expect(res.statusCode).to.be 200 -# expect(res.body.id).to.be newUser.id -# #expect(res.body.token).to.be newUser.apiToken -# done() -# -# diff --git a/test/api.mocha.js b/test/api.mocha.js new file mode 100644 index 0000000000..dd21c0347d --- /dev/null +++ b/test/api.mocha.js @@ -0,0 +1,334 @@ +/*jslint node: true */ +/*global describe, before, beforeEach, it*/ +'use strict'; + +var _ = require('lodash'); +var expect = require('expect.js'); +var async = require('async'); +var superagentDefaults = require('superagent-defaults'); + +var request = superagentDefaults(); + +var conf = require('nconf'); +conf.argv().env().file({file: __dirname + '../config.json'}).defaults(); +conf.set('port','1337'); + +// Override normal ENV values with nconf ENV values (ENV values are used the same way without nconf) +// FIXME can't get nconf file above to load... +process.env.BASE_URL = conf.get("BASE_URL"); +process.env.FACEBOOK_KEY = conf.get("FACEBOOK_KEY"); +process.env.FACEBOOK_SECRET = conf.get("FACEBOOK_SECRET"); +process.env.NODE_DB_URI = 'mongodb://localhost/habitrpg'; + +var User = require('../src/models/user').model; +var Group = require('../src/models/group').model; +var Challenge = require('../src/models/challenge').model; + +var app = require('../src/server'); +var shared = require('habitrpg-shared'); + +// ###### Helpers & Variables ###### +var model, uuid, taskPath, +baseURL = 'http://localhost:3000/api/v2'; + +var expectCode = function (res, code) { + if (code == 200) + expect(res.body.err).to.be(undefined); + expect(res.statusCode).to.be(code); +}; + +describe('API', function () { + var user, _id, apiToken, username, password; + + var registerNewUser = function(cb, main) { + if (main === undefined) main = true; + + var randomID = shared.uuid(); + if (main) { + username = password = randomID; + } + + request.post(baseURL + "/register") + .set('Accept', 'application/json') + .send({ + username: randomID, + password: randomID, + confirmPassword: randomID, + email: randomID + "@gmail.com" + }) + .end(function (res) { + if (!main) return cb(null, res.body); + + _id = res.body._id; + apiToken = res.body.apiToken; + User.findOne({_id: _id, + apiToken: apiToken}, + function (err, _user) { + expect(err).to.not.be.ok(); + user = _user; + request + .set('Accept', 'application/json') + .set('X-API-User', _id) + .set('X-API-Key', apiToken); + cb(null, res.body); + }); + }); + }; + + before(function (done) { + require('../src/server'); // start the server + // then wait for it to do it's thing. TODO make a cb-compatible export of server + setTimeout(done, 2000); + }); + + describe('Without token or user id', function () { + it('/api/v2/status', function (done) { + request.get(baseURL + "/status") + .set('Accept', 'application/json') + .end(function (res) { + expect(res.statusCode).to.be(200); + expect(res.body.status).to.be('up'); + done(); + }); + }); + + it('/api/v2/user', function (done) { + request.get(baseURL + "/user") + .set('Accept', 'application/json') + .end(function (res) { + expect(res.statusCode).to.be(401); + expect(res.body.err).to.be('You must include a token and uid (user id) in your request'); + done(); + }); + }); + }); + + describe('With token and user id', function () { + var currentUser; + + before(function (done) { + registerNewUser(done, true); + }); + + beforeEach(function (done) { + User.findById(_id, function (err, _user) { + currentUser = _user; + done(); + }); + }); + + /** + * GROUPS + */ + describe('Groups', function () { + var group; + + before(function (done) { + request.post(baseURL + "/groups") + .send({name:"TestGroup", type:"party"}) + .end(function (res) { + expectCode(res, 200); + group = res.body; + expect(group.members.length).to.be(1); + expect(group.leader).to.be(user._id); + done(); + }); + }); + + describe('Challenges', function () { + var challenge, updateTodo; + + it('Creates a challenge', function (done) { + request.post(baseURL + "/challenges") + .send({ + group: group._id, + dailys: [{type:'daily', text:'Challenge Daily'}], + todos: [{type:'todo', text:'Challenge Todo', notes:'Challenge Notes'}], + rewards: [], + habits: [], + official: true + }) + .end(function (res) { + expectCode(res, 200); + async.parallel([ + function (cb) { User.findById(_id, cb); }, + function (cb) { Challenge.findById(res.body._id, cb); }, + ], function (err, results) { + var _user = results[0]; + challenge = results[1]; + expect(_user.dailys[_user.dailys.length-1].text).to.be('Challenge Daily'); + updateTodo = _user.todos[_user.todos.length-1]; + expect(updateTodo.text).to.be('Challenge Todo'); + expect(challenge.official).to.be(false); + done(); + }); + }); + }); + + it('User updates challenge notes', function (done) { + updateTodo.notes = "User overriden notes"; + request.put(baseURL + "/user/tasks/" + updateTodo.id) + .send(updateTodo) + .end(function (res) { + done(); // we'll do the check down below + }); + }); + + it('Change challenge daily', function (done) { + challenge.dailys[0].text = 'Updated Daily'; + challenge.todos[0].notes = 'Challenge Updated Todo Notes'; + request.post(baseURL + "/challenges/" + challenge._id) + .send(challenge) + .end(function (res) { + setTimeout(function () { + User.findById(_id, function (err,_user) { + expectCode(res, 200); + expect(_user.dailys[_user.dailys.length-1].text).to.be('Updated Daily'); + expect(res.body.todos[0].notes).to.be('Challenge Updated Todo Notes'); + expect(_user.todos[_user.todos.length-1].notes).to.be('User overriden notes'); + currentUser = _user; + done(); + }); + }, 500); // we have to wait a while for users' tasks to be updated, called async on server + }); + }); + + it('Shows user notes on challenge page', function (done) { + request.get(baseURL + "/challenges/" + challenge._id + "/member/" + _id) + .end(function (res) { + expect(res.body.todos[res.body.todos.length-1].notes).to.be('User overriden notes'); + done(); + }); + }); + + it('Complete To-Dos', function (done) { + var u = currentUser; + request.post(baseURL + "/user/tasks/" + u.todos[0].id + "/up").end(function (res) { + request.post(baseURL + "/user/tasks/" + u.todos[1].id + "/up").end(function (res) { + request.post(baseURL + "/user/tasks/").send({type:'todo'}).end(function (res) { + request.post(baseURL + "/user/tasks/clear-completed").end(function (res) { + expect(_.size(res.body)).to.be(2); + done(); + }); + }); + }); + }); + }); + + it('Admin creates a challenge', function (done) { + User.findByIdAndUpdate(_id, {$set:{'contributor.admin':true}}, function (err,_user) { + expect(err).to.not.be.ok(); + + async.parallel([ + function (cb) { + request.post(baseURL + "/challenges") + .send({group:group._id, dailys: [], todos: [], rewards: [], habits: [], official: false}).end(function (res) { + expect(res.body.official).to.be(false); + cb(); + }); + }, + function (cb) { + request.post(baseURL + "/challenges") + .send({group:group._id, dailys: [], todos: [], rewards: [], habits: [], official: true}).end(function (res) { + expect(res.body.official).to.be(true); + cb(); + }); + }], done); + }); + }); + }); + + describe('Quests', function () { + var party, + participating = [], + notParticipating = []; + + it('Invites some members', function (done) { + async.waterfall([ + + // Register new users + function (cb) { + async.parallel([ + function (cb2) { registerNewUser(cb2,false); }, + function (cb2) { registerNewUser(cb2,false); }, + function (cb2) { registerNewUser(cb2,false); } + ], cb); + }, + + // Send them invitations + function (_party, cb) { + party = _party; + var inviteURL = baseURL + "/groups/" + group._id + "/invite?uuid="; + async.parallel([ + function (cb2) { request.post(inviteURL + party[0]._id).end(cb2); }, + function (cb2) { request.post(inviteURL + party[1]._id).end(cb2); }, + function (cb2) { request.post(inviteURL + party[2]._id).end(cb2); } + ], cb); + }, + + // Accept / Reject + function (results, cb) { + // series since they'll be modifying the same group record + async.series(_.reduce(party, function (m,v,i) { + m.push(function (cb2) { + request.post("#{baseURL}/groups/#{group._id}/join") + .set('X-API-User', party[i]._id) + .set('X-API-Key', party[i].apiToken) + .end(cb2); + }); + return m; + }, []), cb); + }, + + // Make sure the invites stuck + function (whatever, cb) { + Group.findById(group._id, function (err, g) { + expect(g.members.length).to.be(4); + cb(); + }); + } + + ], function (err, results) { + expect(err).to.be.ok(); + done(); + }); + }); + + it('Starts a quest', function (done) { + async.waterfall([ + function (cb) { + request.post(baseURL + "/groups/" + group._id + "/questAccept?key=evilsanta") + .end(function (res) { + expectCode(res, 401); + User.findByIdAndUpdate(_id, {$set: {'items.quests.evilsanta':1}}, cb); + }); + }, + function (_user,cb) { + request.post(baseURL + "/groups/" + group._id + "/questAccept?key=evilsanta") + .end(function (res) { + expectCode(res, 200); + Group.findById(group._id, cb); + }); + }, + function (_group,cb) { + group = _group; //refresh local group + expect(group.quest.key).to.be('evilsanta'); + + async.series(_.reduce(party, function (m,v,i) { + m.push(function (cb2) { + request.post(baseURL + "/groups/" + group._id + "/questAccept") + .set('X-API-User', party[i]._id) + .set('X-API-Key', party[i].apiToken) + .end(function () { cb2(); }); + }); + return m; + }, []), cb); + }], done); + }); + + it("Doesn't include people who aren't participating"); + }); + + }); + }); +}); diff --git a/test/mocha.opts b/test/mocha.opts index 48e0516929..4a830cecd9 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,7 +1,8 @@ --colors --reporter spec --timeout 4000 +--check-leaks --growl --debug --compilers coffee:coffee-script ---globals io \ No newline at end of file +--globals io diff --git a/test/run_tests.sh b/test/run_tests.sh index 5530b716fa..d024d35d71 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -16,5 +16,17 @@ NODE_DB_URI="$TEST_DB_URI" PORT=$TEST_SERVER_PORT node ./src/server.js > /dev/nu NODE_PID=$! trap "kill $NODE_PID && curl http://localhost:4444/selenium-server/driver/?cmd=shutDownSeleniumServer" EXIT -sleep 3 # Wait for Selenium +# Wait for selenium +MAX_WAIT=30 +WAITED=0 +until nc -z localhost 4444; do + if [ $WAITED -ge $MAX_WAIT ]; then + echo "Waited $MAX_WAIT seconds, but Selenium never responded" >&2 + kill $NODE_PID + exit 1 + fi + sleep 1 + let 'WAITED+=1' +done + mocha && grunt karma:continuous && ./node_modules/protractor/bin/protractor protractor.conf.js diff --git a/test/spec/authCtrlSpec.js b/test/spec/authCtrlSpec.js index f36a72ea58..9c630e186c 100644 --- a/test/spec/authCtrlSpec.js +++ b/test/spec/authCtrlSpec.js @@ -2,7 +2,7 @@ describe('Auth Controller', function() { - beforeEach(module('habitrpg')); + beforeEach(module('habitrpgStatic')); describe('AuthCtrl', function(){ var scope, ctrl, user, $httpBackend, $window; @@ -35,4 +35,4 @@ describe('Auth Controller', function() { }); }); -}); \ No newline at end of file +}); diff --git a/test/spec/filtersCtrlSpec.js b/test/spec/filtersCtrlSpec.js index c6a7243e1a..d31855abc2 100644 --- a/test/spec/filtersCtrlSpec.js +++ b/test/spec/filtersCtrlSpec.js @@ -4,9 +4,9 @@ describe('Filters Controller', function() { var scope, user; beforeEach(module('habitrpg')); - beforeEach(inject(function($rootScope, $controller) { + beforeEach(inject(function($rootScope, $controller, Shared) { user = {filters: {}}; - window.habitrpgShared.wrap(user); + Shared.wrap(user); scope = $rootScope.$new(); $controller('FiltersCtrl', {$scope: scope, User: {user: user}}); })); @@ -18,11 +18,11 @@ describe('Filters Controller', function() { expect(user.tags[0]).to.have.property('id'); }); - it('toggles tag filtering', function(){ - var tag = {id: window.habitrpgShared.uuid(), name: 'myTag'}; + it('toggles tag filtering', inject(function(Shared){ + var tag = {id: Shared.uuid(), name: 'myTag'}; scope.toggleFilter(tag); expect(user.filters[tag.id]).to.eql(true); scope.toggleFilter(tag); expect(user.filters[tag.id]).to.eql(false); - }) -}); \ No newline at end of file + })) +}); diff --git a/test/spec/inventoryCtrlSpec.js b/test/spec/inventoryCtrlSpec.js index 9cbf9334ff..b4f2df12f6 100644 --- a/test/spec/inventoryCtrlSpec.js +++ b/test/spec/inventoryCtrlSpec.js @@ -4,20 +4,19 @@ describe('Inventory Controller', function() { var scope, ctrl, user, $rootScope; beforeEach(module('habitrpg')); - beforeEach(inject(function($rootScope, $controller){ + beforeEach(inject(function($rootScope, $controller, Shared){ user = { balance: 4, stats: {gp: 0}, items: {eggs: {Cactus: 1}, hatchingPotions: {Base: 1}, food: {Meat: 1}, pets: {}}, }; - window.habitrpgShared.wrap(user); + Shared.wrap(user); var mockWindow = { confirm: function(msg){ return true; } }; scope = $rootScope.$new(); - $rootScope.Content = window.habitrpgShared.content; ctrl = $controller('InventoryCtrl', {$scope: scope, User: {user: user}, $window: mockWindow}); })); @@ -74,9 +73,9 @@ describe('Inventory Controller', function() { expect(user.items.currentPet).to.eql('Cactus-Base'); }); - it('purchases an egg', function(){ - scope.purchase('eggs', window.habitrpgShared.content.eggs['Wolf']); + it('purchases an egg', inject(function(Content){ + scope.purchase('eggs', Content.eggs['Wolf']); expect(user.balance).to.eql(3.25); expect(user.items.eggs).to.eql({Cactus: 1, Wolf: 1}) - }); + })); }); diff --git a/test/spec/memberServicesSpec.js b/test/spec/memberServicesSpec.js index 394b0809b4..09db1ce698 100644 --- a/test/spec/memberServicesSpec.js +++ b/test/spec/memberServicesSpec.js @@ -6,16 +6,10 @@ describe('memberServices', function() { beforeEach(module('memberServices')); beforeEach(module('habitrpg')); - beforeEach(function(){ - inject(function(_$httpBackend_, $rootScope){ - $httpBackend = _$httpBackend_; - $rootScope.Shared = window.habitrpgShared; - }); - // $rootScope.Shared is set, so now we can inject Members - inject(function(Members){ - members = Members; - }); - }); + beforeEach(inject(function (_$httpBackend_, Members) { + $httpBackend = _$httpBackend_; + members = Members; + })); it('has no members at the beginning', function() { expect(members.members).to.be.an('object'); @@ -43,4 +37,4 @@ describe('memberServices', function() { expect(members.members).to.have.property(uid); }); -}); \ No newline at end of file +}); diff --git a/test/spec/userServicesSpec.js b/test/spec/userServicesSpec.js index 9fea953a3b..9770befb26 100644 --- a/test/spec/userServicesSpec.js +++ b/test/spec/userServicesSpec.js @@ -8,8 +8,7 @@ describe('userServices', function() { beforeEach(function(){ module(function($provide){ - var habitrpgShared = {helpers: {newUser: sinon.spy()}}; - $window = {href: '', alert: sinon.spy(), location: {search: '', pathname: ''}, habitrpgShared: habitrpgShared}; + $window = {href: '', alert: sinon.spy(), location: {search: '', pathname: ''}}; $provide.value('$window', $window); }); @@ -48,4 +47,4 @@ describe('userServices', function() { //TODO where does that null comes from? expect(user.settings.sync.queue).to.eql([null, {}]); }); -}); \ No newline at end of file +}); diff --git a/views/static/front.jade b/views/static/front.jade index c11c381054..6683ccc6bc 100644 --- a/views/static/front.jade +++ b/views/static/front.jade @@ -7,7 +7,7 @@ block title title=env.t('titleFront') block content - div(ng-app='habitrpg') + div(ng-app='habitrpgStatic') #wrap(ng-controller='AuthCtrl') //-include ./header .jumbotron.masthead