Angular Service with $http, $q and testing (test fixtures)

angular.module("myApp.services")
    .service("usersService", ["$http", '$q',
        function ($http, $q) {
            var _self = this;

            _self.getUserDetails = function () {
                return $q(function (resolve, reject) {
                    $http.get('/api/client/me/details')
                        .then(function (result) {
                            resolve(result.data);
                        })
                        .catch(function (error) {
                            reject(error);
                        });
                });
            };

            _self.setUserDetails = function (firstName, lastName) {
                return $q(function (resolve, reject) {
                    $http.post('/api/client/me/details', {
                        firstName: firstName,
                        lastName: lastName
                    })
                        .then(function (result) {
                            resolve(result.data);
                        })
                        .catch(function (error) {
                            reject(error);
                        });
                });
            };
        }]);

<hr />

describe("an usersService", function () {
    var target;

    var httpMock;
    var qMock;

    beforeEach(function () {
        httpMock = fixturesProvider.getSuccessHttpMock("api response");
        qMock = fixturesProvider.getQMock();

        module("myApp.services", function ($provide) {
            $provide.value("$http", httpMock);
            $provide.value("$q", qMock);
        });

        inject(function ($injector) {
            target = $injector.get("usersService");
        });
    });

    describe("#getUserDetails", function () {
        it("it should call the api", function () {
            spyOn(httpMock, "get").and.callThrough();
            target.getUserDetails();
            expect(httpMock.get).toHaveBeenCalledWith("/api/client/me/details")
        });

        it("it should return the result", function () {
            var result = target.getUserDetails();
            expect(result.resolved).toBeTruthy();
            expect(result.value).toBe("api response");
        });
    });

    describe("#setUserDetails", function () {
        it("it should call the api", function () {
            spyOn(httpMock, "post").and.callThrough();
            target.setUserDetails("John", "Doe");
            expect(httpMock.post).toHaveBeenCalledWith("/api/client/me/details", {
                firstName: "John",
                lastName: "Doe"
            })
        });

        it("it should return the result", function () {
            var result = target.setUserDetails("John", "Doe");
            expect(result.resolved).toBeTruthy();
            expect(result.value).toBe("api response");
        });
    });

    describe("when the api call fails", function () {
        beforeEach(function () {
            httpMock.get = fixturesProvider.getFailingHttpMethodMock(500);
            httpMock.post = fixturesProvider.getFailingHttpMethodMock(500);
        });

        describe("#getUserDetails", function () {
            it("it should return the error", function () {
                var result = target.getUserDetails();
                expect(result.resolved).toBeFalsy();
                expect(result.value.status).toBe(500);
            });
        });

        describe("#setUserDetails", function () {
            it("it should return the error", function () {
                var result = target.setUserDetails("John", "Doe");
                expect(result.resolved).toBeFalsy();
                expect(result.value.status).toBe(500);
            });
        });
    });
});

Angular JS – Test Fixtures

var fixturesProvider = new function () {
    var _this = this;

    //---------------
    // promises
    //---------------

    _this.getSuccessPromiseMock = function (result) {
        return {
            then: function (callback) {
                callback(result);
                return {
                    catch: function () {
                    }
                };
            }
        };
    };

    _this.getFailingPromiseMock = function (error) {
        return {
            then: function () {
                return {
                    catch: function (callback) {
                        callback(error);
                    }
                }
            }
        };
    };

    //---------------
    // $http
    //---------------

    _this.getHttpResultMock = function (result) {
        return {
            data: result
        };
    };

    _this.getHttpErrorMock = function (status) {
        return {
            status: status
        };
    };

    _this.getSuccessHttpMethodMock = function (result) {
        return function () {
            return _this.getSuccessPromiseMock(_this.getHttpResultMock(result))
        };
    };

    _this.getFailingHttpMethodMock = function (status) {
        return function () {
            return _this.getFailingPromiseMock(_this.getHttpErrorMock(status))
        };
    };

    _this.getSuccessHttpMock = function (result) {
        return {
            get: _this.getSuccessHttpMethodMock(result),
            post: _this.getSuccessHttpMethodMock(result)
        };
    };

    _this.getFailingHttpMock = function (status) {
        return {
            get: _this.getFailingHttpMethodMock(status),
            post: _this.getFailingHttpMethodMock(status)
        };
    };

    //---------------
    // services
    //---------------

    _this.getSuccessApiMethodMock = function (result) {
        return function () {
            return _this.getSuccessPromiseMock(result);
        };
    };

    _this.getFailingApiMethodMock = function (status) {
        return _this.getFailingHttpMethodMock(status);
    };

    //---------------
    // $q
    //---------------

    _this.getQMock = function () {
        return function (callback) {
            var result = null;

            callback(function (resolveValue) {
                result = {
                    resolved: true,
                    value: resolveValue
                };
            }, function (rejectValue) {
                result = {
                    resolved: false,
                    value: rejectValue
                };
            });

            return result;
        };
    };

    //---------------
    // modals
    //---------------

    _this.getUibModalMock = function () {
        return {
            open: function () {
                return {
                    result: {
                        then: function (callback) {
                            callback();
                        }
                    }
                };
            }
        };
    };

    _this.getUibModalInstanceMock = function () {
        return {
            dismiss: function () {
            },
            close: function () {
            }
        };
    };

    //---------------
    // $window
    //---------------

    _this.getWindowMock = function () {
        return {
            location: {
                href: ""
            }
        };
    };

    //---------------
    // $filter
    //---------------

    _this.getFilterMock = function () {
        return function () {
            return function () {
                return Array.prototype.join.call(arguments, "-");
            };
        };
    }
}();

Javascript – ES6 Generators

console.clear();

//--------------------
// Infinite Generator
//--------------------

function* sequence1(start) {
  while (true) {
    yield start++
  }
}

var s1 = sequence1(5);

console.log("Infinite Generator");
console.log(s1.next()); // <- {value: 5, done: false}
console.log(s1.next()); // <- {value: 6, done: false}
console.log(s1.next()); // <- {value: 7, done: false}
console.log(s1.next()); // <- {value: 8, done: false}


//------------------
// Finite Generator
//------------------

function* sequence2(start) {
  while (start < 7) {
    yield start++
  }
  return 0;
}

var s2 = sequence2(5);

console.log("Finite Generator");
console.log(s2.next()); // <- {value: 5, done: false}
console.log(s2.next()); // <- {value: 6, done: false}
console.log(s2.next()); // <- {value: 0, done: true}
console.log(s2.next()); // <- {value: undefined, done: true}


//------------------
// Next() Arguments
//------------------

function* sequence3(start) {
  while (true) {
    var newStart = yield start++;
    if (newStart) {
      start = newStart;
    }
  }
}

var s3 = sequence3(5);

console.log("Next() Arguments");
console.log(s3.next(2)); // <- {value: 5, done: false}
console.log(s3.next()); // <- {value: 6, done: false}
console.log(s3.next(2)); // <- {value: 2, done: false}
console.log(s3.next()); // <- {value: 3, done: false}

/*
When the execution reach the yield instruction the value is returned and the execution is frozen right before associating the value returned by the yield itself. It will restart from there at the next call to next().

- The 1st call to next() will execute until the yield (so it will return 5). We can look at the 1st call as a sort of initialisation.

- The 2nd call to next() will execute from where the previous execution left, so will check newStart, that in the meatine has been populated with the 2nd call's argument (so undefined), and will loop until the yield again (so it will return 6).

- The 3rd call to next() will execute from where the previous execution left, so will check newStart, that in the meatine has been populated with the 3rd call's argument (so 2), and will loop until the yield again (so it will return 2).
*/


//--------------------
// Awaiting for async
//--------------------

function sumAsync(val1, val2) {
  setTimeout(function() {
    iterator.next(val1 + val2);
  }, 1000);
}

function* generator() {
  console.log("Awaiting for async");
  console.log(yield sumAsync(2, 3));
  console.log(yield sumAsync(3, 4));
  console.log(yield sumAsync(4, 5));
  console.log(yield sumAsync(5, 6));
}

var iterator = generator();
iterator.next();

Javascript – Mapping and Type Checking

Type Checking

To check that an object has been created from the right class:

function typeCheck(configuration) {
     for (var i = 0; i < configuration.length; i++) {
          var element = configuration[i];

          if (element[0].constructor.name != element[1]) {
               console.log("ERROR: type check failed for type", element.type);
               return false;
          }
     }
     return true;
}

Usage:

if typeCheck([
     [ obj1, “Obj1Class” ],
     [ obj2, “Obj2Class” ],
     ...
]){
     //Here we are sure that the objects are of the required type
     ...
}

Mapping

To populate an object of a particular class from a JSon:

function getType(elem) {
     var type = elem == null
          ? "null"
          : elem instanceof Date
               ? "date"
               : elem instanceof Array
                    ? "array"
                    : typeof (elem);

     if (type == “object”) {
          type = "object<" + elem.constructor.name + ">";
     }
     return type;
}

function map(obj1, obj2) {
     if (!!target && !!object) {
          for (var key in obj1) {
               if (Object.prototype.hasOwnProperty.call(obj2, key)) {
                    var key1Type = getType(obj1[key]);
                    var key2Type = getType(obj2[key]);

                    if (key1Type == "null" || key2Type == "null") {
                         obj1[key] = obj2[key];
                    }
                    else if (key1Type == key2Type) {
                         if (key1Type.match(/^object<.+>$/)) {
                              obj1[key] = map(obj1[key], obj2[key])
                         } else {
                              obj1[key] = obj2[key];
                         }
                    }
                    else {
                         console.log("ERROR: mapping failed for property", key, "- wrong type");
                    }
               }
          }
          for (var key in obj2) {
               if (!Object.prototype.hasOwnProperty.call(obj1, key)) {
                    console.log("ERROR: mapping failed for property", key, "- unavailable");
               }
          }
     } else {
          console.log("ERROR: mapping failed - unavailable object(s)");
     }
     return obj1;
}

Usage:

var myObject = map(new MyObjectClass(), {
     prop1: “value1”,
     prop2: “value2”,
     ...
});

Node JS – REST calls with ES6 Promises + Co JS Module

Here is an example of factory that creates an httpService to communicate with an external API with REST. We are using the npm module “request” to perform the HTTP calls. We are using as well ECMAScript 6 classes and promises, as long as the npm module “co”, that allows us to take advantage on the ES6 generators to await for async elaborations that returns a promise.

https://www.npmjs.com/package/co

"use strict";

var request = require("request-promise");
var co = require("co");

class HttpService {
     constructor(baseUri) {
          this.baseUri = baseUri;
          Object.seal(this);
     }

     call(options) {
          var self = this;

          console.log("Calling", self.baseUri + options.uri + "..");

          return new Promise(function (resolve, reject) {
               co(function* () {
                    try {
                         var data = yield request({
                              uri: self.baseUri + options.uri,
                              method: options.method,
                              headers: options.headers,
                              form: options.postData,
                              json: true
                         });
                         console.log(data);
                         resolve(data);
                    } catch (err) {
                         if (err.name == "StatusCodeError") {
                              console.log("ERROR: returned status code was", err.message);
                              reject(err.statusCode);
                         } else {
                              console.log("ERROR:", err.message);
                              reject(500);
                         }
                    }
               });
          });
     }
}

module.exports = {
     make: function (baseUri) {
          return new HttpService(baseUri);
     }
};

Node JS – Services + Globals (Sails JS)

We can define our services inside the “api/services” folder. Each service defined here will be globally accessible by its file name (without extension).

module.exports = {
     myServiceMethod: function (args) {
          return ...;
     }
};

However this approach has some limitation: all the services have to stay on the same level (sub-folders are not supported). So is better to disable this behaviour and to directly go through the standard Node JS require() mechanism.

To disable it we can edit the “config/globals.js” file in this way:

module.exports.globals = {
     ...

     services: false
}

Then we can refer the service we need from any controller or other service in this way (please note that we are using the npm module “app-root-path” to get the root path):

var appRoot = require("app-root-path");
var myService = require(appRoot + "/api/services/myService");

module.exports = {
     ...
}

This is the standard way in Node JS to require a module given its path.


Having a service globally available

Since we disabled the automatic behaviour we now have to explicitly require every service we need. However it could be useful to have some service automatically available without needing to request it everywhere. To have this we can add them to the global object by editing the “config/bootstrap.js” file:

global.appRoot = require('app-root-path');
global.myUtilService = require(global.appRoot + "/api/services/utils/myUtilService");

module.exports.bootstrap = function(cb) {
     ...
};

In this way we can refer the service through global.myUtilService (or even just myUtilService)from everywhere.


Specifying the current path to Node JS (NODE_PATH)

To avoid using external modules like “app-root-path” we can launch Node JS with the information of the current path we want it to consider when we require for modules. In this way we can use relative paths with no problems.

In order to do this we can specify the path with an environment variable before starting the app (in any of the ways we can start it), here is an example:

NODE_PATH=. npm start

In order to have VS Code doing the same thing when we debug the application we can edit its launch configuration (under “.vscode/launch.json”) to add “NODE_PATH”: “.” to the “env” node of the “launch” configuration.

Node JS – Authorisations (Sails JS)

In Sails JS we can define authorisations (among other things) with the policies. Policies are operations (usually checks) that are executed before any call to a controller. Since a policy is agnostic about what controller will be associated to and what other policies will be associated to the same controller, it behaves as a chain’s ring: if the result of a policy is positive it will just call the next ring, that could be another policy or the controller, depending on the configuration.

We define policies under the “api/policies” folder, here is an example of authentication policy reply with an “unauthorised” message whether the user isn’t logged in (we check this by searching for the user’s object in the session).

module.exports = function (req, res, next) {
     if (req.session.userObj) {
          return next();
     }

     return res.json("unauthorized");
};

To apply the policy to our controllers we have to edit the “config/policies.js” file in this way:

module.exports.policies = {
     ...

     "*": “<myPolicyFileNameWithoutExtension>",

     loginController: {
          "index": true
     }
}

With this configuration we are telling Sails JS that we want to apply our policy to all the controllers/actions, apart the loginController/index actions, that will be free to access.

In case our controller is inside a subfolder we can refer to it in this way:

“myFolder/myController”: {
     “myAction”: ...
}