// 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 "net/proxy/proxy_resolver_factory_mojo.h"

#include <list>
#include <map>
#include <queue>
#include <string>

#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/values.h"
#include "mojo/common/common_type_converters.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "net/base/load_states.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/dns/host_resolver.h"
#include "net/log/test_net_log.h"
#include "net/proxy/mojo_proxy_resolver_factory.h"
#include "net/proxy/mojo_proxy_type_converters.h"
#include "net/proxy/proxy_info.h"
#include "net/proxy/proxy_resolver.h"
#include "net/proxy/proxy_resolver_error_observer.h"
#include "net/proxy/proxy_resolver_script_data.h"
#include "net/test/event_waiter.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace net {

namespace {

const char kScriptData[] = "FooBarBaz";
const char kExampleUrl[] = "http://www.example.com";

struct CreateProxyResolverAction {
  enum Action {
    COMPLETE,
    DROP_CLIENT,
    DROP_RESOLVER,
    DROP_BOTH,
    WAIT_FOR_CLIENT_DISCONNECT,
    MAKE_DNS_REQUEST,
  };

  static CreateProxyResolverAction ReturnResult(
      const std::string& expected_pac_script,
      Error error) {
    CreateProxyResolverAction result;
    result.expected_pac_script = expected_pac_script;
    result.error = error;
    return result;
  }

  static CreateProxyResolverAction DropClient(
      const std::string& expected_pac_script) {
    CreateProxyResolverAction result;
    result.expected_pac_script = expected_pac_script;
    result.action = DROP_CLIENT;
    return result;
  }

  static CreateProxyResolverAction DropResolver(
      const std::string& expected_pac_script) {
    CreateProxyResolverAction result;
    result.expected_pac_script = expected_pac_script;
    result.action = DROP_RESOLVER;
    return result;
  }

  static CreateProxyResolverAction DropBoth(
      const std::string& expected_pac_script) {
    CreateProxyResolverAction result;
    result.expected_pac_script = expected_pac_script;
    result.action = DROP_BOTH;
    return result;
  }

  static CreateProxyResolverAction WaitForClientDisconnect(
      const std::string& expected_pac_script) {
    CreateProxyResolverAction result;
    result.expected_pac_script = expected_pac_script;
    result.action = WAIT_FOR_CLIENT_DISCONNECT;
    return result;
  }

  static CreateProxyResolverAction MakeDnsRequest(
      const std::string& expected_pac_script) {
    CreateProxyResolverAction result;
    result.expected_pac_script = expected_pac_script;
    result.action = MAKE_DNS_REQUEST;
    return result;
  }

  std::string expected_pac_script;
  Action action = COMPLETE;
  Error error = OK;
};

struct GetProxyForUrlAction {
  enum Action {
    COMPLETE,
    // Drop the request by closing the reply channel.
    DROP,
    // Disconnect the service.
    DISCONNECT,
    // Wait for the client pipe to be disconnected.
    WAIT_FOR_CLIENT_DISCONNECT,
    // Make a DNS request.
    MAKE_DNS_REQUEST,
  };

  GetProxyForUrlAction() {}
  GetProxyForUrlAction(const GetProxyForUrlAction& old) {
    action = old.action;
    error = old.error;
    expected_url = old.expected_url;
    proxy_servers = old.proxy_servers.Clone();
  }

  static GetProxyForUrlAction ReturnError(const GURL& url, Error error) {
    GetProxyForUrlAction result;
    result.expected_url = url;
    result.error = error;
    return result;
  }

  static GetProxyForUrlAction ReturnServers(
      const GURL& url,
      const mojo::Array<interfaces::ProxyServerPtr>& proxy_servers) {
    GetProxyForUrlAction result;
    result.expected_url = url;
    result.proxy_servers = proxy_servers.Clone();
    return result;
  }

  static GetProxyForUrlAction DropRequest(const GURL& url) {
    GetProxyForUrlAction result;
    result.expected_url = url;
    result.action = DROP;
    return result;
  }

  static GetProxyForUrlAction Disconnect(const GURL& url) {
    GetProxyForUrlAction result;
    result.expected_url = url;
    result.action = DISCONNECT;
    return result;
  }

  static GetProxyForUrlAction WaitForClientDisconnect(const GURL& url) {
    GetProxyForUrlAction result;
    result.expected_url = url;
    result.action = WAIT_FOR_CLIENT_DISCONNECT;
    return result;
  }

  static GetProxyForUrlAction MakeDnsRequest(const GURL& url) {
    GetProxyForUrlAction result;
    result.expected_url = url;
    result.action = MAKE_DNS_REQUEST;
    return result;
  }

  Action action = COMPLETE;
  Error error = OK;
  mojo::Array<interfaces::ProxyServerPtr> proxy_servers;
  GURL expected_url;
};

class MockMojoProxyResolver : public interfaces::ProxyResolver {
 public:
  MockMojoProxyResolver();
  ~MockMojoProxyResolver() override;

  void AddGetProxyAction(GetProxyForUrlAction action);

  void WaitForNextRequest();

  void ClearBlockedClients();

  void AddConnection(mojo::InterfaceRequest<interfaces::ProxyResolver> req);

 private:
  // Overridden from interfaces::ProxyResolver:
  void GetProxyForUrl(
      const mojo::String& url,
      interfaces::ProxyResolverRequestClientPtr client) override;

  void WakeWaiter();

  std::string pac_script_data_;

  std::queue<GetProxyForUrlAction> get_proxy_actions_;

  base::Closure quit_closure_;

  ScopedVector<interfaces::ProxyResolverRequestClientPtr> blocked_clients_;
  mojo::Binding<interfaces::ProxyResolver> binding_;
};

MockMojoProxyResolver::~MockMojoProxyResolver() {
  EXPECT_TRUE(get_proxy_actions_.empty())
      << "Actions remaining: " << get_proxy_actions_.size();
}

MockMojoProxyResolver::MockMojoProxyResolver() : binding_(this) {
}

void MockMojoProxyResolver::AddGetProxyAction(GetProxyForUrlAction action) {
  get_proxy_actions_.push(action);
}

void MockMojoProxyResolver::WaitForNextRequest() {
  base::RunLoop run_loop;
  quit_closure_ = run_loop.QuitClosure();
  run_loop.Run();
}

void MockMojoProxyResolver::WakeWaiter() {
  if (!quit_closure_.is_null())
    quit_closure_.Run();
  quit_closure_.Reset();
}

void MockMojoProxyResolver::ClearBlockedClients() {
  blocked_clients_.clear();
}

void MockMojoProxyResolver::AddConnection(
    mojo::InterfaceRequest<interfaces::ProxyResolver> req) {
  if (binding_.is_bound())
    binding_.Close();
  binding_.Bind(req.Pass());
}

void MockMojoProxyResolver::GetProxyForUrl(
    const mojo::String& url,
    interfaces::ProxyResolverRequestClientPtr client) {
  ASSERT_FALSE(get_proxy_actions_.empty());
  GetProxyForUrlAction action = get_proxy_actions_.front();
  get_proxy_actions_.pop();

  EXPECT_EQ(action.expected_url.spec(), url.To<std::string>());
  client->Alert(url);
  client->OnError(12345, url);
  switch (action.action) {
    case GetProxyForUrlAction::COMPLETE: {
      client->ReportResult(action.error, action.proxy_servers.Pass());
      break;
    }
    case GetProxyForUrlAction::DROP: {
      client.reset();
      break;
    }
    case GetProxyForUrlAction::DISCONNECT: {
      binding_.Close();
      break;
    }
    case GetProxyForUrlAction::WAIT_FOR_CLIENT_DISCONNECT: {
      ASSERT_FALSE(client.WaitForIncomingResponse());
      break;
    }
    case GetProxyForUrlAction::MAKE_DNS_REQUEST: {
      interfaces::HostResolverRequestInfoPtr request(
          interfaces::HostResolverRequestInfo::New());
      request->host = url;
      request->port = 12345;
      interfaces::HostResolverRequestClientPtr dns_client;
      mojo::GetProxy(&dns_client);
      client->ResolveDns(request.Pass(), dns_client.Pass());
      blocked_clients_.push_back(
          new interfaces::ProxyResolverRequestClientPtr(client.Pass()));
      break;
    }
  }
  WakeWaiter();
}

class Request {
 public:
  Request(ProxyResolver* resolver, const GURL& url);

  int Resolve();
  void Cancel();
  int WaitForResult();

  const ProxyInfo& results() const { return results_; }
  LoadState load_state() { return resolver_->GetLoadState(handle_); }
  BoundTestNetLog& net_log() { return net_log_; }

 private:
  ProxyResolver* resolver_;
  const GURL url_;
  ProxyInfo results_;
  ProxyResolver::RequestHandle handle_;
  int error_;
  TestCompletionCallback callback_;
  BoundTestNetLog net_log_;
};

Request::Request(ProxyResolver* resolver, const GURL& url)
    : resolver_(resolver), url_(url), error_(0) {
}

int Request::Resolve() {
  error_ = resolver_->GetProxyForURL(url_, &results_, callback_.callback(),
                                     &handle_, net_log_.bound());
  return error_;
}

void Request::Cancel() {
  resolver_->CancelRequest(handle_);
}

int Request::WaitForResult() {
  error_ = callback_.WaitForResult();
  return error_;
}

class MockMojoProxyResolverFactory : public interfaces::ProxyResolverFactory {
 public:
  MockMojoProxyResolverFactory(
      MockMojoProxyResolver* resolver,
      mojo::InterfaceRequest<interfaces::ProxyResolverFactory> req);
  ~MockMojoProxyResolverFactory() override;

  void AddCreateProxyResolverAction(CreateProxyResolverAction action);

  void WaitForNextRequest();

  void ClearBlockedClients();

 private:
  // Overridden from interfaces::ProxyResolver:
  void CreateResolver(
      const mojo::String& pac_url,
      mojo::InterfaceRequest<interfaces::ProxyResolver> request,
      interfaces::ProxyResolverFactoryRequestClientPtr client) override;

  void WakeWaiter();

  MockMojoProxyResolver* resolver_;
  std::queue<CreateProxyResolverAction> create_resolver_actions_;

  base::Closure quit_closure_;

  ScopedVector<interfaces::ProxyResolverFactoryRequestClientPtr>
      blocked_clients_;
  ScopedVector<mojo::InterfaceRequest<interfaces::ProxyResolver>>
      blocked_resolver_requests_;
  mojo::Binding<interfaces::ProxyResolverFactory> binding_;
};

MockMojoProxyResolverFactory::MockMojoProxyResolverFactory(
    MockMojoProxyResolver* resolver,
    mojo::InterfaceRequest<interfaces::ProxyResolverFactory> req)
    : resolver_(resolver), binding_(this, req.Pass()) {
}

MockMojoProxyResolverFactory::~MockMojoProxyResolverFactory() {
  EXPECT_TRUE(create_resolver_actions_.empty())
      << "Actions remaining: " << create_resolver_actions_.size();
}

void MockMojoProxyResolverFactory::AddCreateProxyResolverAction(
    CreateProxyResolverAction action) {
  create_resolver_actions_.push(action);
}

void MockMojoProxyResolverFactory::WaitForNextRequest() {
  base::RunLoop run_loop;
  quit_closure_ = run_loop.QuitClosure();
  run_loop.Run();
}

void MockMojoProxyResolverFactory::WakeWaiter() {
  if (!quit_closure_.is_null())
    quit_closure_.Run();
  quit_closure_.Reset();
}

void MockMojoProxyResolverFactory::ClearBlockedClients() {
  blocked_clients_.clear();
}

void MockMojoProxyResolverFactory::CreateResolver(
    const mojo::String& pac_script,
    mojo::InterfaceRequest<interfaces::ProxyResolver> request,
    interfaces::ProxyResolverFactoryRequestClientPtr client) {
  ASSERT_FALSE(create_resolver_actions_.empty());
  CreateProxyResolverAction action = create_resolver_actions_.front();
  create_resolver_actions_.pop();

  EXPECT_EQ(action.expected_pac_script, pac_script.To<std::string>());
  client->Alert(pac_script);
  client->OnError(12345, pac_script);
  switch (action.action) {
    case CreateProxyResolverAction::COMPLETE: {
      if (action.error == OK)
        resolver_->AddConnection(request.Pass());
      client->ReportResult(action.error);
      break;
    }
    case CreateProxyResolverAction::DROP_CLIENT: {
      // Save |request| so its pipe isn't closed.
      blocked_resolver_requests_.push_back(
          new mojo::InterfaceRequest<interfaces::ProxyResolver>(
              request.Pass()));
      break;
    }
    case CreateProxyResolverAction::DROP_RESOLVER: {
      // Save |client| so its pipe isn't closed.
      blocked_clients_.push_back(
          new interfaces::ProxyResolverFactoryRequestClientPtr(client.Pass()));
      break;
    }
    case CreateProxyResolverAction::DROP_BOTH: {
      // Both |request| and |client| will be closed.
      break;
    }
    case CreateProxyResolverAction::WAIT_FOR_CLIENT_DISCONNECT: {
      ASSERT_FALSE(client.WaitForIncomingResponse());
      break;
    }
    case CreateProxyResolverAction::MAKE_DNS_REQUEST: {
      interfaces::HostResolverRequestInfoPtr request(
          interfaces::HostResolverRequestInfo::New());
      request->host = pac_script;
      request->port = 12345;
      interfaces::HostResolverRequestClientPtr dns_client;
      mojo::GetProxy(&dns_client);
      client->ResolveDns(request.Pass(), dns_client.Pass());
      blocked_clients_.push_back(
          new interfaces::ProxyResolverFactoryRequestClientPtr(client.Pass()));
      break;
    }
  }
  WakeWaiter();
}

void DeleteResolverFactoryRequestCallback(
    scoped_ptr<ProxyResolverFactory::Request>* request,
    const CompletionCallback& callback,
    int result) {
  ASSERT_TRUE(request);
  EXPECT_TRUE(request->get());
  request->reset();
  callback.Run(result);
}

class MockHostResolver : public HostResolver {
 public:
  enum Event {
    DNS_REQUEST,
  };

  // HostResolver overrides.
  int Resolve(const RequestInfo& info,
              RequestPriority priority,
              AddressList* addresses,
              const CompletionCallback& callback,
              RequestHandle* request_handle,
              const BoundNetLog& source_net_log) override {
    waiter_.NotifyEvent(DNS_REQUEST);
    return ERR_IO_PENDING;
  }
  int ResolveFromCache(const RequestInfo& info,
                       AddressList* addresses,
                       const BoundNetLog& source_net_log) override {
    return ERR_DNS_CACHE_MISS;
  }
  void CancelRequest(RequestHandle req) override {}
  HostCache* GetHostCache() override { return nullptr; }

  EventWaiter<Event>& waiter() { return waiter_; }

 private:
  EventWaiter<Event> waiter_;
};

void CheckCapturedNetLogEntries(const std::string& expected_string,
                                const TestNetLogEntry::List& entries) {
  ASSERT_EQ(2u, entries.size());
  EXPECT_EQ(NetLog::TYPE_PAC_JAVASCRIPT_ALERT, entries[0].type);
  std::string message;
  ASSERT_TRUE(entries[0].GetStringValue("message", &message));
  EXPECT_EQ(expected_string, message);
  ASSERT_FALSE(entries[0].params->HasKey("line_number"));
  message.clear();
  EXPECT_EQ(NetLog::TYPE_PAC_JAVASCRIPT_ERROR, entries[1].type);
  ASSERT_TRUE(entries[1].GetStringValue("message", &message));
  EXPECT_EQ(expected_string, message);
  int line_number = 0;
  ASSERT_TRUE(entries[1].GetIntegerValue("line_number", &line_number));
  EXPECT_EQ(12345, line_number);
}

}  // namespace

class ProxyResolverFactoryMojoTest : public testing::Test,
                                     public MojoProxyResolverFactory {
 public:
  void SetUp() override {
    mock_proxy_resolver_factory_.reset(new MockMojoProxyResolverFactory(
        &mock_proxy_resolver_, mojo::GetProxy(&factory_ptr_)));
    proxy_resolver_factory_mojo_.reset(new ProxyResolverFactoryMojo(
        this, &host_resolver_,
        base::Callback<scoped_ptr<ProxyResolverErrorObserver>()>(), &net_log_));
  }

  scoped_ptr<Request> MakeRequest(const GURL& url) {
    return make_scoped_ptr(new Request(proxy_resolver_mojo_.get(), url));
  }

  scoped_ptr<base::ScopedClosureRunner> CreateResolver(
      const mojo::String& pac_script,
      mojo::InterfaceRequest<interfaces::ProxyResolver> req,
      interfaces::ProxyResolverFactoryRequestClientPtr client) override {
    factory_ptr_->CreateResolver(pac_script, req.Pass(), client.Pass());
    return make_scoped_ptr(
        new base::ScopedClosureRunner(on_delete_callback_.closure()));
  }

  mojo::Array<interfaces::ProxyServerPtr> ProxyServersFromPacString(
      const std::string& pac_string) {
    ProxyInfo proxy_info;
    proxy_info.UsePacString(pac_string);

    return mojo::Array<interfaces::ProxyServerPtr>::From(
        proxy_info.proxy_list().GetAll());
  }

  void CreateProxyResolver() {
    mock_proxy_resolver_factory_->AddCreateProxyResolverAction(
        CreateProxyResolverAction::ReturnResult(kScriptData, OK));
    TestCompletionCallback callback;
    scoped_refptr<ProxyResolverScriptData> pac_script(
        ProxyResolverScriptData::FromUTF8(kScriptData));
    scoped_ptr<ProxyResolverFactory::Request> request;
    ASSERT_EQ(
        OK,
        callback.GetResult(proxy_resolver_factory_mojo_->CreateProxyResolver(
            pac_script, &proxy_resolver_mojo_, callback.callback(), &request)));
    EXPECT_TRUE(request);
    ASSERT_TRUE(proxy_resolver_mojo_);
  }

  void DeleteProxyResolverCallback(const CompletionCallback& callback,
                                   int result) {
    proxy_resolver_mojo_.reset();
    callback.Run(result);
  }

  MockHostResolver host_resolver_;
  TestNetLog net_log_;
  scoped_ptr<MockMojoProxyResolverFactory> mock_proxy_resolver_factory_;
  interfaces::ProxyResolverFactoryPtr factory_ptr_;
  scoped_ptr<ProxyResolverFactory> proxy_resolver_factory_mojo_;

  MockMojoProxyResolver mock_proxy_resolver_;
  TestClosure on_delete_callback_;
  scoped_ptr<ProxyResolver> proxy_resolver_mojo_;
};

TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver) {
  CreateProxyResolver();
  TestNetLogEntry::List entries;
  net_log_.GetEntries(&entries);
  CheckCapturedNetLogEntries(kScriptData, entries);
}

TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver_Empty) {
  TestCompletionCallback callback;
  scoped_refptr<ProxyResolverScriptData> pac_script(
      ProxyResolverScriptData::FromUTF8(""));
  scoped_ptr<ProxyResolverFactory::Request> request;
  EXPECT_EQ(
      ERR_PAC_SCRIPT_FAILED,
      callback.GetResult(proxy_resolver_factory_mojo_->CreateProxyResolver(
          pac_script, &proxy_resolver_mojo_, callback.callback(), &request)));
  EXPECT_FALSE(request);
}

TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver_Url) {
  TestCompletionCallback callback;
  scoped_refptr<ProxyResolverScriptData> pac_script(
      ProxyResolverScriptData::FromURL(GURL(kExampleUrl)));
  scoped_ptr<ProxyResolverFactory::Request> request;
  EXPECT_EQ(
      ERR_PAC_SCRIPT_FAILED,
      callback.GetResult(proxy_resolver_factory_mojo_->CreateProxyResolver(
          pac_script, &proxy_resolver_mojo_, callback.callback(), &request)));
  EXPECT_FALSE(request);
}

TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver_Failed) {
  mock_proxy_resolver_factory_->AddCreateProxyResolverAction(
      CreateProxyResolverAction::ReturnResult(kScriptData,
                                              ERR_PAC_STATUS_NOT_OK));

  TestCompletionCallback callback;
  scoped_refptr<ProxyResolverScriptData> pac_script(
      ProxyResolverScriptData::FromUTF8(kScriptData));
  scoped_ptr<ProxyResolverFactory::Request> request;
  EXPECT_EQ(
      ERR_PAC_STATUS_NOT_OK,
      callback.GetResult(proxy_resolver_factory_mojo_->CreateProxyResolver(
          pac_script, &proxy_resolver_mojo_, callback.callback(), &request)));
  EXPECT_TRUE(request);
  on_delete_callback_.WaitForResult();

  // A second attempt succeeds.
  CreateProxyResolver();
}

TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver_BothDisconnected) {
  mock_proxy_resolver_factory_->AddCreateProxyResolverAction(
      CreateProxyResolverAction::DropBoth(kScriptData));

  scoped_refptr<ProxyResolverScriptData> pac_script(
      ProxyResolverScriptData::FromUTF8(kScriptData));
  scoped_ptr<ProxyResolverFactory::Request> request;
  TestCompletionCallback callback;
  EXPECT_EQ(
      ERR_PAC_SCRIPT_TERMINATED,
      callback.GetResult(proxy_resolver_factory_mojo_->CreateProxyResolver(
          pac_script, &proxy_resolver_mojo_, callback.callback(), &request)));
  EXPECT_TRUE(request);
}

TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver_ClientDisconnected) {
  mock_proxy_resolver_factory_->AddCreateProxyResolverAction(
      CreateProxyResolverAction::DropClient(kScriptData));

  scoped_refptr<ProxyResolverScriptData> pac_script(
      ProxyResolverScriptData::FromUTF8(kScriptData));
  scoped_ptr<ProxyResolverFactory::Request> request;
  TestCompletionCallback callback;
  EXPECT_EQ(
      ERR_PAC_SCRIPT_TERMINATED,
      callback.GetResult(proxy_resolver_factory_mojo_->CreateProxyResolver(
          pac_script, &proxy_resolver_mojo_, callback.callback(), &request)));
  EXPECT_TRUE(request);
}

TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver_ResolverDisconnected) {
  mock_proxy_resolver_factory_->AddCreateProxyResolverAction(
      CreateProxyResolverAction::DropResolver(kScriptData));

  scoped_refptr<ProxyResolverScriptData> pac_script(
      ProxyResolverScriptData::FromUTF8(kScriptData));
  scoped_ptr<ProxyResolverFactory::Request> request;
  TestCompletionCallback callback;
  EXPECT_EQ(
      ERR_PAC_SCRIPT_TERMINATED,
      callback.GetResult(proxy_resolver_factory_mojo_->CreateProxyResolver(
          pac_script, &proxy_resolver_mojo_, callback.callback(), &request)));
  EXPECT_TRUE(request);
  on_delete_callback_.WaitForResult();
}

TEST_F(ProxyResolverFactoryMojoTest,
       CreateProxyResolver_ResolverDisconnected_DeleteRequestInCallback) {
  mock_proxy_resolver_factory_->AddCreateProxyResolverAction(
      CreateProxyResolverAction::DropResolver(kScriptData));

  scoped_refptr<ProxyResolverScriptData> pac_script(
      ProxyResolverScriptData::FromUTF8(kScriptData));
  scoped_ptr<ProxyResolverFactory::Request> request;
  TestCompletionCallback callback;
  EXPECT_EQ(
      ERR_PAC_SCRIPT_TERMINATED,
      callback.GetResult(proxy_resolver_factory_mojo_->CreateProxyResolver(
          pac_script, &proxy_resolver_mojo_,
          base::Bind(&DeleteResolverFactoryRequestCallback, &request,
                     callback.callback()),
          &request)));
  on_delete_callback_.WaitForResult();
}

TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver_Cancel) {
  mock_proxy_resolver_factory_->AddCreateProxyResolverAction(
      CreateProxyResolverAction::WaitForClientDisconnect(kScriptData));

  scoped_refptr<ProxyResolverScriptData> pac_script(
      ProxyResolverScriptData::FromUTF8(kScriptData));
  scoped_ptr<ProxyResolverFactory::Request> request;
  TestCompletionCallback callback;
  EXPECT_EQ(ERR_IO_PENDING, proxy_resolver_factory_mojo_->CreateProxyResolver(
                                pac_script, &proxy_resolver_mojo_,
                                callback.callback(), &request));
  ASSERT_TRUE(request);
  request.reset();

  // The Mojo request is still made.
  mock_proxy_resolver_factory_->WaitForNextRequest();
  on_delete_callback_.WaitForResult();
}

TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver_DnsRequest) {
  mock_proxy_resolver_factory_->AddCreateProxyResolverAction(
      CreateProxyResolverAction::MakeDnsRequest(kScriptData));

  scoped_refptr<ProxyResolverScriptData> pac_script(
      ProxyResolverScriptData::FromUTF8(kScriptData));
  scoped_ptr<ProxyResolverFactory::Request> request;
  TestCompletionCallback callback;
  EXPECT_EQ(ERR_IO_PENDING, proxy_resolver_factory_mojo_->CreateProxyResolver(
                                pac_script, &proxy_resolver_mojo_,
                                callback.callback(), &request));
  ASSERT_TRUE(request);
  host_resolver_.waiter().WaitForEvent(MockHostResolver::DNS_REQUEST);
  mock_proxy_resolver_factory_->ClearBlockedClients();
  callback.WaitForResult();
}

TEST_F(ProxyResolverFactoryMojoTest, GetProxyForURL) {
  const GURL url(kExampleUrl);
  mock_proxy_resolver_.AddGetProxyAction(GetProxyForUrlAction::ReturnServers(
      url, ProxyServersFromPacString("DIRECT")));
  CreateProxyResolver();
  net_log_.Clear();

  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
  EXPECT_EQ(OK, request->WaitForResult());

  EXPECT_EQ("DIRECT", request->results().ToPacString());

  TestNetLogEntry::List entries;
  net_log_.GetEntries(&entries);
  CheckCapturedNetLogEntries(url.spec(), entries);
  entries.clear();
  request->net_log().GetEntries(&entries);
  CheckCapturedNetLogEntries(url.spec(), entries);
}

TEST_F(ProxyResolverFactoryMojoTest, GetProxyForURL_MultipleResults) {
  static const char kPacString[] =
      "PROXY foo1:80;DIRECT;SOCKS foo2:1234;"
      "SOCKS5 foo3:1080;HTTPS foo4:443;QUIC foo6:8888";
  mock_proxy_resolver_.AddGetProxyAction(GetProxyForUrlAction::ReturnServers(
      GURL(kExampleUrl), ProxyServersFromPacString(kPacString)));
  CreateProxyResolver();

  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
  EXPECT_EQ(OK, request->WaitForResult());

  EXPECT_EQ(kPacString, request->results().ToPacString());
}

TEST_F(ProxyResolverFactoryMojoTest, GetProxyForURL_Error) {
  mock_proxy_resolver_.AddGetProxyAction(
      GetProxyForUrlAction::ReturnError(GURL(kExampleUrl), ERR_UNEXPECTED));
  CreateProxyResolver();

  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
  EXPECT_EQ(ERR_UNEXPECTED, request->WaitForResult());

  EXPECT_TRUE(request->results().is_empty());
}

TEST_F(ProxyResolverFactoryMojoTest, GetProxyForURL_Cancel) {
  mock_proxy_resolver_.AddGetProxyAction(
      GetProxyForUrlAction::WaitForClientDisconnect(GURL(kExampleUrl)));
  CreateProxyResolver();

  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
  request->Cancel();

  // The Mojo request is still made.
  mock_proxy_resolver_.WaitForNextRequest();
}

TEST_F(ProxyResolverFactoryMojoTest, GetProxyForURL_MultipleRequests) {
  mock_proxy_resolver_.AddGetProxyAction(GetProxyForUrlAction::ReturnServers(
      GURL(kExampleUrl), ProxyServersFromPacString("DIRECT")));
  mock_proxy_resolver_.AddGetProxyAction(GetProxyForUrlAction::ReturnServers(
      GURL("https://www.chromium.org"),
      ProxyServersFromPacString("HTTPS foo:443")));
  CreateProxyResolver();

  scoped_ptr<Request> request1(MakeRequest(GURL(kExampleUrl)));
  EXPECT_EQ(ERR_IO_PENDING, request1->Resolve());
  scoped_ptr<Request> request2(MakeRequest(GURL("https://www.chromium.org")));
  EXPECT_EQ(ERR_IO_PENDING, request2->Resolve());

  EXPECT_EQ(OK, request1->WaitForResult());
  EXPECT_EQ(OK, request2->WaitForResult());

  EXPECT_EQ("DIRECT", request1->results().ToPacString());
  EXPECT_EQ("HTTPS foo:443", request2->results().ToPacString());
}

TEST_F(ProxyResolverFactoryMojoTest, GetProxyForURL_Disconnect) {
  mock_proxy_resolver_.AddGetProxyAction(
      GetProxyForUrlAction::Disconnect(GURL(kExampleUrl)));
  CreateProxyResolver();
  {
    scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
    EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
    EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, request->WaitForResult());
    EXPECT_TRUE(request->results().is_empty());
  }

  {
    // Calling GetProxyForURL after a disconnect should fail.
    scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
    EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, request->Resolve());
  }
}

TEST_F(ProxyResolverFactoryMojoTest, GetProxyForURL_ClientClosed) {
  mock_proxy_resolver_.AddGetProxyAction(
      GetProxyForUrlAction::DropRequest(GURL(kExampleUrl)));
  CreateProxyResolver();

  scoped_ptr<Request> request1(MakeRequest(GURL(kExampleUrl)));
  EXPECT_EQ(ERR_IO_PENDING, request1->Resolve());

  EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, request1->WaitForResult());
}

TEST_F(ProxyResolverFactoryMojoTest, GetProxyForURL_DeleteInCallback) {
  mock_proxy_resolver_.AddGetProxyAction(GetProxyForUrlAction::ReturnServers(
      GURL(kExampleUrl), ProxyServersFromPacString("DIRECT")));
  CreateProxyResolver();

  ProxyInfo results;
  TestCompletionCallback callback;
  ProxyResolver::RequestHandle handle;
  BoundNetLog net_log;
  EXPECT_EQ(
      OK,
      callback.GetResult(proxy_resolver_mojo_->GetProxyForURL(
          GURL(kExampleUrl), &results,
          base::Bind(&ProxyResolverFactoryMojoTest::DeleteProxyResolverCallback,
                     base::Unretained(this), callback.callback()),
          &handle, net_log)));
  on_delete_callback_.WaitForResult();
}

TEST_F(ProxyResolverFactoryMojoTest,
       GetProxyForURL_DeleteInCallbackFromDisconnect) {
  mock_proxy_resolver_.AddGetProxyAction(
      GetProxyForUrlAction::Disconnect(GURL(kExampleUrl)));
  CreateProxyResolver();

  ProxyInfo results;
  TestCompletionCallback callback;
  ProxyResolver::RequestHandle handle;
  BoundNetLog net_log;
  EXPECT_EQ(
      ERR_PAC_SCRIPT_TERMINATED,
      callback.GetResult(proxy_resolver_mojo_->GetProxyForURL(
          GURL(kExampleUrl), &results,
          base::Bind(&ProxyResolverFactoryMojoTest::DeleteProxyResolverCallback,
                     base::Unretained(this), callback.callback()),
          &handle, net_log)));
  on_delete_callback_.WaitForResult();
}

TEST_F(ProxyResolverFactoryMojoTest, GetProxyForURL_DnsRequest) {
  mock_proxy_resolver_.AddGetProxyAction(
      GetProxyForUrlAction::MakeDnsRequest(GURL(kExampleUrl)));
  CreateProxyResolver();

  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
  EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, request->load_state());

  host_resolver_.waiter().WaitForEvent(MockHostResolver::DNS_REQUEST);
  EXPECT_EQ(LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT, request->load_state());
  mock_proxy_resolver_.ClearBlockedClients();
  request->WaitForResult();
}

TEST_F(ProxyResolverFactoryMojoTest, DeleteResolver) {
  CreateProxyResolver();
  proxy_resolver_mojo_.reset();
  on_delete_callback_.WaitForResult();
}
}  // namespace net