diff --git a/.gitignore b/.gitignore index 67a0dad..bfb6505 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ node_modules -npm-debug.log bower_components components +*.log diff --git a/README.md b/README.md index 52fdb11..626c740 100644 --- a/README.md +++ b/README.md @@ -104,3 +104,15 @@ It is also possible to stop specific request from being retried, by returning `` return false; return config; }) + +###Testing + +Run tests and watch for changes: +``` +npm test +``` + +Run tests only one time: +``` +npm run test-single-run +``` diff --git a/bower.json b/bower.json index 54fac45..a95aa85 100644 --- a/bower.json +++ b/bower.json @@ -11,7 +11,8 @@ "angular": "^1.2" }, "ignore": [ - "package.json" + "package.json", + "spec" ], "license": "MIT" } diff --git a/package.json b/package.json index 30affc7..f498423 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,11 @@ ], "homepage": "https://github.com/witoldsz/angular-http-auth", "devDependencies": { + "angular": "^1.2", + "angular-mocks": "^1.2", + "jasmine-core": "^2.5.2", + "karma": "^1.3.0", + "karma-phantomjs-launcher": "~1.0.2", "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "uglifyjs": "^2.4.10" @@ -28,7 +33,12 @@ "build": "npm run build:minify -s && npm run build:copy -s", "build:minify": "uglifyjs src/http-auth-interceptor.js -o dist/http-auth-interceptor.min.js -c", "build:copy": "cat src/http-auth-interceptor.js > dist/http-auth-interceptor.js", + "preversion": "npm test", "version": "npm run build && git add -A dist", - "postversion": "git push && git push --tags" + "postversion": "git push && git push --tags", + "pretest": "npm install", + "test": "karma start spec/karma.conf.js", + "pretest-single-run": "npm install", + "test-single-run": "karma start spec/karma.conf.js --single-run" } } diff --git a/spec/http-auth-interceptor.spec.js b/spec/http-auth-interceptor.spec.js new file mode 100644 index 0000000..931adf3 --- /dev/null +++ b/spec/http-auth-interceptor.spec.js @@ -0,0 +1,91 @@ +'use strict'; + +describe('http-auth-interceptor Module', function () { + + // Load http-auth-interceptor module + beforeEach(module('http-auth-interceptor')); + + describe('Test that interceptor is configured', function () { + + var httpProvider; + + beforeEach(function ($httpProvider) { + httpProvider = $httpProvider; + }); + + it('`$httpProvider.interceptors` should contain `requestService`', inject(function () { + expect(httpProvider.interceptors).toContain('requestService'); + })); + + }); + + describe('Test `authService`', function () { + + it('can get an instance of `authService`', inject(function(authService) { + expect(authService).toBeDefined(); + })); + + describe('Events', function() { + + var authService, $httpBackend, $http, $scope; + var methods = ['GET', 'POST', 'UPDATE', 'DELETE']; + + beforeEach(function() { + // Get services and make them available + inject(function(_authService_, _$httpBackend_, _$http_, _$rootScope_) { + authService = _authService_; + $httpBackend = _$httpBackend_; + $http = _$http_; + $scope = _$rootScope_; + }); + + // Spy on $emit to detect events + spyOn($scope, '$broadcast'); + }); + + it('should broadcast "event:auth-loginRequired" on http 401 respones and "event:auth-loginConfirmed" after calling loginConfirmed', function() { + angular.forEach(methods, function(method) { + // require authentication (http 401) + $httpBackend.expect(method, '/myresource').respond(401); + $http({ method: method, url: '/myresource' }); + $httpBackend.flush(); + expect($scope.$broadcast).toHaveBeenCalledWith('event:auth-loginRequired', jasmine.any(Object)); + + // confirm auth + $httpBackend.expect(method, '/myresource').respond(200); + authService.loginConfirmed(); + expect($scope.$broadcast).toHaveBeenCalledWith('event:auth-loginConfirmed', undefined); + }); + }); + + it('should broadcast "event:auth-loginRequired" on http 401 respones and "event:auth-loginCancelled" after calling loginConfirmed', function() { + angular.forEach(methods, function(method) { + // require authentication (http 401) + $httpBackend.expect(method, '/myresource').respond(401); + $http({ method: method, url: '/myresource' }); + $httpBackend.flush(); + expect($scope.$broadcast).toHaveBeenCalledWith('event:auth-loginRequired', jasmine.any(Object)); + + // confirm auth + authService.loginCancelled(); + expect($scope.$broadcast).toHaveBeenCalledWith('event:auth-loginCancelled', undefined); + }); + }); + + it('should not broadcast any event on responses other than 401', function() { + // most of the following tests don't make sense, but they also don't hurt + for(status = 100; status <= 599; status++) { + angular.forEach(methods, function(method) { + $httpBackend.expect(method, '/myresource').respond(status); + $http({ method: method, url: '/myresource' }); + $httpBackend.flush(); + expect($scope.$broadcast).not.toHaveBeenCalled(); + }); + } + }); + + }); + + }); + +}); diff --git a/spec/karma.conf.js b/spec/karma.conf.js new file mode 100644 index 0000000..056d5ac --- /dev/null +++ b/spec/karma.conf.js @@ -0,0 +1,59 @@ +'use strict'; + +// Karma configuration +module.exports = function(config) { + config.set({ + + basePath: '../', + + // List of files / patterns to load in the browser + files: [ + 'node_modules/angular/angular.js', + 'node_modules/angular-mocks/angular-mocks.js', + 'src/http-auth-interceptor.js', + 'spec/*.spec.js' + ], + + // Test results reporter to use + // Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'x + reporters: ['progress'], + + // Web server port + port: 9876, + + // Enable / disable colors in the output (reporters and logs) + colors: true, + + // Level of logging + // Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // Enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // Frameworks to use + frameworks: ['jasmine'], + + plugins: [ + 'karma-jasmine', + 'karma-phantomjs-launcher' + ], + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: ['PhantomJS'], + + // If browser does not capture in given timeout [ms], kill it + captureTimeout: 60000, + + // Continuous Integration mode + // If true, will capture browsers, run tests and exit + singleRun: false + }); +}; diff --git a/src/http-auth-interceptor.js b/src/http-auth-interceptor.js index 0fd9789..5252215 100644 --- a/src/http-auth-interceptor.js +++ b/src/http-auth-interceptor.js @@ -47,29 +47,31 @@ * On 403 response (without 'ignoreAuthModule' option) discards the request * and broadcasts 'event:auth-forbidden'. */ - .config(['$httpProvider', function($httpProvider) { - $httpProvider.interceptors.push(['$rootScope', '$q', 'httpBuffer', function($rootScope, $q, httpBuffer) { - return { - responseError: function(rejection) { - var config = rejection.config || {}; - if (!config.ignoreAuthModule) { - switch (rejection.status) { - case 401: - var deferred = $q.defer(); - var bufferLength = httpBuffer.append(config, deferred); - if (bufferLength === 1) - $rootScope.$broadcast('event:auth-loginRequired', rejection); - return deferred.promise; - case 403: - $rootScope.$broadcast('event:auth-forbidden', rejection); - break; - } + .factory('requestService', ['$rootScope', '$q', 'httpBuffer', function($rootScope, $q, httpBuffer) { + return { + responseError: function(rejection) { + var config = rejection.config || {}; + if (!config.ignoreAuthModule) { + switch (rejection.status) { + case 401: + var deferred = $q.defer(); + var bufferLength = httpBuffer.append(config, deferred); + if (bufferLength === 1) + $rootScope.$broadcast('event:auth-loginRequired', rejection); + return deferred.promise; + case 403: + $rootScope.$broadcast('event:auth-forbidden', rejection); + break; } - // otherwise, default behaviour - return $q.reject(rejection); } - }; - }]); + // otherwise, default behaviour + return $q.reject(rejection); + } + }; + }]) + + .config(['$httpProvider', function($httpProvider) { + $httpProvider.interceptors.push('requestService'); }]); /**