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 <string>
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/pickle.h"
10#include "base/run_loop.h"
11#include "content/browser/appcache/appcache_response.h"
12#include "content/browser/appcache/appcache_service_impl.h"
13#include "content/browser/appcache/mock_appcache_storage.h"
14#include "net/base/completion_callback.h"
15#include "net/base/io_buffer.h"
16#include "net/http/http_response_headers.h"
17#include "testing/gtest/include/gtest/gtest.h"
18
19namespace content {
20namespace {
21
22const int64 kMockGroupId = 1;
23const int64 kMockCacheId = 1;
24const int64 kMockResponseId = 1;
25const int64 kMissingCacheId = 5;
26const int64 kMissingResponseId = 5;
27const char kMockHeaders[] =
28    "HTTP/1.0 200 OK\0Content-Length: 5\0\0";
29const char kMockBody[] = "Hello";
30const int kMockBodySize = 5;
31
32class MockResponseReader : public AppCacheResponseReader {
33 public:
34  MockResponseReader(int64 response_id,
35                     net::HttpResponseInfo* info, int info_size,
36                     const char* data, int data_size)
37      : AppCacheResponseReader(response_id, 0, NULL),
38        info_(info), info_size_(info_size),
39        data_(data), data_size_(data_size) {
40  }
41  virtual void ReadInfo(HttpResponseInfoIOBuffer* info_buf,
42                        const net::CompletionCallback& callback) OVERRIDE {
43    info_buffer_ = info_buf;
44    callback_ = callback;  // Cleared on completion.
45
46    int rv = info_.get() ? info_size_ : net::ERR_FAILED;
47    info_buffer_->http_info.reset(info_.release());
48    info_buffer_->response_data_size = data_size_;
49    ScheduleUserCallback(rv);
50  }
51  virtual void ReadData(net::IOBuffer* buf, int buf_len,
52                        const net::CompletionCallback& callback) OVERRIDE {
53    buffer_ = buf;
54    buffer_len_ = buf_len;
55    callback_ = callback;  // Cleared on completion.
56
57    if (!data_) {
58      ScheduleUserCallback(net::ERR_CACHE_READ_FAILURE);
59      return;
60    }
61    DCHECK(buf_len >= data_size_);
62    memcpy(buf->data(), data_, data_size_);
63    ScheduleUserCallback(data_size_);
64    data_size_ = 0;
65  }
66
67 private:
68  void ScheduleUserCallback(int result) {
69    base::MessageLoop::current()->PostTask(FROM_HERE,
70        base::Bind(&MockResponseReader::InvokeUserCompletionCallback,
71                   weak_factory_.GetWeakPtr(), result));
72  }
73
74  scoped_ptr<net::HttpResponseInfo> info_;
75  int info_size_;
76  const char* data_;
77  int data_size_;
78};
79
80}  // namespace
81
82
83class AppCacheServiceImplTest : public testing::Test {
84 public:
85  AppCacheServiceImplTest()
86      : kOrigin("http://hello/"),
87        kManifestUrl(kOrigin.Resolve("manifest")),
88        service_(new AppCacheServiceImpl(NULL)),
89        delete_result_(net::OK), delete_completion_count_(0),
90        deletion_callback_(
91            base::Bind(&AppCacheServiceImplTest::OnDeleteAppCachesComplete,
92                       base::Unretained(this))) {
93    // Setup to use mock storage.
94    service_->storage_.reset(new MockAppCacheStorage(service_.get()));
95  }
96
97  void OnDeleteAppCachesComplete(int result) {
98    delete_result_ = result;
99    ++delete_completion_count_;
100  }
101
102  MockAppCacheStorage* mock_storage() {
103    return static_cast<MockAppCacheStorage*>(service_->storage());
104  }
105
106  void ResetStorage() {
107    service_->storage_.reset(new MockAppCacheStorage(service_.get()));
108  }
109
110  bool IsGroupStored(const GURL& manifest_url) {
111    return mock_storage()->IsGroupForManifestStored(manifest_url);
112  }
113
114  int CountPendingHelpers() {
115    return service_->pending_helpers_.size();
116  }
117
118  void SetupMockGroup() {
119    scoped_ptr<net::HttpResponseInfo> info(MakeMockResponseInfo());
120    const int kMockInfoSize = GetResponseInfoSize(info.get());
121
122    // Create a mock group, cache, and entry and stuff them into mock storage.
123    scoped_refptr<AppCacheGroup> group(
124        new AppCacheGroup(service_->storage(), kManifestUrl, kMockGroupId));
125    scoped_refptr<AppCache> cache(
126        new AppCache(service_->storage(), kMockCacheId));
127    cache->AddEntry(
128        kManifestUrl,
129        AppCacheEntry(AppCacheEntry::MANIFEST, kMockResponseId,
130                      kMockInfoSize + kMockBodySize));
131    cache->set_complete(true);
132    group->AddCache(cache.get());
133    mock_storage()->AddStoredGroup(group.get());
134    mock_storage()->AddStoredCache(cache.get());
135  }
136
137  void SetupMockReader(
138      bool valid_info, bool valid_data, bool valid_size) {
139    net::HttpResponseInfo* info = valid_info ? MakeMockResponseInfo() : NULL;
140    int info_size = info ? GetResponseInfoSize(info) : 0;
141    const char* data = valid_data ? kMockBody : NULL;
142    int data_size = valid_size ? kMockBodySize : 3;
143    mock_storage()->SimulateResponseReader(
144        new MockResponseReader(kMockResponseId, info, info_size,
145                               data, data_size));
146  }
147
148  net::HttpResponseInfo* MakeMockResponseInfo() {
149    net::HttpResponseInfo* info = new net::HttpResponseInfo;
150    info->request_time = base::Time::Now();
151    info->response_time = base::Time::Now();
152    info->was_cached = false;
153    info->headers = new net::HttpResponseHeaders(
154        std::string(kMockHeaders, arraysize(kMockHeaders)));
155    return info;
156  }
157
158  int GetResponseInfoSize(const net::HttpResponseInfo* info) {
159    Pickle pickle;
160    return PickleResponseInfo(&pickle, info);
161  }
162
163  int PickleResponseInfo(Pickle* pickle, const net::HttpResponseInfo* info) {
164    const bool kSkipTransientHeaders = true;
165    const bool kTruncated = false;
166    info->Persist(pickle, kSkipTransientHeaders, kTruncated);
167    return pickle->size();
168  }
169
170  const GURL kOrigin;
171  const GURL kManifestUrl;
172
173  scoped_ptr<AppCacheServiceImpl> service_;
174  int delete_result_;
175  int delete_completion_count_;
176  net::CompletionCallback deletion_callback_;
177
178 private:
179  base::MessageLoop message_loop_;
180};
181
182TEST_F(AppCacheServiceImplTest, DeleteAppCachesForOrigin) {
183  // Without giving mock storage simiulated info, should fail.
184  service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback_);
185  EXPECT_EQ(0, delete_completion_count_);
186  base::RunLoop().RunUntilIdle();
187  EXPECT_EQ(1, delete_completion_count_);
188  EXPECT_EQ(net::ERR_FAILED, delete_result_);
189  delete_completion_count_ = 0;
190
191  // Should succeed given an empty info collection.
192  mock_storage()->SimulateGetAllInfo(new content::AppCacheInfoCollection);
193  service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback_);
194  EXPECT_EQ(0, delete_completion_count_);
195  base::RunLoop().RunUntilIdle();
196  EXPECT_EQ(1, delete_completion_count_);
197  EXPECT_EQ(net::OK, delete_result_);
198  delete_completion_count_ = 0;
199
200  scoped_refptr<AppCacheInfoCollection> info(new AppCacheInfoCollection);
201
202  // Should succeed given a non-empty info collection.
203  AppCacheInfo mock_manifest_1;
204  AppCacheInfo mock_manifest_2;
205  AppCacheInfo mock_manifest_3;
206  mock_manifest_1.manifest_url = kOrigin.Resolve("manifest1");
207  mock_manifest_2.manifest_url = kOrigin.Resolve("manifest2");
208  mock_manifest_3.manifest_url = kOrigin.Resolve("manifest3");
209  AppCacheInfoVector info_vector;
210  info_vector.push_back(mock_manifest_1);
211  info_vector.push_back(mock_manifest_2);
212  info_vector.push_back(mock_manifest_3);
213  info->infos_by_origin[kOrigin] = info_vector;
214  mock_storage()->SimulateGetAllInfo(info.get());
215  service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback_);
216  EXPECT_EQ(0, delete_completion_count_);
217  base::RunLoop().RunUntilIdle();
218  EXPECT_EQ(1, delete_completion_count_);
219  EXPECT_EQ(net::OK, delete_result_);
220  delete_completion_count_ = 0;
221
222  // Should fail if storage fails to delete.
223  info->infos_by_origin[kOrigin] = info_vector;
224  mock_storage()->SimulateGetAllInfo(info.get());
225  mock_storage()->SimulateMakeGroupObsoleteFailure();
226  service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback_);
227  EXPECT_EQ(0, delete_completion_count_);
228  base::RunLoop().RunUntilIdle();
229  EXPECT_EQ(1, delete_completion_count_);
230  EXPECT_EQ(net::ERR_FAILED, delete_result_);
231  delete_completion_count_ = 0;
232
233  // Should complete with abort error if the service is deleted
234  // prior to a delete completion.
235  service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback_);
236  EXPECT_EQ(0, delete_completion_count_);
237  service_.reset();  // kill it
238  EXPECT_EQ(1, delete_completion_count_);
239  EXPECT_EQ(net::ERR_ABORTED, delete_result_);
240  delete_completion_count_ = 0;
241
242  // Let any tasks lingering from the sudden deletion run and verify
243  // no other completion calls occur.
244  base::RunLoop().RunUntilIdle();
245  EXPECT_EQ(0, delete_completion_count_);
246}
247
248TEST_F(AppCacheServiceImplTest, CheckAppCacheResponse) {
249  // Check a non-existing manifest.
250  EXPECT_FALSE(IsGroupStored(kManifestUrl));
251  service_->CheckAppCacheResponse(kManifestUrl, 1, 1);
252  base::RunLoop().RunUntilIdle();
253  EXPECT_EQ(0, CountPendingHelpers());
254  EXPECT_FALSE(IsGroupStored(kManifestUrl));
255  ResetStorage();
256
257  // Check a response that looks good.
258  // Nothing should be deleted.
259  SetupMockGroup();
260  EXPECT_TRUE(IsGroupStored(kManifestUrl));
261  SetupMockReader(true, true, true);
262  service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId, kMockResponseId);
263  base::RunLoop().RunUntilIdle();
264  EXPECT_EQ(0, CountPendingHelpers());
265  EXPECT_TRUE(IsGroupStored(kManifestUrl));
266  ResetStorage();
267
268  // Check a response for which there is no cache entry.
269  // The group should get deleted.
270  SetupMockGroup();
271  service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId,
272                                  kMissingResponseId);
273  base::RunLoop().RunUntilIdle();
274  EXPECT_EQ(0, CountPendingHelpers());
275  EXPECT_FALSE(IsGroupStored(kManifestUrl));
276  ResetStorage();
277
278  // Check a response for which there is no manifest entry in a newer version
279  // of the cache. Nothing should get deleted in this case.
280  SetupMockGroup();
281  service_->CheckAppCacheResponse(kManifestUrl, kMissingCacheId,
282                                  kMissingResponseId);
283  base::RunLoop().RunUntilIdle();
284  EXPECT_EQ(0, CountPendingHelpers());
285  EXPECT_TRUE(IsGroupStored(kManifestUrl));
286  ResetStorage();
287
288  // Check a response with bad headers.
289  SetupMockGroup();
290  service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId, kMockResponseId);
291  SetupMockReader(false, true, true);
292  base::RunLoop().RunUntilIdle();
293  EXPECT_EQ(0, CountPendingHelpers());
294  EXPECT_FALSE(IsGroupStored(kManifestUrl));
295  ResetStorage();
296
297  // Check a response with bad data.
298  SetupMockGroup();
299  service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId, kMockResponseId);
300  SetupMockReader(true, false, true);
301  base::RunLoop().RunUntilIdle();
302  EXPECT_EQ(0, CountPendingHelpers());
303  EXPECT_FALSE(IsGroupStored(kManifestUrl));
304  ResetStorage();
305
306  // Check a response with truncated data.
307  SetupMockGroup();
308  service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId, kMockResponseId);
309  SetupMockReader(true, true, false);
310  base::RunLoop().RunUntilIdle();
311  EXPECT_EQ(0, CountPendingHelpers());
312  EXPECT_FALSE(IsGroupStored(kManifestUrl));
313  ResetStorage();
314
315  service_.reset();  // Clean up.
316  base::RunLoop().RunUntilIdle();
317}
318
319// Just tests the backoff scheduling function, not the actual reinit function.
320TEST_F(AppCacheServiceImplTest, ScheduleReinitialize) {
321  const base::TimeDelta kNoDelay;
322  const base::TimeDelta kOneSecond(base::TimeDelta::FromSeconds(1));
323  const base::TimeDelta k30Seconds(base::TimeDelta::FromSeconds(30));
324  const base::TimeDelta kOneHour(base::TimeDelta::FromHours(1));
325
326  // Do things get initialized as expected?
327  scoped_ptr<AppCacheServiceImpl> service(new AppCacheServiceImpl(NULL));
328  EXPECT_TRUE(service->last_reinit_time_.is_null());
329  EXPECT_FALSE(service->reinit_timer_.IsRunning());
330  EXPECT_EQ(kNoDelay, service->next_reinit_delay_);
331
332  // Do we see artifacts of the timer pending and such?
333  service->ScheduleReinitialize();
334  EXPECT_TRUE(service->reinit_timer_.IsRunning());
335  EXPECT_EQ(kNoDelay, service->reinit_timer_.GetCurrentDelay());
336  EXPECT_EQ(k30Seconds, service->next_reinit_delay_);
337
338  // Nothing should change if already scheduled
339  service->ScheduleReinitialize();
340  EXPECT_TRUE(service->reinit_timer_.IsRunning());
341  EXPECT_EQ(kNoDelay, service->reinit_timer_.GetCurrentDelay());
342  EXPECT_EQ(k30Seconds, service->next_reinit_delay_);
343
344  // Does the delay increase as expected?
345  service->reinit_timer_.Stop();
346  service->last_reinit_time_ = base::Time::Now() - kOneSecond;
347  service->ScheduleReinitialize();
348  EXPECT_TRUE(service->reinit_timer_.IsRunning());
349  EXPECT_EQ(k30Seconds, service->reinit_timer_.GetCurrentDelay());
350  EXPECT_EQ(k30Seconds + k30Seconds, service->next_reinit_delay_);
351
352  // Does the delay reset as expected?
353  service->reinit_timer_.Stop();
354  service->last_reinit_time_ = base::Time::Now() -
355                               base::TimeDelta::FromHours(2);
356  service->ScheduleReinitialize();
357  EXPECT_TRUE(service->reinit_timer_.IsRunning());
358  EXPECT_EQ(kNoDelay, service->reinit_timer_.GetCurrentDelay());
359  EXPECT_EQ(k30Seconds, service->next_reinit_delay_);
360
361  // Does the delay max out as expected?
362  service->reinit_timer_.Stop();
363  service->last_reinit_time_ = base::Time::Now() - kOneSecond;
364  service->next_reinit_delay_ = kOneHour;
365  service->ScheduleReinitialize();
366  EXPECT_TRUE(service->reinit_timer_.IsRunning());
367  EXPECT_EQ(kOneHour, service->reinit_timer_.GetCurrentDelay());
368  EXPECT_EQ(kOneHour, service->next_reinit_delay_);
369
370  // Fine to delete while pending.
371  service.reset(NULL);
372}
373
374
375
376}  // namespace content
377