Skip to content
This repository
Browse code

HTTP Auth Interceptor Module Demo.

  • Loading branch information...
commit c29bf3c70af423a77cf8f09aaddbf3b5c5792517 1 parent 515d1f4
Witold Szczerba authored
24 app/content-mocks.js
... ... @@ -0,0 +1,24 @@
  1 +angular.module('content-mocks',['ngMockE2E'])
  2 + .run(function($httpBackend) {
  3 +
  4 + var authorized = false;
  5 + $httpBackend.whenPOST('auth/login').respond(function(method, url, data) {
  6 + authorized = true;
  7 + return [200,''];
  8 + });
  9 + $httpBackend.whenPOST('auth/logout').respond(function(method, url, data) {
  10 + authorized = false;
  11 + return [200,''];
  12 + });
  13 +
  14 +
  15 + $httpBackend.whenGET('data/public').respond("Public content");
  16 + $httpBackend.whenGET('data/protected').respond(function() {
  17 + return authorized ? [200,'Protected content available'] : [401,'Auth required'];
  18 + });
  19 +
  20 + //otherwise
  21 +
  22 + $httpBackend.whenGET(/.*/).passThrough();
  23 +
  24 + });
28 app/content.js
... ... @@ -0,0 +1,28 @@
  1 +angular.module('angular-auth-demo').controller({
  2 + ContentController: function ($scope, $http) {
  3 +
  4 + $scope.publicContent = [];
  5 + $scope.restrictedContent = [];
  6 +
  7 + $scope.publicAction = function() {
  8 + $http.get('data/public').success(function(response) {
  9 + $scope.publicContent.push(response);
  10 + });
  11 + }
  12 +
  13 + $scope.restrictedAction = function() {
  14 + $http.get('data/protected').success(function(response) {
  15 + // this piece of code will not be executed until user is authenticated
  16 + $scope.restrictedContent.push(response);
  17 + });
  18 + }
  19 +
  20 + $scope.logout = function() {
  21 + $http.post('auth/logout').success(function() {
  22 + $scope.restrictedContent = [];
  23 + });
  24 + }
  25 + }
  26 +
  27 +});
  28 +
11 app/login.js
... ... @@ -0,0 +1,11 @@
  1 +angular.module('angular-auth-demo').controller({
  2 + LoginController: function ($scope, $http, authService) {
  3 + $scope.submit = function() {
  4 + $http.post('auth/login').success(function() {
  5 + authService.loginConfirmed();
  6 + });
  7 + }
  8 + }
  9 +
  10 +});
  11 +
123 app/main.css
... ... @@ -0,0 +1,123 @@
  1 +/* feat: before angular is ready, display loading animation */
  2 +body.waiting-for-angular div#content-outer {
  3 + display: none;
  4 +}
  5 +body.waiting-for-angular div#initializing-panel {
  6 + display: block;
  7 + background: url(../images/loading.gif) center center no-repeat;
  8 + position:absolute;
  9 + top:0;
  10 + left:0;
  11 + width: 100%;
  12 + height: 100%;
  13 +}
  14 +
  15 +
  16 +* {
  17 + margin: 0;
  18 + padding: 0;
  19 +}
  20 +
  21 +html, body {
  22 + height: 100%;
  23 +}
  24 +body {
  25 + background: #fff;
  26 + color: #393939;
  27 + font-family: Arial;
  28 + font-size: 0px;
  29 + line-height: 0;
  30 +}
  31 +#content-outer {
  32 + background: url(../images/content_repeat.jpg) repeat-x;
  33 + background-color: white;
  34 +}
  35 +#content {
  36 + color: #333;
  37 + font-family: Arial, Helvetica, sans-serif;
  38 + font-size: 12px;
  39 + line-height: 18px;
  40 + margin: 0 auto 0 auto;
  41 + max-width: 1260px;
  42 + min-width: 780px;
  43 + padding: 35px 0px 30px 0px;
  44 +}
  45 +h2 {
  46 + color: #393939;
  47 + font-size: 16px;
  48 + font-weight: bold;
  49 + line-height: 20px;
  50 + margin-bottom: 10px;
  51 +}
  52 +h3 {
  53 + color: #92b22c;
  54 + font-size: 14px;
  55 + font-weight: bold;
  56 + line-height: 18px;
  57 + margin-bottom: 10px;
  58 +}
  59 +/* LOGIN -------------------------------------------------------------------------------- */
  60 +
  61 +#login-bg {
  62 + background: url(../images/login/login_bg.jpg) no-repeat top center;
  63 +}
  64 +#login-holder {
  65 + margin: 0px auto 0 auto;
  66 + width: 508px;
  67 + padding-top: 100px;
  68 +}
  69 +#loginbox {
  70 + background: url(../images/login/loginbox_bg.png) no-repeat;
  71 + font-size: 12px;
  72 + height: 212px;
  73 + line-height: 12px;
  74 + padding-top: 60px;
  75 + position: relative;
  76 + width: 508px;
  77 +}
  78 +#login-inner {
  79 + color: #161616;
  80 + font-family: Tahoma;
  81 + font-size: 13px;
  82 + line-height: 12px;
  83 + margin: 0 auto;
  84 + width: 310px;
  85 +}
  86 +#login-inner label {
  87 + color: #161616;
  88 + cursor: pointer;
  89 + font-family: Tahoma;
  90 + font-weight: bold;
  91 + line-height: 12px;
  92 + padding-left: 10px;
  93 +
  94 +}
  95 +#login-inner th {
  96 + padding: 0 0 6px 0;
  97 + text-align: left;
  98 + width: 95px;
  99 +}
  100 +#login-inner td {
  101 + padding: 0 0 6px 0;
  102 +}
  103 +.login-inp {
  104 + background: url(../images/login/inp_login.gif) no-repeat;
  105 + border: none;
  106 + color: #fff;
  107 + font-size: 16px;
  108 + height: 28px;
  109 + padding: 6px 6px 0 10px;
  110 + width: 204px;
  111 +}
  112 +.submit-login {
  113 + background: url(../images/login/submit_login.gif) no-repeat;
  114 + border: none;
  115 + cursor: pointer;
  116 + display: block;
  117 + height: 29px;
  118 + text-indent: -3000px;
  119 + width: 73px;
  120 +}
  121 +.submit-login:hover {
  122 + background: url(../images/login/submit_login.gif) no-repeat 0 -29px;
  123 +}
30 app/main.js
... ... @@ -0,0 +1,30 @@
  1 +angular.module('angular-auth-demo', ['http-auth-interceptor','content-mocks'])
  2 + /**
  3 + * This directive will find itself inside HTML as a class,
  4 + * and will remove that class, so CSS will remove loading image and show app content.
  5 + * It is also responsible for showing/hiding login form.
  6 + */
  7 + .directive('authDemoApplication', function() {
  8 + return {
  9 + restrict: 'C',
  10 + link: function(scope, elem, attrs) {
  11 + //once Angular is started, remove class:
  12 + elem.removeClass('waiting-for-angular');
  13 +
  14 + var login = elem.find('#login-holder');
  15 + var main = elem.find('#content');
  16 +
  17 + login.hide();
  18 +
  19 + scope.$on('event:auth-loginRequired', function() {
  20 + login.slideDown('slow', function() {
  21 + main.hide();
  22 + });
  23 + });
  24 + scope.$on('event:auth-loginConfirmed', function() {
  25 + main.show();
  26 + login.slideUp();
  27 + });
  28 + }
  29 + }
  30 + });
BIN  images/content_repeat.jpg
BIN  images/loading.gif
BIN  images/login/icon_back_login.gif
BIN  images/login/inp_login.gif
BIN  images/login/login_bg.jpg
BIN  images/login/loginbox_bg.png
BIN  images/login/submit_login.gif
81 index.html
... ... @@ -1 +1,80 @@
1   -My GitHub Page
  1 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  2 +<html xmlns="http://www.w3.org/1999/xhtml">
  3 + <head>
  4 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  5 + <title>Angular authorization demo application</title>
  6 +
  7 + <link rel="stylesheet" href="app/main.css" type="text/css" media="screen" />
  8 +
  9 + <script type="text/javascript"></script>
  10 +
  11 + </head>
  12 +
  13 + <body ng-app="angular-auth-demo" class="auth-demo-application waiting-for-angular">
  14 +
  15 + <div id="initializing-panel"></div>
  16 +
  17 + <div id="content-outer">
  18 +
  19 + <div id="login-holder">
  20 + <div id="loginbox">
  21 + <div id="login-inner" ng:controller="LoginController">
  22 + <form ng-submit="submit()">
  23 + <table border="0" cellpadding="0" cellspacing="0">
  24 + <tr>
  25 + <th>Username</th>
  26 + <td><input type="text" class="login-inp" ng-model="username"/></td>
  27 + </tr>
  28 + <tr>
  29 + <th>Password</th>
  30 + <td><input type="password" class="login-inp" ng-model="password"/></td>
  31 + </tr>
  32 + <tr>
  33 + <th></th>
  34 + <td><input type="submit" class="submit-login"/></td>
  35 + </tr>
  36 + </table>
  37 + </form>
  38 + </div>
  39 + <div class="clear"></div>
  40 + </div>
  41 + </div>
  42 +
  43 + <div id="content" ng-controller="ContentController">
  44 +
  45 + <h1>HTTP Auth Interceptor Module Demo</h1>
  46 + <h2>for AngularJS</h2>
  47 +
  48 + <h3>Press the button bellow to access public data.</h3>
  49 + <input type="button" value="Public" ng-click="publicAction()"></input>
  50 + <p ng-repeat="p in publicContent">{{p}}</p>
  51 +
  52 + <br/>
  53 + <br/>
  54 +
  55 + <h3>Press the button bellow to access protected data.</h3>
  56 + <input type="button" value="Protected" ng-click="restrictedAction()"></input>
  57 + <p ng-repeat="r in restrictedContent">{{r}}</p>
  58 +
  59 + <br/>
  60 + <br/>
  61 +
  62 + <h3>Press the button to log out.</h3>
  63 + <input type="button" value="Log out" ng-click="logout()"></input>
  64 + <p>
  65 + The button above will actually kill current user's session.
  66 + </p>
  67 +
  68 + </div>
  69 + <div class="clear">&nbsp;</div>
  70 + </div>
  71 + <script src="lib/jquery-1.7.2.js" type="text/javascript"></script>
  72 + <script src="lib/angular-1.0.1.js" type="text/javascript"></script>
  73 + <script src="lib/angular-mocks-1.0.1.js" type="text/javascript"></script>
  74 + <script src="lib/http-auth-interceptor.js" type="text/javascript"></script>
  75 + <script src="app/content-mocks.js" type="text/javascript"></script>
  76 + <script src="app/main.js" type="text/javascript"></script>
  77 + <script src="app/login.js" type="text/javascript"></script>
  78 + <script src="app/content.js" type="text/javascript"></script>
  79 + </body>
  80 +</html>
14,327 lib/angular-1.0.1.js
14,327 additions, 0 deletions not shown
1,719 lib/angular-mocks-1.0.1.js
... ... @@ -0,0 +1,1719 @@
  1 +
  2 +/**
  3 + * @license AngularJS v1.0.1
  4 + * (c) 2010-2012 Google, Inc. http://angularjs.org
  5 + * License: MIT
  6 + *
  7 + * TODO(vojta): wrap whole file into closure during build
  8 + */
  9 +
  10 +/**
  11 + * @ngdoc overview
  12 + * @name angular.mock
  13 + * @description
  14 + *
  15 + * Namespace from 'angular-mocks.js' which contains testing related code.
  16 + */
  17 +angular.mock = {};
  18 +
  19 +/**
  20 + * ! This is a private undocumented service !
  21 + *
  22 + * @name ngMock.$browser
  23 + *
  24 + * @description
  25 + * This service is a mock implementation of {@link ng.$browser}. It provides fake
  26 + * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr,
  27 + * cookies, etc...
  28 + *
  29 + * The api of this service is the same as that of the real {@link ng.$browser $browser}, except
  30 + * that there are several helper methods available which can be used in tests.
  31 + */
  32 +angular.mock.$BrowserProvider = function() {
  33 + this.$get = function(){
  34 + return new angular.mock.$Browser();
  35 + };
  36 +};
  37 +
  38 +angular.mock.$Browser = function() {
  39 + var self = this;
  40 +
  41 + this.isMock = true;
  42 + self.$$url = "http://server/";
  43 + self.$$lastUrl = self.$$url; // used by url polling fn
  44 + self.pollFns = [];
  45 +
  46 + // TODO(vojta): remove this temporary api
  47 + self.$$completeOutstandingRequest = angular.noop;
  48 + self.$$incOutstandingRequestCount = angular.noop;
  49 +
  50 +
  51 + // register url polling fn
  52 +
  53 + self.onUrlChange = function(listener) {
  54 + self.pollFns.push(
  55 + function() {
  56 + if (self.$$lastUrl != self.$$url) {
  57 + self.$$lastUrl = self.$$url;
  58 + listener(self.$$url);
  59 + }
  60 + }
  61 + );
  62 +
  63 + return listener;
  64 + };
  65 +
  66 + self.cookieHash = {};
  67 + self.lastCookieHash = {};
  68 + self.deferredFns = [];
  69 + self.deferredNextId = 0;
  70 +
  71 + self.defer = function(fn, delay) {
  72 + delay = delay || 0;
  73 + self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
  74 + self.deferredFns.sort(function(a,b){ return a.time - b.time;});
  75 + return self.deferredNextId++;
  76 + };
  77 +
  78 +
  79 + self.defer.now = 0;
  80 +
  81 +
  82 + self.defer.cancel = function(deferId) {
  83 + var fnIndex;
  84 +
  85 + angular.forEach(self.deferredFns, function(fn, index) {
  86 + if (fn.id === deferId) fnIndex = index;
  87 + });
  88 +
  89 + if (fnIndex !== undefined) {
  90 + self.deferredFns.splice(fnIndex, 1);
  91 + return true;
  92 + }
  93 +
  94 + return false;
  95 + };
  96 +
  97 +
  98 + /**
  99 + * @name ngMock.$browser#defer.flush
  100 + * @methodOf ngMock.$browser
  101 + *
  102 + * @description
  103 + * Flushes all pending requests and executes the defer callbacks.
  104 + *
  105 + * @param {number=} number of milliseconds to flush. See {@link #defer.now}
  106 + */
  107 + self.defer.flush = function(delay) {
  108 + if (angular.isDefined(delay)) {
  109 + self.defer.now += delay;
  110 + } else {
  111 + if (self.deferredFns.length) {
  112 + self.defer.now = self.deferredFns[self.deferredFns.length-1].time;
  113 + } else {
  114 + throw Error('No deferred tasks to be flushed');
  115 + }
  116 + }
  117 +
  118 + while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) {
  119 + self.deferredFns.shift().fn();
  120 + }
  121 + };
  122 + /**
  123 + * @name ngMock.$browser#defer.now
  124 + * @propertyOf ngMock.$browser
  125 + *
  126 + * @description
  127 + * Current milliseconds mock time.
  128 + */
  129 +
  130 + self.$$baseHref = '';
  131 + self.baseHref = function() {
  132 + return this.$$baseHref;
  133 + };
  134 +};
  135 +angular.mock.$Browser.prototype = {
  136 +
  137 +/**
  138 + * @name ngMock.$browser#poll
  139 + * @methodOf ngMock.$browser
  140 + *
  141 + * @description
  142 + * run all fns in pollFns
  143 + */
  144 + poll: function poll() {
  145 + angular.forEach(this.pollFns, function(pollFn){
  146 + pollFn();
  147 + });
  148 + },
  149 +
  150 + addPollFn: function(pollFn) {
  151 + this.pollFns.push(pollFn);
  152 + return pollFn;
  153 + },
  154 +
  155 + url: function(url, replace) {
  156 + if (url) {
  157 + this.$$url = url;
  158 + return this;
  159 + }
  160 +
  161 + return this.$$url;
  162 + },
  163 +
  164 + cookies: function(name, value) {
  165 + if (name) {
  166 + if (value == undefined) {
  167 + delete this.cookieHash[name];
  168 + } else {
  169 + if (angular.isString(value) && //strings only
  170 + value.length <= 4096) { //strict cookie storage limits
  171 + this.cookieHash[name] = value;
  172 + }
  173 + }
  174 + } else {
  175 + if (!angular.equals(this.cookieHash, this.lastCookieHash)) {
  176 + this.lastCookieHash = angular.copy(this.cookieHash);
  177 + this.cookieHash = angular.copy(this.cookieHash);
  178 + }
  179 + return this.cookieHash;
  180 + }
  181 + },
  182 +
  183 + notifyWhenNoOutstandingRequests: function(fn) {
  184 + fn();
  185 + }
  186 +};
  187 +
  188 +
  189 +/**
  190 + * @ngdoc object
  191 + * @name ngMock.$exceptionHandlerProvider
  192 + *
  193 + * @description
  194 + * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed
  195 + * into the `$exceptionHandler`.
  196 + */
  197 +
  198 +/**
  199 + * @ngdoc object
  200 + * @name ngMock.$exceptionHandler
  201 + *
  202 + * @description
  203 + * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed
  204 + * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration
  205 + * information.
  206 + */
  207 +
  208 +angular.mock.$ExceptionHandlerProvider = function() {
  209 + var handler;
  210 +
  211 + /**
  212 + * @ngdoc method
  213 + * @name ngMock.$exceptionHandlerProvider#mode
  214 + * @methodOf ngMock.$exceptionHandlerProvider
  215 + *
  216 + * @description
  217 + * Sets the logging mode.
  218 + *
  219 + * @param {string} mode Mode of operation, defaults to `rethrow`.
  220 + *
  221 + * - `rethrow`: If any errors are are passed into the handler in tests, it typically
  222 + * means that there is a bug in the application or test, so this mock will
  223 + * make these tests fail.
  224 + * - `log`: Sometimes it is desirable to test that an error is throw, for this case the `log` mode stores the
  225 + * error and allows later assertion of it.
  226 + * See {@link ngMock.$log#assertEmpty assertEmpty()} and
  227 + * {@link ngMock.$log#reset reset()}
  228 + */
  229 + this.mode = function(mode) {
  230 + switch(mode) {
  231 + case 'rethrow':
  232 + handler = function(e) {
  233 + throw e;
  234 + };
  235 + break;
  236 + case 'log':
  237 + var errors = [];
  238 +
  239 + handler = function(e) {
  240 + if (arguments.length == 1) {
  241 + errors.push(e);
  242 + } else {
  243 + errors.push([].slice.call(arguments, 0));
  244 + }
  245 + };
  246 +
  247 + handler.errors = errors;
  248 + break;
  249 + default:
  250 + throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!");
  251 + }
  252 + };
  253 +
  254 + this.$get = function() {
  255 + return handler;
  256 + };
  257 +
  258 + this.mode('rethrow');
  259 +};
  260 +
  261 +
  262 +/**
  263 + * @ngdoc service
  264 + * @name ngMock.$log
  265 + *
  266 + * @description
  267 + * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays
  268 + * (one array per logging level). These arrays are exposed as `logs` property of each of the
  269 + * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`.
  270 + *
  271 + */
  272 +angular.mock.$LogProvider = function() {
  273 +
  274 + function concat(array1, array2, index) {
  275 + return array1.concat(Array.prototype.slice.call(array2, index));
  276 + }
  277 +
  278 +
  279 + this.$get = function () {
  280 + var $log = {
  281 + log: function() { $log.log.logs.push(concat([], arguments, 0)); },
  282 + warn: function() { $log.warn.logs.push(concat([], arguments, 0)); },
  283 + info: function() { $log.info.logs.push(concat([], arguments, 0)); },
  284 + error: function() { $log.error.logs.push(concat([], arguments, 0)); }
  285 + };
  286 +
  287 + /**
  288 + * @ngdoc method
  289 + * @name ngMock.$log#reset
  290 + * @methodOf ngMock.$log
  291 + *
  292 + * @description
  293 + * Reset all of the logging arrays to empty.
  294 + */
  295 + $log.reset = function () {
  296 + /**
  297 + * @ngdoc property
  298 + * @name ngMock.$log#log.logs
  299 + * @propertyOf ngMock.$log
  300 + *
  301 + * @description
  302 + * Array of logged messages.
  303 + */
  304 + $log.log.logs = [];
  305 + /**
  306 + * @ngdoc property
  307 + * @name ngMock.$log#warn.logs
  308 + * @propertyOf ngMock.$log
  309 + *
  310 + * @description
  311 + * Array of logged messages.
  312 + */
  313 + $log.warn.logs = [];
  314 + /**
  315 + * @ngdoc property
  316 + * @name ngMock.$log#info.logs
  317 + * @propertyOf ngMock.$log
  318 + *
  319 + * @description
  320 + * Array of logged messages.
  321 + */
  322 + $log.info.logs = [];
  323 + /**
  324 + * @ngdoc property
  325 + * @name ngMock.$log#error.logs
  326 + * @propertyOf ngMock.$log
  327 + *
  328 + * @description
  329 + * Array of logged messages.
  330 + */
  331 + $log.error.logs = [];
  332 + };
  333 +
  334 + /**
  335 + * @ngdoc method
  336 + * @name ngMock.$log#assertEmpty
  337 + * @methodOf ngMock.$log
  338 + *
  339 + * @description
  340 + * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown.
  341 + */
  342 + $log.assertEmpty = function() {
  343 + var errors = [];
  344 + angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) {
  345 + angular.forEach($log[logLevel].logs, function(log) {
  346 + angular.forEach(log, function (logItem) {
  347 + errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || ''));
  348 + });
  349 + });
  350 + });
  351 + if (errors.length) {
  352 + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " +
  353 + "log message was not checked and removed:");
  354 + errors.push('');
  355 + throw new Error(errors.join('\n---------\n'));
  356 + }
  357 + };
  358 +
  359 + $log.reset();
  360 + return $log;
  361 + };
  362 +};
  363 +
  364 +
  365 +(function() {
  366 + var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
  367 +
  368 + function jsonStringToDate(string){
  369 + var match;
  370 + if (match = string.match(R_ISO8061_STR)) {
  371 + var date = new Date(0),
  372 + tzHour = 0,
  373 + tzMin = 0;
  374 + if (match[9]) {
  375 + tzHour = int(match[9] + match[10]);
  376 + tzMin = int(match[9] + match[11]);
  377 + }
  378 + date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
  379 + date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
  380 + return date;
  381 + }
  382 + return string;
  383 + }
  384 +
  385 + function int(str) {
  386 + return parseInt(str, 10);
  387 + }
  388 +
  389 + function padNumber(num, digits, trim) {
  390 + var neg = '';
  391 + if (num < 0) {
  392 + neg = '-';
  393 + num = -num;
  394 + }
  395 + num = '' + num;
  396 + while(num.length < digits) num = '0' + num;
  397 + if (trim)
  398 + num = num.substr(num.length - digits);
  399 + return neg + num;
  400 + }
  401 +
  402 +
  403 + /**
  404 + * @ngdoc object
  405 + * @name angular.mock.TzDate
  406 + * @description
  407 + *
  408 + * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
  409 + *
  410 + * Mock of the Date type which has its timezone specified via constroctor arg.
  411 + *
  412 + * The main purpose is to create Date-like instances with timezone fixed to the specified timezone
  413 + * offset, so that we can test code that depends on local timezone settings without dependency on
  414 + * the time zone settings of the machine where the code is running.
  415 + *
  416 + * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
  417 + * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
  418 + *
  419 + * @example
  420 + * !!!! WARNING !!!!!
  421 + * This is not a complete Date object so only methods that were implemented can be called safely.
  422 + * To make matters worse, TzDate instances inherit stuff from Date via a prototype.
  423 + *
  424 + * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
  425 + * incomplete we might be missing some non-standard methods. This can result in errors like:
  426 + * "Date.prototype.foo called on incompatible Object".
  427 + *
  428 + * <pre>
  429 + * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
  430 + * newYearInBratislava.getTimezoneOffset() => -60;
  431 + * newYearInBratislava.getFullYear() => 2010;
  432 + * newYearInBratislava.getMonth() => 0;
  433 + * newYearInBratislava.getDate() => 1;
  434 + * newYearInBratislava.getHours() => 0;
  435 + * newYearInBratislava.getMinutes() => 0;
  436 + * </pre>
  437 + *
  438 + */
  439 + angular.mock.TzDate = function (offset, timestamp) {
  440 + var self = new Date(0);
  441 + if (angular.isString(timestamp)) {
  442 + var tsStr = timestamp;
  443 +
  444 + self.origDate = jsonStringToDate(timestamp);
  445 +
  446 + timestamp = self.origDate.getTime();
  447 + if (isNaN(timestamp))
  448 + throw {
  449 + name: "Illegal Argument",
  450 + message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
  451 + };
  452 + } else {
  453 + self.origDate = new Date(timestamp);
  454 + }
  455 +
  456 + var localOffset = new Date(timestamp).getTimezoneOffset();
  457 + self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
  458 + self.date = new Date(timestamp + self.offsetDiff);
  459 +
  460 + self.getTime = function() {
  461 + return self.date.getTime() - self.offsetDiff;
  462 + };
  463 +
  464 + self.toLocaleDateString = function() {
  465 + return self.date.toLocaleDateString();
  466 + };
  467 +
  468 + self.getFullYear = function() {
  469 + return self.date.getFullYear();
  470 + };
  471 +
  472 + self.getMonth = function() {
  473 + return self.date.getMonth();
  474 + };
  475 +
  476 + self.getDate = function() {
  477 + return self.date.getDate();
  478 + };
  479 +
  480 + self.getHours = function() {
  481 + return self.date.getHours();
  482 + };
  483 +
  484 + self.getMinutes = function() {
  485 + return self.date.getMinutes();
  486 + };
  487 +
  488 + self.getSeconds = function() {
  489 + return self.date.getSeconds();
  490 + };
  491 +
  492 + self.getTimezoneOffset = function() {
  493 + return offset * 60;
  494 + };
  495 +
  496 + self.getUTCFullYear = function() {
  497 + return self.origDate.getUTCFullYear();
  498 + };
  499 +
  500 + self.getUTCMonth = function() {
  501 + return self.origDate.getUTCMonth();
  502 + };
  503 +
  504 + self.getUTCDate = function() {
  505 + return self.origDate.getUTCDate();
  506 + };
  507 +
  508 + self.getUTCHours = function() {
  509 + return self.origDate.getUTCHours();
  510 + };
  511 +
  512 + self.getUTCMinutes = function() {
  513 + return self.origDate.getUTCMinutes();
  514 + };
  515 +
  516 + self.getUTCSeconds = function() {
  517 + return self.origDate.getUTCSeconds();
  518 + };
  519 +
  520 + self.getUTCMilliseconds = function() {
  521 + return self.origDate.getUTCMilliseconds();
  522 + };
  523 +
  524 + self.getDay = function() {
  525 + return self.date.getDay();
  526 + };
  527 +
  528 + // provide this method only on browsers that already have it
  529 + if (self.toISOString) {
  530 + self.toISOString = function() {
  531 + return padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
  532 + padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
  533 + padNumber(self.origDate.getUTCDate(), 2) + 'T' +
  534 + padNumber(self.origDate.getUTCHours(), 2) + ':' +
  535 + padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
  536 + padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
  537 + padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'
  538 + }
  539 + }
  540 +
  541 + //hide all methods not implemented in this mock that the Date prototype exposes
  542 + var unimplementedMethods = ['getMilliseconds', 'getUTCDay',
  543 + 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
  544 + 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
  545 + 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
  546 + 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString',
  547 + 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
  548 +
  549 + angular.forEach(unimplementedMethods, function(methodName) {
  550 + self[methodName] = function() {
  551 + throw Error("Method '" + methodName + "' is not implemented in the TzDate mock");
  552 + };
  553 + });
  554 +
  555 + return self;
  556 + };
  557 +
  558 + //make "tzDateInstance instanceof Date" return true
  559 + angular.mock.TzDate.prototype = Date.prototype;
  560 +})();
  561 +
  562 +
  563 +/**
  564 + * @ngdoc function
  565 + * @name angular.mock.debug
  566 + * @description
  567 + *
  568 + * *NOTE*: this is not an injectable instance, just a globally available function.
  569 + *
  570 + * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging.
  571 + *
  572 + * This method is also available on window, where it can be used to display objects on debug console.
  573 + *
  574 + * @param {*} object - any object to turn into string.
  575 + * @return {string} a serialized string of the argument
  576 + */
  577 +angular.mock.dump = function(object) {
  578 + return serialize(object);
  579 +
  580 + function serialize(object) {
  581 + var out;
  582 +
  583 + if (angular.isElement(object)) {
  584 + object = angular.element(object);
  585 + out = angular.element('<div></div>');
  586 + angular.forEach(object, function(element) {
  587 + out.append(angular.element(element).clone());
  588 + });
  589 + out = out.html();
  590 + } else if (angular.isArray(object)) {
  591 + out = [];
  592 + angular.forEach(object, function(o) {
  593 + out.push(serialize(o));
  594 + });
  595 + out = '[ ' + out.join(', ') + ' ]';
  596 + } else if (angular.isObject(object)) {
  597 + if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) {
  598 + out = serializeScope(object);
  599 + } else if (object instanceof Error) {
  600 + out = object.stack || ('' + object.name + ': ' + object.message);
  601 + } else {
  602 + out = angular.toJson(object, true);
  603 + }
  604 + } else {
  605 + out = String(object);
  606 + }
  607 +
  608 + return out;
  609 + }
  610 +
  611 + function serializeScope(scope, offset) {
  612 + offset = offset || ' ';
  613 + var log = [offset + 'Scope(' + scope.$id + '): {'];
  614 + for ( var key in scope ) {
  615 + if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) {
  616 + log.push(' ' + key + ': ' + angular.toJson(scope[key]));
  617 + }
  618 + }
  619 + var child = scope.$$childHead;
  620 + while(child) {
  621 + log.push(serializeScope(child, offset + ' '));
  622 + child = child.$$nextSibling;
  623 + }
  624 + log.push('}');
  625 + return log.join('\n' + offset);
  626 + }
  627 +};
  628 +
  629 +/**
  630 + * @ngdoc object
  631 + * @name ngMock.$httpBackend
  632 + * @description
  633 + * Fake HTTP backend implementation suitable for unit testing application that use the
  634 + * {@link ng.$http $http service}.
  635 + *
  636 + * *Note*: For fake http backend implementation suitable for end-to-end testing or backend-less
  637 + * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}.
  638 + *
  639 + * During unit testing, we want our unit tests to run quickly and have no external dependencies so
  640 + * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or
  641 + * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is
  642 + * to verify whether a certain request has been sent or not, or alternatively just let the
  643 + * application make requests, respond with pre-trained responses and assert that the end result is
  644 + * what we expect it to be.
  645 + *
  646 + * This mock implementation can be used to respond with static or dynamic responses via the
  647 + * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc).
  648 + *
  649 + * When an Angular application needs some data from a server, it calls the $http service, which
  650 + * sends the request to a real server using $httpBackend service. With dependency injection, it is
  651 + * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
  652 + * the requests and respond with some testing data without sending a request to real server.
  653 + *
  654 + * There are two ways to specify what test data should be returned as http responses by the mock
  655 + * backend when the code under test makes http requests:
  656 + *
  657 + * - `$httpBackend.expect` - specifies a request expectation
  658 + * - `$httpBackend.when` - specifies a backend definition
  659 + *
  660 + *
  661 + * # Request Expectations vs Backend Definitions
  662 + *
  663 + * Request expectations provide a way to make assertions about requests made by the application and
  664 + * to define responses for those requests. The test will fail if the expected requests are not made
  665 + * or they are made in the wrong order.
  666 + *
  667 + * Backend definitions allow you to define a fake backend for your application which doesn't assert
  668 + * if a particular request was made or not, it just returns a trained response if a request is made.
  669 + * The test will pass whether or not the request gets made during testing.
  670 + *
  671 + *
  672 + * <table class="table">
  673 + * <tr><th width="220px"></th><th>Request expectations</th><th>Backend definitions</th></tr>
  674 + * <tr>
  675 + * <th>Syntax</th>
  676 + * <td>.expect(...).respond(...)</td>
  677 + * <td>.when(...).respond(...)</td>
  678 + * </tr>
  679 + * <tr>
  680 + * <th>Typical usage</th>
  681 + * <td>strict unit tests</td>
  682 + * <td>loose (black-box) unit testing</td>
  683 + * </tr>
  684 + * <tr>
  685 + * <th>Fulfills multiple requests</th>
  686 + * <td>NO</td>
  687 + * <td>YES</td>
  688 + * </tr>
  689 + * <tr>
  690 + * <th>Order of requests matters</th>
  691 + * <td>YES</td>
  692 + * <td>NO</td>
  693 + * </tr>
  694 + * <tr>
  695 + * <th>Request required</th>
  696 + * <td>YES</td>
  697 + * <td>NO</td>
  698 + * </tr>
  699 + * <tr>
  700 + * <th>Response required</th>
  701 + * <td>optional (see below)</td>
  702 + * <td>YES</td>
  703 + * </tr>
  704 + * </table>
  705 + *
  706 + * In cases where both backend definitions and request expectations are specified during unit
  707 + * testing, the request expectations are evaluated first.
  708 + *
  709 + * If a request expectation has no response specified, the algorithm will search your backend
  710 + * definitions for an appropriate response.
  711 + *
  712 + * If a request didn't match any expectation or if the expectation doesn't have the response
  713 + * defined, the backend definitions are evaluated in sequential order to see if any of them match
  714 + * the request. The response from the first matched definition is returned.
  715 + *
  716 + *
  717 + * # Flushing HTTP requests
  718 + *
  719 + * The $httpBackend used in production, always responds to requests with responses asynchronously.
  720 + * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are
  721 + * hard to write, follow and maintain. At the same time the testing mock, can't respond
  722 + * synchronously because that would change the execution of the code under test. For this reason the
  723 + * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending
  724 + * requests and thus preserving the async api of the backend, while allowing the test to execute
  725 + * synchronously.
  726 + *
  727 + *
  728 + * # Unit testing with mock $httpBackend
  729 + *
  730 + * <pre>
  731 + // controller
  732 + function MyController($scope, $http) {
  733 + $http.get('/auth.py').success(function(data) {
  734 + $scope.user = data;
  735 + });
  736 +
  737 + this.saveMessage = function(message) {
  738 + $scope.status = 'Saving...';
  739 + $http.post('/add-msg.py', message).success(function(response) {
  740 + $scope.status = '';
  741 + }).error(function() {
  742 + $scope.status = 'ERROR!';
  743 + });
  744 + };
  745 + }
  746 +
  747 + // testing controller
  748 + var $http;
  749 +
  750 + beforeEach(inject(function($injector) {
  751 + $httpBackend = $injector.get('$httpBackend');
  752 +
  753 + // backend definition common for all tests
  754 + $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
  755 + }));
  756 +
  757 +
  758 + afterEach(function() {
  759 + $httpBackend.verifyNoOutstandingExpectation();
  760 + $httpBackend.verifyNoOutstandingRequest();
  761 + });
  762 +
  763 +
  764 + it('should fetch authentication token', function() {
  765 + $httpBackend.expectGET('/auth.py');
  766 + var controller = scope.$new(MyController);
  767 + $httpBackend.flush();
  768 + });
  769 +
  770 +
  771 + it('should send msg to server', function() {
  772 + // now you don’t care about the authentication, but
  773 + // the controller will still send the request and
  774 + // $httpBackend will respond without you having to
  775 + // specify the expectation and response for this request
  776 + $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
  777 +
  778 + var controller = scope.$new(MyController);
  779 + $httpBackend.flush();
  780 + controller.saveMessage('message content');
  781 + expect(controller.status).toBe('Saving...');
  782 + $httpBackend.flush();
  783 + expect(controller.status).toBe('');
  784 + });
  785 +
  786 +
  787 + it('should send auth header', function() {
  788 + $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
  789 + // check if the header was send, if it wasn't the expectation won't
  790 + // match the request and the test will fail
  791 + return headers['Authorization'] == 'xxx';
  792 + }).respond(201, '');
  793 +
  794 + var controller = scope.$new(MyController);
  795 + controller.saveMessage('whatever');
  796 + $httpBackend.flush();
  797 + });
  798 + </pre>
  799 + */
  800 +angular.mock.$HttpBackendProvider = function() {
  801 + this.$get = [createHttpBackendMock];
  802 +};
  803 +
  804 +/**
  805 + * General factory function for $httpBackend mock.
  806 + * Returns instance for unit testing (when no arguments specified):
  807 + * - passing through is disabled
  808 + * - auto flushing is disabled
  809 + *
  810 + * Returns instance for e2e testing (when `$delegate` and `$browser` specified):
  811 + * - passing through (delegating request to real backend) is enabled
  812 + * - auto flushing is enabled
  813 + *
  814 + * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified)
  815 + * @param {Object=} $browser Auto-flushing enabled if specified
  816 + * @return {Object} Instance of $httpBackend mock
  817 + */
  818 +function createHttpBackendMock($delegate, $browser) {
  819 + var definitions = [],
  820 + expectations = [],
  821 + responses = [],
  822 + responsesPush = angular.bind(responses, responses.push);
  823 +
  824 + function createResponse(status, data, headers) {
  825 + if (angular.isFunction(status)) return status;
  826 +
  827 + return function() {
  828 + return angular.isNumber(status)
  829 + ? [status, data, headers]
  830 + : [200, status, data];
  831 + };
  832 + }
  833 +
  834 + // TODO(vojta): change params to: method, url, data, headers, callback
  835 + function $httpBackend(method, url, data, callback, headers) {
  836 + var xhr = new MockXhr(),
  837 + expectation = expectations[0],
  838 + wasExpected = false;
  839 +
  840 + function prettyPrint(data) {
  841 + return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
  842 + ? data
  843 + : angular.toJson(data);
  844 + }
  845 +
  846 + if (expectation && expectation.match(method, url)) {
  847 + if (!expectation.matchData(data))
  848 + throw Error('Expected ' + expectation + ' with different data\n' +
  849 + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
  850 +
  851 + if (!expectation.matchHeaders(headers))
  852 + throw Error('Expected ' + expectation + ' with different headers\n' +
  853 + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
  854 + prettyPrint(headers));
  855 +
  856 + expectations.shift();
  857 +
  858 + if (expectation.response) {
  859 + responses.push(function() {
  860 + var response = expectation.response(method, url, data, headers);
  861 + xhr.$$respHeaders = response[2];
  862 + callback(response[0], response[1], xhr.getAllResponseHeaders());
  863 + });
  864 + return;
  865 + }
  866 + wasExpected = true;
  867 + }
  868 +
  869 + var i = -1, definition;
  870 + while ((definition = definitions[++i])) {
  871