Skip to content
This repository has been archived by the owner on Apr 1, 2019. It is now read-only.

Commit

Permalink
Merge pull request #139 from mozilla/cachetasks_update_cache
Browse files Browse the repository at this point in the history
Teach cachetasks to update requests if needed
  • Loading branch information
Marcos Caceres committed Nov 17, 2015
2 parents 6868020 + 95255eb commit 60c4346
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 8 deletions.
29 changes: 26 additions & 3 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = function(config) {

// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ["mocha", "chai-as-promised", "chai"],
frameworks: ["mocha", "chai-as-promised", "chai", "express-http-server"],

// list of files / patterns to load in the browser
files: [{
Expand Down Expand Up @@ -49,8 +49,8 @@ module.exports = function(config) {
match: ".*",
name: "Service-Worker-Allowed",
value: "/"
},
],
},
],

proxies: {
'/sw.js': 'http://localhost:9876/base/src/sw.js',
Expand Down Expand Up @@ -173,6 +173,29 @@ module.exports = function(config) {
}
},

expressHttpServer: {
port: 9999,
// this function takes express app object and allows you to modify it
// to your liking. For more see http://expressjs.com/4x/api.html
appVisitor(app) {
//CORS middleware
var cors = require('cors');
var corsOptions = {
origin: '*',
allowedHeaders: "statusoverride"
};
app.use(cors(corsOptions));
app.options('/update-tests', cors(corsOptions));
app.get('/update-tests', (req, res) => {
if (req.get("statusoverride")) {
res.status(req.get("statusoverride"));
}
res.set("ReponseFrom", "server");
res.send("<meta charset=utf8><h1>This is a test</h1>");
});
}
},

browserNoActivityTimeout: 10000,

browserDisconnectTimeout: 2000,
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"chai-as-promised": "^5.1.0",
"cli-color": "^1.1.0",
"codeclimate-test-reporter": "^0.1.0",
"cors": "^2.7.1",
"fs-extra": "^0.26.2",
"handlebars": "^4.0.4",
"html2js": "^0.2.0",
Expand All @@ -31,6 +32,7 @@
"karma-chai-as-promised": "^0.1.2",
"karma-coverage": "^0.5.0",
"karma-coveralls": "^1.1.2",
"karma-express-http-server": "0.0.1",
"karma-firefox-launcher": "~0.1",
"karma-html2js-preprocessor": "^0.1.0",
"karma-mocha": "^0.2.0",
Expand Down
45 changes: 44 additions & 1 deletion src/js/lib/cachetasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/*jshint browser:true, worker:true*/
/*globals async, Request, Response, caches*/
/*globals async, Request, Response, caches, fetch*/
/*exported CacheTasks */
"use strict";
(function(exports) {
Expand Down Expand Up @@ -190,6 +190,49 @@
return true;
}, this);
},
/**
* Update the response associated with a request.
*
* @param {Request} request The request to check.
* @param {String} cacheName The name of the cache to use.
* @param {Object} options options
* - force: true or false
* @return {Promise} Resolves once operations complete.
*/
update(request, cacheName, options = {force: false}) {
return async.task(function*() {
let cache = yield MemoizedCaches.open(cacheName);
let response = yield cache.match(request);
let shouldUpdate = !response || options.force || this.isStale(response);
// We don't need to update, so just return what we have.
if (!shouldUpdate) {
return response;
}
// Let's try to update.
try {
let potentialResponse = yield fetch(request);
// check that the fetching was "ok" (i.e., in the 200 range).
if (potentialResponse.ok) {
response = potentialResponse;
yield this.put(request, response, cacheName);
} else {
let url = request.url || request;
let status = potentialResponse.status;
let msg = `Response not OK for: ${url} (Got back a ${status}).`;
console.error(msg);
}
} catch (err) {
// if we don't have a response at all, all we can do is throw.
if (!response) {
throw err;
}
let msg = `Exception when fetching: ${request.url || request}`;
console.warn(msg);
return response;
}
return response;
}, this);
},
/**
* Respond to a request from the SW's caches.
*
Expand Down
143 changes: 140 additions & 3 deletions src/test/lib/cachetasks_spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* jshint esnext: true, expr: true */
/* globals expect, async, it, CacheTasks, caches, Request, Response*/
/* globals expect, async, it, CacheTasks, caches, Request, Response, Headers*/
"use strict";
const CACHENAME = "test";

Expand All @@ -21,7 +21,7 @@ var tRequest = testType(new Request(""));
describe("CacheTasks", function() {
describe("deleteCaches() method", () => {
it("should delete all caches when no argument is given.", async(function*() {
var cacheNames = [CACHENAME, "foo", "bar", "baz", "bing"];
var cacheNames = [CACHENAME, "foo", "bar", "baz", "bing", "skeleton_cache"];
for (var name of cacheNames) {
yield caches.open(name);
}
Expand All @@ -31,7 +31,8 @@ describe("CacheTasks", function() {
return cacheNames.includes(name) && value === true && hasCache === false;
});
for (var entry of result.entries()) {
expect(yield checkNameValue(entry)).to.be.true;
let test = yield checkNameValue(entry);
expect(test).to.be.true;
}
var keys = yield caches.keys();
expect(keys.length).to.equal(0);
Expand All @@ -56,6 +57,10 @@ describe("CacheTasks", function() {
var result = yield CacheTasks.addAll(["js/lib/async.js"], CACHENAME);
expect(result).to.be.true;
}));
it("should result in false when trying to add garbage to the cache.", async(function*() {
var result = yield CacheTasks.addAll(["file:///foo", "about:config"], CACHENAME);
expect(result).to.be.false;
}));
});

describe("match() method", () => {
Expand Down Expand Up @@ -275,4 +280,136 @@ describe("CacheTasks", function() {
expect(hasEntry).to.be.false;
}));
});
describe("update() method", () => {
it("should not update a response that is not stale.", async(function*() {
var request = new Request("not-stale");
var response = new Response(".");
var theFuture = new Date(Date.now() + 100000).toGMTString();
var now = new Date(Date.now()).toGMTString();
response.headers.set("Date", now);
response.headers.set("Expires", theFuture);
response.headers.set("X-Test", "pass");
yield CacheTasks.put(request, response, CACHENAME);
var updatedResponse = yield CacheTasks.update(request, CACHENAME);
expect(updatedResponse.headers.get("X-Test")).to.equal("pass");
expect(updatedResponse.headers.get("Date")).to.equal(now);
expect(updatedResponse.headers.get("Expires")).to.equal(theFuture);
}));
it("should update a response that has expired.", async(function*() {
var request = new Request("./");
var response = new Response(".");
var thePast = new Date(Date.now() - 100000).toGMTString();
var now = new Date(Date.now()).toGMTString();
response.headers.set("Date", thePast);
response.headers.set("Expires", thePast);
response.headers.set("X-Test", "fail");
yield CacheTasks.put(request, response, CACHENAME);
var updatedResponse = yield CacheTasks.update(request, CACHENAME);
expect(updatedResponse.headers.get("Expires")).to.not.equal(thePast);
expect(updatedResponse.headers.get("Date")).to.equal(now);
expect(updatedResponse.headers.get("X-Test")).to.equal(null);
}));
it("should put a request that doesn't exist.", async(function*() {
var request = new Request("./");
yield CacheTasks.delete(request, CACHENAME);
var response = yield CacheTasks.update("./", CACHENAME);
expect(response).to.be.ok;
}));
it("should forcibly update a request.", async(function*() {
var request = new Request("http://localhost:9999/update-tests");
var response = new Response("");
var thePast = new Date(Date.now() - 100000).toGMTString();
var theFuture = new Date(Date.now() + 100000).toGMTString();
response.headers.set("Date", thePast);
response.headers.set("Expires", theFuture);
response.headers.set("X-Test", "client");
yield CacheTasks.put(request, response, CACHENAME);
var ops = {force: true};
request = new Request("http://localhost:9999/update-tests");
var updatedResponse = yield CacheTasks.update(request, CACHENAME, ops);
expect(updatedResponse.headers.get("X-Test")).to.equal(null);
}));
it("should not update a response when not forced.", async(function*() {
var request = new Request("update-test");
var response = new Response(".");
var theFuture = new Date(Date.now() + 100000).toGMTString();
var now = new Date(Date.now()).toGMTString();
response.headers.set("Date", now);
response.headers.set("Expires", theFuture);
response.headers.set("X-Test", "pass");
yield CacheTasks.put(request, response, CACHENAME);
var ops = {force: false};
var updatedResponse = yield CacheTasks.update(request, CACHENAME, ops);
expect(updatedResponse.headers.get("X-Test")).to.equal("pass");
expect(updatedResponse.headers.get("Date")).to.equal(now);
expect(updatedResponse.headers.get("Expires")).to.equal(theFuture);
}));
it("should return an existing/expired response when fetch fails.", async(function*() {
// The port should cause a network error
var request = new Request("http://localhost:12345/");
var response = new Response(".");
var thePast = new Date(Date.now() - 100000).toGMTString();
var now = new Date(Date.now()).toGMTString();
response.headers.set("Date", now);
response.headers.set("Expires", thePast);
response.headers.set("X-Test", "pass");
// store the bad req/resp pair, as it it was good.
yield CacheTasks.put(request, response, CACHENAME);
var updatedResponse = yield CacheTasks.update(request, CACHENAME);
expect(updatedResponse.headers.get("X-Test")).to.equal("pass");
expect(updatedResponse.headers.get("Date")).to.equal(now);
expect(updatedResponse.headers.get("Expires")).to.equal(thePast);
yield CacheTasks.delete(request, CACHENAME);
}));
it("should return the cached response for errors in the 400 and 500 range.", async(function*() {
var cachedRequest = new Request("http://localhost:9999/update-tests/");
var response = new Response(".");
var thePast = new Date(Date.now() - 1000000).toGMTString();
var uniqueValue = "pass" + Math.random();
response.headers.set("Date", thePast);
response.headers.set("Expires", thePast);
response.headers.set("X-Test", uniqueValue);
// store the bad req/resp pair, as it it was good.
yield CacheTasks.put(cachedRequest, response, CACHENAME);
var errorCodes = [400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410,
411, 412, 413, 414, 415, 416, 417, 418, 421, 426, 428, 429, 431, 500,
501, 502, 503, 504, 505, 506, 507, 511];
for (var errorCode of errorCodes) {
let headers = new Headers();
headers.set("statusoverride", String(errorCode));
let request = new Request("http://localhost:9999/update-tests/", {
headers
});
var updatedResponse = yield CacheTasks.update(request, CACHENAME);
expect(updatedResponse.headers.get("X-Test")).to.equal(uniqueValue);
expect(updatedResponse.headers.get("Date")).to.equal(thePast);
expect(updatedResponse.headers.get("Expires")).to.equal(thePast);
}
yield CacheTasks.delete(cachedRequest, CACHENAME);
}));
it("should throw when forced, but fetch fails.", async(function*() {
// The port should cause a network error
var request = new Request("http://localhost:12345/");
var response = new Response(".");
var thePast = new Date(Date.now() - 100000).toGMTString();
var now = new Date(Date.now()).toGMTString();
response.headers.set("Date", now);
response.headers.set("Expires", thePast);
response.headers.set("X-Test", "pass");
// store the bad req/resp pair, as it was good.
yield CacheTasks.put(request, response, CACHENAME);
var promise = CacheTasks.update(request, CACHENAME, {force: true});
promise.should.eventually.be.rejected;
try {
yield promise;
} catch (err) {}
yield CacheTasks.delete(request, CACHENAME);
}));
it("should throw when fetch fails, and there is no fallback response.", ()=> {
var request = new Request("http://localhost:12345/");
CacheTasks.update(request, CACHENAME).should.be.rejected;
CacheTasks.update(request, CACHENAME, {force: true}).should.be.rejected;
CacheTasks.update(request, CACHENAME, {force: false}).should.be.rejected;
});
});
});
3 changes: 2 additions & 1 deletion src/test/sw_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
describe("Service worker registration", function() {
it("should correctly register and activate the Service Worker.", async(function*() {
// force reload on first load.
navigator.serviceWorker.register("sw.js");
var registration = yield navigator.serviceWorker.register("sw.js");
var sw = (yield navigator.serviceWorker.ready).active;
expect(sw).to.be.ok;
yield registration.unregister();
}));
});

0 comments on commit 60c4346

Please sign in to comment.