Index: content/browser/loader/async_revalidation_driver_unittest.cc |
diff --git a/content/browser/loader/async_revalidation_driver_unittest.cc b/content/browser/loader/async_revalidation_driver_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1f5a7e4b1f0d3bc488b1c31d4b226c6c6a554328 |
--- /dev/null |
+++ b/content/browser/loader/async_revalidation_driver_unittest.cc |
@@ -0,0 +1,397 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "content/browser/loader/async_revalidation_driver.h" |
+ |
+#include <string> |
+#include <type_traits> |
+#include <utility> |
+#include <vector> |
+ |
+#include "base/bind.h" |
+#include "base/callback.h" |
+#include "base/location.h" |
+#include "base/macros.h" |
+#include "base/run_loop.h" |
+#include "content/public/browser/client_certificate_delegate.h" |
+#include "content/public/common/content_client.h" |
+#include "content/public/test/test_browser_thread_bundle.h" |
+#include "content/test/test_content_browser_client.h" |
+#include "ipc/ipc_message.h" |
+#include "net/base/net_errors.h" |
+#include "net/base/network_delegate_impl.h" |
+#include "net/base/request_priority.h" |
+#include "net/ssl/ssl_cert_request_info.h" |
+#include "net/url_request/url_request_job_factory.h" |
+#include "net/url_request/url_request_job_factory_impl.h" |
+#include "net/url_request/url_request_status.h" |
+#include "net/url_request/url_request_test_job.h" |
+#include "net/url_request/url_request_test_util.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace content { |
+namespace { |
+ |
+// Dummy implementation of ResourceThrottle, an instance of which is needed to |
+// initialize AsyncRevalidationDriver. |
+class ResourceThrottleStub : public ResourceThrottle { |
+ public: |
+ ResourceThrottleStub() {} |
+ |
+ // If true, defers the request in WillStartRequest. |
+ void set_defer_request_on_will_start_request( |
+ bool defer_request_on_will_start_request) { |
+ defer_request_on_will_start_request_ = defer_request_on_will_start_request; |
+ } |
+ |
+ // ResourceThrottler implementation: |
+ void WillStartRequest(bool* defer) override { |
+ *defer = defer_request_on_will_start_request_; |
+ } |
+ |
+ const char* GetNameForLogging() const override { |
+ return "ResourceThrottleStub"; |
+ } |
+ |
+ private: |
+ bool defer_request_on_will_start_request_ = false; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ResourceThrottleStub); |
+}; |
+ |
+// There are multiple layers of boilerplate needed to use a URLRequestTestJob |
+// subclass. Subclasses of AsyncRevalidationDriverTest can use |
+// BindCreateProtocolHandlerCallback() to bypass most of that boilerplate. |
+using CreateProtocolHandlerCallback = |
+ base::Callback<scoped_ptr<net::URLRequestJobFactory::ProtocolHandler>()>; |
+ |
+template <typename T> |
+CreateProtocolHandlerCallback BindCreateProtocolHandlerCallback() { |
+ static_assert(std::is_base_of<net::URLRequestJob, T>::value, |
+ "Template argument to BindCreateProtocolHandlerCallback() must " |
+ "be a subclass of URLRequestJob."); |
+ |
+ class TemplatedProtocolHandler |
+ : public net::URLRequestJobFactory::ProtocolHandler { |
+ public: |
+ static scoped_ptr<net::URLRequestJobFactory::ProtocolHandler> Create() { |
+ return make_scoped_ptr(new TemplatedProtocolHandler()); |
+ } |
+ |
+ // URLRequestJobFactory::ProtocolHandler implementation: |
+ net::URLRequestJob* MaybeCreateJob( |
+ net::URLRequest* request, |
+ net::NetworkDelegate* network_delegate) const override { |
+ return new T(request, network_delegate); |
+ } |
+ }; |
+ |
+ return base::Bind(&TemplatedProtocolHandler::Create); |
+} |
+ |
+// An implementation of NetworkDelegate that captures the status of the last |
+// URLRequest to be destroyed. |
+class StatusCapturingNetworkDelegate : public net::NetworkDelegateImpl { |
+ public: |
+ const net::URLRequestStatus& last_status() { return last_status_; } |
+ |
+ private: |
+ // net::NetworkDelegate implementation. |
+ void OnURLRequestDestroyed(net::URLRequest* request) override { |
+ last_status_ = request->status(); |
+ } |
+ |
+ net::URLRequestStatus last_status_; |
+}; |
+ |
+class AsyncRevalidationDriverTest : public testing::Test { |
+ protected: |
+ // Constructor for test fixtures that subclass this one. |
+ AsyncRevalidationDriverTest( |
+ const CreateProtocolHandlerCallback& create_protocol_handler_callback) |
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), |
+ create_protocol_handler_callback_(create_protocol_handler_callback), |
+ raw_ptr_resource_throttle_(nullptr), |
+ raw_ptr_request_(nullptr) { |
+ test_url_request_context_.set_job_factory(&job_factory_); |
+ test_url_request_context_.set_network_delegate(&network_delegate_); |
+ } |
+ |
+ // Constructor for tests that use this fixture directly. |
+ AsyncRevalidationDriverTest() |
+ : AsyncRevalidationDriverTest( |
+ base::Bind(net::URLRequestTestJob::CreateProtocolHandler)) {} |
+ |
+ bool async_revalidation_complete_called() const { |
+ return async_revalidation_complete_called_; |
+ } |
+ |
+ const net::URLRequestStatus& last_status() { |
+ return network_delegate_.last_status(); |
+ } |
+ |
+ void SetUpAsyncRevalidationDriverWithRequestToUrl(const GURL& url) { |
+ scoped_ptr<net::URLRequest> request(test_url_request_context_.CreateRequest( |
+ url, net::DEFAULT_PRIORITY, nullptr /* delegate */)); |
+ raw_ptr_request_ = request.get(); |
+ raw_ptr_resource_throttle_ = new ResourceThrottleStub(); |
+ // This use of base::Unretained() is safe because |driver_|, and the closure |
+ // passed to it, will be destroyed before this object is. |
+ driver_.reset(new AsyncRevalidationDriver( |
+ std::move(request), make_scoped_ptr(raw_ptr_resource_throttle_), |
+ base::Bind(&AsyncRevalidationDriverTest::OnAsyncRevalidationComplete, |
+ base::Unretained(this)))); |
+ } |
+ |
+ void SetUpAsyncRevalidationDriverWithDefaultRequest() { |
+ SetUpAsyncRevalidationDriverWithRequestToUrl( |
+ net::URLRequestTestJob::test_url_1()); |
+ } |
+ |
+ void SetUp() override { |
+ job_factory_.SetProtocolHandler("test", |
+ create_protocol_handler_callback_.Run()); |
+ SetUpAsyncRevalidationDriverWithDefaultRequest(); |
+ } |
+ |
+ void OnAsyncRevalidationComplete() { |
+ EXPECT_FALSE(async_revalidation_complete_called_); |
+ async_revalidation_complete_called_ = true; |
+ driver_.reset(); |
+ } |
+ |
+ TestBrowserThreadBundle thread_bundle_; |
+ net::URLRequestJobFactoryImpl job_factory_; |
+ net::TestURLRequestContext test_url_request_context_; |
+ StatusCapturingNetworkDelegate network_delegate_; |
+ CreateProtocolHandlerCallback create_protocol_handler_callback_; |
+ |
+ // The AsyncRevalidationDriver owns the URLRequest and the ResourceThrottle. |
+ ResourceThrottleStub* raw_ptr_resource_throttle_; |
+ net::URLRequest* raw_ptr_request_; |
+ scoped_ptr<AsyncRevalidationDriver> driver_; |
+ bool async_revalidation_complete_called_ = false; |
+}; |
+ |
+TEST_F(AsyncRevalidationDriverTest, NormalRequestCompletes) { |
+ driver_->StartRequest(); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_TRUE(async_revalidation_complete_called()); |
+} |
+ |
+// Verifies that request that should be deferred at start is deferred. |
+TEST_F(AsyncRevalidationDriverTest, DeferOnStart) { |
+ raw_ptr_resource_throttle_->set_defer_request_on_will_start_request(true); |
+ |
+ driver_->StartRequest(); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_FALSE(raw_ptr_request_->is_pending()); |
+ EXPECT_FALSE(async_revalidation_complete_called()); |
+} |
+ |
+// Verifies that resuming a deferred request works. Assumes that DeferOnStart |
+// passes. |
+TEST_F(AsyncRevalidationDriverTest, ResumeDeferredRequestWorks) { |
+ raw_ptr_resource_throttle_->set_defer_request_on_will_start_request(true); |
+ |
+ driver_->StartRequest(); |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ ResourceController* driver_as_resource_controller = driver_.get(); |
+ driver_as_resource_controller->Resume(); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_TRUE(async_revalidation_complete_called()); |
+} |
+ |
+// Verifies that redirects are not followed. |
+TEST_F(AsyncRevalidationDriverTest, RedirectsAreNotFollowed) { |
+ SetUpAsyncRevalidationDriverWithRequestToUrl( |
+ net::URLRequestTestJob::test_url_redirect_to_url_2()); |
+ |
+ driver_->StartRequest(); |
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) |
+ base::RunLoop().RunUntilIdle(); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_FALSE(last_status().is_success()); |
+ EXPECT_EQ(net::ERR_ABORTED, last_status().error()); |
+ EXPECT_TRUE(async_revalidation_complete_called()); |
+} |
+ |
+// A mock URLRequestJob which simulates an SSL client auth request. |
+class MockClientCertURLRequestJob : public net::URLRequestTestJob { |
+ public: |
+ MockClientCertURLRequestJob(net::URLRequest* request, |
+ net::NetworkDelegate* network_delegate) |
+ : net::URLRequestTestJob(request, network_delegate, true), |
+ weak_factory_(this) {} |
+ |
+ // net::URLRequestTestJob implementation: |
+ void Start() override { |
+ scoped_refptr<net::SSLCertRequestInfo> cert_request_info( |
+ new net::SSLCertRequestInfo); |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MockClientCertURLRequestJob::NotifyCertificateRequested, |
+ weak_factory_.GetWeakPtr(), cert_request_info)); |
+ } |
+ |
+ void ContinueWithCertificate( |
+ net::X509Certificate* cert, |
+ net::SSLPrivateKey* client_private_key) override { |
+ ADD_FAILURE() << "Certificate supplied."; |
+ } |
+ |
+ void Kill() override { |
+ weak_factory_.InvalidateWeakPtrs(); |
+ URLRequestJob::Kill(); |
+ } |
+ |
+ private: |
+ base::WeakPtrFactory<MockClientCertURLRequestJob> weak_factory_; |
+}; |
+ |
+class AsyncRevalidationDriverClientCertTest |
+ : public AsyncRevalidationDriverTest { |
+ protected: |
+ AsyncRevalidationDriverClientCertTest() |
+ : AsyncRevalidationDriverTest( |
+ BindCreateProtocolHandlerCallback<MockClientCertURLRequestJob>()) {} |
+}; |
+ |
+// Test browser client that causes the test to fail if SelectClientCertificate() |
+// is called. Automatically sets itself as the browser client when constructed |
+// and restores the old browser client in the destructor. |
+class ScopedDontSelectCertificateBrowserClient |
+ : public TestContentBrowserClient { |
+ public: |
+ ScopedDontSelectCertificateBrowserClient() { |
+ old_client_ = SetBrowserClientForTesting(this); |
+ } |
+ |
+ ~ScopedDontSelectCertificateBrowserClient() override { |
+ SetBrowserClientForTesting(old_client_); |
+ } |
+ |
+ void SelectClientCertificate( |
+ WebContents* web_contents, |
+ net::SSLCertRequestInfo* cert_request_info, |
+ scoped_ptr<ClientCertificateDelegate> delegate) override { |
+ ADD_FAILURE() << "SelectClientCertificate was called."; |
+ } |
+ |
+ private: |
+ ContentBrowserClient* old_client_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ScopedDontSelectCertificateBrowserClient); |
+}; |
+ |
+// Verifies that async revalidation requests do not attempt to provide client |
+// certificates. |
+TEST_F(AsyncRevalidationDriverClientCertTest, RequestRejected) { |
+ // Ensure that SelectClientCertificate is not called during this test. |
+ ScopedDontSelectCertificateBrowserClient test_client; |
+ |
+ // Start the request and wait for it to pause. |
+ driver_->StartRequest(); |
+ |
+ // Because TestBrowserThreadBundle only uses one real thread, this is |
+ // sufficient to ensure that tasks posted to the "UI thread" have run. |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ // Check that the request aborted. |
+ EXPECT_FALSE(last_status().is_success()); |
+ EXPECT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, last_status().error()); |
+ EXPECT_TRUE(async_revalidation_complete_called()); |
+} |
+ |
+// A mock URLRequestJob which simulates an SSL certificate error. |
+class MockSSLErrorURLRequestJob : public net::URLRequestTestJob { |
+ public: |
+ MockSSLErrorURLRequestJob(net::URLRequest* request, |
+ net::NetworkDelegate* network_delegate) |
+ : net::URLRequestTestJob(request, network_delegate, true), |
+ weak_factory_(this) {} |
+ |
+ // net::URLRequestTestJob implementation: |
+ void Start() override { |
+ // This SSLInfo isn't really valid, but it is good enough for testing. |
+ net::SSLInfo ssl_info; |
+ ssl_info.SetCertError(net::ERR_CERT_DATE_INVALID); |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MockSSLErrorURLRequestJob::NotifySSLCertificateError, |
+ weak_factory_.GetWeakPtr(), ssl_info, false)); |
+ } |
+ |
+ void ContinueDespiteLastError() override { |
+ ADD_FAILURE() << "ContinueDespiteLastError called."; |
+ } |
+ |
+ private: |
+ base::WeakPtrFactory<MockSSLErrorURLRequestJob> weak_factory_; |
+}; |
+ |
+class AsyncRevalidationDriverSSLErrorTest : public AsyncRevalidationDriverTest { |
+ protected: |
+ AsyncRevalidationDriverSSLErrorTest() |
+ : AsyncRevalidationDriverTest( |
+ BindCreateProtocolHandlerCallback<MockSSLErrorURLRequestJob>()) {} |
+}; |
+ |
+// Verifies that async revalidation requests do not attempt to recover from SSL |
+// certificate errors. |
+TEST_F(AsyncRevalidationDriverSSLErrorTest, RequestWithSSLErrorRejected) { |
+ // Start the request and wait for it to pause. |
+ driver_->StartRequest(); |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ // Check that the request has been aborted. |
+ EXPECT_FALSE(last_status().is_success()); |
+ EXPECT_EQ(net::ERR_ABORTED, last_status().error()); |
+ EXPECT_TRUE(async_revalidation_complete_called()); |
+} |
+ |
+// A URLRequestTestJob that sets |request_time| and |was_cached| on their |
+// response_info, and causes the test to fail if Read() is called. |
+class FromCacheURLRequestJob : public net::URLRequestTestJob { |
+ public: |
+ FromCacheURLRequestJob(net::URLRequest* request, |
+ net::NetworkDelegate* network_delegate) |
+ : net::URLRequestTestJob(request, network_delegate, true) {} |
+ |
+ void GetResponseInfo(net::HttpResponseInfo* info) override { |
+ URLRequestTestJob::GetResponseInfo(info); |
+ info->request_time = base::Time::Now(); |
+ info->was_cached = true; |
+ } |
+ |
+ int ReadRawData(net::IOBuffer* buf, int buf_size) override { |
+ ADD_FAILURE() << "ReadRawData() was called."; |
+ return URLRequestTestJob::ReadRawData(buf, buf_size); |
+ } |
+ |
+ private: |
+ ~FromCacheURLRequestJob() override {} |
+ |
+ DISALLOW_COPY_AND_ASSIGN(FromCacheURLRequestJob); |
+}; |
+ |
+class AsyncRevalidationDriverFromCacheTest |
+ : public AsyncRevalidationDriverTest { |
+ protected: |
+ AsyncRevalidationDriverFromCacheTest() |
+ : AsyncRevalidationDriverTest( |
+ BindCreateProtocolHandlerCallback<FromCacheURLRequestJob>()) {} |
+}; |
+ |
+TEST_F(AsyncRevalidationDriverFromCacheTest, |
+ CacheNotReadOnSuccessfulRevalidation) { |
+ driver_->StartRequest(); |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ EXPECT_TRUE(async_revalidation_complete_called()); |
+} |
+ |
+} // namespace |
+} // namespace content |