Adding client test coverage.

This commit is contained in:
cdriscol
2015-07-28 17:52:03 -06:00
parent 460ef53aed
commit d5ea5c99d2
16 changed files with 1361 additions and 345 deletions

2
.gitignore vendored
View File

@@ -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

View File

@@ -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"

View File

@@ -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;
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');
});
// Create a sample article response
var sampleArticleResponse = new Articles({
_id: '525cf20451979dea2c000001',
title: 'An Article about MEAN',
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);
// Run controller functionality
scope.create();
$httpBackend.flush();
// 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);
}));
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
});
scope.create();
$httpBackend.flush();
expect(scope.error).toBe(errorMessage);
});
});
describe('$scope.update()', function() {
beforeEach(function() {
// Mock article in scope
scope.article = mockArticle;
});
// Fixture mock form input values
scope.title = 'An Article about MEAN';
scope.content = 'MEAN rocks!';
it('should update a valid article', inject(function(Articles) {
// Set PUT response
$httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond();
// Set POST response
$httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(sampleArticleResponse);
// Run controller functionality
scope.update();
$httpBackend.flush();
// Run controller functionality
scope.create();
$httpBackend.flush();
// Test URL location to new object
expect($location.path()).toBe('/articles/' + mockArticle._id);
}));
// Test form inputs are reset
expect(scope.title).toEqual('');
expect(scope.content).toEqual('');
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
});
// Test URL redirection after the article was created
expect($location.path()).toBe('/articles/' + sampleArticleResponse._id);
}));
scope.update();
$httpBackend.flush();
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!'
expect(scope.error).toBe(errorMessage);
}));
});
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);
});
// Mock article in scope
scope.article = sampleArticlePutData;
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 PUT response
$httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond();
describe('scope.remove()', function() {
beforeEach(function() {
spyOn($location, 'path');
scope.article = mockArticle;
// Run controller functionality
scope.update();
$httpBackend.flush();
$httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204);
// Test URL location to new object
expect($location.path()).toBe('/articles/' + sampleArticlePutData._id);
}));
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'
scope.remove();
$httpBackend.flush();
});
// Create new articles array and include the article
scope.articles = [sampleArticle];
// Set expected DELETE response
$httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204);
// Run controller functionality
scope.remove(sampleArticle);
$httpBackend.flush();
// Test array after successful delete
expect(scope.articles.length).toBe(0);
}));
it('should redirect to articles', function() {
expect($location.path).toHaveBeenCalledWith('articles');
});
});
});
}());

View File

@@ -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();
});
});
});
});
}());

View File

@@ -3,177 +3,175 @@
//Menu service used for managing menus
angular.module('core').service('Menus', [
function() {
// Define a set of default roles
this.defaultRoles = ['*'];
function() {
// Define a set of default roles
this.defaultRoles = ['*'];
// Define the menus object
this.menus = {};
// 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;
// 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;
}
return false;
};
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');
}
// 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');
}
};
return false;
};
// Get the menu object by menu id
this.getMenu = function(menuId) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// 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];
};
// Return the menu object
return this.menus[menuId];
};
// Add new menu object by menu id
this.addMenu = function(menuId, options) {
options = options || {};
// 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
};
// 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];
};
// 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);
// 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];
};
// Return the menu object
delete this.menus[menuId];
};
// Add menu item object
this.addMenuItem = function(menuId, options) {
options = options || {};
// Add menu item object
this.addMenuItem = function(menuId, options) {
options = options || {};
// Validate that the menu exists
this.validateMenuExistance(menuId);
// 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
});
// 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]);
}
}
// 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];
};
// Return the menu object
return this.menus[menuId];
};
// Add submenu item object
this.addSubMenuItem = function(menuId, parentItemState, options) {
options = options || {};
// Add submenu item object
this.addSubMenuItem = function(menuId, parentItemState, options) {
options = options || {};
// Validate that the menu exists
this.validateMenuExistance(menuId);
// 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
});
}
}
// 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];
};
// 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);
// 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);
}
}
// 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];
};
// 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);
// 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);
}
}
}
// 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];
};
// Return the menu object
return this.menus[menuId];
};
//Adding the topbar menu
this.addMenu('topbar', {
isPublic: false
});
}
//Adding the topbar menu
this.addMenu('topbar', {
isPublic: false
});
}
]);

View File

@@ -1,24 +1,64 @@
'use strict';
(function() {
describe('HeaderController', function() {
//Initialize global variables
var scope,
HeaderController;
describe('HeaderController', function() {
//Initialize global variables
var scope,
HeaderController,
$state,
Authentication;
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
beforeEach(inject(function($controller, $rootScope, _$state_, _Authentication_) {
scope = $rootScope.$new();
$state = _$state_;
Authentication = _Authentication_;
HeaderController = $controller('HeaderController', {
$scope: scope
});
}));
HeaderController = $controller('HeaderController', {
$scope: scope
});
}));
it('should expose the authentication service', function() {
expect(scope.authentication).toBeTruthy();
});
});
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();
});
});
});
})();

View File

@@ -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);
});
});
});
})();

View File

@@ -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;
};
})();

View File

@@ -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));

View File

@@ -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;

View File

@@ -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([{

View File

@@ -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);

View File

@@ -1,6 +1,6 @@
'use strict';
module.exports = function(app) {
module.exports = function (app) {
// User Routes
var users = require('../controllers/users.server.controller');

View File

@@ -1,118 +1,146 @@
'use strict';
(function() {
// Authentication controller Spec
describe('AuthenticationController', function() {
// Initialize global variables
var AuthenticationController,
scope,
$httpBackend,
$stateParams,
$location;
// 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)
};
}
};
}
});
});
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));
// 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();
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_;
// Point global variables to injected services
$stateParams = _$stateParams_;
$httpBackend = _$httpBackend_;
$location = _$location_;
// Initialize the Authentication controller
AuthenticationController = $controller('AuthenticationController', {
$scope: scope
});
}));
// 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');
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();
scope.signin();
$httpBackend.flush();
// Test scope value
expect(scope.authentication.user).toEqual('Fred');
expect($location.url()).toEqual('/');
});
// 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'
});
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();
scope.signin();
$httpBackend.flush();
// Test scope value
expect(scope.error).toEqual('Missing credentials');
});
// 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';
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'
});
// Test expected POST request
$httpBackend.expectPOST('/api/auth/signin').respond(400, {
'message': 'Unknown user'
});
scope.signin();
$httpBackend.flush();
scope.signin();
$httpBackend.flush();
// Test scope value
expect(scope.error).toEqual('Unknown user');
});
});
// 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');
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();
scope.signup();
$httpBackend.flush();
// test scope value
expect(scope.authentication.user).toBe('Fred');
expect(scope.error).toEqual(undefined);
expect($location.url()).toBe('/');
});
// 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'
});
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();
scope.signup();
$httpBackend.flush();
// Test scope value
expect(scope.error).toBe('Username already exists');
});
});
});
// 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('/');
});
});
});
}());

View File

@@ -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');
});
});
});
});
});
}());

View File

@@ -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);
});
});