diff --git a/modules/articles/client/articles.client.module.js b/modules/articles/client/articles.client.module.js index 4f2b96b347..c0ce87fb67 100644 --- a/modules/articles/client/articles.client.module.js +++ b/modules/articles/client/articles.client.module.js @@ -2,6 +2,8 @@ 'use strict'; app.registerModule('articles'); + app.registerModule('articles.admin', ['core.admin']); + app.registerModule('articles.admin.routes', ['core.admin.routes']); app.registerModule('articles.services'); app.registerModule('articles.routes', ['ui.router', 'articles.services']); })(ApplicationConfiguration); diff --git a/modules/articles/client/config/articles-admin.client.config.js b/modules/articles/client/config/articles-admin.client.config.js new file mode 100644 index 0000000000..0bd3b63fe2 --- /dev/null +++ b/modules/articles/client/config/articles-admin.client.config.js @@ -0,0 +1,17 @@ +(function () { + 'use strict'; + + // Configuring the Articles Admin module + angular + .module('articles.admin') + .run(menuConfig); + + menuConfig.$inject = ['Menus']; + + function menuConfig(Menus) { + Menus.addSubMenuItem('topbar', 'admin', { + title: 'Manage Articles', + state: 'admin.articles.list' + }); + } +})(); diff --git a/modules/articles/client/config/articles-admin.client.routes.js b/modules/articles/client/config/articles-admin.client.routes.js new file mode 100644 index 0000000000..934e131a2f --- /dev/null +++ b/modules/articles/client/config/articles-admin.client.routes.js @@ -0,0 +1,65 @@ +(function () { + 'use strict'; + + angular + .module('articles.admin.routes') + .config(routeConfig); + + routeConfig.$inject = ['$stateProvider']; + + function routeConfig($stateProvider) { + $stateProvider + .state('admin.articles', { + abstract: true, + url: '/articles', + template: '' + }) + .state('admin.articles.list', { + url: '', + templateUrl: 'modules/articles/client/views/admin/list-articles.client.view.html', + controller: 'ArticlesListController', + controllerAs: 'vm', + data: { + roles: ['admin'] + } + }) + .state('admin.articles.create', { + url: '/create', + templateUrl: 'modules/articles/client/views/admin/form-article.client.view.html', + controller: 'ArticlesController', + controllerAs: 'vm', + data: { + roles: ['admin'] + }, + resolve: { + articleResolve: newArticle + } + }) + .state('admin.articles.edit', { + url: '/:articleId/edit', + templateUrl: 'modules/articles/client/views/admin/form-article.client.view.html', + controller: 'ArticlesController', + controllerAs: 'vm', + data: { + roles: ['admin'] + }, + resolve: { + articleResolve: getArticle + } + }); + } + + getArticle.$inject = ['$stateParams', 'ArticlesService']; + + function getArticle($stateParams, ArticlesService) { + return ArticlesService.get({ + articleId: $stateParams.articleId + }).$promise; + } + + newArticle.$inject = ['ArticlesService']; + + function newArticle(ArticlesService) { + return new ArticlesService(); + } +})(); diff --git a/modules/articles/client/config/articles.client.config.js b/modules/articles/client/config/articles.client.config.js index 1f8853e494..7d5b1ee7fd 100644 --- a/modules/articles/client/config/articles.client.config.js +++ b/modules/articles/client/config/articles.client.config.js @@ -18,14 +18,8 @@ // Add the dropdown list item Menus.addSubMenuItem('topbar', 'articles', { title: 'List Articles', - state: 'articles.list' - }); - - // Add the dropdown create item - Menus.addSubMenuItem('topbar', 'articles', { - title: 'Create Article', - state: 'articles.create', - roles: ['user'] + state: 'articles.list', + roles: ['*'] }); } })(); diff --git a/modules/articles/client/config/articles.client.routes.js b/modules/articles/client/config/articles.client.routes.js index b7671b1180..0daf23566d 100644 --- a/modules/articles/client/config/articles.client.routes.js +++ b/modules/articles/client/config/articles.client.routes.js @@ -23,32 +23,6 @@ pageTitle: 'Articles List' } }) - .state('articles.create', { - url: '/create', - templateUrl: 'modules/articles/client/views/form-article.client.view.html', - controller: 'ArticlesController', - controllerAs: 'vm', - resolve: { - articleResolve: newArticle - }, - data: { - roles: ['user', 'admin'], - pageTitle : 'Articles Create' - } - }) - .state('articles.edit', { - url: '/:articleId/edit', - templateUrl: 'modules/articles/client/views/form-article.client.view.html', - controller: 'ArticlesController', - controllerAs: 'vm', - resolve: { - articleResolve: getArticle - }, - data: { - roles: ['user', 'admin'], - pageTitle: 'Edit Article {{ articleResolve.title }}' - } - }) .state('articles.view', { url: '/:articleId', templateUrl: 'modules/articles/client/views/view-article.client.view.html', @@ -70,10 +44,4 @@ articleId: $stateParams.articleId }).$promise; } - - newArticle.$inject = ['ArticlesService']; - - function newArticle(ArticlesService) { - return new ArticlesService(); - } })(); diff --git a/modules/articles/client/controllers/admin/article.client.controller.js b/modules/articles/client/controllers/admin/article.client.controller.js new file mode 100644 index 0000000000..9ecabffd0c --- /dev/null +++ b/modules/articles/client/controllers/admin/article.client.controller.js @@ -0,0 +1,50 @@ +(function () { + 'use strict'; + + angular + .module('articles.admin') + .controller('ArticlesController', ArticlesController); + + ArticlesController.$inject = ['$scope', '$state', 'articleResolve', 'Authentication']; + + function ArticlesController($scope, $state, article, Authentication) { + var vm = this; + + vm.article = article; + vm.authentication = Authentication; + vm.error = null; + vm.form = {}; + vm.remove = remove; + vm.save = save; + + // Remove existing Article + function remove() { + if (confirm('Are you sure you want to delete?')) { + vm.article.$remove($state.go('admin.articles.list')); + } + } + + // Save Article + function save(isValid) { + if (!isValid) { + $scope.$broadcast('show-errors-check-validity', 'vm.form.articleForm'); + return false; + } + + // TODO: move create/update logic to service + if (vm.article._id) { + vm.article.$update(successCallback, errorCallback); + } else { + vm.article.$save(successCallback, errorCallback); + } + + function successCallback(res) { + $state.go('admin.articles.list'); // should we send the User to the list or the updated Article's view? + } + + function errorCallback(res) { + vm.error = res.data.message; + } + } + } +})(); diff --git a/modules/articles/client/controllers/admin/list-articles.client.controller.js b/modules/articles/client/controllers/admin/list-articles.client.controller.js new file mode 100644 index 0000000000..934e521de2 --- /dev/null +++ b/modules/articles/client/controllers/admin/list-articles.client.controller.js @@ -0,0 +1,15 @@ +(function () { + 'use strict'; + + angular + .module('articles') + .controller('ArticlesListController', ArticlesListController); + + ArticlesListController.$inject = ['ArticlesService']; + + function ArticlesListController(ArticlesService) { + var vm = this; + + vm.articles = ArticlesService.query(); + } +})(); diff --git a/modules/articles/client/views/form-article.client.view.html b/modules/articles/client/views/admin/form-article.client.view.html similarity index 89% rename from modules/articles/client/views/form-article.client.view.html rename to modules/articles/client/views/admin/form-article.client.view.html index 145a1b70a0..cc118cc0ff 100644 --- a/modules/articles/client/views/form-article.client.view.html +++ b/modules/articles/client/views/admin/form-article.client.view.html @@ -2,6 +2,11 @@ +
+ + + +
diff --git a/modules/articles/client/views/admin/list-articles.client.view.html b/modules/articles/client/views/admin/list-articles.client.view.html new file mode 100644 index 0000000000..6deddf2032 --- /dev/null +++ b/modules/articles/client/views/admin/list-articles.client.view.html @@ -0,0 +1,26 @@ +
+ + +
+ No articles yet, why don't you create one? +
+
diff --git a/modules/articles/client/views/list-articles.client.view.html b/modules/articles/client/views/list-articles.client.view.html index e0e964d05c..c117ba8fbe 100644 --- a/modules/articles/client/views/list-articles.client.view.html +++ b/modules/articles/client/views/list-articles.client.view.html @@ -15,7 +15,4 @@

-
- No articles yet, why don't you create one? -
diff --git a/modules/articles/client/views/view-article.client.view.html b/modules/articles/client/views/view-article.client.view.html index 1dd88c662d..89b313a569 100644 --- a/modules/articles/client/views/view-article.client.view.html +++ b/modules/articles/client/views/view-article.client.view.html @@ -2,14 +2,6 @@ -
- - - - - - -
Posted on diff --git a/modules/articles/server/policies/articles.server.policy.js b/modules/articles/server/policies/articles.server.policy.js index f18d96ffa5..f27396216f 100644 --- a/modules/articles/server/policies/articles.server.policy.js +++ b/modules/articles/server/policies/articles.server.policy.js @@ -25,7 +25,7 @@ exports.invokeRolesPolicies = function () { roles: ['user'], allows: [{ resources: '/api/articles', - permissions: ['get', 'post'] + permissions: ['get'] }, { resources: '/api/articles/:articleId', permissions: ['get'] diff --git a/modules/articles/tests/client/admin.articles.client.controller.tests.js b/modules/articles/tests/client/admin.articles.client.controller.tests.js new file mode 100644 index 0000000000..536a0c9526 --- /dev/null +++ b/modules/articles/tests/client/admin.articles.client.controller.tests.js @@ -0,0 +1,168 @@ +(function () { + 'use strict'; + + describe('Articles Controller Tests', function () { + // Initialize global variables + var ArticlesController, + $scope, + $httpBackend, + $state, + Authentication, + ArticlesService, + 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 + // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. + // When the toEqualData matcher compares two objects, it takes only object properties into + // account and ignores methods. + beforeEach(function () { + jasmine.addMatchers({ + toEqualData: function (util, customEqualityTesters) { + return { + compare: function (actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + // Then we can start by loading 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, _$state_, _$httpBackend_, _Authentication_, _ArticlesService_) { + // Set a new global scope + $scope = $rootScope.$new(); + + // Point global variables to injected services + $httpBackend = _$httpBackend_; + $state = _$state_; + Authentication = _Authentication_; + ArticlesService = _ArticlesService_; + + // create mock article + mockArticle = new ArticlesService({ + _id: '525a8422f6d0f87f0e407a33', + title: 'An Article about MEAN', + content: 'MEAN rocks!' + }); + + // Mock logged in user + Authentication.user = { + roles: ['user'] + }; + + // Initialize the Articles controller. + ArticlesController = $controller('ArticlesController as vm', { + $scope: $scope, + articleResolve: {} + }); + + //Spy on state go + spyOn($state, 'go'); + })); + + describe('vm.save() as create', function () { + var sampleArticlePostData; + + beforeEach(function () { + // Create a sample article object + sampleArticlePostData = new ArticlesService({ + title: 'An Article about MEAN', + content: 'MEAN rocks!' + }); + + $scope.vm.article = sampleArticlePostData; + }); + + it('should send a POST request with the form input values and then locate to new object URL', inject(function (ArticlesService) { + // Set POST response + $httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(mockArticle); + + // Run controller functionality + $scope.vm.save(true); + $httpBackend.flush(); + + // Test URL redirection after the article was created + expect($state.go).toHaveBeenCalledWith('admin.articles.list'); + })); + + it('should set $scope.vm.error if error', function () { + var errorMessage = 'this is an error message'; + $httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(400, { + message: errorMessage + }); + + $scope.vm.save(true); + $httpBackend.flush(); + + expect($scope.vm.error).toBe(errorMessage); + }); + }); + + describe('vm.save() as update', function () { + beforeEach(function () { + // Mock article in $scope + $scope.vm.article = mockArticle; + }); + + it('should update a valid article', inject(function (ArticlesService) { + // Set PUT response + $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(); + + // Run controller functionality + $scope.vm.save(true); + $httpBackend.flush(); + + // Test URL location to new object + expect($state.go).toHaveBeenCalledWith('admin.articles.list'); + })); + + it('should set $scope.vm.error if error', inject(function (ArticlesService) { + var errorMessage = 'error'; + $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(400, { + message: errorMessage + }); + + $scope.vm.save(true); + $httpBackend.flush(); + + expect($scope.vm.error).toBe(errorMessage); + })); + }); + + describe('vm.remove()', function () { + beforeEach(function () { + //Setup articles + $scope.vm.article = mockArticle; + }); + + it('should delete the article and redirect to articles', function () { + //Return true on confirm message + spyOn(window, 'confirm').and.returnValue(true); + + $httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204); + + $scope.vm.remove(); + $httpBackend.flush(); + + expect($state.go).toHaveBeenCalledWith('admin.articles.list'); + }); + + it('should should not delete the article and not redirect', function () { + //Return false on confirm message + spyOn(window, 'confirm').and.returnValue(false); + + $scope.vm.remove(); + + expect($state.go).not.toHaveBeenCalled(); + }); + }); + }); +})(); diff --git a/modules/articles/tests/client/admin.articles.client.routes.tests.js b/modules/articles/tests/client/admin.articles.client.routes.tests.js new file mode 100644 index 0000000000..40005b75af --- /dev/null +++ b/modules/articles/tests/client/admin.articles.client.routes.tests.js @@ -0,0 +1,163 @@ +(function () { + 'use strict'; + + describe('Articles Route Tests', function () { + // Initialize global variables + var $scope, + ArticlesService; + + //We can start by loading 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 ($rootScope, _ArticlesService_) { + // Set a new global scope + $scope = $rootScope.$new(); + ArticlesService = _ArticlesService_; + })); + + describe('Route Config', function () { + describe('Main Route', function () { + var mainstate; + beforeEach(inject(function ($state) { + mainstate = $state.get('admin.articles'); + })); + + it('Should have the correct URL', function () { + expect(mainstate.url).toEqual('/articles'); + }); + + it('Should be abstract', function () { + expect(mainstate.abstract).toBe(true); + }); + + it('Should have template', function () { + expect(mainstate.template).toBe(''); + }); + }); + + describe('List Route', function () { + var liststate; + beforeEach(inject(function ($state) { + liststate = $state.get('admin.articles.list'); + })); + + it('Should have the correct URL', function () { + expect(liststate.url).toEqual(''); + }); + + it('Should be not abstract', function () { + expect(liststate.abstract).toBe(undefined); + }); + + it('Should have templateUrl', function () { + expect(liststate.templateUrl).toBe('modules/articles/client/views/admin/list-articles.client.view.html'); + }); + }); + + describe('Create Route', function () { + var createstate, + ArticlesController, + mockArticle; + + beforeEach(inject(function ($controller, $state, $templateCache) { + createstate = $state.get('admin.articles.create'); + $templateCache.put('modules/articles/client/views/admin/form-article.client.view.html', ''); + + // create mock article + mockArticle = new ArticlesService(); + + //Initialize Controller + ArticlesController = $controller('ArticlesController as vm', { + $scope: $scope, + articleResolve: mockArticle + }); + })); + + it('Should have the correct URL', function () { + expect(createstate.url).toEqual('/create'); + }); + + it('Should have a resolve function', function () { + expect(typeof createstate.resolve).toEqual('object'); + expect(typeof createstate.resolve.articleResolve).toEqual('function'); + }); + + it('should respond to URL', inject(function ($state) { + expect($state.href(createstate)).toEqual('/admin/articles/create'); + })); + + it('should attach an article to the controller scope', function () { + expect($scope.vm.article._id).toBe(mockArticle._id); + expect($scope.vm.article._id).toBe(undefined); + }); + + it('Should not be abstract', function () { + expect(createstate.abstract).toBe(undefined); + }); + + it('Should have templateUrl', function () { + expect(createstate.templateUrl).toBe('modules/articles/client/views/admin/form-article.client.view.html'); + }); + }); + + describe('Edit Route', function () { + var editstate, + ArticlesController, + mockArticle; + + beforeEach(inject(function ($controller, $state, $templateCache) { + editstate = $state.get('admin.articles.edit'); + $templateCache.put('modules/articles/client/views/admin/form-article.client.view.html', ''); + + // create mock article + mockArticle = new ArticlesService({ + _id: '525a8422f6d0f87f0e407a33', + title: 'An Article about MEAN', + content: 'MEAN rocks!' + }); + + //Initialize Controller + ArticlesController = $controller('ArticlesController as vm', { + $scope: $scope, + articleResolve: mockArticle + }); + })); + + it('Should have the correct URL', function () { + expect(editstate.url).toEqual('/:articleId/edit'); + }); + + it('Should have a resolve function', function () { + expect(typeof editstate.resolve).toEqual('object'); + expect(typeof editstate.resolve.articleResolve).toEqual('function'); + }); + + it('should respond to URL', inject(function ($state) { + expect($state.href(editstate, { + articleId: 1 + })).toEqual('/admin/articles/1/edit'); + })); + + it('should attach an article to the controller scope', function () { + expect($scope.vm.article._id).toBe(mockArticle._id); + }); + + it('Should not be abstract', function () { + expect(editstate.abstract).toBe(undefined); + }); + + it('Should have templateUrl', function () { + expect(editstate.templateUrl).toBe('modules/articles/client/views/admin/form-article.client.view.html'); + }); + + xit('Should go to unauthorized route', function () { + + }); + }); + + }); + }); +})(); diff --git a/modules/articles/tests/client/admin.list.articles.client.controller.tests.js b/modules/articles/tests/client/admin.list.articles.client.controller.tests.js new file mode 100644 index 0000000000..241c928e31 --- /dev/null +++ b/modules/articles/tests/client/admin.list.articles.client.controller.tests.js @@ -0,0 +1,92 @@ +(function () { + 'use strict'; + + describe('Admin Articles List Controller Tests', function () { + // Initialize global variables + var ArticlesListController, + $scope, + $httpBackend, + $state, + Authentication, + ArticlesService, + 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 + // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. + // When the toEqualData matcher compares two objects, it takes only object properties into + // account and ignores methods. + beforeEach(function () { + jasmine.addMatchers({ + toEqualData: function (util, customEqualityTesters) { + return { + compare: function (actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + // Then we can start by loading 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, _$state_, _$httpBackend_, _Authentication_, _ArticlesService_) { + // Set a new global scope + $scope = $rootScope.$new(); + + // Point global variables to injected services + $httpBackend = _$httpBackend_; + $state = _$state_; + Authentication = _Authentication_; + ArticlesService = _ArticlesService_; + + // create mock article + mockArticle = new ArticlesService({ + _id: '525a8422f6d0f87f0e407a33', + title: 'An Article about MEAN', + content: 'MEAN rocks!' + }); + + // Mock logged in user + Authentication.user = { + roles: ['user', 'admin'] + }; + + // Initialize the Articles List controller. + ArticlesListController = $controller('ArticlesListController as vm', { + $scope: $scope + }); + + //Spy on state go + spyOn($state, 'go'); + })); + + describe('Instantiate', function () { + var mockArticleList; + + beforeEach(function () { + mockArticleList = [mockArticle, mockArticle]; + }); + + it('should send a GET request and return all articles', inject(function (ArticlesService) { + // Set POST response + $httpBackend.expectGET('api/articles').respond(mockArticleList); + + + $httpBackend.flush(); + + // Test form inputs are reset + expect($scope.vm.articles.length).toEqual(2); + expect($scope.vm.articles[0]).toEqual(mockArticle); + expect($scope.vm.articles[1]).toEqual(mockArticle); + + })); + }); + }); +})(); diff --git a/modules/articles/tests/client/articles.client.controller.tests.js b/modules/articles/tests/client/articles.client.controller.tests.js index d9107e6856..854acc30f3 100644 --- a/modules/articles/tests/client/articles.client.controller.tests.js +++ b/modules/articles/tests/client/articles.client.controller.tests.js @@ -67,106 +67,5 @@ //Spy on state go spyOn($state, 'go'); })); - - describe('vm.save() as create', function () { - var sampleArticlePostData; - - beforeEach(function () { - // Create a sample article object - sampleArticlePostData = new ArticlesService({ - title: 'An Article about MEAN', - content: 'MEAN rocks!' - }); - - $scope.vm.article = sampleArticlePostData; - }); - - it('should send a POST request with the form input values and then locate to new object URL', inject(function (ArticlesService) { - // Set POST response - $httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(mockArticle); - - // Run controller functionality - $scope.vm.save(true); - $httpBackend.flush(); - - // Test URL redirection after the article was created - expect($state.go).toHaveBeenCalledWith('articles.view', { - articleId: mockArticle._id - }); - })); - - it('should set $scope.vm.error if error', function () { - var errorMessage = 'this is an error message'; - $httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(400, { - message: errorMessage - }); - - $scope.vm.save(true); - $httpBackend.flush(); - - expect($scope.vm.error).toBe(errorMessage); - }); - }); - - describe('vm.save() as update', function () { - beforeEach(function () { - // Mock article in $scope - $scope.vm.article = mockArticle; - }); - - it('should update a valid article', inject(function (ArticlesService) { - // Set PUT response - $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(); - - // Run controller functionality - $scope.vm.save(true); - $httpBackend.flush(); - - // Test URL location to new object - expect($state.go).toHaveBeenCalledWith('articles.view', { - articleId: mockArticle._id - }); - })); - - it('should set $scope.vm.error if error', inject(function (ArticlesService) { - var errorMessage = 'error'; - $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(400, { - message: errorMessage - }); - - $scope.vm.save(true); - $httpBackend.flush(); - - expect($scope.vm.error).toBe(errorMessage); - })); - }); - - describe('vm.remove()', function () { - beforeEach(function () { - //Setup articles - $scope.vm.article = mockArticle; - }); - - it('should delete the article and redirect to articles', function () { - //Return true on confirm message - spyOn(window, 'confirm').and.returnValue(true); - - $httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204); - - $scope.vm.remove(); - $httpBackend.flush(); - - expect($state.go).toHaveBeenCalledWith('articles.list'); - }); - - it('should should not delete the article and not redirect', function () { - //Return false on confirm message - spyOn(window, 'confirm').and.returnValue(false); - - $scope.vm.remove(); - - expect($state.go).not.toHaveBeenCalled(); - }); - }); }); })(); diff --git a/modules/articles/tests/client/articles.client.routes.tests.js b/modules/articles/tests/client/articles.client.routes.tests.js index 32608c2af2..14097093ca 100644 --- a/modules/articles/tests/client/articles.client.routes.tests.js +++ b/modules/articles/tests/client/articles.client.routes.tests.js @@ -38,111 +38,33 @@ }); }); - describe('View Route', function () { - var viewstate, - ArticlesController, - mockArticle; - - beforeEach(inject(function ($controller, $state, $templateCache) { - viewstate = $state.get('articles.view'); - $templateCache.put('modules/articles/client/views/view-article.client.view.html', ''); - - // create mock article - mockArticle = new ArticlesService({ - _id: '525a8422f6d0f87f0e407a33', - title: 'An Article about MEAN', - content: 'MEAN rocks!' - }); - - //Initialize Controller - ArticlesController = $controller('ArticlesController as vm', { - $scope: $scope, - articleResolve: mockArticle - }); - })); - - it('Should have the correct URL', function () { - expect(viewstate.url).toEqual('/:articleId'); - }); - - it('Should have a resolve function', function () { - expect(typeof viewstate.resolve).toEqual('object'); - expect(typeof viewstate.resolve.articleResolve).toEqual('function'); - }); - - it('should respond to URL', inject(function ($state) { - expect($state.href(viewstate, { - articleId: 1 - })).toEqual('/articles/1'); - })); - - it('should attach an article to the controller scope', function () { - expect($scope.vm.article._id).toBe(mockArticle._id); - }); - - it('Should not be abstract', function () { - expect(viewstate.abstract).toBe(undefined); - }); - - it('Should have templateUrl', function () { - expect(viewstate.templateUrl).toBe('modules/articles/client/views/view-article.client.view.html'); - }); - }); - - describe('Create Route', function () { - var createstate, - ArticlesController, - mockArticle; - - beforeEach(inject(function ($controller, $state, $templateCache) { - createstate = $state.get('articles.create'); - $templateCache.put('modules/articles/client/views/form-article.client.view.html', ''); - - // create mock article - mockArticle = new ArticlesService(); - - //Initialize Controller - ArticlesController = $controller('ArticlesController as vm', { - $scope: $scope, - articleResolve: mockArticle - }); + describe('List Route', function () { + var liststate; + beforeEach(inject(function ($state) { + liststate = $state.get('articles.list'); })); it('Should have the correct URL', function () { - expect(createstate.url).toEqual('/create'); - }); - - it('Should have a resolve function', function () { - expect(typeof createstate.resolve).toEqual('object'); - expect(typeof createstate.resolve.articleResolve).toEqual('function'); - }); - - it('should respond to URL', inject(function ($state) { - expect($state.href(createstate)).toEqual('/articles/create'); - })); - - it('should attach an article to the controller scope', function () { - expect($scope.vm.article._id).toBe(mockArticle._id); - expect($scope.vm.article._id).toBe(undefined); + expect(liststate.url).toEqual(''); }); it('Should not be abstract', function () { - expect(createstate.abstract).toBe(undefined); + expect(liststate.abstract).toBe(undefined); }); it('Should have templateUrl', function () { - expect(createstate.templateUrl).toBe('modules/articles/client/views/form-article.client.view.html'); + expect(liststate.templateUrl).toBe('modules/articles/client/views/list-articles.client.view.html'); }); }); - describe('Edit Route', function () { - var editstate, + describe('View Route', function () { + var viewstate, ArticlesController, mockArticle; beforeEach(inject(function ($controller, $state, $templateCache) { - editstate = $state.get('articles.edit'); - $templateCache.put('modules/articles/client/views/form-article.client.view.html', ''); + viewstate = $state.get('articles.view'); + $templateCache.put('modules/articles/client/views/view-article.client.view.html', ''); // create mock article mockArticle = new ArticlesService({ @@ -159,18 +81,18 @@ })); it('Should have the correct URL', function () { - expect(editstate.url).toEqual('/:articleId/edit'); + expect(viewstate.url).toEqual('/:articleId'); }); it('Should have a resolve function', function () { - expect(typeof editstate.resolve).toEqual('object'); - expect(typeof editstate.resolve.articleResolve).toEqual('function'); + expect(typeof viewstate.resolve).toEqual('object'); + expect(typeof viewstate.resolve.articleResolve).toEqual('function'); }); it('should respond to URL', inject(function ($state) { - expect($state.href(editstate, { + expect($state.href(viewstate, { articleId: 1 - })).toEqual('/articles/1/edit'); + })).toEqual('/articles/1'); })); it('should attach an article to the controller scope', function () { @@ -178,18 +100,13 @@ }); it('Should not be abstract', function () { - expect(editstate.abstract).toBe(undefined); + expect(viewstate.abstract).toBe(undefined); }); it('Should have templateUrl', function () { - expect(editstate.templateUrl).toBe('modules/articles/client/views/form-article.client.view.html'); - }); - - xit('Should go to unauthorized route', function () { - + expect(viewstate.templateUrl).toBe('modules/articles/client/views/view-article.client.view.html'); }); }); - }); }); })(); diff --git a/modules/articles/tests/server/admin.article.server.routes.tests.js b/modules/articles/tests/server/admin.article.server.routes.tests.js new file mode 100644 index 0000000000..6b88dbc4c6 --- /dev/null +++ b/modules/articles/tests/server/admin.article.server.routes.tests.js @@ -0,0 +1,278 @@ +'use strict'; + +var should = require('should'), + request = require('supertest'), + path = require('path'), + mongoose = require('mongoose'), + User = mongoose.model('User'), + Article = mongoose.model('Article'), + express = require(path.resolve('./config/lib/express')); + +/** + * Globals + */ +var app, agent, credentials, user, article; + +/** + * Article routes tests + */ +describe('Article Admin 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: 'M3@n.jsI$Aw3$0m3' + }; + + // Create a new user + user = new User({ + firstName: 'Full', + lastName: 'Name', + displayName: 'Full Name', + email: 'test@test.com', + roles: ['user', 'admin'], + username: credentials.username, + password: credentials.password, + provider: 'local' + }); + + // Save a user to the test db and create new article + user.save(function () { + article = { + title: 'Article Title', + content: 'Article Content' + }; + + done(); + }); + }); + + it('should be able to save an article if logged in', function (done) { + agent.post('/api/auth/signin') + .send(credentials) + .expect(200) + .end(function (signinErr, signinRes) { + // Handle signin error + if (signinErr) { + return done(signinErr); + } + + // Get the userId + var userId = user.id; + + // Save a new article + agent.post('/api/articles') + .send(article) + .expect(200) + .end(function (articleSaveErr, articleSaveRes) { + // Handle article save error + if (articleSaveErr) { + return done(articleSaveErr); + } + + // Get a list of articles + agent.get('/api/articles') + .end(function (articlesGetErr, articlesGetRes) { + // Handle article save error + if (articlesGetErr) { + return done(articlesGetErr); + } + + // Get articles list + var articles = articlesGetRes.body; + + // Set assertions + (articles[0].user._id).should.equal(userId); + (articles[0].title).should.match('Article Title'); + + // Call the assertion callback + done(); + }); + }); + }); + }); + + it('should be able to update an article if signed in', function (done) { + agent.post('/api/auth/signin') + .send(credentials) + .expect(200) + .end(function (signinErr, signinRes) { + // Handle signin error + if (signinErr) { + return done(signinErr); + } + + // Get the userId + var userId = user.id; + + // Save a new article + agent.post('/api/articles') + .send(article) + .expect(200) + .end(function (articleSaveErr, articleSaveRes) { + // Handle article save error + if (articleSaveErr) { + return done(articleSaveErr); + } + + // Update article title + article.title = 'WHY YOU GOTTA BE SO MEAN?'; + + // Update an existing article + agent.put('/api/articles/' + articleSaveRes.body._id) + .send(article) + .expect(200) + .end(function (articleUpdateErr, articleUpdateRes) { + // Handle article update error + if (articleUpdateErr) { + return done(articleUpdateErr); + } + + // Set assertions + (articleUpdateRes.body._id).should.equal(articleSaveRes.body._id); + (articleUpdateRes.body.title).should.match('WHY YOU GOTTA BE SO MEAN?'); + + // Call the assertion callback + done(); + }); + }); + }); + }); + + it('should not be able to save an article if no title is provided', function (done) { + // Invalidate title field + article.title = ''; + + agent.post('/api/auth/signin') + .send(credentials) + .expect(200) + .end(function (signinErr, signinRes) { + // Handle signin error + if (signinErr) { + return done(signinErr); + } + + // Get the userId + var userId = user.id; + + // Save a new article + agent.post('/api/articles') + .send(article) + .expect(400) + .end(function (articleSaveErr, articleSaveRes) { + // Set message assertion + (articleSaveRes.body.message).should.match('Title cannot be blank'); + + // Handle article save error + done(articleSaveErr); + }); + }); + }); + + it('should be able to delete an article if signed in', function (done) { + agent.post('/api/auth/signin') + .send(credentials) + .expect(200) + .end(function (signinErr, signinRes) { + // Handle signin error + if (signinErr) { + return done(signinErr); + } + + // Get the userId + var userId = user.id; + + // Save a new article + agent.post('/api/articles') + .send(article) + .expect(200) + .end(function (articleSaveErr, articleSaveRes) { + // Handle article save error + if (articleSaveErr) { + return done(articleSaveErr); + } + + // Delete an existing article + agent.delete('/api/articles/' + articleSaveRes.body._id) + .send(article) + .expect(200) + .end(function (articleDeleteErr, articleDeleteRes) { + // Handle article error error + if (articleDeleteErr) { + return done(articleDeleteErr); + } + + // Set assertions + (articleDeleteRes.body._id).should.equal(articleSaveRes.body._id); + + // Call the assertion callback + done(); + }); + }); + }); + }); + + it('should be able to get a single article if signed in and verify the custom "isCurrentUserOwner" field is set to "true"', function (done) { + // Create new article model instance + article.user = user; + var articleObj = new Article(article); + + agent.post('/api/auth/signin') + .send(credentials) + .expect(200) + .end(function (signinErr, signinRes) { + // Handle signin error + if (signinErr) { + return done(signinErr); + } + + // Get the userId + var userId = user.id; + + // Save a new article + agent.post('/api/articles') + .send(article) + .expect(200) + .end(function (articleSaveErr, articleSaveRes) { + // Handle article save error + if (articleSaveErr) { + return done(articleSaveErr); + } + + // Get the article + agent.get('/api/articles/' + articleSaveRes.body._id) + .expect(200) + .end(function (articleInfoErr, articleInfoRes) { + // Handle article error + if (articleInfoErr) { + return done(articleInfoErr); + } + + // Set assertions + (articleInfoRes.body._id).should.equal(articleSaveRes.body._id); + (articleInfoRes.body.title).should.equal(article.title); + + // Assert that the "isCurrentUserOwner" field is set to true since the current User created it + (articleInfoRes.body.isCurrentUserOwner).should.equal(true); + + // Call the assertion callback + done(); + }); + }); + }); + }); + + afterEach(function (done) { + User.remove().exec(function () { + Article.remove().exec(done); + }); + }); +}); diff --git a/modules/articles/tests/server/article.server.routes.tests.js b/modules/articles/tests/server/article.server.routes.tests.js index 1dcf5da3bc..ef5138024c 100644 --- a/modules/articles/tests/server/article.server.routes.tests.js +++ b/modules/articles/tests/server/article.server.routes.tests.js @@ -55,7 +55,7 @@ describe('Article CRUD tests', function () { }); }); - it('should be able to save an article if logged in', function (done) { + it('should not be able to save an article if logged in without the "admin" role', function (done) { agent.post('/api/auth/signin') .send(credentials) .expect(200) @@ -65,38 +65,14 @@ describe('Article CRUD tests', function () { return done(signinErr); } - // Get the userId - var userId = user.id; - - // Save a new article agent.post('/api/articles') .send(article) - .expect(200) + .expect(403) .end(function (articleSaveErr, articleSaveRes) { - // Handle article save error - if (articleSaveErr) { - return done(articleSaveErr); - } - - // Get a list of articles - agent.get('/api/articles') - .end(function (articlesGetErr, articlesGetRes) { - // Handle article save error - if (articlesGetErr) { - return done(articlesGetErr); - } - - // Get articles list - var articles = articlesGetRes.body; - - // Set assertions - (articles[0].user._id).should.equal(userId); - (articles[0].title).should.match('Article Title'); - - // Call the assertion callback - done(); - }); + // Call the assertion callback + done(articleSaveErr); }); + }); }); @@ -110,10 +86,7 @@ describe('Article CRUD tests', function () { }); }); - it('should not be able to save an article if no title is provided', function (done) { - // Invalidate title field - article.title = ''; - + it('should not be able to update an article if signed in without the "admin" role', function (done) { agent.post('/api/auth/signin') .send(credentials) .expect(200) @@ -123,70 +96,16 @@ describe('Article CRUD tests', function () { return done(signinErr); } - // Get the userId - var userId = user.id; - - // Save a new article agent.post('/api/articles') .send(article) - .expect(400) + .expect(403) .end(function (articleSaveErr, articleSaveRes) { - // Set message assertion - (articleSaveRes.body.message).should.match('Title cannot be blank'); - - // Handle article save error + // Call the assertion callback done(articleSaveErr); }); }); }); - it('should be able to update an article if signed in', function (done) { - agent.post('/api/auth/signin') - .send(credentials) - .expect(200) - .end(function (signinErr, signinRes) { - // Handle signin error - if (signinErr) { - return done(signinErr); - } - - // Get the userId - var userId = user.id; - - // Save a new article - agent.post('/api/articles') - .send(article) - .expect(200) - .end(function (articleSaveErr, articleSaveRes) { - // Handle article save error - if (articleSaveErr) { - return done(articleSaveErr); - } - - // Update article title - article.title = 'WHY YOU GOTTA BE SO MEAN?'; - - // Update an existing article - agent.put('/api/articles/' + articleSaveRes.body._id) - .send(article) - .expect(200) - .end(function (articleUpdateErr, articleUpdateRes) { - // Handle article update error - if (articleUpdateErr) { - return done(articleUpdateErr); - } - - // Set assertions - (articleUpdateRes.body._id).should.equal(articleSaveRes.body._id); - (articleUpdateRes.body.title).should.match('WHY YOU GOTTA BE SO MEAN?'); - - // Call the assertion callback - done(); - }); - }); - }); - }); - it('should be able to get a list of articles if not signed in', function (done) { // Create new article model instance var articleObj = new Article(article); @@ -247,7 +166,7 @@ describe('Article CRUD tests', function () { }); }); - it('should be able to delete an article if signed in', function (done) { + it('should not be able to delete an article if signed in without the "admin" role', function (done) { agent.post('/api/auth/signin') .send(credentials) .expect(200) @@ -257,35 +176,12 @@ describe('Article CRUD tests', function () { return done(signinErr); } - // Get the userId - var userId = user.id; - - // Save a new article agent.post('/api/articles') .send(article) - .expect(200) + .expect(403) .end(function (articleSaveErr, articleSaveRes) { - // Handle article save error - if (articleSaveErr) { - return done(articleSaveErr); - } - - // Delete an existing article - agent.delete('/api/articles/' + articleSaveRes.body._id) - .send(article) - .expect(200) - .end(function (articleDeleteErr, articleDeleteRes) { - // Handle article error error - if (articleDeleteErr) { - return done(articleDeleteErr); - } - - // Set assertions - (articleDeleteRes.body._id).should.equal(articleSaveRes.body._id); - - // Call the assertion callback - done(); - }); + // Call the assertion callback + done(articleSaveErr); }); }); }); @@ -328,7 +224,8 @@ describe('Article CRUD tests', function () { email: 'orphan@test.com', username: _creds.username, password: _creds.password, - provider: 'local' + provider: 'local', + roles: ['admin'] }); _orphan.save(function (err, orphan) { @@ -400,58 +297,7 @@ describe('Article CRUD tests', function () { }); }); - it('should be able to get a single article if signed in and verify the custom "isCurrentUserOwner" field is set to "true"', function (done) { - // Create new article model instance - article.user = user; - var articleObj = new Article(article); - - // Save the article - articleObj.save(function () { - agent.post('/api/auth/signin') - .send(credentials) - .expect(200) - .end(function (signinErr, signinRes) { - // Handle signin error - if (signinErr) { - return done(signinErr); - } - - // Get the userId - var userId = user.id; - - // Save a new article - agent.post('/api/articles') - .send(article) - .expect(200) - .end(function (articleSaveErr, articleSaveRes) { - // Handle article save error - if (articleSaveErr) { - return done(articleSaveErr); - } - - // Get the article - agent.get('/api/articles/' + articleSaveRes.body._id) - .expect(200) - .end(function (articleInfoErr, articleInfoRes) { - // Handle article error - if (articleInfoErr) { - return done(articleInfoErr); - } - - // Set assertions - (articleInfoRes.body._id).should.equal(articleSaveRes.body._id); - (articleInfoRes.body.title).should.equal(article.title); - - // Assert that the "isCurrentUserOwner" field is set to true since the current User created it - (articleInfoRes.body.isCurrentUserOwner).should.equal(true); - - // Call the assertion callback - done(); - }); - }); - }); - }); - }); + it('should be able to get a single article if not signed in and verify the custom "isCurrentUserOwner" field is set to "false"', function (done) { // Create new article model instance @@ -474,22 +320,23 @@ describe('Article CRUD tests', function () { it('should be able to get single article, that a different user created, if logged in & verify the "isCurrentUserOwner" field is set to "false"', function (done) { // Create temporary user creds var _creds = { - username: 'temp', + username: 'articleowner', password: 'M3@n.jsI$Aw3$0m3' }; - // Create temporary user - var _user = new User({ + // Create user that will create the Article + var _articleOwner = new User({ firstName: 'Full', lastName: 'Name', displayName: 'Full Name', email: 'temp@test.com', username: _creds.username, password: _creds.password, - provider: 'local' + provider: 'local', + roles: ['admin', 'user'] }); - _user.save(function (err, _user) { + _articleOwner.save(function (err, _user) { // Handle save error if (err) { return done(err); @@ -497,7 +344,7 @@ describe('Article CRUD tests', function () { // Sign in with the user that will create the Article agent.post('/api/auth/signin') - .send(credentials) + .send(_creds) .expect(200) .end(function (signinErr, signinRes) { // Handle signin error @@ -506,7 +353,7 @@ describe('Article CRUD tests', function () { } // Get the userId - var userId = user._id; + var userId = _user._id; // Save a new article agent.post('/api/articles') @@ -523,9 +370,9 @@ describe('Article CRUD tests', function () { should.exist(articleSaveRes.body.user); should.equal(articleSaveRes.body.user._id, userId); - // now signin with the temporary user + // now signin with the test suite user agent.post('/api/auth/signin') - .send(_creds) + .send(credentials) .expect(200) .end(function (err, res) { // Handle signin error