From d5ea5c99d297bcd5c0fc7420cbd9294f839be703 Mon Sep 17 00:00:00 2001 From: cdriscol Date: Tue, 28 Jul 2015 17:52:03 -0600 Subject: [PATCH] Adding client test coverage. --- .gitignore | 2 +- bower.json | 2 +- .../articles.client.controller.tests.js | 179 ++++--- .../client/chat.client.controller.tests.js | 90 +++- .../client/services/menus.client.service.js | 342 ++++++------ .../client/header.client.controller.tests.js | 80 ++- .../client/menus.client.service.tests.js | 489 ++++++++++++++++++ .../client/socket.io.client.service.tests.js | 24 + .../controllers/admin.server.controller.js | 6 + .../users.authorization.server.controller.js | 10 +- ...ver.policies.js => admin.server.policy.js} | 2 +- .../server/routes/admin.server.routes.js | 17 +- .../server/routes/users.server.routes.js | 2 +- .../authentication.client.controller.tests.js | 256 +++++---- .../password.client.controller.tests.js | 198 +++++++ .../tests/server/user.server.routes.tests.js | 107 ++++ 16 files changed, 1411 insertions(+), 395 deletions(-) create mode 100644 modules/core/tests/client/menus.client.service.tests.js create mode 100644 modules/core/tests/client/socket.io.client.service.tests.js rename modules/users/server/policies/{admin.server.policies.js => admin.server.policy.js} (96%) create mode 100644 modules/users/tests/client/password.client.controller.tests.js create mode 100644 modules/users/tests/server/user.server.routes.tests.js diff --git a/.gitignore b/.gitignore index b413fc72de..7e67393863 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ public/lib/ app/tests/coverage/ .bower-*/ .idea/ +coverage/ # MEAN.js app and assets # ====================== @@ -68,4 +69,3 @@ mongod *.ntvs* *.njsproj *.sln - diff --git a/bower.json b/bower.json index 8ea5593a60..5e87d4d990 100644 --- a/bower.json +++ b/bower.json @@ -11,7 +11,7 @@ "angular-bootstrap": "~0.13", "angular-ui-utils": "bower", "angular-ui-router": "~0.2", - "angular-file-upload": "~1.1.5" + "angular-file-upload": "1.1.5" }, "resolutions": { "angular": "~1.3" diff --git a/modules/articles/tests/client/articles.client.controller.tests.js b/modules/articles/tests/client/articles.client.controller.tests.js index e05f07ecbb..3ecc3a1b80 100644 --- a/modules/articles/tests/client/articles.client.controller.tests.js +++ b/modules/articles/tests/client/articles.client.controller.tests.js @@ -9,7 +9,9 @@ $httpBackend, $stateParams, $location, - Authentication; + Authentication, + Articles, + mockArticle; // The $resource service augments the response object with methods for updating and deleting the resource. // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match @@ -36,7 +38,7 @@ // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). // This allows us to inject a service but then attach it to a variable // with the same name as the service. - beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_, _Authentication_) { + beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_, _Authentication_, _Articles_) { // Set a new global scope scope = $rootScope.$new(); @@ -45,6 +47,14 @@ $httpBackend = _$httpBackend_; $location = _$location_; Authentication = _Authentication_; + Articles = _Articles_; + + // create mock article + mockArticle = new Articles({ + _id: '525a8422f6d0f87f0e407a33', + title: 'An Article about MEAN', + content: 'MEAN rocks!' + }); // Mock logged in user Authentication.user = { @@ -58,14 +68,8 @@ })); it('$scope.find() should create an array with at least one article object fetched from XHR', inject(function(Articles) { - // Create sample article using the Articles service - var sampleArticle = new Articles({ - title: 'An Article about MEAN', - content: 'MEAN rocks!' - }); - // Create a sample articles array that includes the new article - var sampleArticles = [sampleArticle]; + var sampleArticles = [mockArticle]; // Set GET response $httpBackend.expectGET('api/articles').respond(sampleArticles); @@ -79,99 +83,128 @@ })); it('$scope.findOne() should create an array with one article object fetched from XHR using a articleId URL parameter', inject(function(Articles) { - // Define a sample article object - var sampleArticle = new Articles({ - title: 'An Article about MEAN', - content: 'MEAN rocks!' - }); - // Set the URL parameter - $stateParams.articleId = '525a8422f6d0f87f0e407a33'; + $stateParams.articleId = mockArticle._id; // Set GET response - $httpBackend.expectGET(/api\/articles\/([0-9a-fA-F]{24})$/).respond(sampleArticle); + $httpBackend.expectGET(/api\/articles\/([0-9a-fA-F]{24})$/).respond(mockArticle); // Run controller functionality scope.findOne(); $httpBackend.flush(); // Test scope value - expect(scope.article).toEqualData(sampleArticle); + expect(scope.article).toEqualData(mockArticle); })); - it('$scope.create() with valid form data should send a POST request with the form input values and then locate to new object URL', inject(function(Articles) { - // Create a sample article object - var sampleArticlePostData = new Articles({ - title: 'An Article about MEAN', - content: 'MEAN rocks!' - }); + describe('$scope.craete()', function() { + var sampleArticlePostData; - // Create a sample article response - var sampleArticleResponse = new Articles({ - _id: '525cf20451979dea2c000001', - title: 'An Article about MEAN', - content: 'MEAN rocks!' + beforeEach(function() { + // Create a sample article object + sampleArticlePostData = new Articles({ + title: 'An Article about MEAN', + content: 'MEAN rocks!' + }); + + // Fixture mock form input values + scope.title = 'An Article about MEAN'; + scope.content = 'MEAN rocks!'; + + spyOn($location, 'path'); }); - // Fixture mock form input values - scope.title = 'An Article about MEAN'; - scope.content = 'MEAN rocks!'; + it('should send a POST request with the form input values and then locate to new object URL', inject(function(Articles) { + // Set POST response + $httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(mockArticle); - // Set POST response - $httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(sampleArticleResponse); + // Run controller functionality + scope.create(); + $httpBackend.flush(); - // Run controller functionality - scope.create(); - $httpBackend.flush(); + // Test form inputs are reset + expect(scope.title).toEqual(''); + expect(scope.content).toEqual(''); - // Test form inputs are reset - expect(scope.title).toEqual(''); - expect(scope.content).toEqual(''); + // Test URL redirection after the article was created + expect($location.path.calls.mostRecent().args[0]).toBe('articles/' + mockArticle._id); + })); - // Test URL redirection after the article was created - expect($location.path()).toBe('/articles/' + sampleArticleResponse._id); - })); + it('should set scope.error if save error', function() { + var errorMessage = 'this is an error message'; + $httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(400, { + message: errorMessage + }); - it('$scope.update() should update a valid article', inject(function(Articles) { - // Define a sample article put data - var sampleArticlePutData = new Articles({ - _id: '525cf20451979dea2c000001', - title: 'An Article about MEAN', - content: 'MEAN Rocks!' + scope.create(); + $httpBackend.flush(); + + expect(scope.error).toBe(errorMessage); }); + }); - // Mock article in scope - scope.article = sampleArticlePutData; + describe('$scope.update()', function() { + beforeEach(function() { + // Mock article in scope + scope.article = mockArticle; + }); - // Set PUT response - $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(); + it('should update a valid article', inject(function(Articles) { + // Set PUT response + $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(); - // Run controller functionality - scope.update(); - $httpBackend.flush(); + // Run controller functionality + scope.update(); + $httpBackend.flush(); - // Test URL location to new object - expect($location.path()).toBe('/articles/' + sampleArticlePutData._id); - })); + // Test URL location to new object + expect($location.path()).toBe('/articles/' + mockArticle._id); + })); + + it('should set scope.error to error response message', inject(function(Articles) { + var errorMessage = 'error'; + $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(400, { + message: errorMessage + }); + + scope.update(); + $httpBackend.flush(); + + expect(scope.error).toBe(errorMessage); + })); + }); - it('$scope.remove() should send a DELETE request with a valid articleId and remove the article from the scope', inject(function(Articles) { - // Create new article object - var sampleArticle = new Articles({ - _id: '525a8422f6d0f87f0e407a33' + describe('$scope.remove(article)', function() { + beforeEach(function() { + // Create new articles array and include the article + scope.articles = [mockArticle, {}]; + + // Set expected DELETE response + $httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204); + + // Run controller functionality + scope.remove(mockArticle); }); - // Create new articles array and include the article - scope.articles = [sampleArticle]; + it('should send a DELETE request with a valid articleId and remove the article from the scope', inject(function(Articles) { + expect(scope.articles.length).toBe(1); + })); + }); - // Set expected DELETE response - $httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204); + describe('scope.remove()', function() { + beforeEach(function() { + spyOn($location, 'path'); + scope.article = mockArticle; - // Run controller functionality - scope.remove(sampleArticle); - $httpBackend.flush(); + $httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204); - // Test array after successful delete - expect(scope.articles.length).toBe(0); - })); + scope.remove(); + $httpBackend.flush(); + }); + + it('should redirect to articles', function() { + expect($location.path).toHaveBeenCalledWith('articles'); + }); + }); }); }()); diff --git a/modules/chat/tests/client/chat.client.controller.tests.js b/modules/chat/tests/client/chat.client.controller.tests.js index 8a31f1930f..cf677b0220 100644 --- a/modules/chat/tests/client/chat.client.controller.tests.js +++ b/modules/chat/tests/client/chat.client.controller.tests.js @@ -4,7 +4,91 @@ * Chat client controller tests */ (function() { - describe('ChatController', function() { - // TODO: Add chat client controller tests - }); + describe('ChatController', function() { + //Initialize global variables + var scope, + Socket, + ChatController, + $timeout, + $location, + Authentication; + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + beforeEach(inject(function($controller, $rootScope, _Socket_, _Authentication_, _$timeout_, _$location_) { + scope = $rootScope.$new(); + Socket = _Socket_; + $timeout = _$timeout_; + $location = _$location_; + Authentication = _Authentication_; + })); + + describe('when user logged out', function() { + beforeEach(inject(function($controller, $rootScope, _Socket_, _Authentication_, _$timeout_, _$location_) { + Authentication.user = undefined; + spyOn($location, 'path'); + ChatController = $controller('ChatController', { + $scope: scope, + }); + })); + + it('should redirect logged out user to /', function() { + expect($location.path).toHaveBeenCalledWith('/'); + }); + }); + + describe('when user logged in', function() { + beforeEach(inject(function($controller, $rootScope, _Socket_, _Authentication_, _$timeout_, _$location_) { + Authentication.user = { + name: 'user', + roles: ['user'] + }; + + ChatController = $controller('ChatController', { + $scope: scope, + }); + })); + + it('should make sure socket is connected', function() { + expect(Socket.socket).toBeTruthy(); + }); + + it('should define messages array', function() { + expect(scope.messages).toBeDefined(); + expect(scope.messages.length).toBe(0); + }); + + describe('sendMessage', function() { + var text = 'hello world!'; + beforeEach(function() { + scope.messageText = text; + scope.sendMessage(); + $timeout.flush(); + }); + + it('should add message to messages', function() { + expect(scope.messages.length).toBe(1); + }); + + it('should add message with proper text attribute set', function() { + expect(scope.messages[0].text).toBe(text); + }); + + it('should clear messageText', function() { + expect(scope.messageText).toBe(''); + }); + }); + + describe('$destroy()', function() { + beforeEach(function() { + scope.$destroy(); + }); + + it('should remove chatMessage listener', function() { + expect(Socket.socket.cbs.chatMessage).toBeUndefined(); + }); + }); + }); + }); }()); diff --git a/modules/core/client/services/menus.client.service.js b/modules/core/client/services/menus.client.service.js index 020d7cf32b..cac12a4419 100644 --- a/modules/core/client/services/menus.client.service.js +++ b/modules/core/client/services/menus.client.service.js @@ -3,177 +3,175 @@ //Menu service used for managing menus angular.module('core').service('Menus', [ - function() { - // Define a set of default roles - this.defaultRoles = ['*']; - - // Define the menus object - this.menus = {}; - - // A private function for rendering decision - var shouldRender = function(user) { - if (user) { - if (!!~this.roles.indexOf('*')) { - return true; - } else { - for (var userRoleIndex in user.roles) { - for (var roleIndex in this.roles) { - if (this.roles[roleIndex] === user.roles[userRoleIndex]) { - return true; - } - } - } - } - } else { - return this.isPublic; + function() { + // Define a set of default roles + this.defaultRoles = ['*']; + + // Define the menus object + this.menus = {}; + + // A private function for rendering decision + var shouldRender = function(user) { + if (user) { + if (!!~this.roles.indexOf('*')) { + return true; + } else { + for (var userRoleIndex in user.roles) { + for (var roleIndex in this.roles) { + if (this.roles[roleIndex] === user.roles[userRoleIndex]) { + return true; + } } - - return false; - }; - - // Validate menu existance - this.validateMenuExistance = function(menuId) { - if (menuId && menuId.length) { - if (this.menus[menuId]) { - return true; - } else { - throw new Error('Menu does not exists'); - } - } else { - throw new Error('MenuId was not provided'); - } - - return false; - }; - - // Get the menu object by menu id - this.getMenu = function(menuId) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Return the menu object - return this.menus[menuId]; - }; - - // Add new menu object by menu id - this.addMenu = function(menuId, options) { - options = options || {}; - - // Create the new menu - this.menus[menuId] = { - isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? true : options.isPublic), - roles: options.roles || this.defaultRoles, - items: options.items || [], - shouldRender: shouldRender - }; - - // Return the menu object - return this.menus[menuId]; - }; - - // Remove existing menu object by menu id - this.removeMenu = function(menuId) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Return the menu object - delete this.menus[menuId]; - }; - - // Add menu item object - this.addMenuItem = function(menuId, options) { - options = options || {}; - - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Push new menu item - this.menus[menuId].items.push({ - title: options.title || '', - state: options.state || '', - type: options.type || 'item', - class: options.class, - isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? this.menus[menuId].isPublic : options.isPublic), - roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].roles : options.roles), - position: options.position || 0, - items: [], - shouldRender: shouldRender - }); - - // Add submenu items - if (options.items) { - for (var i in options.items) { - this.addSubMenuItem(menuId, options.link, options.items[i]); - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - // Add submenu item object - this.addSubMenuItem = function(menuId, parentItemState, options) { - options = options || {}; - - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Search for menu item - for (var itemIndex in this.menus[menuId].items) { - if (this.menus[menuId].items[itemIndex].state === parentItemState) { - // Push new submenu item - this.menus[menuId].items[itemIndex].items.push({ - title: options.title || '', - state: options.state|| '', - isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : options.isPublic), - roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : options.roles), - position: options.position || 0, - shouldRender: shouldRender - }); - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - // Remove existing menu object by menu id - this.removeMenuItem = function(menuId, menuItemURL) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Search for menu item to remove - for (var itemIndex in this.menus[menuId].items) { - if (this.menus[menuId].items[itemIndex].link === menuItemURL) { - this.menus[menuId].items.splice(itemIndex, 1); - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - // Remove existing menu object by menu id - this.removeSubMenuItem = function(menuId, submenuItemURL) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Search for menu item to remove - for (var itemIndex in this.menus[menuId].items) { - for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { - if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { - this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); - } - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - //Adding the topbar menu - this.addMenu('topbar', { - isPublic: false - }); - } + } + } + } else { + return this.isPublic; + } + + return false; + }; + + // Validate menu existance + this.validateMenuExistance = function(menuId) { + if (menuId && menuId.length) { + if (this.menus[menuId]) { + return true; + } else { + throw new Error('Menu does not exist'); + } + } else { + throw new Error('MenuId was not provided'); + } + }; + + // Get the menu object by menu id + this.getMenu = function(menuId) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Return the menu object + return this.menus[menuId]; + }; + + // Add new menu object by menu id + this.addMenu = function(menuId, options) { + options = options || {}; + + // Create the new menu + this.menus[menuId] = { + isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? true : options.isPublic), + roles: options.roles || this.defaultRoles, + items: options.items || [], + shouldRender: shouldRender + }; + + // Return the menu object + return this.menus[menuId]; + }; + + // Remove existing menu object by menu id + this.removeMenu = function(menuId) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Return the menu object + delete this.menus[menuId]; + }; + + // Add menu item object + this.addMenuItem = function(menuId, options) { + options = options || {}; + + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Push new menu item + this.menus[menuId].items.push({ + title: options.title || '', + state: options.state || '', + type: options.type || 'item', + class: options.class, + isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? this.menus[menuId].isPublic : options.isPublic), + roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].roles : options.roles), + position: options.position || 0, + items: [], + shouldRender: shouldRender + }); + + // Add submenu items + if (options.items) { + for (var i in options.items) { + this.addSubMenuItem(menuId, options.link, options.items[i]); + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + // Add submenu item object + this.addSubMenuItem = function(menuId, parentItemState, options) { + options = options || {}; + + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item + for (var itemIndex in this.menus[menuId].items) { + if (this.menus[menuId].items[itemIndex].state === parentItemState) { + // Push new submenu item + this.menus[menuId].items[itemIndex].items.push({ + title: options.title || '', + state: options.state || '', + isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : options.isPublic), + roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : options.roles), + position: options.position || 0, + shouldRender: shouldRender + }); + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + // Remove existing menu object by menu id + this.removeMenuItem = function(menuId, menuItemURL) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item to remove + for (var itemIndex in this.menus[menuId].items) { + if (this.menus[menuId].items[itemIndex].link === menuItemURL) { + this.menus[menuId].items.splice(itemIndex, 1); + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + // Remove existing menu object by menu id + this.removeSubMenuItem = function(menuId, submenuItemURL) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item to remove + for (var itemIndex in this.menus[menuId].items) { + for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { + if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { + this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); + } + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + //Adding the topbar menu + this.addMenu('topbar', { + isPublic: false + }); + } ]); diff --git a/modules/core/tests/client/header.client.controller.tests.js b/modules/core/tests/client/header.client.controller.tests.js index b45cc26b37..3b193f688f 100644 --- a/modules/core/tests/client/header.client.controller.tests.js +++ b/modules/core/tests/client/header.client.controller.tests.js @@ -1,24 +1,64 @@ 'use strict'; (function() { - describe('HeaderController', function() { - //Initialize global variables - var scope, - HeaderController; - - // Load the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - - beforeEach(inject(function($controller, $rootScope) { - scope = $rootScope.$new(); - - HeaderController = $controller('HeaderController', { - $scope: scope - }); - })); - - it('should expose the authentication service', function() { - expect(scope.authentication).toBeTruthy(); - }); - }); + describe('HeaderController', function() { + //Initialize global variables + var scope, + HeaderController, + $state, + Authentication; + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + beforeEach(inject(function($controller, $rootScope, _$state_, _Authentication_) { + scope = $rootScope.$new(); + $state = _$state_; + Authentication = _Authentication_; + + HeaderController = $controller('HeaderController', { + $scope: scope + }); + })); + + it('should expose the authentication service', function() { + expect(scope.authentication).toBe(Authentication); + }); + + it('should expose the $state service', function() { + expect(scope.$state).toBe($state); + }); + + it('should default menu to collapsed', function() { + expect(scope.isCollapsed).toBeFalsy(); + }); + + describe('when toggleCollapsibleMenu', function() { + var defaultCollapse; + beforeEach(function() { + defaultCollapse = scope.isCollapsed; + scope.toggleCollapsibleMenu(); + }); + + it('should toggle isCollapsed to non default value', function() { + expect(scope.isCollapsed).not.toBe(defaultCollapse); + }); + + it('should then toggle isCollapsed back to default value', function() { + scope.toggleCollapsibleMenu(); + expect(scope.isCollapsed).toBe(defaultCollapse); + }); + }); + + describe('when view state changes', function() { + beforeEach(function() { + scope.isCollapsed = true; + scope.$broadcast('$stateChangeSuccess'); + }); + + it('should set isCollapsed to false', function() { + expect(scope.isCollapsed).toBeFalsy(); + }); + }); + }); })(); diff --git a/modules/core/tests/client/menus.client.service.tests.js b/modules/core/tests/client/menus.client.service.tests.js new file mode 100644 index 0000000000..e693eae448 --- /dev/null +++ b/modules/core/tests/client/menus.client.service.tests.js @@ -0,0 +1,489 @@ +'use strict'; + +(function() { + describe('Menus', function() { + //Initialize global variables + var scope, + Menus; + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + beforeEach(inject(function(_Menus_) { + Menus = _Menus_; + })); + + it('should have topbar added', function() { + expect(Menus.menus.topbar).toBeDefined(); + }); + + it('should have private topbar', function() { + expect(Menus.menus.topbar.isPublic).toBeFalsy(); + }); + + it('should have default roles to *', function() { + expect(Menus.defaultRoles).toEqual(['*']); + }); + + describe('addMenu', function() { + describe('with no options', function() { + var menuId = 'menu1', + menu; + beforeEach(function() { + menu = Menus.addMenu(menuId); + }); + + it('should return menu object', function() { + expect(menu).toBeDefined(); + }); + + it('should default roles', function() { + expect(menu.roles).toEqual(Menus.defaultRoles); + }); + + it('should have empty items', function() { + expect(menu.items).toEqual([]); + }); + + it('should be public by default', function() { + expect(menu.isPublic).toBeTruthy(); + }); + + it('should set shouldRender to shouldRender function handle', function() { + expect(menu.shouldRender()).toBeTruthy(); + }); + }); + + describe('with options', function() { + var menu, + options = { + roles: ['a', 'b', 'c'], + items: ['d', 'e', 'f'] + }; + beforeEach(function() { + menu = Menus.addMenu('menu1', options); + }); + + it('should set isPublic to true if options.isPublic equal to null', function() { + var menu = Menus.addMenu('menu1', { + isPublic: null + }); + expect(menu.isPublic).toBeTruthy(); + }); + + it('should set isPublic to true if options.isPublic equal to undefined', function() { + expect(menu.isPublic).toBeTruthy(); + }); + + it('should set items to options.items list', function() { + expect(menu.items).toBe(options.items); + }); + + it('should set roles to options.roles list', function() { + expect(menu.roles).toBe(options.roles); + }); + }); + }); + + describe('shouldRender', function() { + var menuOptions = { + roles: ['*', 'menurole'] + }, + menu; + beforeEach(function() { + menu = Menus.addMenu('menu1', menuOptions); + }); + + describe('when logged out', function() { + it('should render if menu is public', function() { + expect(menu.shouldRender()).toBeTruthy(); + }); + + it('should not render if menu is private', function() { + menu = Menus.addMenu('menu1', { + isPublic: false + }); + expect(menu.shouldRender()).toBeFalsy(); + }); + }); + + describe('when logged in', function() { + var user = { + roles: ['1', 'menurole', '2'] + }; + describe('menu with * role', function() { + it('should render', function() { + expect(menu.shouldRender(user)).toBeTruthy(); + }); + }); + + describe('menu without * role', function() { + beforeEach(function() { + menu = Menus.addMenu('menu1', { + roles: ['b', 'menurole', 'c'] + }); + }); + + it('should render if user has same role as menu', function() { + expect(menu.shouldRender(user)).toBeTruthy(); + }); + + it('should not render if user has different roles', function() { + user = { + roles: ['1', '2', '3'] + }; + expect(menu.shouldRender(user)).toBeFalsy(); + }); + }); + }); + }); + + describe('validateMenuExistance', function() { + describe('when menuId not provided', function() { + it('should throw menuId error', function() { + expect(Menus.validateMenuExistance).toThrowError('MenuId was not provided'); + }); + }); + + describe('when menu does not exist', function() { + it('should throw no menu error', function() { + var target = function() { + Menus.validateMenuExistance('noMenuId'); + }; + expect(target).toThrowError('Menu does not exist'); + }); + }); + + describe('when menu exists', function() { + var menuId = 'menuId'; + beforeEach(function() { + Menus.menus[menuId] = {}; + }); + + it('should return truthy', function() { + expect(Menus.validateMenuExistance(menuId)).toBeTruthy(); + }); + }); + }); + + describe('removeMenu', function() { + var menu = { + id: 'menuId' + }; + beforeEach(function() { + Menus.menus[menu.id] = menu; + Menus.validateMenuExistance = jasmine.createSpy(); + Menus.removeMenu(menu.id); + }); + + it('should remove existing menu from menus', function() { + expect(Menus.menus).not.toContain(menu.id); + }); + + it('validates menu existance before removing', function() { + expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menu.id); + }); + }); + + describe('addMenuItem', function() { + var menuId = 'menu1', + subMenuItem1 = { + title: 'sub1' + }, + subMenuItem2 = { + title: 'sub2' + }, + menuItemOptions = { + title: 'title', + state: 'state', + type: 'type', + class: 'class', + isPublic: false, + roles: ['a', 'b'], + link: 'link', + position: 2, + items: [subMenuItem1, subMenuItem2] + }, + menu, + menuItem; + + beforeEach(function() { + Menus.validateMenuExistance = jasmine.createSpy(); + Menus.addSubMenuItem = jasmine.createSpy(); + Menus.addMenu(menuId, { + roles: ['a', 'b'] + }); + menu = Menus.addMenuItem(menuId, menuItemOptions); + menuItem = menu.items[0]; + }); + + it('should validate menu existance', function() { + expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menuId); + }); + + it('should return the menu', function() { + expect(menu).toBeDefined(); + }); + + it('should set menu item shouldRender function', function() { + expect(menuItem.shouldRender).toBeDefined(); + }); + + describe('with options set', function() { + it('should add menu item to menu', function() { + expect(menu.items.length).toBe(1); + }); + + it('should set menu item title to options title', function() { + expect(menuItem.title).toBe(menuItemOptions.title); + }); + + it('should set menu item state to options state', function() { + expect(menuItem.state).toBe(menuItemOptions.state); + }); + + it('should set menu item type to options type', function() { + expect(menuItem.type).toBe(menuItemOptions.type); + }); + + it('should set menu item class to options class', function() { + expect(menuItem.class).toBe(menuItemOptions.class); + }); + + it('should set menu item isPublic to options isPublic', function() { + expect(menuItem.isPublic).toBe(menuItemOptions.isPublic); + }); + + it('should set menu item position to options position', function() { + expect(menuItem.position).toBe(menuItemOptions.position); + }); + + it('should call addSubMenuItem for each item in options', function() { + expect(Menus.addSubMenuItem).toHaveBeenCalledWith(menuId, menuItemOptions.link, subMenuItem1); + expect(Menus.addSubMenuItem).toHaveBeenCalledWith(menuId, menuItemOptions.link, subMenuItem2); + }); + }); + + describe('without options set', function() { + beforeEach(function() { + menu = Menus.addMenuItem(menuId); + menuItem = menu.items[1]; + }); + + it('should set menu item type to item', function() { + expect(menuItem.type).toBe('item'); + }); + + it('should set menu item title to empty', function() { + expect(menuItem.title).toBe(''); + }); + + it('should set menu item isPublic to menu.isPublic', function() { + expect(menuItem.isPublic).toBe(menu.isPublic); + }); + + it('should set menu item roles to menu roles', function() { + expect(menuItem.roles).toEqual(menu.roles); + }); + + it('should set menu item position to 0', function() { + expect(menuItem.position).toBe(0); + }); + }); + }); + + describe('removeMenuItem', function() { + var menuId = 'menuId', + menuItemURL = 'url', + menuItem1 = { + link: menuItemURL + }, + menuItem2 = { + link: '' + }, + newMenu = { + items: [menuItem1, menuItem2] + }, + menu = null; + + beforeEach(function() { + Menus.menus.menuId = newMenu; + Menus.validateMenuExistance = jasmine.createSpy(); + menu = Menus.removeMenuItem(menuId, menuItemURL); + }); + + it('should return menu object', function() { + expect(menu).not.toBeNull(); + }); + + it('should validate menu existance', function() { + expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menuId); + }); + + it('should remove sub menu items with same link', function() { + expect(menu.items.length).toBe(1); + expect(menu.items[0]).toBe(menuItem2); + }); + }); + + describe('addSubMenuItem', function() { + var subItemOptions = { + title: 'title', + state: 'state', + isPublic: false, + roles: ['a', 'b'], + position: 4 + }; + var menuId = 'menu1', + menuItem1 = { + state: 'state', + items: [], + isPublic: false + }, + menuItem2 = { + state: 'state2', + items: [], + isPublic: true, + roles: ['a'] + }, + menuItem3 = { + state: 'state3', + items: [] + }, + newMenu = { + items: [menuItem1, menuItem2, menuItem3] + }, + menu; + + beforeEach(function() { + Menus.validateMenuExistance = jasmine.createSpy(); + Menus.menus[menuId] = newMenu; + Menus.addSubMenuItem(menuId, menuItem1.state, subItemOptions); + menu = Menus.addSubMenuItem(menuId, menuItem2.state); + }); + + afterEach(function() { + menuItem1.items = []; + menuItem2.items = []; + }); + + it('should return menu object', function() { + expect(menu).toEqual(newMenu); + }); + + it('should validate menu existance', function() { + expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menuId); + }); + + it('should not add sub menu item to menu item of different state', function() { + expect(menuItem3.items.length).toBe(0); + }); + + it('should set shouldRender', function() { + expect(menuItem1.items[0].shouldRender).toBeDefined(); + }); + + describe('with options set', function() { + var subMenuItem; + beforeEach(function() { + subMenuItem = menuItem1.items[0]; + }); + + it('should add sub menu item to menu item', function() { + expect(menuItem1.items.length).toBe(1); + }); + + it('should set isPublic to options isPublic', function() { + expect(subMenuItem.isPublic).toBe(subItemOptions.isPublic); + }); + + it('should set title to options title', function() { + expect(subMenuItem.title).toBe(subItemOptions.title); + }); + + it('should set state to options state', function() { + expect(subMenuItem.state).toBe(subItemOptions.state); + }); + + it('should set roles to options roles', function() { + expect(subMenuItem.roles).toEqual(subItemOptions.roles); + }); + + it('should set position to options position', function() { + expect(subMenuItem.position).toEqual(subItemOptions.position); + }); + }); + + describe('without optoins set', function() { + var subMenuItem; + beforeEach(function() { + subMenuItem = menuItem2.items[0]; + }); + + it('should add sub menu item to menu item', function() { + expect(menuItem2.items.length).toBe(1); + }); + + it('should set isPublic to parent isPublic', function() { + expect(subMenuItem.isPublic).toBe(menuItem2.isPublic); + }); + + it('should set title to blank', function() { + expect(subMenuItem.title).toBe(''); + }); + + it('should set state to blank', function() { + expect(subMenuItem.state).toBe(''); + }); + + it('should set roles to parent roles', function() { + expect(subMenuItem.roles).toEqual(menuItem2.roles); + }); + + it('should set position to 0', function() { + expect(subMenuItem.position).toBe(0); + }); + }); + }); + + describe('removeSubMenuItem', function() { + var menuId = 'menu1', + subMenuItem1 = { + link: 'link1' + }, + subMenuItem2 = { + link: 'link2' + }, + menuItem1 = { + state: 'state', + items: [subMenuItem1, subMenuItem2], + }, + menuItem2 = { + state: 'state2', + items: [], + }, + newMenu = { + items: [menuItem1, menuItem2] + }, + menu; + beforeEach(function() { + Menus.validateMenuExistance = jasmine.createSpy(); + Menus.menus[menuId] = newMenu; + menu = Menus.removeSubMenuItem(menuId, subMenuItem1.link); + }); + + it('should validate menu existance', function() { + expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menuId); + }); + + it('should return menu object', function() { + expect(menu).toEqual(newMenu); + }); + + it('should remove sub menu item', function() { + expect(menuItem1.items.length).toBe(1); + expect(menuItem1.items[0]).toEqual(subMenuItem2); + }); + }); + }); +})(); diff --git a/modules/core/tests/client/socket.io.client.service.tests.js b/modules/core/tests/client/socket.io.client.service.tests.js new file mode 100644 index 0000000000..732189f952 --- /dev/null +++ b/modules/core/tests/client/socket.io.client.service.tests.js @@ -0,0 +1,24 @@ +(function() { + 'use strict'; + + /* Creates a mock of socket.io for the browser. + * Functionality of the service is tested through + * the chat controller tests. + */ + window.io = function() { + this.cbs = {}; + this.on = function(msg, cb) { + this.cbs[msg] = cb; + }; + this.emit = function(msg, data) { + this.cbs[msg](data); + }; + this.removeListener = function(msg) { + delete this.cbs[msg]; + }; + this.connect = function() { + this.socket = {}; + }; + return this; + }; +})(); diff --git a/modules/users/server/controllers/admin.server.controller.js b/modules/users/server/controllers/admin.server.controller.js index c2998f9e9a..14063fb74f 100644 --- a/modules/users/server/controllers/admin.server.controller.js +++ b/modules/users/server/controllers/admin.server.controller.js @@ -74,6 +74,12 @@ exports.list = function (req, res) { * User middleware */ exports.userByID = function (req, res, next, id) { + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).send({ + message: 'User is invalid' + }); + } + User.findById(id, '-salt -password').exec(function (err, user) { if (err) return next(err); if (!user) return next(new Error('Failed to load user ' + id)); diff --git a/modules/users/server/controllers/users/users.authorization.server.controller.js b/modules/users/server/controllers/users/users.authorization.server.controller.js index 9ad24fc3ef..ea35abf346 100644 --- a/modules/users/server/controllers/users/users.authorization.server.controller.js +++ b/modules/users/server/controllers/users/users.authorization.server.controller.js @@ -10,10 +10,16 @@ var _ = require('lodash'), /** * User middleware */ -exports.userByID = function(req, res, next, id) { +exports.userByID = function (req, res, next, id) { + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).send({ + message: 'User is invalid' + }); + } + User.findOne({ _id: id - }).exec(function(err, user) { + }).exec(function (err, user) { if (err) return next(err); if (!user) return next(new Error('Failed to load User ' + id)); req.profile = user; diff --git a/modules/users/server/policies/admin.server.policies.js b/modules/users/server/policies/admin.server.policy.js similarity index 96% rename from modules/users/server/policies/admin.server.policies.js rename to modules/users/server/policies/admin.server.policy.js index b0860ec82b..d7b7a43fdb 100644 --- a/modules/users/server/policies/admin.server.policies.js +++ b/modules/users/server/policies/admin.server.policy.js @@ -9,7 +9,7 @@ var acl = require('acl'); acl = new acl(new acl.memoryBackend()); /** - * Invoke Articles Permissions + * Invoke Admin Permissions */ exports.invokeRolesPolicies = function () { acl.allow([{ diff --git a/modules/users/server/routes/admin.server.routes.js b/modules/users/server/routes/admin.server.routes.js index d2fd4f4630..c98acb2ce6 100644 --- a/modules/users/server/routes/admin.server.routes.js +++ b/modules/users/server/routes/admin.server.routes.js @@ -3,19 +3,22 @@ /** * Module dependencies. */ -var adminPolicy = require('../policies/admin.server.policies'), +var adminPolicy = require('../policies/admin.server.policy'), admin = require('../controllers/admin.server.controller'); module.exports = function (app) { + // User route registration first. Ref: #713 + require('./users.server.routes.js')(app); + // Users collection routes - app.route('/api/users').all(adminPolicy.isAllowed) - .get(admin.list); + app.route('/api/users') + .get(adminPolicy.isAllowed, admin.list); // Single user routes - app.route('/api/users/:userId').all(adminPolicy.isAllowed) - .get(admin.read) - .put(admin.update) - .delete(admin.delete); + app.route('/api/users/:userId') + .get(adminPolicy.isAllowed, admin.read) + .put(adminPolicy.isAllowed, admin.update) + .delete(adminPolicy.isAllowed, admin.delete); // Finish by binding the user middleware app.param('userId', admin.userByID); diff --git a/modules/users/server/routes/users.server.routes.js b/modules/users/server/routes/users.server.routes.js index d74a428190..0feb276478 100644 --- a/modules/users/server/routes/users.server.routes.js +++ b/modules/users/server/routes/users.server.routes.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = function(app) { +module.exports = function (app) { // User Routes var users = require('../controllers/users.server.controller'); diff --git a/modules/users/tests/client/authentication.client.controller.tests.js b/modules/users/tests/client/authentication.client.controller.tests.js index 348ec474c6..ac86701874 100644 --- a/modules/users/tests/client/authentication.client.controller.tests.js +++ b/modules/users/tests/client/authentication.client.controller.tests.js @@ -1,118 +1,146 @@ 'use strict'; (function() { - // Authentication controller Spec - describe('AuthenticationController', function() { - // Initialize global variables - var AuthenticationController, - scope, - $httpBackend, - $stateParams, - $location; - - beforeEach(function() { - jasmine.addMatchers({ - toEqualData: function(util, customEqualityTesters) { - return { - compare: function(actual, expected) { - return { - pass: angular.equals(actual, expected) - }; - } - }; - } - }); - }); - - // Load the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - - // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). - // This allows us to inject a service but then attach it to a variable - // with the same name as the service. - beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { - // Set a new global scope - scope = $rootScope.$new(); - - // Point global variables to injected services - $stateParams = _$stateParams_; - $httpBackend = _$httpBackend_; - $location = _$location_; - - // Initialize the Authentication controller - AuthenticationController = $controller('AuthenticationController', { - $scope: scope - }); - })); - - - it('$scope.signin() should login with a correct user and password', function() { - // Test expected GET request - $httpBackend.when('POST', '/api/auth/signin').respond(200, 'Fred'); - - scope.signin(); - $httpBackend.flush(); - - // Test scope value - expect(scope.authentication.user).toEqual('Fred'); - expect($location.url()).toEqual('/'); - }); - - it('$scope.signin() should fail to log in with nothing', function() { - // Test expected POST request - $httpBackend.expectPOST('/api/auth/signin').respond(400, { - 'message': 'Missing credentials' - }); - - scope.signin(); - $httpBackend.flush(); - - // Test scope value - expect(scope.error).toEqual('Missing credentials'); - }); - - it('$scope.signin() should fail to log in with wrong credentials', function() { - // Foo/Bar combo assumed to not exist - scope.authentication.user = 'Foo'; - scope.credentials = 'Bar'; - - // Test expected POST request - $httpBackend.expectPOST('/api/auth/signin').respond(400, { - 'message': 'Unknown user' - }); - - scope.signin(); - $httpBackend.flush(); - - // Test scope value - expect(scope.error).toEqual('Unknown user'); - }); - - it('$scope.signup() should register with correct data', function() { - // Test expected GET request - scope.authentication.user = 'Fred'; - $httpBackend.when('POST', '/api/auth/signup').respond(200, 'Fred'); - - scope.signup(); - $httpBackend.flush(); - - // test scope value - expect(scope.authentication.user).toBe('Fred'); - expect(scope.error).toEqual(undefined); - expect($location.url()).toBe('/'); - }); - - it('$scope.signup() should fail to register with duplicate Username', function() { - // Test expected POST request - $httpBackend.when('POST', '/api/auth/signup').respond(400, { - 'message': 'Username already exists' - }); - - scope.signup(); - $httpBackend.flush(); - - // Test scope value - expect(scope.error).toBe('Username already exists'); - }); - }); + // Authentication controller Spec + describe('AuthenticationController', function() { + // Initialize global variables + var AuthenticationController, + scope, + $httpBackend, + $stateParams, + $location; + + beforeEach(function() { + jasmine.addMatchers({ + toEqualData: function(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + describe('Logged out user', function() { + // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). + // This allows us to inject a service but then attach it to a variable + // with the same name as the service. + beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { + // Set a new global scope + scope = $rootScope.$new(); + + // Point global variables to injected services + $stateParams = _$stateParams_; + $httpBackend = _$httpBackend_; + $location = _$location_; + + // Initialize the Authentication controller + AuthenticationController = $controller('AuthenticationController', { + $scope: scope + }); + })); + + describe('$scope.signin()', function() { + it('should login with a correct user and password', function() { + // Test expected GET request + $httpBackend.when('POST', '/api/auth/signin').respond(200, 'Fred'); + + scope.signin(); + $httpBackend.flush(); + + // Test scope value + expect(scope.authentication.user).toEqual('Fred'); + expect($location.url()).toEqual('/'); + }); + + it('should fail to log in with nothing', function() { + // Test expected POST request + $httpBackend.expectPOST('/api/auth/signin').respond(400, { + 'message': 'Missing credentials' + }); + + scope.signin(); + $httpBackend.flush(); + + // Test scope value + expect(scope.error).toEqual('Missing credentials'); + }); + + it('should fail to log in with wrong credentials', function() { + // Foo/Bar combo assumed to not exist + scope.authentication.user = 'Foo'; + scope.credentials = 'Bar'; + + // Test expected POST request + $httpBackend.expectPOST('/api/auth/signin').respond(400, { + 'message': 'Unknown user' + }); + + scope.signin(); + $httpBackend.flush(); + + // Test scope value + expect(scope.error).toEqual('Unknown user'); + }); + }); + + describe('$scope.signup()', function() { + it('should register with correct data', function() { + // Test expected GET request + scope.authentication.user = 'Fred'; + $httpBackend.when('POST', '/api/auth/signup').respond(200, 'Fred'); + + scope.signup(); + $httpBackend.flush(); + + // test scope value + expect(scope.authentication.user).toBe('Fred'); + expect(scope.error).toEqual(undefined); + expect($location.url()).toBe('/'); + }); + + it('should fail to register with duplicate Username', function() { + // Test expected POST request + $httpBackend.when('POST', '/api/auth/signup').respond(400, { + 'message': 'Username already exists' + }); + + scope.signup(); + $httpBackend.flush(); + + // Test scope value + expect(scope.error).toBe('Username already exists'); + }); + }); + }); + + describe('Logged in user', function() { + beforeEach(inject(function($controller, $rootScope, _$location_, _Authentication_) { + scope = $rootScope.$new(); + + $location = _$location_; + $location.path = jasmine.createSpy().and.returnValue(true); + + // Mock logged in user + _Authentication_.user = { + username: 'test', + roles: ['user'] + }; + + AuthenticationController = $controller('AuthenticationController', { + $scope: scope + }); + })); + + it('should be redirected to home', function() { + expect($location.path).toHaveBeenCalledWith('/'); + }); + }); + }); }()); diff --git a/modules/users/tests/client/password.client.controller.tests.js b/modules/users/tests/client/password.client.controller.tests.js new file mode 100644 index 0000000000..86be25f575 --- /dev/null +++ b/modules/users/tests/client/password.client.controller.tests.js @@ -0,0 +1,198 @@ +'use strict'; + +(function() { + // Authentication controller Spec + describe('PasswordController', function() { + // Initialize global variables + var PasswordController, + scope, + $httpBackend, + $stateParams, + $location, + $window; + + beforeEach(function() { + jasmine.addMatchers({ + toEqualData: function(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + describe('Logged in user', function() { + beforeEach(inject(function($controller, $rootScope, _Authentication_, _$stateParams_, _$httpBackend_, _$location_) { + // Set a new global scope + scope = $rootScope.$new(); + + // Point global variables to injected services + $stateParams = _$stateParams_; + $httpBackend = _$httpBackend_; + $location = _$location_; + $location.path = jasmine.createSpy().and.returnValue(true); + + // Mock logged in user + _Authentication_.user = { + username: 'test', + roles: ['user'] + }; + + // Initialize the Authentication controller + PasswordController = $controller('PasswordController', { + $scope: scope + }); + })); + + it('should redirect logged in user to home', function() { + expect($location.path).toHaveBeenCalledWith('/'); + }); + }); + + describe('Logged out user', function() { + beforeEach(inject(function($controller, $rootScope, _$window_, _$stateParams_, _$httpBackend_, _$location_) { + // Set a new global scope + scope = $rootScope.$new(); + + // Point global variables to injected services + $stateParams = _$stateParams_; + $httpBackend = _$httpBackend_; + $location = _$location_; + $location.path = jasmine.createSpy().and.returnValue(true); + $window = _$window_; + $window.user = null; + + // Initialize the Authentication controller + PasswordController = $controller('PasswordController', { + $scope: scope + }); + })); + + it('should not redirect to home', function() { + expect($location.path).not.toHaveBeenCalledWith('/'); + }); + + describe('askForPasswordReset', function() { + var credentials = { + username: 'test', + password: 'test' + }; + beforeEach(function() { + scope.credentials = credentials; + }); + + it('should clear scope.success and scope.error', function() { + scope.success = 'test'; + scope.error = 'test'; + scope.askForPasswordReset(); + + expect(scope.success).toBeNull(); + expect(scope.error).toBeNull(); + }); + + describe('POST error', function() { + var errorMessage = 'No account with that username has been found'; + beforeEach(function() { + $httpBackend.when('POST', '/api/auth/forgot', credentials).respond(400, { + 'message': errorMessage + }); + + scope.askForPasswordReset(); + $httpBackend.flush(); + }); + + it('should clear form', function() { + expect(scope.credentials).toBe(null); + }); + + it('should set error to response message', function() { + expect(scope.error).toBe(errorMessage); + }); + }); + + describe('POST success', function() { + var successMessage = 'An email has been sent to the provided email with further instructions.'; + beforeEach(function() { + $httpBackend.when('POST', '/api/auth/forgot', credentials).respond({ + 'message': successMessage + }); + + scope.askForPasswordReset(); + $httpBackend.flush(); + }); + + it('should clear form', function() { + expect(scope.credentials).toBe(null); + }); + + it('should set success to response message', function() { + expect(scope.success).toBe(successMessage); + }); + }); + }); + + describe('resetUserPassword', function() { + var token = 'testToken'; + var passwordDetails = { + password: 'test' + }; + beforeEach(function() { + $stateParams.token = token; + scope.passwordDetails = passwordDetails; + }); + + it('should clear scope.success and scope.error', function() { + scope.success = 'test'; + scope.error = 'test'; + scope.resetUserPassword(); + + expect(scope.success).toBeNull(); + expect(scope.error).toBeNull(); + }); + + it('POST error should set scope.error to response message', function() { + var errorMessage = 'Passwords do not match'; + $httpBackend.when('POST', '/api/auth/reset/' + token, passwordDetails).respond(400, { + 'message': errorMessage + }); + + scope.resetUserPassword(); + $httpBackend.flush(); + + expect(scope.error).toBe(errorMessage); + }); + + describe('POST success', function() { + var user = { + username: 'test' + }; + beforeEach(function() { + $httpBackend.when('POST', '/api/auth/reset/' + token, passwordDetails).respond(user); + + scope.resetUserPassword(); + $httpBackend.flush(); + }); + + it('should clear password form', function() { + expect(scope.passwordDetails).toBe(null); + }); + + it('should attach user profile', function() { + expect(scope.authentication.user).toEqual(user); + }); + + it('should redirect to password reset success view', function() { + expect($location.path).toHaveBeenCalledWith('/password/reset/success'); + }); + }); + }); + }); + }); +}()); diff --git a/modules/users/tests/server/user.server.routes.tests.js b/modules/users/tests/server/user.server.routes.tests.js new file mode 100644 index 0000000000..e897691aef --- /dev/null +++ b/modules/users/tests/server/user.server.routes.tests.js @@ -0,0 +1,107 @@ +'use strict'; + +var should = require('should'), + request = require('supertest'), + path = require('path'), + mongoose = require('mongoose'), + User = mongoose.model('User'), + express = require(path.resolve('./config/lib/express')); + +/** + * Globals + */ +var app, agent, credentials, user, admin; + +/** + * User routes tests + */ +describe('User CRUD tests', function () { + before(function (done) { + // Get application + app = express.init(mongoose); + agent = request.agent(app); + + done(); + }); + + beforeEach(function (done) { + // Create user credentials + credentials = { + username: 'username', + password: 'password' + }; + + // Create a new user + user = new User({ + firstName: 'Full', + lastName: 'Name', + displayName: 'Full Name', + email: 'test@test.com', + username: credentials.username, + password: credentials.password, + provider: 'local' + }); + + // Save a user to the test db and create new article + user.save(function () { + done(); + }); + }); + + it('should not be able to retrieve a list of users if not admin', function (done) { + agent.post('/api/auth/signin') + .send(credentials) + .expect(200) + .end(function (signinErr, signinRes) { + // Handle signin error + if (signinErr) { + return done(signinErr); + } + + // Save a new article + agent.get('/api/users') + .expect(403) + .end(function (usersGetErr, usersGetRes) { + if (usersGetErr) { + return done(usersGetErr); + } + + return done(); + }); + }); + }); + + it('should be able to retrieve a list of users if admin', function (done) { + user.roles = ['user', 'admin']; + + user.save(function () { + agent.post('/api/auth/signin') + .send(credentials) + .expect(200) + .end(function (signinErr, signinRes) { + // Handle signin error + if (signinErr) { + return done(signinErr); + } + + // Save a new article + agent.get('/api/users') + .expect(200) + .end(function (usersGetErr, usersGetRes) { + if (usersGetErr) { + return done(usersGetErr); + } + + usersGetRes.body.should.be.instanceof(Array).and.have.lengthOf(1); + + // Call the assertion callback + done(); + }); + }); + }); + }); + + afterEach(function (done) { + User.remove().exec(done); + }); +});