summaryrefslogtreecommitdiffstats
path: root/net/http/http_cache_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'net/http/http_cache_unittest.cc')
-rw-r--r--net/http/http_cache_unittest.cc1012
1 files changed, 1012 insertions, 0 deletions
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
new file mode 100644
index 0000000..8f5db81
--- /dev/null
+++ b/net/http/http_cache_unittest.cc
@@ -0,0 +1,1012 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_cache.h"
+
+#include <windows.h>
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/load_flags.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_transaction_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#pragma warning(disable: 4355)
+
+namespace {
+
+//-----------------------------------------------------------------------------
+// mock disk cache (a very basic memory cache implementation)
+
+class MockDiskEntry : public disk_cache::Entry,
+ public base::RefCounted<MockDiskEntry> {
+ public:
+ MockDiskEntry() : test_mode_(0), doomed_(false) {
+ }
+
+ MockDiskEntry(const std::string& key) : key_(key), doomed_(false) {
+ const MockTransaction* t = FindMockTransaction(GURL(key));
+ DCHECK(t);
+ test_mode_ = t->test_mode;
+ }
+
+ ~MockDiskEntry() {
+ }
+
+ bool is_doomed() const { return doomed_; }
+
+ virtual void Doom() {
+ doomed_ = true;
+ }
+
+ virtual void Close() {
+ Release();
+ }
+
+ virtual std::string GetKey() const {
+ return key_;
+ }
+
+ virtual Time GetLastUsed() const {
+ return Time::FromInternalValue(0);
+ }
+
+ virtual Time GetLastModified() const {
+ return Time::FromInternalValue(0);
+ }
+
+ virtual int32 GetDataSize(int index) const {
+ DCHECK(index >= 0 && index < 2);
+ return static_cast<int32>(data_[index].size());
+ }
+
+ virtual int ReadData(int index, int offset, char* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ DCHECK(index >= 0 && index < 2);
+
+ if (offset < 0 || offset > static_cast<int>(data_[index].size()))
+ return net::ERR_FAILED;
+ if (offset == data_[index].size())
+ return 0;
+
+ int num = std::min(buf_len, static_cast<int>(data_[index].size()) - offset);
+ memcpy(buf, &data_[index][offset], num);
+
+ if (!callback || (test_mode_ & TEST_MODE_SYNC_CACHE_READ))
+ return num;
+
+ CallbackLater(callback, num);
+ return net::ERR_IO_PENDING;
+ }
+
+ virtual int WriteData(int index, int offset, const char* buf, int buf_len,
+ net::CompletionCallback* callback, bool truncate) {
+ DCHECK(index >= 0 && index < 2);
+ DCHECK(truncate);
+
+ if (offset < 0 || offset > static_cast<int>(data_[index].size()))
+ return net::ERR_FAILED;
+
+ data_[index].resize(offset + buf_len);
+ if (buf_len)
+ memcpy(&data_[index][offset], buf, buf_len);
+ return buf_len;
+ }
+
+ private:
+ // Unlike the callbacks for MockHttpTransaction, we want this one to run even
+ // if the consumer called Close on the MockDiskEntry. We achieve that by
+ // leveraging the fact that this class is reference counted.
+ void CallbackLater(net::CompletionCallback* callback, int result) {
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &MockDiskEntry::RunCallback, callback, result));
+ }
+ void RunCallback(net::CompletionCallback* callback, int result) {
+ callback->Run(result);
+ }
+
+ std::string key_;
+ std::vector<char> data_[2];
+ int test_mode_;
+ bool doomed_;
+};
+
+class MockDiskCache : public disk_cache::Backend {
+ public:
+ MockDiskCache() : open_count_(0), create_count_(0), fail_requests_(0) {
+ }
+
+ ~MockDiskCache() {
+ EntryMap::iterator it = entries_.begin();
+ for (; it != entries_.end(); ++it)
+ it->second->Release();
+ }
+
+ virtual int32 GetEntryCount() const {
+ return static_cast<int32>(entries_.size());
+ }
+
+ virtual bool OpenEntry(const std::string& key, disk_cache::Entry** entry) {
+ if (fail_requests_)
+ return false;
+
+ EntryMap::iterator it = entries_.find(key);
+ if (it == entries_.end())
+ return false;
+
+ if (it->second->is_doomed()) {
+ it->second->Release();
+ entries_.erase(it);
+ return false;
+ }
+
+ open_count_++;
+
+ it->second->AddRef();
+ *entry = it->second;
+
+ return true;
+ }
+
+ virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry) {
+ if (fail_requests_)
+ return false;
+
+ EntryMap::iterator it = entries_.find(key);
+ DCHECK(it == entries_.end());
+
+ create_count_++;
+
+ MockDiskEntry* new_entry = new MockDiskEntry(key);
+
+ new_entry->AddRef();
+ entries_[key] = new_entry;
+
+ new_entry->AddRef();
+ *entry = new_entry;
+
+ return true;
+ }
+
+ virtual bool DoomEntry(const std::string& key) {
+ EntryMap::iterator it = entries_.find(key);
+ if (it != entries_.end()) {
+ it->second->Release();
+ entries_.erase(it);
+ }
+ return true;
+ }
+
+ virtual bool DoomAllEntries() {
+ return false;
+ }
+
+ virtual bool DoomEntriesBetween(const Time initial_time,
+ const Time end_time) {
+ return true;
+ }
+
+ virtual bool DoomEntriesSince(const Time initial_time) {
+ return true;
+ }
+
+ virtual bool OpenNextEntry(void** iter, disk_cache::Entry** next_entry) {
+ return false;
+ }
+
+ virtual void EndEnumeration(void** iter) {}
+
+ virtual void GetStats(
+ std::vector<std::pair<std::string, std::string> >* stats) {
+ }
+
+ // returns number of times a cache entry was successfully opened
+ int open_count() const { return open_count_; }
+
+ // returns number of times a cache entry was successfully created
+ int create_count() const { return create_count_; }
+
+ // Fail any subsequent CreateEntry and OpenEntry.
+ void set_fail_requests() { fail_requests_ = true; }
+
+ private:
+ typedef stdext::hash_map<std::string, MockDiskEntry*> EntryMap;
+ EntryMap entries_;
+ int open_count_;
+ int create_count_;
+ bool fail_requests_;
+};
+
+class MockHttpCache {
+ public:
+ MockHttpCache() : http_cache_(new MockNetworkLayer(), new MockDiskCache()) {
+ }
+
+ net::HttpCache* http_cache() { return &http_cache_; }
+
+ MockNetworkLayer* network_layer() {
+ return static_cast<MockNetworkLayer*>(http_cache_.network_layer());
+ }
+ MockDiskCache* disk_cache() {
+ return static_cast<MockDiskCache*>(http_cache_.disk_cache());
+ }
+
+ private:
+ net::HttpCache http_cache_;
+};
+
+
+//-----------------------------------------------------------------------------
+// helpers
+
+void ReadAndVerifyTransaction(net::HttpTransaction* trans,
+ const MockTransaction& trans_info) {
+ std::string content;
+ int rv = ReadTransaction(trans, &content);
+
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_EQ(strlen(trans_info.data), content.size());
+ EXPECT_EQ(0, memcmp(trans_info.data, content.data(), content.size()));
+}
+
+void RunTransactionTest(net::HttpCache* cache,
+ const MockTransaction& trans_info) {
+ MockHttpRequest request(trans_info);
+ TestCompletionCallback callback;
+
+ // write to the cache
+
+ net::HttpTransaction* trans = cache->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response);
+
+ ReadAndVerifyTransaction(trans, trans_info);
+
+ trans->Destroy();
+}
+
+} // namespace
+
+
+//-----------------------------------------------------------------------------
+// tests
+
+
+TEST(HttpCache, CreateThenDestroy) {
+ MockHttpCache cache;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ trans->Destroy();
+}
+
+TEST(HttpCache, SimpleGET) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGETNoDiskCache) {
+ MockHttpCache cache;
+
+ cache.disk_cache()->set_fail_requests();
+
+ // Read from the network, and don't use the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Hit) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to read from the cache
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Miss) {
+ MockHttpCache cache;
+
+ // force this transaction to read from the cache
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ MockHttpRequest request(transaction);
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_CACHE_MISS, rv);
+
+ trans->Destroy();
+
+ EXPECT_EQ(0, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadPreferringCache_Hit) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to read from the cache if valid
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_PREFERRING_CACHE;
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadPreferringCache_Miss) {
+ MockHttpCache cache;
+
+ // force this transaction to read from the cache if valid
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_PREFERRING_CACHE;
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadBypassCache) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to write to the cache again
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_BYPASS_CACHE;
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadBypassCache_Implicit) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to write to the cache again
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "pragma: no-cache";
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadBypassCache_Implicit2) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to write to the cache again
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "cache-control: no-cache";
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadValidateCache) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // read from the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to validate the cache
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadValidateCache_Implicit) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // read from the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to validate the cache
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "cache-control: max-age=0";
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_ManyReaders) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ struct Context {
+ int result;
+ TestCompletionCallback callback;
+ net::HttpTransaction* trans;
+
+ Context(net::HttpTransaction* t) : result(net::ERR_IO_PENDING), trans(t) {
+ }
+ };
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 5;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ context_list.push_back(
+ new Context(cache.http_cache()->CreateTransaction()));
+
+ Context* c = context_list[i];
+ int rv = c->trans->Start(&request, &c->callback);
+ if (rv != net::ERR_IO_PENDING)
+ c->result = rv;
+ }
+
+ // the first request should be a writer at this point, and the subsequent
+ // requests should be pending.
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ if (c->result == net::ERR_IO_PENDING)
+ c->result = c->callback.WaitForResult();
+ ReadAndVerifyTransaction(c->trans, kSimpleGET_Transaction);
+ }
+
+ // we should not have had to re-open the disk entry
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ c->trans->Destroy();
+ delete c;
+ }
+}
+
+TEST(HttpCache, SimpleGET_ManyWriters_CancelFirst) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ struct Context {
+ int result;
+ TestCompletionCallback callback;
+ net::HttpTransaction* trans;
+
+ Context(net::HttpTransaction* t) : result(net::ERR_IO_PENDING), trans(t) {
+ }
+ };
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 2;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ context_list.push_back(
+ new Context(cache.http_cache()->CreateTransaction()));
+
+ Context* c = context_list[i];
+ int rv = c->trans->Start(&request, &c->callback);
+ if (rv != net::ERR_IO_PENDING)
+ c->result = rv;
+ }
+
+ // the first request should be a writer at this point, and the subsequent
+ // requests should be pending.
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ if (c->result == net::ERR_IO_PENDING)
+ c->result = c->callback.WaitForResult();
+ // destroy only the first transaction
+ if (i == 0) {
+ c->trans->Destroy();
+ delete c;
+ context_list[i] = NULL;
+ }
+ }
+
+ // complete the rest of the transactions
+ for (int i = 1; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ ReadAndVerifyTransaction(c->trans, kSimpleGET_Transaction);
+ }
+
+ // we should have had to re-open the disk entry
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ for (int i = 1; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ c->trans->Destroy();
+ delete c;
+ }
+}
+
+TEST(HttpCache, SimpleGET_AbandonedCacheRead) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::OK, rv);
+
+ char buf[256];
+ rv = trans->Read(buf, sizeof(buf), &callback);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Test that destroying the transaction while it is reading from the cache
+ // works properly.
+ trans->Destroy();
+
+ // Make sure we pump any pending events, which should include a call to
+ // HttpCache::Transaction::OnCacheReadCompleted.
+ MessageLoop::current()->Quit();
+ MessageLoop::current()->Run();
+}
+
+TEST(HttpCache, TypicalGET_ConditionalRequest) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kTypicalGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // get the same URL again, but this time we expect it to result
+ // in a conditional request.
+ RunTransactionTest(cache.http_cache(), kTypicalGET_Transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+static void ETagGet_ConditionalRequest_Handler(
+ const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_TRUE(request->extra_headers.find("If-None-Match") != std::string::npos);
+ response_status->assign("HTTP/1.1 304 Not Modified");
+ response_headers->assign(kETagGET_Transaction.response_headers);
+ response_data->clear();
+}
+
+TEST(HttpCache, ETagGET_ConditionalRequest_304) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kETagGET_Transaction);
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // get the same URL again, but this time we expect it to result
+ // in a conditional request.
+ transaction.load_flags = net::LOAD_VALIDATE_CACHE;
+ transaction.handler = ETagGet_ConditionalRequest_Handler;
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimplePOST_SkipsCache) {
+ MockHttpCache cache;
+
+ // Test that we skip the cache for POST requests. Eventually, we will want
+ // to cache these, but we'll still have cases where skipping the cache makes
+ // sense, so we want to make sure that it works properly.
+
+ RunTransactionTest(cache.http_cache(), kSimplePOST_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimplePOST_LoadOnlyFromCache_Miss) {
+ MockHttpCache cache;
+
+ // Test that we skip the cache for POST requests. Eventually, we will want
+ // to cache these, but we'll still have cases where skipping the cache makes
+ // sense, so we want to make sure that it works properly.
+
+ MockTransaction transaction(kSimplePOST_Transaction);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ MockHttpRequest request(transaction);
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_CACHE_MISS, rv);
+
+ trans->Destroy();
+
+ EXPECT_EQ(0, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, RangeGET_SkipsCache) {
+ MockHttpCache cache;
+
+ // Test that we skip the cache for POST requests. Eventually, we will want
+ // to cache these, but we'll still have cases where skipping the cache makes
+ // sense, so we want to make sure that it works properly.
+
+ RunTransactionTest(cache.http_cache(), kRangeGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "If-None-Match: foo";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ transaction.request_headers = "If-Modified-Since: Wed, 28 Nov 2007 00:45:20 GMT";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SyncRead) {
+ MockHttpCache cache;
+
+ // This test ensures that a read that completes synchronously does not cause
+ // any problems.
+
+ ScopedMockTransaction transaction(kSimpleGET_Transaction);
+ transaction.test_mode |= (TEST_MODE_SYNC_CACHE_START |
+ TEST_MODE_SYNC_CACHE_READ);
+
+ MockHttpRequest r1(transaction),
+ r2(transaction),
+ r3(transaction);
+
+ TestTransactionConsumer c1(cache.http_cache()),
+ c2(cache.http_cache()),
+ c3(cache.http_cache());
+
+ c1.Start(&r1);
+
+ r2.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ c2.Start(&r2);
+
+ r3.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ c3.Start(&r3);
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(c1.is_done());
+ EXPECT_TRUE(c2.is_done());
+ EXPECT_TRUE(c3.is_done());
+
+ EXPECT_EQ(net::OK, c1.error());
+ EXPECT_EQ(net::OK, c2.error());
+ EXPECT_EQ(net::OK, c3.error());
+}
+
+TEST(HttpCache, ValidationResultsIn200) {
+ MockHttpCache cache;
+
+ // This test ensures that a conditional request, which results in a 200
+ // instead of a 304, properly truncates the existing response data.
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kETagGET_Transaction);
+
+ // force this transaction to validate the cache
+ MockTransaction transaction(kETagGET_Transaction);
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // read from the cache
+ RunTransactionTest(cache.http_cache(), kETagGET_Transaction);
+}
+
+TEST(HttpCache, CachedRedirect) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction kTestTransaction(kSimpleGET_Transaction);
+ kTestTransaction.status = "HTTP/1.1 301 Moved Permanently";
+ kTestTransaction.response_headers = "Location: http://www.bar.com/\n";
+
+ MockHttpRequest request(kTestTransaction);
+ TestCompletionCallback callback;
+
+ // write to the cache
+ {
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* info = trans->GetResponseInfo();
+ ASSERT_TRUE(info);
+
+ EXPECT_EQ(info->headers->response_code(), 301);
+
+ std::string location;
+ info->headers->EnumerateHeader(NULL, "Location", &location);
+ EXPECT_EQ(location, "http://www.bar.com/");
+
+ // now, destroy the transaction without actually reading the response body.
+ // we want to test that it is still getting cached.
+ trans->Destroy();
+ }
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // read from the cache
+ {
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* info = trans->GetResponseInfo();
+ ASSERT_TRUE(info);
+
+ EXPECT_EQ(info->headers->response_code(), 301);
+
+ std::string location;
+ info->headers->EnumerateHeader(NULL, "Location", &location);
+ EXPECT_EQ(location, "http://www.bar.com/");
+
+ // now, destroy the transaction without actually reading the response body.
+ // we want to test that it is still getting cached.
+ trans->Destroy();
+ }
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, CacheControlNoStore) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kSimpleGET_Transaction);
+ transaction.response_headers = "cache-control: no-store\n";
+
+ // initial load
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // try loading again; it should result in a network fetch
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ disk_cache::Entry* entry;
+ bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry);
+ EXPECT_FALSE(exists);
+}
+
+TEST(HttpCache, CacheControlNoStore2) {
+ // this test is similar to the above test, except that the initial response
+ // is cachable, but when it is validated, no-store is received causing the
+ // cached document to be deleted.
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kETagGET_Transaction);
+
+ // initial load
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // try loading again; it should result in a network fetch
+ transaction.load_flags = net::LOAD_VALIDATE_CACHE;
+ transaction.response_headers = "cache-control: no-store\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ disk_cache::Entry* entry;
+ bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry);
+ EXPECT_FALSE(exists);
+}
+
+TEST(HttpCache, CacheControlNoStore3) {
+ // this test is similar to the above test, except that the response is a 304
+ // instead of a 200. this should never happen in practice, but it seems like
+ // a good thing to verify that we still destroy the cache entry.
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kETagGET_Transaction);
+
+ // initial load
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // try loading again; it should result in a network fetch
+ transaction.load_flags = net::LOAD_VALIDATE_CACHE;
+ transaction.response_headers = "cache-control: no-store\n";
+ transaction.status = "HTTP/1.1 304 Not Modified";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ disk_cache::Entry* entry;
+ bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry);
+ EXPECT_FALSE(exists);
+}
+
+// Ensure that we don't cache requests served over bad HTTPS.
+TEST(HttpCache, SimpleGET_SSLError) {
+ MockHttpCache cache;
+
+ MockTransaction transaction = kSimpleGET_Transaction;
+ transaction.cert_status = net::CERT_STATUS_REVOKED;
+ ScopedMockTransaction scoped_transaction(transaction);
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // Test that it was not cached.
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ MockHttpRequest request(transaction);
+ TestCompletionCallback callback;
+
+ net::HttpTransaction* trans = cache.http_cache()->CreateTransaction();
+ ASSERT_TRUE(trans);
+
+ int rv = trans->Start(&request, &callback);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_CACHE_MISS, rv);
+
+ trans->Destroy();
+}