blob: 6792e647923cfa68350edec2088bc797812e76b0 [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: morlovich@google.com (Maksim Orlovich)
#include "net/instaweb/rewriter/public/cacheable_resource_base.h"
#include "net/instaweb/http/public/counting_url_async_fetcher.h"
#include "net/instaweb/http/public/http_cache.h"
#include "net/instaweb/http/public/http_cache_failure.h"
#include "net/instaweb/http/public/http_value.h"
#include "net/instaweb/http/public/mock_url_fetcher.h"
#include "net/instaweb/http/public/request_context.h"
#include "net/instaweb/rewriter/cached_result.pb.h"
#include "net/instaweb/rewriter/public/mock_resource_callback.h"
#include "net/instaweb/rewriter/public/resource.h" // for Resource, etc
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_message_handler.h"
#include "pagespeed/kernel/base/ref_counted_ptr.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/string.h" // for GoogleString
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/cache/lru_cache.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/request_headers.h"
#include "pagespeed/kernel/http/response_headers.h"
namespace net_instaweb {
namespace {
const char kTestUrl[] = "http://www.example.com/";
const char kTestRef[] = "http://www.example.com/some_page";
const char kContent[] = "content!";
class TestResource : public CacheableResourceBase {
public:
explicit TestResource(RewriteDriver* rewrite_driver)
: CacheableResourceBase("test", kTestUrl, kTestUrl, NULL, rewrite_driver),
cache_key_(kTestUrl),
do_prepare_request_(false),
do_prepare_response_(false) {
}
static void InitStats(Statistics* stats) {
CacheableResourceBase::InitStats("test", stats);
}
virtual GoogleString cache_key() const { return cache_key_; }
void set_cache_key(StringPiece ck) {
ck.CopyToString(&cache_key_);
}
virtual void PrepareRequest(const RequestContextPtr& request_context,
RequestHeaders* headers) {
if (do_prepare_request_) {
// To test that this gets invoked properly, we set the referer header
// since MockUrlFetcher records those.
headers->Replace(HttpAttributes::kReferer, kTestRef);
request_context->AddSessionAuthorizedFetchOrigin(kTestUrl);
}
}
virtual void PrepareResponseHeaders(ResponseHeaders* headers) {
if (do_prepare_response_) {
headers->Remove(HttpAttributes::kCacheControl, "private");
}
}
void set_do_prepare_request(bool x) {
do_prepare_request_ = x;
}
void set_do_prepare_response(bool x) {
do_prepare_response_ = x;
}
// Wipe any loaded values, but not the configuration.
void Reset() {
HTTPValue empty_value;
// Don't want it to totally empty, or it won't get set.
empty_value.Write("", server_context()->message_handler());
Link(&empty_value, server_context()->message_handler());
LinkFallbackValue(&empty_value);
}
protected:
GoogleString cache_key_;
bool do_prepare_request_;
bool do_prepare_response_;
};
class MockFreshenCallback : public Resource::FreshenCallback {
public:
MockFreshenCallback(const ResourcePtr& resource,
InputInfo* input_info)
: FreshenCallback(resource),
input_info_(input_info),
done_(false),
extend_success_(false) {
}
virtual InputInfo* input_info() { return input_info_; }
virtual void Done(bool lock_failure, bool extend_success) {
done_ = true;
extend_success_ = extend_success;
}
bool done() const { return done_; }
bool extend_success() const { return extend_success_; }
private:
InputInfo* input_info_;
bool done_;
bool extend_success_;
};
} // namespace
class CacheableResourceBaseTest : public RewriteTestBase {
protected:
virtual void SetUp() {
RewriteTestBase::SetUp();
TestResource::InitStats(server_context()->statistics());
resource_.reset(new TestResource(rewrite_driver()));
}
void CheckStats(TestResource* resource,
int expect_hits,
int expect_recent_fetch_failures,
int expect_recent_uncacheables_miss,
int expect_recent_uncacheables_failure,
int expect_misses) {
EXPECT_EQ(expect_hits,
resource->hits_->Get());
EXPECT_EQ(expect_recent_fetch_failures,
resource->recent_fetch_failures_->Get());
EXPECT_EQ(expect_recent_uncacheables_miss,
resource->recent_uncacheables_miss_->Get());
EXPECT_EQ(expect_recent_uncacheables_failure,
resource->recent_uncacheables_failure_->Get());
EXPECT_EQ(expect_misses,
resource->misses_->Get());
}
RefCountedPtr<TestResource> resource_;
};
TEST_F(CacheableResourceBaseTest, BasicCached) {
SetResponseWithDefaultHeaders(kTestUrl, kContentTypeText,
kContent, 1000);
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback);
EXPECT_TRUE(callback.done());
EXPECT_TRUE(callback.success());
EXPECT_EQ(kContent, resource_->ExtractUncompressedContents());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 0, 0, 0, 0, 1);
// 2nd read should be cached.
resource_->Reset();
MockResourceCallback callback2(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback2);
EXPECT_TRUE(callback2.done());
EXPECT_TRUE(callback2.success());
EXPECT_EQ(kContent, resource_->ExtractUncompressedContents());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 1, 0, 0, 0, 1);
// Make sure freshening happens. The rest resource is set to 1000 sec ttl,
// so forward time 900 seconds ahead.
AdvanceTimeMs(900 * Timer::kSecondMs);
resource_->Reset();
MockResourceCallback callback3(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback3);
EXPECT_TRUE(callback3.done());
EXPECT_TRUE(callback3.success());
EXPECT_EQ(kContent, resource_->ExtractUncompressedContents());
// Freshening resulted in an extra fetch
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 2, 0, 0, 0, 1);
}
TEST_F(CacheableResourceBaseTest, Fallback) {
options()->set_serve_stale_if_fetch_error(true);
mock_url_fetcher()->Disable();
mock_url_fetcher()->set_error_message("oww!");
ResponseHeaders ok_response_headers;
SetDefaultLongCacheHeaders(&kContentTypeText, &ok_response_headers);
HTTPValue fallback;
fallback.SetHeaders(&ok_response_headers);
EXPECT_TRUE(fallback.Write("fallback", server_context()->message_handler()));
EXPECT_TRUE(fallback.Flush(server_context()->message_handler()));
resource_->LinkFallbackValue(&fallback);
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback);
EXPECT_TRUE(callback.done());
EXPECT_TRUE(callback.success());
EXPECT_STREQ("fallback", resource_->ExtractUncompressedContents());
EXPECT_EQ(kFetchStatusOtherError, resource_->fetch_response_status());
}
TEST_F(CacheableResourceBaseTest, FallbackEmpty) {
// Regression test. This used to CHECK-fail in some intermediate revisions.
options()->set_serve_stale_if_fetch_error(true);
mock_url_fetcher()->Disable();
mock_url_fetcher()->set_error_message("oww!");
ResponseHeaders ok_response_headers;
SetDefaultLongCacheHeaders(&kContentTypeText, &ok_response_headers);
HTTPValue fallback;
fallback.SetHeaders(&ok_response_headers);
EXPECT_TRUE(fallback.Write("", server_context()->message_handler()));
EXPECT_TRUE(fallback.Flush(server_context()->message_handler()));
resource_->LinkFallbackValue(&fallback);
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback);
EXPECT_TRUE(callback.done());
EXPECT_TRUE(callback.success());
EXPECT_STREQ("", resource_->ExtractUncompressedContents());
EXPECT_EQ(kFetchStatusOtherError, resource_->fetch_response_status());
}
TEST_F(CacheableResourceBaseTest, Private) {
ResponseHeaders response_headers;
SetDefaultLongCacheHeaders(&kContentTypeText, &response_headers);
response_headers.Add(HttpAttributes::kCacheControl, "private");
SetFetchResponse(kTestUrl, response_headers, kContent);
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback);
EXPECT_TRUE(callback.done());
EXPECT_FALSE(callback.success());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 0, 0, 0, 0, 1);
// The non-cacheability should be cached.
resource_->Reset();
MockResourceCallback callback2(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback2);
EXPECT_TRUE(callback2.done());
EXPECT_FALSE(callback2.success());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 0, 0, 0, 1, 1);
}
TEST_F(CacheableResourceBaseTest, PrivateForFetch) {
// This test private + kLoadEvenIfNotCacheable.
ResponseHeaders response_headers;
SetDefaultLongCacheHeaders(&kContentTypeText, &response_headers);
response_headers.Add(HttpAttributes::kCacheControl, "private");
SetFetchResponse(kTestUrl, response_headers, kContent);
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kLoadEvenIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback);
EXPECT_TRUE(callback.done());
EXPECT_TRUE(callback.success());
EXPECT_EQ(kContent, resource_->ExtractUncompressedContents());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 0, 0, 0, 0, 1);
// Since it's non-cacheable, but we have kLoadEvenIfNotCacheable
// set, we should re-fetch it.
resource_->Reset();
MockResourceCallback callback2(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kLoadEvenIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback2);
EXPECT_TRUE(callback2.done());
EXPECT_TRUE(callback2.success());
EXPECT_EQ(kContent, resource_->ExtractUncompressedContents());
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 0, 0, 1, 0, 1);
}
TEST_F(CacheableResourceBaseTest, FetchFailure) {
SetFetchFailOnUnexpected(false);
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kLoadEvenIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback);
EXPECT_TRUE(callback.done());
EXPECT_FALSE(callback.success());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 0, 0, 0, 0, 1);
// Failure should get cached, and we should take advantage of it.
resource_->Reset();
MockResourceCallback callback2(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kLoadEvenIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback2);
EXPECT_TRUE(callback2.done());
EXPECT_FALSE(callback2.success());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 0, 1, 0, 0, 1);
// Now advance time, should force a refetch.
int64 remember_sec = server_context()->http_cache()->failure_caching_ttl_sec(
kFetchStatusOtherError);
AdvanceTimeMs(2 * remember_sec * Timer::kSecondMs);
resource_->Reset();
MockResourceCallback callback3(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kLoadEvenIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback3);
EXPECT_TRUE(callback3.done());
EXPECT_FALSE(callback3.success());
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
CheckStats(resource_.get(), 0, 1, 0, 0, 2);
}
TEST_F(CacheableResourceBaseTest, DropHandling) {
// First have mock fetcher emulate a drop.
ResponseHeaders not_found;
SetDefaultLongCacheHeaders(&kContentTypeHtml, &not_found);
not_found.SetStatusAndReason(HttpStatus::kUnavailable);
not_found.Add(HttpAttributes::kXPsaLoadShed, "1");
SetFetchResponse("http://www.example.com/", not_found, "hmm");
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kLoadEvenIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback);
EXPECT_TRUE(callback.done());
EXPECT_FALSE(resource_->IsValidAndCacheable());
resource_->Reset();
// Now have the fetch result in a 200.
ResponseHeaders found;
SetDefaultLongCacheHeaders(&kContentTypeCss, &found);
found.SetStatusAndReason(HttpStatus::kOK);
SetFetchResponse("http://www.example.com/", found, "* {display: stylish; }");
// Still cached since background fetch.
MockResourceCallback callback2(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kLoadEvenIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback2);
EXPECT_TRUE(callback2.done());
EXPECT_FALSE(resource_->IsValidAndCacheable());
resource_->Reset();
// Marked as a non-background, will retry.
resource_->set_is_background_fetch(false);
MockResourceCallback callback3(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kLoadEvenIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback3);
EXPECT_TRUE(callback3.done());
EXPECT_TRUE(resource_->IsValidAndCacheable());
}
TEST_F(CacheableResourceBaseTest, FreshenInfo) {
SetResponseWithDefaultHeaders(kTestUrl, kContentTypeText,
kContent, 1000);
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback);
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
EXPECT_TRUE(callback.done());
EXPECT_TRUE(callback.success());
InputInfo input_info;
resource_->FillInPartitionInputInfo(Resource::kIncludeInputHash,
&input_info);
InputInfo input_info2 = input_info;
// Move time ahead so freshening actually does something.
AdvanceTimeMs(900 * Timer::kSecondMs);
MockFreshenCallback freshen_cb(ResourcePtr(resource_.get()), &input_info);
resource_->Freshen(&freshen_cb, message_handler());
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
EXPECT_TRUE(freshen_cb.done());
EXPECT_TRUE(freshen_cb.extend_success());
// Expiration time must have moved ahead, too.
EXPECT_EQ(1000 * Timer::kSecondMs + timer()->NowMs(),
input_info.expiration_time_ms());
// The above freshened from fetches, now we should be able to do it
// from cache as well.
MockFreshenCallback freshen_cb2(ResourcePtr(resource_.get()), &input_info2);
resource_->Freshen(&freshen_cb2, message_handler());
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
EXPECT_TRUE(freshen_cb.done());
EXPECT_TRUE(freshen_cb.extend_success());
EXPECT_EQ(1000 * Timer::kSecondMs + timer()->NowMs(),
input_info2.expiration_time_ms());
}
TEST_F(CacheableResourceBaseTest, SameUrlDifferentKey) {
SetResponseWithDefaultHeaders(kTestUrl, kContentTypeText,
kContent, 1000);
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback);
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
EXPECT_TRUE(callback.done());
EXPECT_TRUE(callback.success());
RefCountedPtr<TestResource> resource2(new TestResource(rewrite_driver()));
resource2->set_cache_key("http://other_key.org/");
MockResourceCallback callback2(ResourcePtr(resource2.get()),
server_context()->thread_system());
resource2->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback2);
// 2 fetches, since we are using different keys.
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
EXPECT_TRUE(callback2.done());
EXPECT_TRUE(callback2.success());
// Both should be cached successfully now.
resource_->Reset();
MockResourceCallback callback3(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback3);
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
EXPECT_TRUE(callback3.done());
EXPECT_TRUE(callback3.success());
// Delete resource's cache entry to make sure that #2 is really using
// something different.
lru_cache()->Delete(HttpCacheKey(kTestUrl));
// Re-fetch r1, to make sure the delete worked
resource_->Reset();
MockResourceCallback callback4(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback4);
EXPECT_EQ(3, counting_url_async_fetcher()->fetch_count());
EXPECT_TRUE(callback4.done());
EXPECT_TRUE(callback4.success());
// Now fetch r2, should still be hashed.
resource2->Reset();
MockResourceCallback callback5(ResourcePtr(resource2.get()),
server_context()->thread_system());
resource2->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback5);
EXPECT_EQ(3, counting_url_async_fetcher()->fetch_count());
EXPECT_TRUE(callback.done());
EXPECT_TRUE(callback.success());
// Now test freshening, with both ops happening simultaneously to make
// sure the locking is correct.
SetupWaitFetcher();
InputInfo input_info, input2_info;
resource_->FillInPartitionInputInfo(Resource::kIncludeInputHash,
&input_info);
resource2->FillInPartitionInputInfo(Resource::kIncludeInputHash,
&input2_info);
AdvanceTimeMs(900 * Timer::kSecondMs);
MockFreshenCallback freshen_cb(ResourcePtr(resource_.get()), &input_info);
resource_->Freshen(&freshen_cb, message_handler());
MockFreshenCallback freshen2_cb(ResourcePtr(resource2.get()), &input2_info);
resource2->Freshen(&freshen2_cb, message_handler());
CallFetcherCallbacks();
EXPECT_EQ(5, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(1000 * Timer::kSecondMs + timer()->NowMs(),
input_info.expiration_time_ms());
EXPECT_EQ(1000 * Timer::kSecondMs + timer()->NowMs(),
input2_info.expiration_time_ms());
}
TEST_F(CacheableResourceBaseTest, PrepareHooks) {
// Test to see that PrepareRequest works.
SetResponseWithDefaultHeaders(kTestUrl, kContentTypeText,
kContent, 1000);
MockResourceCallback callback(ResourcePtr(resource_.get()),
server_context()->thread_system());
RequestContextPtr request_context(
RequestContext::NewTestRequestContext(server_context()->thread_system()));
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
request_context, &callback);
EXPECT_TRUE(callback.done());
EXPECT_TRUE(callback.success());
EXPECT_EQ(kContent, resource_->ExtractUncompressedContents());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
// w/o the hook
EXPECT_EQ("", mock_url_fetcher()->last_referer());
EXPECT_FALSE(request_context->IsSessionAuthorizedFetchOrigin(kTestUrl));
// now turn the hook on.
resource_->Reset();
resource_->set_do_prepare_request(true);
lru_cache()->Delete(HttpCacheKey(kTestUrl));
MockResourceCallback callback2(ResourcePtr(resource_.get()),
server_context()->thread_system());
RequestContextPtr request_context2(
RequestContext::NewTestRequestContext(server_context()->thread_system()));
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
request_context2, &callback2);
EXPECT_TRUE(callback2.done());
EXPECT_TRUE(callback2.success());
EXPECT_EQ(kContent, resource_->ExtractUncompressedContents());
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
// the PrepareRequest() hook should have changed the referrer.
EXPECT_EQ(kTestRef, mock_url_fetcher()->last_referer());
// ... And authorized a domain
EXPECT_TRUE(request_context2->IsSessionAuthorizedFetchOrigin(kTestUrl));
// Now test with and without PrepareResponseHeaders
// Set up a private resource.
ResponseHeaders response_headers;
SetDefaultLongCacheHeaders(&kContentTypeText, &response_headers);
response_headers.Add(HttpAttributes::kCacheControl, "private");
SetFetchResponse(kTestUrl, response_headers, kContent);
MockResourceCallback callback3(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->Reset();
lru_cache()->Delete(HttpCacheKey(kTestUrl));
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback3);
EXPECT_TRUE(callback3.done());
// By default, fetch fails.
EXPECT_FALSE(callback3.success());
EXPECT_EQ(3, counting_url_async_fetcher()->fetch_count());
// Note: can't check response_headers here since resource wasn't loaded.
// Now try with a PrepareResponseHeaders removing cache-control: private
lru_cache()->Delete(HttpCacheKey(kTestUrl));
resource_->set_do_prepare_response(true);
MockResourceCallback callback4(ResourcePtr(resource_.get()),
server_context()->thread_system());
resource_->Reset();
resource_->LoadAsync(Resource::kReportFailureIfNotCacheable,
RequestContext::NewTestRequestContext(
server_context()->thread_system()),
&callback4);
EXPECT_TRUE(callback4.done());
EXPECT_TRUE(callback4.success()); // due to messing with headers.
EXPECT_FALSE(resource_->response_headers()->HasValue(
HttpAttributes::kCacheControl, "private"));
EXPECT_EQ(4, counting_url_async_fetcher()->fetch_count());
}
} // namespace net_instaweb