Add end to end tests for WebSockets in net/websockets/

Some things (notably proxies) are impossible to test using layout tests,
and expensive and difficult to test using browser_tests.

Add a way to do simple end-to-end tests in net_unittests.

BUG=441709
TEST=net_unittests

Review URL: https://codereview.chromium.org/722343003

Cr-Commit-Position: refs/heads/master@{#313045}
diff --git a/net/BUILD.gn b/net/BUILD.gn
index f235c301..52444d59 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -1262,6 +1262,7 @@
         "websockets/websocket_deflate_stream_test.cc",
         "websockets/websocket_deflater_test.cc",
         "websockets/websocket_errors_test.cc",
+        "websockets/websocket_end_to_end_test.cc",
         "websockets/websocket_extension_parser_test.cc",
         "websockets/websocket_frame_parser_test.cc",
         "websockets/websocket_frame_test.cc",
diff --git a/net/net.gypi b/net/net.gypi
index 6da22e3..5b5a5f9 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -1718,6 +1718,7 @@
       'websockets/websocket_deflate_predictor_impl_test.cc',
       'websockets/websocket_deflate_stream_test.cc',
       'websockets/websocket_deflater_test.cc',
+      'websockets/websocket_end_to_end_test.cc',
       'websockets/websocket_errors_test.cc',
       'websockets/websocket_extension_parser_test.cc',
       'websockets/websocket_frame_parser_test.cc',
diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc
new file mode 100644
index 0000000..0b5944f
--- /dev/null
+++ b/net/websockets/websocket_end_to_end_test.cc
@@ -0,0 +1,370 @@
+// Copyright 2014 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.
+
+// End-to-end tests for WebSocket.
+//
+// A python server is (re)started for each test, which is moderately
+// inefficient. However, it makes these tests a good fit for scenarios which
+// require special server configurations.
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "net/base/auth.h"
+#include "net/base/network_delegate.h"
+#include "net/base/test_data_directory.h"
+#include "net/proxy/proxy_service.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/url_request/url_request_test_util.h"
+#include "net/websockets/websocket_channel.h"
+#include "net/websockets/websocket_event_interface.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/origin.h"
+
+namespace net {
+
+namespace {
+
+static const char kEchoServer[] = "echo-with-no-extension";
+
+// An implementation of WebSocketEventInterface that waits for and records the
+// results of the connect.
+class ConnectTestingEventInterface : public WebSocketEventInterface {
+ public:
+  ConnectTestingEventInterface();
+
+  void WaitForResponse();
+
+  bool failed() const { return failed_; }
+
+  // Only set if the handshake failed, otherwise empty.
+  std::string failure_message() const;
+
+  std::string selected_subprotocol() const;
+
+  std::string extensions() const;
+
+  // Implementation of WebSocketEventInterface.
+  ChannelState OnAddChannelResponse(bool fail,
+                                    const std::string& selected_subprotocol,
+                                    const std::string& extensions) override;
+
+  ChannelState OnDataFrame(bool fin,
+                           WebSocketMessageType type,
+                           const std::vector<char>& data) override;
+
+  ChannelState OnFlowControl(int64 quota) override;
+
+  ChannelState OnClosingHandshake() override;
+
+  ChannelState OnDropChannel(bool was_clean,
+                             uint16 code,
+                             const std::string& reason) override;
+
+  ChannelState OnFailChannel(const std::string& message) override;
+
+  ChannelState OnStartOpeningHandshake(
+      scoped_ptr<WebSocketHandshakeRequestInfo> request) override;
+
+  ChannelState OnFinishOpeningHandshake(
+      scoped_ptr<WebSocketHandshakeResponseInfo> response) override;
+
+  ChannelState OnSSLCertificateError(
+      scoped_ptr<SSLErrorCallbacks> ssl_error_callbacks,
+      const GURL& url,
+      const SSLInfo& ssl_info,
+      bool fatal) override;
+
+ private:
+  void QuitNestedEventLoop();
+
+  // failed_ is true if the handshake failed (ie. OnFailChannel was called).
+  bool failed_;
+  std::string selected_subprotocol_;
+  std::string extensions_;
+  std::string failure_message_;
+  base::RunLoop run_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConnectTestingEventInterface);
+};
+
+ConnectTestingEventInterface::ConnectTestingEventInterface() : failed_(true) {
+}
+
+void ConnectTestingEventInterface::WaitForResponse() {
+  run_loop_.Run();
+}
+
+std::string ConnectTestingEventInterface::failure_message() const {
+  return failure_message_;
+}
+
+std::string ConnectTestingEventInterface::selected_subprotocol() const {
+  return selected_subprotocol_;
+}
+
+std::string ConnectTestingEventInterface::extensions() const {
+  return extensions_;
+}
+
+// Make the function definitions below less verbose.
+typedef ConnectTestingEventInterface::ChannelState ChannelState;
+
+ChannelState ConnectTestingEventInterface::OnAddChannelResponse(
+    bool fail,
+    const std::string& selected_subprotocol,
+    const std::string& extensions) {
+  failed_ = fail;
+  selected_subprotocol_ = selected_subprotocol;
+  extensions_ = extensions;
+  QuitNestedEventLoop();
+  return fail ? CHANNEL_DELETED : CHANNEL_ALIVE;
+}
+
+ChannelState ConnectTestingEventInterface::OnDataFrame(
+    bool fin,
+    WebSocketMessageType type,
+    const std::vector<char>& data) {
+  return CHANNEL_ALIVE;
+}
+
+ChannelState ConnectTestingEventInterface::OnFlowControl(int64 quota) {
+  return CHANNEL_ALIVE;
+}
+
+ChannelState ConnectTestingEventInterface::OnClosingHandshake() {
+  return CHANNEL_ALIVE;
+}
+
+ChannelState ConnectTestingEventInterface::OnDropChannel(
+    bool was_clean,
+    uint16 code,
+    const std::string& reason) {
+  return CHANNEL_DELETED;
+}
+
+ChannelState ConnectTestingEventInterface::OnFailChannel(
+    const std::string& message) {
+  failed_ = true;
+  failure_message_ = message;
+  QuitNestedEventLoop();
+  return CHANNEL_DELETED;
+}
+
+ChannelState ConnectTestingEventInterface::OnStartOpeningHandshake(
+    scoped_ptr<WebSocketHandshakeRequestInfo> request) {
+  return CHANNEL_ALIVE;
+}
+
+ChannelState ConnectTestingEventInterface::OnFinishOpeningHandshake(
+    scoped_ptr<WebSocketHandshakeResponseInfo> response) {
+  return CHANNEL_ALIVE;
+}
+
+ChannelState ConnectTestingEventInterface::OnSSLCertificateError(
+    scoped_ptr<SSLErrorCallbacks> ssl_error_callbacks,
+    const GURL& url,
+    const SSLInfo& ssl_info,
+    bool fatal) {
+  base::MessageLoop::current()->PostTask(
+      FROM_HERE, base::Bind(&SSLErrorCallbacks::CancelSSLRequest,
+                            base::Owned(ssl_error_callbacks.release()),
+                            ERR_SSL_PROTOCOL_ERROR, &ssl_info));
+  return CHANNEL_ALIVE;
+}
+
+void ConnectTestingEventInterface::QuitNestedEventLoop() {
+  run_loop_.Quit();
+}
+
+// A subclass of TestNetworkDelegate that additionally implements the
+// OnResolveProxy callback and records the information passed to it.
+class TestNetworkDelegateWithProxyInfo : public TestNetworkDelegate {
+ public:
+  TestNetworkDelegateWithProxyInfo() {}
+
+  struct ResolvedProxyInfo {
+    GURL url;
+    ProxyInfo proxy_info;
+  };
+
+  const ResolvedProxyInfo& resolved_proxy_info() const {
+    return resolved_proxy_info_;
+  }
+
+ protected:
+  void OnResolveProxy(const GURL& url,
+                      int load_flags,
+                      const ProxyService& proxy_service,
+                      ProxyInfo* result) override {
+    resolved_proxy_info_.url = url;
+    resolved_proxy_info_.proxy_info = *result;
+  }
+
+ private:
+  ResolvedProxyInfo resolved_proxy_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestNetworkDelegateWithProxyInfo);
+};
+
+class WebSocketEndToEndTest : public ::testing::Test {
+ protected:
+  WebSocketEndToEndTest()
+      : event_interface_(new ConnectTestingEventInterface),
+        network_delegate_(new TestNetworkDelegateWithProxyInfo),
+        context_(true),
+        channel_(make_scoped_ptr(event_interface_), &context_),
+        initialised_context_(false) {}
+
+  // Initialise the URLRequestContext. Normally done automatically by
+  // ConnectAndWait(). This method is for the use of tests that need the
+  // URLRequestContext initialised before calling ConnectAndWait().
+  void InitialiseContext() {
+    context_.set_network_delegate(network_delegate_.get());
+    context_.Init();
+    initialised_context_ = true;
+  }
+
+  // Send the connect request to |socket_url| and wait for a response. Returns
+  // true if the handshake succeeded.
+  bool ConnectAndWait(const GURL& socket_url) {
+    if (!initialised_context_) {
+      InitialiseContext();
+    }
+    std::vector<std::string> sub_protocols;
+    url::Origin origin("http://localhost");
+    channel_.SendAddChannelRequest(GURL(socket_url), sub_protocols, origin);
+    event_interface_->WaitForResponse();
+    return !event_interface_->failed();
+  }
+
+  ConnectTestingEventInterface* event_interface_;  // owned by channel_
+  scoped_ptr<TestNetworkDelegateWithProxyInfo> network_delegate_;
+  TestURLRequestContext context_;
+  WebSocketChannel channel_;
+  bool initialised_context_;
+};
+
+// None of these tests work on Android.
+// TODO(ricea): Make these tests work on Android. See crbug.com/441711.
+#if defined(OS_ANDROID)
+#define DISABLED_ON_ANDROID(test) DISABLED_##test
+#else
+#define DISABLED_ON_ANDROID(test) test
+#endif
+
+// Basic test of connectivity. If this test fails, nothing else can be expected
+// to work.
+TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(BasicSmokeTest)) {
+  SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
+                              SpawnedTestServer::kLocalhost,
+                              GetWebSocketTestDataDirectory());
+  ASSERT_TRUE(ws_server.Start());
+  EXPECT_TRUE(ConnectAndWait(ws_server.GetURL(kEchoServer)));
+}
+
+// Test for issue crbug.com/433695 "Unencrypted WebSocket connection via
+// authenticated proxy times out"
+// TODO(ricea): Enable this when the issue is fixed.
+TEST_F(WebSocketEndToEndTest, DISABLED_HttpsProxyUnauthedFails) {
+  SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
+                                 SpawnedTestServer::kLocalhost,
+                                 base::FilePath());
+  SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
+                              SpawnedTestServer::kLocalhost,
+                              GetWebSocketTestDataDirectory());
+  ASSERT_TRUE(proxy_server.StartInBackground());
+  ASSERT_TRUE(ws_server.StartInBackground());
+  ASSERT_TRUE(proxy_server.BlockUntilStarted());
+  ASSERT_TRUE(ws_server.BlockUntilStarted());
+  std::string proxy_config =
+      "https=" + proxy_server.host_port_pair().ToString();
+  scoped_ptr<ProxyService> proxy_service(
+      ProxyService::CreateFixed(proxy_config));
+  ASSERT_TRUE(proxy_service);
+  context_.set_proxy_service(proxy_service.get());
+  EXPECT_FALSE(ConnectAndWait(ws_server.GetURL(kEchoServer)));
+  EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
+}
+
+TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HttpsWssProxyUnauthedFails)) {
+  SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
+                                 SpawnedTestServer::kLocalhost,
+                                 base::FilePath());
+  SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS,
+                               SpawnedTestServer::kLocalhost,
+                               GetWebSocketTestDataDirectory());
+  ASSERT_TRUE(proxy_server.StartInBackground());
+  ASSERT_TRUE(wss_server.StartInBackground());
+  ASSERT_TRUE(proxy_server.BlockUntilStarted());
+  ASSERT_TRUE(wss_server.BlockUntilStarted());
+  std::string proxy_config =
+      "https=" + proxy_server.host_port_pair().ToString();
+  scoped_ptr<ProxyService> proxy_service(
+      ProxyService::CreateFixed(proxy_config));
+  ASSERT_TRUE(proxy_service);
+  context_.set_proxy_service(proxy_service.get());
+  EXPECT_FALSE(ConnectAndWait(wss_server.GetURL(kEchoServer)));
+  EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
+}
+
+// Regression test for crbug/426736 "WebSocket connections not using configured
+// system HTTPS Proxy".
+TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HttpsProxyUsed)) {
+  SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
+                                 SpawnedTestServer::kLocalhost,
+                                 base::FilePath());
+  SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
+                              SpawnedTestServer::kLocalhost,
+                              GetWebSocketTestDataDirectory());
+  ASSERT_TRUE(proxy_server.StartInBackground());
+  ASSERT_TRUE(ws_server.StartInBackground());
+  ASSERT_TRUE(proxy_server.BlockUntilStarted());
+  ASSERT_TRUE(ws_server.BlockUntilStarted());
+  std::string proxy_config = "https=" +
+                             proxy_server.host_port_pair().ToString() + ";" +
+                             "http=" + proxy_server.host_port_pair().ToString();
+  scoped_ptr<ProxyService> proxy_service(
+      ProxyService::CreateFixed(proxy_config));
+  context_.set_proxy_service(proxy_service.get());
+  InitialiseContext();
+
+  // The test server doesn't have an unauthenticated proxy mode. WebSockets
+  // cannot provide auth information that isn't already cached, so it's
+  // necessary to preflight an HTTP request to authenticate against the proxy.
+  std::string scheme("http");
+  GURL::Replacements replacements;
+  replacements.SetSchemeStr(scheme);
+  // It doesn't matter what the URL is, as long as it is an HTTP navigation.
+  GURL http_page =
+      ws_server.GetURL("connect_check.html").ReplaceComponents(replacements);
+  TestDelegate delegate;
+  delegate.set_credentials(
+      AuthCredentials(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar")));
+  {
+    scoped_ptr<URLRequest> request(
+        context_.CreateRequest(http_page, DEFAULT_PRIORITY, &delegate, NULL));
+    request->Start();
+    // TestDelegate exits the message loop when the request completes by
+    // default.
+    base::RunLoop().Run();
+    EXPECT_TRUE(delegate.auth_required_called());
+  }
+
+  GURL ws_url = ws_server.GetURL(kEchoServer);
+  EXPECT_TRUE(ConnectAndWait(ws_url));
+  const TestNetworkDelegateWithProxyInfo::ResolvedProxyInfo& info =
+      network_delegate_->resolved_proxy_info();
+  EXPECT_EQ(ws_url, info.url);
+  EXPECT_TRUE(info.proxy_info.is_http());
+}
+
+}  // namespace
+
+}  // namespace net