From 29c9b2a08f0abd9ddd0d59f1244521a247db99fb Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 28 Aug 2015 15:24:05 -0700 Subject: [PATCH] [Identity] Add identity aggregator Add an aggregator to handle exposing the user's identity, nasa/openmctweb#92 --- platform/identity/bundle.json | 11 ++ platform/identity/src/IdentityAggregator.js | 65 ++++++++++ .../identity/test/IdentityAggregatorSpec.js | 122 ++++++++++++++++++ platform/identity/test/suite.json | 3 + 4 files changed, 201 insertions(+) create mode 100644 platform/identity/bundle.json create mode 100644 platform/identity/src/IdentityAggregator.js create mode 100644 platform/identity/test/IdentityAggregatorSpec.js create mode 100644 platform/identity/test/suite.json diff --git a/platform/identity/bundle.json b/platform/identity/bundle.json new file mode 100644 index 0000000000..b10e221261 --- /dev/null +++ b/platform/identity/bundle.json @@ -0,0 +1,11 @@ +{ + "extensions": { + "components": [ + { + "type": "aggregator", + "provides": "identityService", + "depends": [ "$q" ] + } + ] + } +} diff --git a/platform/identity/src/IdentityAggregator.js b/platform/identity/src/IdentityAggregator.js new file mode 100644 index 0000000000..98deaeb9b6 --- /dev/null +++ b/platform/identity/src/IdentityAggregator.js @@ -0,0 +1,65 @@ +/*global define*/ + +define( + function () { + "use strict"; + + /** + * Provides information about the currently logged-in + * user, if available. + * + * @interface IdentityService + */ + + /** + * Get information about the current user. This returns a promise + * which will resolve to metadata about the user, or undefined if + * no information about the user is available. + * + * @method IdentityService#getUser + * @returns {Promise.} metadata about the current user + */ + + /** + * Metadata about a user. + * + * @typedef UserMetadata + * @property {string} name the user's human-readable name + * @property {string} key the user's machine-readable name + */ + + /** + * Aggregator for multiple identity services. Exposes the first + * defined identity provided by any provider, according to + * priority order. + * + * @implements {IdentityService} + * @memberof platform/identity + */ + function IdentityAggregator($q, providers) { + this.providers = providers; + this.$q = $q; + } + + function delegateGetUser(provider) { + return provider.getUser(); + } + + function identity(value) { + return value; + } + + function giveFirst(results) { + return results.filter(identity)[0]; + } + + IdentityAggregator.prototype.getUser = function () { + var $q = this.$q, + promises = this.providers.map(delegateGetUser); + + return $q.all(promises).then(giveFirst); + }; + + return IdentityAggregator; + } +); diff --git a/platform/identity/test/IdentityAggregatorSpec.js b/platform/identity/test/IdentityAggregatorSpec.js new file mode 100644 index 0000000000..366af2cf7a --- /dev/null +++ b/platform/identity/test/IdentityAggregatorSpec.js @@ -0,0 +1,122 @@ +/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../src/IdentityAggregator"], + function (IdentityAggregator) { + "use strict"; + + describe("The identity aggregator", function () { + var mockProviders, + mockQ, + resolves, + mockPromise, + mockCallback, + testUsers, + aggregator; + + function callbackCalled() { + return mockCallback.calls.length > 0; + } + + function resolveProviderPromises() { + ['a', 'b', 'c'].forEach(function (id, i) { + resolves[id](testUsers[i]); + }); + } + + beforeEach(function () { + testUsers = [ + { key: "user0", name: "User Zero" }, + { key: "user1", name: "User One" }, + { key: "user2", name: "User Two" } + ]; + + resolves = {}; + + mockProviders = ['a', 'b', 'c'].map(function (id) { + var mockProvider = jasmine.createSpyObj( + 'provider-' + id, + [ 'getUser' ] + ); + + mockProvider.getUser.andReturn(new Promise(function (r) { + resolves[id] = r; + })); + + return mockProvider; + }); + + mockQ = jasmine.createSpyObj('$q', ['all']); + mockQ.all.andCallFake(function (promises) { + return Promise.all(promises); + }); + + mockCallback = jasmine.createSpy('callback'); + + aggregator = new IdentityAggregator( + mockQ, + mockProviders + ); + }); + + it("delegates to the aggregated providers", function () { + // Verify precondition + mockProviders.forEach(function (p) { + expect(p.getUser).not.toHaveBeenCalled(); + }); + + aggregator.getUser(); + + mockProviders.forEach(function (p) { + expect(p.getUser).toHaveBeenCalled(); + }); + }); + + it("returns the first result when it is defined", function () { + aggregator.getUser().then(mockCallback); + + resolveProviderPromises(); + + waitsFor(callbackCalled); + runs(function () { + expect(mockCallback).toHaveBeenCalledWith(testUsers[0]); + }); + }); + + it("returns a later result when earlier results are undefined", function () { + testUsers[0] = undefined; + + aggregator.getUser().then(mockCallback); + + resolveProviderPromises(); + + waitsFor(callbackCalled); + runs(function () { + expect(mockCallback).toHaveBeenCalledWith(testUsers[1]); + }); + }); + + it("returns undefined when no providers expose users", function () { + testUsers = [ undefined, undefined, undefined ]; + + aggregator.getUser().then(mockCallback); + + resolveProviderPromises(); + + waitsFor(callbackCalled); + runs(function () { + expect(mockCallback).toHaveBeenCalledWith(undefined); + }); + }); + + it("returns undefined when there are no providers", function () { + new IdentityAggregator(mockQ, []).getUser().then(mockCallback); + waitsFor(callbackCalled); + runs(function () { + expect(mockCallback).toHaveBeenCalledWith(undefined); + }); + }); + + }); + } +); diff --git a/platform/identity/test/suite.json b/platform/identity/test/suite.json new file mode 100644 index 0000000000..9575d0263c --- /dev/null +++ b/platform/identity/test/suite.json @@ -0,0 +1,3 @@ +[ + "IdentityAggregator" +]