1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/service_worker/service_worker_cache.h"
6
7#include "base/files/file_path.h"
8#include "base/files/scoped_temp_dir.h"
9#include "base/message_loop/message_loop_proxy.h"
10#include "base/run_loop.h"
11#include "content/browser/fileapi/chrome_blob_storage_context.h"
12#include "content/browser/fileapi/mock_url_request_delegate.h"
13#include "content/common/service_worker/service_worker_types.h"
14#include "content/public/browser/browser_thread.h"
15#include "content/public/test/test_browser_context.h"
16#include "content/public/test/test_browser_thread_bundle.h"
17#include "net/url_request/url_request_context.h"
18#include "net/url_request/url_request_context_getter.h"
19#include "net/url_request/url_request_job_factory_impl.h"
20#include "storage/browser/blob/blob_data_handle.h"
21#include "storage/browser/blob/blob_storage_context.h"
22#include "storage/browser/blob/blob_url_request_job_factory.h"
23#include "storage/common/blob/blob_data.h"
24#include "testing/gtest/include/gtest/gtest.h"
25
26namespace content {
27
28namespace {
29const char kTestData[] = "Hello World";
30
31// Returns a BlobProtocolHandler that uses |blob_storage_context|. Caller owns
32// the memory.
33storage::BlobProtocolHandler* CreateMockBlobProtocolHandler(
34    storage::BlobStorageContext* blob_storage_context) {
35  // The FileSystemContext and MessageLoopProxy are not actually used but a
36  // MessageLoopProxy is needed to avoid a DCHECK in BlobURLRequestJob ctor.
37  return new storage::BlobProtocolHandler(
38      blob_storage_context, NULL, base::MessageLoopProxy::current().get());
39}
40
41}  // namespace
42
43class ServiceWorkerCacheTest : public testing::Test {
44 public:
45  ServiceWorkerCacheTest()
46      : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
47        callback_error_(ServiceWorkerCache::ErrorTypeOK) {}
48
49  virtual void SetUp() OVERRIDE {
50    ChromeBlobStorageContext* blob_storage_context =
51        ChromeBlobStorageContext::GetFor(&browser_context_);
52    // Wait for chrome_blob_storage_context to finish initializing.
53    base::RunLoop().RunUntilIdle();
54    blob_storage_context_ = blob_storage_context->context();
55
56    url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl);
57    url_request_job_factory_->SetProtocolHandler(
58        "blob", CreateMockBlobProtocolHandler(blob_storage_context->context()));
59
60    net::URLRequestContext* url_request_context =
61        browser_context_.GetRequestContext()->GetURLRequestContext();
62
63    url_request_context->set_job_factory(url_request_job_factory_.get());
64
65    CreateRequests(blob_storage_context);
66
67    if (MemoryOnly()) {
68      cache_ = ServiceWorkerCache::CreateMemoryCache(
69          url_request_context,
70          blob_storage_context->context()->AsWeakPtr());
71    } else {
72      ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
73      cache_ = ServiceWorkerCache::CreatePersistentCache(
74          temp_dir_.path(),
75          url_request_context,
76          blob_storage_context->context()->AsWeakPtr());
77    }
78  }
79
80  virtual void TearDown() OVERRIDE {
81    base::RunLoop().RunUntilIdle();
82  }
83
84  void CreateRequests(ChromeBlobStorageContext* blob_storage_context) {
85    ServiceWorkerHeaderMap headers;
86    headers.insert(std::make_pair("a", "a"));
87    headers.insert(std::make_pair("b", "b"));
88    body_request_ = ServiceWorkerFetchRequest(
89        GURL("http://example.com/body.html"), "GET", headers, GURL(""), false);
90    no_body_request_ =
91        ServiceWorkerFetchRequest(GURL("http://example.com/no_body.html"),
92                                  "GET",
93                                  headers,
94                                  GURL(""),
95                                  false);
96
97    std::string expected_response;
98    for (int i = 0; i < 100; ++i)
99      expected_blob_data_ += kTestData;
100
101    scoped_refptr<storage::BlobData> blob_data(
102        new storage::BlobData("blob-id:myblob"));
103    blob_data->AppendData(expected_blob_data_);
104
105    blob_handle_ =
106        blob_storage_context->context()->AddFinishedBlob(blob_data.get());
107
108    body_response_ = ServiceWorkerResponse(GURL("http://example.com/body.html"),
109                                           200,
110                                           "OK",
111                                           headers,
112                                           blob_handle_->uuid());
113
114    no_body_response_ = ServiceWorkerResponse(
115        GURL("http://example.com/no_body.html"), 200, "OK", headers, "");
116  }
117
118  scoped_ptr<ServiceWorkerFetchRequest> CopyFetchRequest(
119      const ServiceWorkerFetchRequest& request) {
120    return make_scoped_ptr(new ServiceWorkerFetchRequest(request.url,
121                                                         request.method,
122                                                         request.headers,
123                                                         request.referrer,
124                                                         request.is_reload));
125  }
126
127  scoped_ptr<ServiceWorkerResponse> CopyFetchResponse(
128      const ServiceWorkerResponse& response) {
129    return make_scoped_ptr(new ServiceWorkerResponse(response.url,
130                                                     response.status_code,
131                                                     response.status_text,
132                                                     response.headers,
133                                                     response.blob_uuid));
134  }
135
136  bool Put(const ServiceWorkerFetchRequest& request,
137           const ServiceWorkerResponse& response) {
138    scoped_ptr<base::RunLoop> loop(new base::RunLoop());
139
140    cache_->Put(CopyFetchRequest(request),
141                CopyFetchResponse(response),
142                base::Bind(&ServiceWorkerCacheTest::ErrorTypeCallback,
143                           base::Unretained(this),
144                           base::Unretained(loop.get())));
145    // TODO(jkarlin): These functions should use base::RunLoop().RunUntilIdle()
146    // once the cache uses a passed in MessageLoopProxy instead of the CACHE
147    // thread.
148    loop->Run();
149
150    return callback_error_ == ServiceWorkerCache::ErrorTypeOK;
151  }
152
153  bool Match(const ServiceWorkerFetchRequest& request) {
154    scoped_ptr<base::RunLoop> loop(new base::RunLoop());
155
156    cache_->Match(CopyFetchRequest(request),
157                  base::Bind(&ServiceWorkerCacheTest::ResponseAndErrorCallback,
158                             base::Unretained(this),
159                             base::Unretained(loop.get())));
160    loop->Run();
161
162    return callback_error_ == ServiceWorkerCache::ErrorTypeOK;
163  }
164
165  bool Delete(const ServiceWorkerFetchRequest& request) {
166    scoped_ptr<base::RunLoop> loop(new base::RunLoop());
167
168    cache_->Delete(CopyFetchRequest(request),
169                   base::Bind(&ServiceWorkerCacheTest::ErrorTypeCallback,
170                              base::Unretained(this),
171                              base::Unretained(loop.get())));
172    loop->Run();
173
174    return callback_error_ == ServiceWorkerCache::ErrorTypeOK;
175  }
176
177  bool Keys() {
178    scoped_ptr<base::RunLoop> loop(new base::RunLoop());
179
180    cache_->Keys(base::Bind(&ServiceWorkerCacheTest::RequestsCallback,
181                            base::Unretained(this),
182                            base::Unretained(loop.get())));
183    loop->Run();
184
185    return callback_error_ == ServiceWorkerCache::ErrorTypeOK;
186  }
187
188  void RequestsCallback(base::RunLoop* run_loop,
189                        ServiceWorkerCache::ErrorType error,
190                        scoped_ptr<ServiceWorkerCache::Requests> requests) {
191    callback_error_ = error;
192    callback_strings_.clear();
193    for (size_t i = 0u; i < requests->size(); ++i)
194      callback_strings_.push_back(requests->at(i).url.spec());
195    run_loop->Quit();
196  }
197
198  void ErrorTypeCallback(base::RunLoop* run_loop,
199                         ServiceWorkerCache::ErrorType error) {
200    callback_error_ = error;
201    run_loop->Quit();
202  }
203
204  void ResponseAndErrorCallback(
205      base::RunLoop* run_loop,
206      ServiceWorkerCache::ErrorType error,
207      scoped_ptr<ServiceWorkerResponse> response,
208      scoped_ptr<storage::BlobDataHandle> body_handle) {
209    callback_error_ = error;
210    callback_response_ = response.Pass();
211
212    if (error == ServiceWorkerCache::ErrorTypeOK &&
213        !callback_response_->blob_uuid.empty()) {
214      callback_response_data_ = body_handle.Pass();
215    }
216
217    run_loop->Quit();
218  }
219
220  void CopyBody(storage::BlobDataHandle* blob_handle, std::string* output) {
221    storage::BlobData* data = blob_handle->data();
222    std::vector<storage::BlobData::Item> items = data->items();
223    for (size_t i = 0, max = items.size(); i < max; ++i)
224      output->append(items[i].bytes(), items[i].length());
225  }
226
227  bool VerifyKeys(const std::vector<std::string>& expected_keys) {
228    if (expected_keys.size() != callback_strings_.size())
229      return false;
230
231    std::set<std::string> found_set;
232    for (int i = 0, max = callback_strings_.size(); i < max; ++i)
233      found_set.insert(callback_strings_[i]);
234
235    for (int i = 0, max = expected_keys.size(); i < max; ++i) {
236      if (found_set.find(expected_keys[i]) == found_set.end())
237        return false;
238    }
239    return true;
240  }
241
242  virtual bool MemoryOnly() { return false; }
243
244 protected:
245  TestBrowserContext browser_context_;
246  TestBrowserThreadBundle browser_thread_bundle_;
247  scoped_ptr<net::URLRequestJobFactoryImpl> url_request_job_factory_;
248  storage::BlobStorageContext* blob_storage_context_;
249
250  base::ScopedTempDir temp_dir_;
251  scoped_refptr<ServiceWorkerCache> cache_;
252
253  ServiceWorkerFetchRequest body_request_;
254  ServiceWorkerResponse body_response_;
255  ServiceWorkerFetchRequest no_body_request_;
256  ServiceWorkerResponse no_body_response_;
257  scoped_ptr<storage::BlobDataHandle> blob_handle_;
258  std::string expected_blob_data_;
259
260  ServiceWorkerCache::ErrorType callback_error_;
261  scoped_ptr<ServiceWorkerResponse> callback_response_;
262  scoped_ptr<storage::BlobDataHandle> callback_response_data_;
263  std::vector<std::string> callback_strings_;
264};
265
266class ServiceWorkerCacheTestP : public ServiceWorkerCacheTest,
267                                public testing::WithParamInterface<bool> {
268  virtual bool MemoryOnly() OVERRIDE { return !GetParam(); }
269};
270
271TEST_P(ServiceWorkerCacheTestP, PutNoBody) {
272  EXPECT_TRUE(Put(no_body_request_, no_body_response_));
273}
274
275TEST_P(ServiceWorkerCacheTestP, PutBody) {
276  EXPECT_TRUE(Put(body_request_, body_response_));
277}
278
279TEST_F(ServiceWorkerCacheTest, PutBodyDropBlobRef) {
280  scoped_ptr<base::RunLoop> loop(new base::RunLoop());
281  cache_->Put(CopyFetchRequest(body_request_),
282              CopyFetchResponse(body_response_),
283              base::Bind(&ServiceWorkerCacheTestP::ErrorTypeCallback,
284                         base::Unretained(this),
285                         base::Unretained(loop.get())));
286  // The handle should be held by the cache now so the deref here should be
287  // okay.
288  blob_handle_.reset();
289  loop->Run();
290
291  EXPECT_EQ(ServiceWorkerCache::ErrorTypeOK, callback_error_);
292}
293
294TEST_P(ServiceWorkerCacheTestP, MatchNoBody) {
295  EXPECT_TRUE(Put(no_body_request_, no_body_response_));
296  EXPECT_TRUE(Match(no_body_request_));
297  EXPECT_EQ(200, callback_response_->status_code);
298  EXPECT_STREQ("OK", callback_response_->status_text.c_str());
299  EXPECT_STREQ("http://example.com/no_body.html",
300               callback_response_->url.spec().c_str());
301}
302
303TEST_P(ServiceWorkerCacheTestP, MatchBody) {
304  EXPECT_TRUE(Put(body_request_, body_response_));
305  EXPECT_TRUE(Match(body_request_));
306  EXPECT_EQ(200, callback_response_->status_code);
307  EXPECT_STREQ("OK", callback_response_->status_text.c_str());
308  EXPECT_STREQ("http://example.com/body.html",
309               callback_response_->url.spec().c_str());
310  std::string response_body;
311  CopyBody(callback_response_data_.get(), &response_body);
312  EXPECT_STREQ(expected_blob_data_.c_str(), response_body.c_str());
313}
314
315TEST_P(ServiceWorkerCacheTestP, Vary) {
316  body_request_.headers["vary_foo"] = "foo";
317  body_response_.headers["vary"] = "vary_foo";
318  EXPECT_TRUE(Put(body_request_, body_response_));
319  EXPECT_TRUE(Match(body_request_));
320
321  body_request_.headers["vary_foo"] = "bar";
322  EXPECT_FALSE(Match(body_request_));
323
324  body_request_.headers.erase("vary_foo");
325  EXPECT_FALSE(Match(body_request_));
326}
327
328TEST_P(ServiceWorkerCacheTestP, EmptyVary) {
329  body_response_.headers["vary"] = "";
330  EXPECT_TRUE(Put(body_request_, body_response_));
331  EXPECT_TRUE(Match(body_request_));
332
333  body_request_.headers["zoo"] = "zoo";
334  EXPECT_TRUE(Match(body_request_));
335}
336
337TEST_P(ServiceWorkerCacheTestP, NoVaryButDiffHeaders) {
338  EXPECT_TRUE(Put(body_request_, body_response_));
339  EXPECT_TRUE(Match(body_request_));
340
341  body_request_.headers["zoo"] = "zoo";
342  EXPECT_TRUE(Match(body_request_));
343}
344
345TEST_P(ServiceWorkerCacheTestP, VaryMultiple) {
346  body_request_.headers["vary_foo"] = "foo";
347  body_request_.headers["vary_bar"] = "bar";
348  body_response_.headers["vary"] = " vary_foo    , vary_bar";
349  EXPECT_TRUE(Put(body_request_, body_response_));
350  EXPECT_TRUE(Match(body_request_));
351
352  body_request_.headers["vary_bar"] = "foo";
353  EXPECT_FALSE(Match(body_request_));
354
355  body_request_.headers.erase("vary_bar");
356  EXPECT_FALSE(Match(body_request_));
357}
358
359TEST_P(ServiceWorkerCacheTestP, VaryNewHeader) {
360  body_request_.headers["vary_foo"] = "foo";
361  body_response_.headers["vary"] = " vary_foo, vary_bar";
362  EXPECT_TRUE(Put(body_request_, body_response_));
363  EXPECT_TRUE(Match(body_request_));
364
365  body_request_.headers["vary_bar"] = "bar";
366  EXPECT_FALSE(Match(body_request_));
367}
368
369TEST_P(ServiceWorkerCacheTestP, VaryStar) {
370  body_response_.headers["vary"] = "*";
371  EXPECT_TRUE(Put(body_request_, body_response_));
372  EXPECT_FALSE(Match(body_request_));
373}
374
375TEST_P(ServiceWorkerCacheTestP, EmptyKeys) {
376  EXPECT_TRUE(Keys());
377  EXPECT_EQ(0u, callback_strings_.size());
378}
379
380TEST_P(ServiceWorkerCacheTestP, TwoKeys) {
381  EXPECT_TRUE(Put(no_body_request_, no_body_response_));
382  EXPECT_TRUE(Put(body_request_, body_response_));
383  EXPECT_TRUE(Keys());
384  EXPECT_EQ(2u, callback_strings_.size());
385  std::vector<std::string> expected_keys;
386  expected_keys.push_back(no_body_request_.url.spec());
387  expected_keys.push_back(body_request_.url.spec());
388  EXPECT_TRUE(VerifyKeys(expected_keys));
389}
390
391TEST_P(ServiceWorkerCacheTestP, TwoKeysThenOne) {
392  EXPECT_TRUE(Put(no_body_request_, no_body_response_));
393  EXPECT_TRUE(Put(body_request_, body_response_));
394  EXPECT_TRUE(Keys());
395  EXPECT_EQ(2u, callback_strings_.size());
396  std::vector<std::string> expected_keys;
397  expected_keys.push_back(no_body_request_.url.spec());
398  expected_keys.push_back(body_request_.url.spec());
399  EXPECT_TRUE(VerifyKeys(expected_keys));
400
401  EXPECT_TRUE(Delete(body_request_));
402  EXPECT_TRUE(Keys());
403  EXPECT_EQ(1u, callback_strings_.size());
404  std::vector<std::string> expected_key;
405  expected_key.push_back(no_body_request_.url.spec());
406  EXPECT_TRUE(VerifyKeys(expected_key));
407}
408
409// TODO(jkarlin): Once SimpleCache is working bug-free on Windows reenable these
410// tests. In the meanwhile we know that Windows operations will be a little
411// flaky (though not crashy). See https://crbug.com/409109
412#ifndef OS_WIN
413TEST_P(ServiceWorkerCacheTestP, DeleteNoBody) {
414  EXPECT_TRUE(Put(no_body_request_, no_body_response_));
415  EXPECT_TRUE(Match(no_body_request_));
416  EXPECT_TRUE(Delete(no_body_request_));
417  EXPECT_FALSE(Match(no_body_request_));
418  EXPECT_FALSE(Delete(no_body_request_));
419  EXPECT_TRUE(Put(no_body_request_, no_body_response_));
420  EXPECT_TRUE(Match(no_body_request_));
421  EXPECT_TRUE(Delete(no_body_request_));
422}
423
424TEST_P(ServiceWorkerCacheTestP, DeleteBody) {
425  EXPECT_TRUE(Put(body_request_, body_response_));
426  EXPECT_TRUE(Match(body_request_));
427  EXPECT_TRUE(Delete(body_request_));
428  EXPECT_FALSE(Match(body_request_));
429  EXPECT_FALSE(Delete(body_request_));
430  EXPECT_TRUE(Put(body_request_, body_response_));
431  EXPECT_TRUE(Match(body_request_));
432  EXPECT_TRUE(Delete(body_request_));
433}
434
435TEST_P(ServiceWorkerCacheTestP, QuickStressNoBody) {
436  for (int i = 0; i < 100; ++i) {
437    EXPECT_FALSE(Match(no_body_request_));
438    EXPECT_TRUE(Put(no_body_request_, no_body_response_));
439    EXPECT_TRUE(Match(no_body_request_));
440    EXPECT_TRUE(Delete(no_body_request_));
441  }
442}
443
444TEST_P(ServiceWorkerCacheTestP, QuickStressBody) {
445  for (int i = 0; i < 100; ++i) {
446    ASSERT_FALSE(Match(body_request_));
447    ASSERT_TRUE(Put(body_request_, body_response_));
448    ASSERT_TRUE(Match(body_request_));
449    ASSERT_TRUE(Delete(body_request_));
450  }
451}
452#endif  // OS_WIN
453
454TEST_F(ServiceWorkerCacheTest, CaselessServiceWorkerResponseHeaders) {
455  // ServiceWorkerCache depends on ServiceWorkerResponse having caseless
456  // headers so that it can quickly lookup vary headers.
457  ServiceWorkerResponse response(
458      GURL("http://www.example.com"), 200, "OK", ServiceWorkerHeaderMap(), "");
459  response.headers["content-type"] = "foo";
460  response.headers["Content-Type"] = "bar";
461  EXPECT_EQ("bar", response.headers["content-type"]);
462}
463
464TEST_F(ServiceWorkerCacheTest, CaselessServiceWorkerFetchRequestHeaders) {
465  // ServiceWorkerCache depends on ServiceWorkerFetchRequest having caseless
466  // headers so that it can quickly lookup vary headers.
467  ServiceWorkerFetchRequest request(GURL("http://www.example.com"),
468                                         "GET",
469                                         ServiceWorkerHeaderMap(),
470                                         GURL(""),
471                                         false);
472  request.headers["content-type"] = "foo";
473  request.headers["Content-Type"] = "bar";
474  EXPECT_EQ("bar", request.headers["content-type"]);
475}
476
477INSTANTIATE_TEST_CASE_P(ServiceWorkerCacheTest,
478                        ServiceWorkerCacheTestP,
479                        ::testing::Values(false, true));
480
481}  // namespace content
482