From 6679eb33ac4afcb7c0fa969c29080d725924e211 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Wed, 17 Dec 2014 17:12:47 +1000 Subject: [PATCH] Added cache prediction profile validation --- src/components/services/predictiveCache.js | 71 ++++++- .../services/predictiveCache.spec.js | 175 +++++++++++++++--- .../services/vendorServices/externals.js | 4 +- 3 files changed, 222 insertions(+), 28 deletions(-) diff --git a/src/components/services/predictiveCache.js b/src/components/services/predictiveCache.js index 7afad2ab..5711c8f1 100644 --- a/src/components/services/predictiveCache.js +++ b/src/components/services/predictiveCache.js @@ -9,14 +9,79 @@ angular match: null, request: [], progression: [], - count: 0, + count: 10, method: "HEAD" }; + var acceptableVerbs = ["GET", "HEAD", "POST", "PUT", "DELETE"]; + var defaultProgression = 1; + var unnamedProfiles = 0; - return { - addProfile: function predictiveCache(profile) { + function validateProfile(settings) { + settings = angular.extend({}, defaults, settings); + if (settings.name) { + if (!angular.isString(settings.name)) { + throw new Error("The provided name must be a string"); + } } + else { + unnamedProfiles++; + settings.name = "UnnamedProfile" + unnamedProfiles; + } + + if (settings.match) { + if (!(settings.match instanceof RegExp)) { + throw new Error("The value for match must be a regular expression"); + } + } + else { + throw new Error("A value for match must be provided"); + } + + if (angular.isArray(settings.request) && settings.request.length > 0) { + var isStrings = settings.request.every(angular.isString); + if (!isStrings) { + throw new Error("requests must be an array of strings"); + } + } + else { + throw new Error("requests must be an array of strings"); + } + + // http://stackoverflow.com/a/16046903 + //var numGroups = (new RegExp(settings.match.toString() + '|')).exec('').length - 1; + var isArray = angular.isArray(settings.progression), + isEmpty = isArray && settings.progression.length === 0, + isNumber = angular.isNumber(settings.progression) && !isArray, + isNumberFunctionArray = isArray && settings.progression.every(function (value) { + return angular.isFunction(value) || angular.isNumber(value); + }); + if (settings.progression === null || settings.progression === undefined) { + settings.progression = defaultProgression; + } + else if (isEmpty || !isNumber && !isNumberFunctionArray) { + throw new Error("progression must be an array of numbers/functions"); + } + + if(!angular.isNumber(settings.count) || settings.count < 0) { + throw new Error("count must be a positive integer"); + } + + if (acceptableVerbs.indexOf(settings.method) == -1) { + throw new Error("A valid http method is required"); + } + + return settings; } + + return function predictiveCache(profile) { + if (!angular.isObject(profile)) { + throw new Error("A profile is required"); + } + + var settings = validateProfile(profile); + + return settings; + }; }] ); \ No newline at end of file diff --git a/src/components/services/predictiveCache.spec.js b/src/components/services/predictiveCache.spec.js index 5ce544c1..8b84f4a9 100644 --- a/src/components/services/predictiveCache.spec.js +++ b/src/components/services/predictiveCache.spec.js @@ -1,40 +1,167 @@ describe("The predictiveCache service", function () { var predictiveCache; - + var testProfile; beforeEach(module("bawApp.services")); beforeEach(inject(["predictiveCache", function (_predictiveCache) { predictiveCache = _predictiveCache; - }])); - var testProfile = { - name: "Media cache ahead", - match: /google\.com\?page=(.*)&skip=(.*)/, - request: ["one url", "another url"], - progression: [ - function(data, previous) { - return previous + 30.0; - }, - function(data, previous) { - var next = previous + 30.0; - if (next >= data.max) { - return; - } - else { - return next; + testProfile = { + name: "Media cache ahead", + match: /google\.com\?page=(.*)&skip=(.*)/, + request: ["one url", "another url"], + progression: [ + 30.0, + function (data, previous) { + var next = previous + 30.0; + if (next >= data.max) { + return; + } + else { + return next; + } } - } - ], - count: 10, - method: "HEAD" - }; + ], + count: 10, + method: "HEAD" + }; + }])); - it("requires an object to function", function() { - expect(function() { + it("requires an object to function", function () { + expect(function () { predictiveCache(); }).toThrowError(Error, "A profile is required"); }); + + it("returns the profile that was passed into it", function () { + var profile = predictiveCache(testProfile); + + var pj = JSON.stringify(profile), + tj = JSON.stringify(testProfile); + + expect(pj).toBe(tj); + }); + + it("expects the profile to accept the http verbs", function () { + var verbs = ["GET", "HEAD", "POST", "PUT", "DELETE"]; + + // should occur without exception + verbs.forEach(function (verb) { + testProfile.method = verb; + predictiveCache(testProfile); + }); + + expect(function () { + testProfile.method = "anything else"; + predictiveCache(testProfile); + }).toThrowError(Error, "A valid http method is required"); + }); + + it("sets the default http verb to HEAD if omitted", function () { + delete testProfile.method; + var profile = predictiveCache(testProfile); + + expect(profile.method).toBe("HEAD"); + }); + + it("requires name to be a string", function () { + testProfile.name = 33; + + expect(function () { + predictiveCache(testProfile); + }).toThrowError("The provided name must be a string"); + }); + + it("will provide an automatic name if one is not supplied", function () { + delete testProfile.name; + var profile = predictiveCache(testProfile); + expect(profile.name).toBe("UnnamedProfile1"); + + profile = predictiveCache(testProfile); + expect(profile.name).toBe("UnnamedProfile2"); + }); + + it("will fail if the supplied match regular expression is missing", function () { + delete testProfile.match; + + expect(function () { + predictiveCache(testProfile); + }).toThrowError("A value for match must be provided"); + }); + + it("will fail if the supplied match regular expression is not a RegExp", function () { + testProfile.match = "test"; + + expect(function () { + predictiveCache(testProfile); + }).toThrowError("The value for match must be a regular expression"); + }); + + it("ensures the request value is an array of strings", function () { + var failValues = [null, [], 33, [33]]; + + failValues.forEach(function (item) { + testProfile.request = item; + + expect(function () { + predictiveCache(testProfile); + }).toThrowError("requests must be an array of strings"); + }); + }); + + [ + {key: "null", in: null, out: 1}, + {key: "undefined", in: undefined, out: 1}, + {key: "number", in: 3, out: 3}, + {key: "array of numbers or functions", in: [1, function (i) {return i;}]} + ].forEach(function (progressionTest) { + it("allows the progression value to be " + progressionTest.key, function () { + testProfile.progression = progressionTest.in; + + var profile = predictiveCache(testProfile); + expect(profile.progression).toBe(progressionTest.out || progressionTest.in); + }); + }); + + [ + {key: "a string", in: "test"}, + {key: "empty array", in: []}, + {key: "array not of numbers or functions, strings", in: ["testing"]}, + {key: "array not of numbers or functions, null/undefined", in: [null, undefined, 3.0]}, + {key: "array not of numbers or functions, object", in: [{}]} + ].forEach(function (progressionTest) { + it("disallows the progression value to be " + progressionTest.key, function () { + testProfile.progression = progressionTest.in; + + expect(function() { + var profile = predictiveCache(testProfile); + }).toThrowError("progression must be an array of numbers/functions"); + }); + }); + + it("ensures count is a number", function() { + var profile = predictiveCache(testProfile); + expect(profile.count).toBe(testProfile.count); + + testProfile.count = "fkasnfiabfias"; + expect(function() { + predictiveCache(testProfile) + }).toThrowError("count must be a positive integer"); + + testProfile.count = -1; + expect(function() { + predictiveCache(testProfile) + }).toThrowError("count must be a positive integer"); + }); + + it("allows a default value to be used for count", function() { + delete testProfile.count; + + var profile = predictiveCache(testProfile); + expect(profile.count).toBe(10); + + }) }); \ No newline at end of file diff --git a/src/components/services/vendorServices/externals.js b/src/components/services/vendorServices/externals.js index 6d680183..0dbd2658 100644 --- a/src/components/services/vendorServices/externals.js +++ b/src/components/services/vendorServices/externals.js @@ -31,7 +31,9 @@ angular delete window.d3; } else { - console.warn("D3 not on window, hack not required"); + if (!window.jasmine) { + console.warn("D3 not on window, hack not required"); + } } }]);