From 77801e458e96965d47f306b23e1a98b0e68e9736 Mon Sep 17 00:00:00 2001 From: Andrew Bloomgarden Date: Sun, 12 Jan 2014 15:00:47 -0800 Subject: [PATCH 1/5] Change static module name to habitrpgStatic. The way the tests are written now, the habitrpg module definition in static.js was clobbering the one in app.js. In other words, most of the tests weren't testing what they thought they were testing. --- karma.conf.js | 12 +++ public/js/app.js | 4 +- public/js/controllers/authCtrl.js | 167 +++++++++++++++--------------- public/js/static.js | 4 +- test/spec/authCtrlSpec.js | 4 +- views/static/front.jade | 2 +- 6 files changed, 103 insertions(+), 90 deletions(-) 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/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/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/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 From dc368bb652c80919c9d5ae7c2ef5ecdb0bab04dd Mon Sep 17 00:00:00 2001 From: Andrew Bloomgarden Date: Sun, 12 Jan 2014 15:05:15 -0800 Subject: [PATCH 2/5] Remove references to $rootScope.Shared & .Content in non-view code. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead, have them request services that return them so tests don’t need to prepare special state. --- public/js/controllers/challengesCtrl.js | 8 ++++---- public/js/controllers/groupsCtrl.js | 6 +++--- public/js/controllers/inventoryCtrl.js | 7 +++---- public/js/controllers/notificationCtrl.js | 4 ++-- public/js/controllers/rootCtrl.js | 12 ++++++------ public/js/controllers/tasksCtrl.js | 6 +++--- public/js/controllers/userCtrl.js | 8 ++++---- public/js/services/memberServices.js | 10 +++++----- public/js/services/sharedServices.js | 15 +++++++++++++++ public/manifest.json | 3 ++- test/spec/filtersCtrlSpec.js | 12 ++++++------ test/spec/inventoryCtrlSpec.js | 11 +++++------ test/spec/memberServicesSpec.js | 16 +++++----------- test/spec/userServicesSpec.js | 5 ++--- 14 files changed, 65 insertions(+), 58 deletions(-) create mode 100644 public/js/services/sharedServices.js 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/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/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 +}); From 689dddee7f5f08039b69bc57801846a9c16b375c Mon Sep 17 00:00:00 2001 From: Andrew Bloomgarden Date: Sun, 12 Jan 2014 16:55:19 -0800 Subject: [PATCH 3/5] Actually wait for Selenium to start. --- test/run_tests.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 From c9e3b37ff803e8b1ac30b95d0264324e1f08dae5 Mon Sep 17 00:00:00 2001 From: Andrew Bloomgarden Date: Mon, 13 Jan 2014 00:03:01 -0800 Subject: [PATCH 4/5] Rewrite API tests in normal JS. --- test/api.mocha.coffee | 656 ------------------------------------------ test/api.mocha.js | 334 +++++++++++++++++++++ test/mocha.opts | 3 +- 3 files changed, 336 insertions(+), 657 deletions(-) delete mode 100644 test/api.mocha.coffee create mode 100644 test/api.mocha.js 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 From f94ea084d78d7e35e997b00d89ccb7e5faca6381 Mon Sep 17 00:00:00 2001 From: Andrew Bloomgarden Date: Mon, 13 Jan 2014 22:04:16 -0800 Subject: [PATCH 5/5] Update README to account for API tests in JS. --- test/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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