Skip to content

Commit

Permalink
Added cache prediction profile validation
Browse files Browse the repository at this point in the history
  • Loading branch information
atruskie committed Dec 17, 2014
1 parent 10da40f commit 6679eb3
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 28 deletions.
71 changes: 68 additions & 3 deletions src/components/services/predictiveCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}]
);
175 changes: 151 additions & 24 deletions src/components/services/predictiveCache.spec.js
Original file line number Diff line number Diff line change
@@ -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);

})
});
4 changes: 3 additions & 1 deletion src/components/services/vendorServices/externals.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}

}]);

0 comments on commit 6679eb3

Please sign in to comment.