Bug 1120715 - Part 4: Add tests for Request.cache; r=bkelly
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 01 Mar 2016 17:34:31 -0500
changeset 339606 f58317eb7f23592fbdb6586e889dba513c346c09
parent 339605 1308f7beeaa112f0c1d750cd24b0f18f28253329
child 339607 1a97c346298fc7a658beec5c3b8e69c209b4fe92
push id12762
push userbmo:rail@mozilla.com
push dateFri, 11 Mar 2016 19:47:45 +0000
reviewersbkelly
bugs1120715
milestone48.0a1
Bug 1120715 - Part 4: Add tests for Request.cache; r=bkelly
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/fetch/api/request/request-idl.html.ini
testing/web-platform/meta/fetch/api/request/request-structure.html.ini
testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js
testing/web-platform/tests/fetch/api/request/request-cache.html
testing/web-platform/tests/fetch/api/request/resources/cache.py
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -15525,16 +15525,20 @@
         "path": "fetch/api/redirect/redirect-mode-worker.html",
         "url": "/fetch/api/redirect/redirect-mode-worker.html"
       },
       {
         "path": "fetch/api/redirect/redirect-mode.html",
         "url": "/fetch/api/redirect/redirect-mode.html"
       },
       {
+        "path": "fetch/api/request/request-cache.html",
+        "url": "/fetch/api/request/request-cache.html"
+      },
+      {
         "path": "fetch/api/request/request-clone.sub.html",
         "url": "/fetch/api/request/request-clone.sub.html"
       },
       {
         "path": "fetch/api/request/request-consume.html",
         "url": "/fetch/api/request/request-consume.html"
       },
       {
@@ -34730,16 +34734,23 @@
     "deleted": [],
     "items": {
       "testharness": {
         "dom/collections/HTMLCollection-supported-property-indices.html": [
           {
             "path": "dom/collections/HTMLCollection-supported-property-indices.html",
             "url": "/dom/collections/HTMLCollection-supported-property-indices.html"
           }
+        ],
+        "fetch/api/request/request-cache.html": [
+          {
+            "path": "fetch/api/request/request-cache.html",
+            "timeout": "long",
+            "url": "/fetch/api/request/request-cache.html"
+          }
         ]
       }
     },
     "reftest_nodes": {}
   },
   "reftest_nodes": {
     "2dcontext/building-paths/canvas_complexshapes_arcto_001.htm": [
       {
--- a/testing/web-platform/meta/fetch/api/request/request-idl.html.ini
+++ b/testing/web-platform/meta/fetch/api/request/request-idl.html.ini
@@ -1,26 +1,20 @@
 [request-idl.html]
   type: testharness
   [Request interface: attribute type]
     expected: FAIL
 
   [Request interface: attribute destination]
     expected: FAIL
 
-  [Request interface: attribute cache]
-    expected: FAIL
-
   [Request interface: attribute integrity]
     expected: FAIL
 
   [Request interface: new Request("") must inherit property "type" with the proper type (3)]
     expected: FAIL
 
   [Request interface: new Request("") must inherit property "destination" with the proper type (4)]
     expected: FAIL
 
-  [Request interface: new Request("") must inherit property "cache" with the proper type (9)]
-    expected: FAIL
-
   [Request interface: new Request("") must inherit property "integrity" with the proper type (11)]
     expected: FAIL
 
--- a/testing/web-platform/meta/fetch/api/request/request-structure.html.ini
+++ b/testing/web-platform/meta/fetch/api/request/request-structure.html.ini
@@ -1,14 +1,11 @@
 [request-structure.html]
   type: testharness
   [Check type attribute]
     expected: FAIL
 
   [Check destination attribute]
     expected: FAIL
 
-  [Check cache attribute]
-    expected: FAIL
-
   [Check integrity attribute]
     expected: FAIL
 
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html
@@ -422,10 +422,45 @@ async_test(function(t) {
             'Fragment Not Found',
             'Service worker should not expose URL fragments.');
           frame.remove();
           return service_worker_unregister_and_done(t, scope);
         })
       .catch(unreached_rejection(t));
   }, 'Service Worker must not expose FetchEvent URL fragments.');
 
+async_test(function(t) {
+    var scope = 'resources/simple.html?cache';
+    var frame;
+    var cacheTypes = [
+      undefined, 'default', 'no-store', 'reload', 'no-cache', 'force-cache'
+    ];
+    service_worker_unregister_and_register(t, worker, scope)
+      .then(function(reg) {
+          return wait_for_state(t, reg.installing, 'activated');
+        })
+      .then(function() { return with_iframe(scope); })
+      .then(function(f) {
+          frame = f;
+          var tests = cacheTypes.map(function(type) {
+            return new Promise(function(resolve) {
+                return frame.contentWindow.fetch(scope + '=' + type,
+                                                 {cache: type})
+                  .then(function(response) { return response.text(); })
+                  .then(function(response_text) {
+                      var expected = (type === undefined) ? 'default' : type;
+                      assert_equals(response_text, expected,
+                                    'Service Worker should respond to fetch with the correct type');
+                    })
+                  .then(resolve);
+              });
+          });
+          return Promise.all(tests);
+        })
+      .then(function() {
+          frame.remove();
+          return service_worker_unregister_and_done(t, scope);
+        })
+      .catch(unreached_rejection(t));
+  }, 'Service Worker responds to fetch event with the correct cache types');
+
 </script>
 </body>
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js
@@ -86,32 +86,37 @@ function handleFragmentCheck(event) {
   if (event.request.url.indexOf('#') === -1) {
     body = 'Fragment Not Found';
   } else {
     body = 'Fragment Found';
   }
   event.respondWith(new Response(body));
 }
 
+function handleCache(event) {
+  event.respondWith(new Response(event.request.cache));
+}
+
 self.addEventListener('fetch', function(event) {
     var url = event.request.url;
     var handlers = [
       { pattern: '?string', fn: handleString },
       { pattern: '?blob', fn: handleBlob },
       { pattern: '?referrerFull', fn: handleReferrerFull },
       { pattern: '?referrerPolicy', fn: handleReferrerPolicy },
       { pattern: '?referrer', fn: handleReferrer },
       { pattern: '?clientId', fn: handleClientId },
       { pattern: '?ignore', fn: function() {} },
       { pattern: '?null', fn: handleNullBody },
       { pattern: '?fetch', fn: handleFetch },
       { pattern: '?form-post', fn: handleFormPost },
       { pattern: '?multiple-respond-with', fn: handleMultipleRespondWith },
       { pattern: '?used-check', fn: handleUsedCheck },
-      { pattern: '?fragment-check', fn: handleFragmentCheck }
+      { pattern: '?fragment-check', fn: handleFragmentCheck },
+      { pattern: '?cache', fn: handleCache },
     ];
 
     var handler = null;
     for (var i = 0; i < handlers.length; ++i) {
       if (url.indexOf(handlers[i].pattern) != -1) {
         handler = handlers[i];
         break;
       }
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache.html
@@ -0,0 +1,306 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Request cache</title>
+    <meta name="help" href="https://fetch.spec.whatwg.org/#request">
+    <meta name="timeout" content="long">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script>
+    var tests = [
+      {
+        name: 'RequestCache "default" mode checks the cache for previously cached content and goes to the network for stale responses',
+        state: "stale",
+        request_cache: ["default", "default"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "default" mode checks the cache for previously cached content and avoids going to the network if a fresh response exists',
+        state: "fresh",
+        request_cache: ["default", "default"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "no-cache" mode revalidates stale responses found in the cache',
+        state: "stale",
+        request_cache: ["default", "no-cache"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+        expected_max_age_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "no-cache" mode revalidates fresh responses found in the cache',
+        state: "fresh",
+        request_cache: ["default", "no-cache"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+        expected_max_age_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for stale responses',
+        state: "stale",
+        request_cache: ["default", "force-cache"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for fresh responses',
+        state: "fresh",
+        request_cache: ["default", "force-cache"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found',
+        state: "stale",
+        request_cache: ["force-cache"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found',
+        state: "fresh",
+        request_cache: ["force-cache"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary',
+        state: "stale",
+        vary: "*",
+        request_cache: ["default", "force-cache"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary',
+        state: "fresh",
+        vary: "*",
+        request_cache: ["default", "force-cache"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network',
+        state: "stale",
+        request_cache: ["force-cache", "default"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network',
+        state: "fresh",
+        request_cache: ["force-cache", "default"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless',
+        state: "stale",
+        request_cache: ["default", "no-store"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless',
+        state: "fresh",
+        request_cache: ["default", "no-store"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "no-store" mode does not store the response in the cache',
+        state: "stale",
+        request_cache: ["no-store", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [true, false],
+      },
+      {
+        name: 'RequestCache "no-store" mode does not store the response in the cache',
+        state: "fresh",
+        request_cache: ["no-store", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [true, false],
+      },
+      {
+        name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache',
+        state: "stale",
+        cache_control: "no-store",
+        request_cache: ["default", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache',
+        state: "fresh",
+        cache_control: "no-store",
+        request_cache: ["default", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless',
+        state: "stale",
+        request_cache: ["default", "reload"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless',
+        state: "fresh",
+        request_cache: ["default", "reload"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "reload" mode does store the response in the cache',
+        state: "stale",
+        request_cache: ["reload", "default"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [true, false],
+      },
+      {
+        name: 'RequestCache "reload" mode does store the response in the cache',
+        state: "fresh",
+        request_cache: ["reload", "default"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [true],
+      },
+      {
+        name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored',
+        state: "stale",
+        request_cache: ["default", "reload", "default"],
+        expected_validation_headers: [false, false, true],
+        expected_no_cache_headers: [false, true, false],
+      },
+      {
+        name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored',
+        state: "fresh",
+        request_cache: ["default", "reload", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+    ];
+    function make_url(uuid, id, value, content, info) {
+      var now = new Date();
+      var dates = {
+        fresh: new Date(now.getFullYear() + 1, now.getMonth(), now.getDay()).toGMTString(),
+        stale: new Date(now.getFullYear() - 1, now.getMonth(), now.getDay()).toGMTString(),
+      };
+      var vary = "";
+      if ("vary" in info) {
+        vary = "&vary=" + info.vary;
+      }
+      var cache_control = "";
+      if ("cache_control" in info) {
+        cache_control = "&cache_control=" + info.cache_control;
+      }
+      return "resources/cache.py?token=" + uuid +
+             "&content=" + content +
+             "&" + id + "=" + value +
+             "&expires=" + dates[info.state] + vary + cache_control;
+    }
+    function server_state(uuid) {
+      return fetch("resources/cache.py?querystate&token=" + uuid)
+        .then(function(response) {
+          return response.text();
+        }).then(function(text) {
+          return JSON.parse(text);
+        });
+    }
+    function populate_cache(url, content, cache) {
+      return fetch(url, {cache: cache})
+        .then(function(response) {
+          assert_equals(response.status, 200);
+          assert_equals(response.statusText, "OK");
+          return response.text();
+        }).then(function(text) {
+          assert_equals(text, content);
+        });
+    }
+    function make_test(type, info) {
+      return function(test) {
+        var uuid = token();
+        var identifier = (type == "tag" ? Math.random() : new Date().toGMTString());
+        var content = Math.random().toString();
+        var url = make_url(uuid, type, identifier, content, info);
+        var fetch_functions = [function() {
+          return populate_cache(url, content, info.request_cache[0]);
+        }];
+        for (var i = 1; i < info.request_cache.length; ++i) {
+          fetch_functions.push(function(idx) {
+            return fetch(url, {cache: info.request_cache[idx]})
+              .then(function(response) {
+                assert_equals(response.status, 200);
+                assert_equals(response.statusText, "OK");
+                return response.text();
+              }).then(function(text) {
+                assert_equals(text, content);
+              });
+          });
+        }
+        var i = 0;
+        function run_next_step() {
+          if (fetch_functions.length) {
+            return fetch_functions.shift()(i++)
+              .then(run_next_step);
+          } else {
+            return Promise.resolve();
+          }
+        }
+        return run_next_step()
+          .then(function() {
+            // Now, query the server state
+            return server_state(uuid);
+          }).then(function(state) {
+            var expectedState = [];
+            info.expected_validation_headers.forEach(function (validate) {
+              if (validate) {
+                if (type == "tag") {
+                  expectedState.push({"If-None-Match": '"' + identifier + '"'});
+                } else {
+                  expectedState.push({"If-Modified-Since": identifier});
+                }
+              } else {
+                expectedState.push({});
+              }
+            });
+            for (var i = 0; i < info.expected_no_cache_headers.length; ++i) {
+              if (info.expected_no_cache_headers[i]) {
+                expectedState[i]["Pragma"] = "no-cache";
+                expectedState[i]["Cache-Control"] = "no-cache";
+              }
+            }
+            if ("expected_max_age_headers" in info) {
+              for (var i = 0; i < info.expected_max_age_headers.length; ++i) {
+                if (info.expected_max_age_headers[i]) {
+                  expectedState[i]["Cache-Control"] = "max-age=0";
+                }
+              }
+            }
+            assert_equals(state.length, expectedState.length);
+            for (var i = 0; i < state.length; ++i) {
+              for (var header in state[i]) {
+                assert_equals(state[i][header], expectedState[i][header]);
+                delete expectedState[i][header];
+              }
+              for (var header in expectedState[i]) {
+                assert_false(header in state[i]);
+              }
+            }
+          });
+      };
+    }
+    tests.forEach(function(info) {
+      promise_test(make_test("tag", info), info.name + " with Etag and " + info.state + " response");
+      promise_test(make_test("date", info), info.name + " with date and " + info.state + " response");
+    });
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/resources/cache.py
@@ -0,0 +1,51 @@
+def main(request, response):
+    token = request.GET.first("token", None)
+    if "querystate" in request.GET:
+        from json import JSONEncoder
+        response.headers.set("Content-Type", "text/plain")
+        return JSONEncoder().encode(request.server.stash.take(token))
+    content = request.GET.first("content", None)
+    tag = request.GET.first("tag", None)
+    date = request.GET.first("date", None)
+    expires = request.GET.first("expires", None)
+    vary = request.GET.first("vary", None)
+    cc = request.GET.first("cache_control", None)
+    inm = request.headers.get("If-None-Match", None)
+    ims = request.headers.get("If-Modified-Since", None)
+    pragma = request.headers.get("Pragma", None)
+    cache_control = request.headers.get("Cache-Control", None)
+
+    server_state = request.server.stash.take(token)
+    if not server_state:
+        server_state = []
+    state = dict()
+    if inm:
+        state["If-None-Match"] = inm
+    if ims:
+        state["If-Modified-Since"] = ims
+    if pragma:
+        state["Pragma"] = pragma
+    if cache_control:
+        state["Cache-Control"] = cache_control
+    server_state.append(state)
+    request.server.stash.put(token, server_state)
+
+    if tag:
+        response.headers.set("ETag", '"%s"' % tag)
+    elif date:
+        response.headers.set("Last-Modified", date)
+    if expires:
+        response.headers.set("Expires", expires)
+    if vary:
+        response.headers.set("Vary", vary)
+    if cc:
+        response.headers.set("Cache-Control", cc)
+
+    if ((inm is not None and inm == tag) or
+        (ims is not None and ims == date)):
+        response.status = (304, "Not Modified")
+        return ""
+    else:
+        response.status = (200, "OK")
+        response.headers.set("Content-Type", "text/plain")
+        return content