chromium/net/proxy_resolution/win/windows_system_proxy_resolution_service_unittest.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/proxy_resolution/win/windows_system_proxy_resolution_service.h"

#include <memory>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "net/base/network_isolation_key.h"
#include "net/base/proxy_server.h"
#include "net/base/proxy_string_util.h"
#include "net/base/test_completion_callback.h"
#include "net/proxy_resolution/configured_proxy_resolution_service.h"
#include "net/proxy_resolution/proxy_config.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_list.h"
#include "net/proxy_resolution/win/windows_system_proxy_resolution_request.h"
#include "net/proxy_resolution/win/windows_system_proxy_resolver.h"
#include "net/proxy_resolution/win/winhttp_status.h"
#include "net/test/gtest_util.h"
#include "net/test/test_with_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using net::test::IsError;
using net::test::IsOk;

namespace net {

namespace {

const GURL kResourceUrl("https://example.test:8080/");

class MockRequest : public WindowsSystemProxyResolver::Request {
 public:
  MockRequest(WindowsSystemProxyResolutionRequest* callback_target,
              const ProxyList& proxy_list,
              WinHttpStatus winhttp_status,
              int windows_error) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(&MockRequest::DoCallback, weak_ptr_factory_.GetWeakPtr(),
                       callback_target, proxy_list, winhttp_status,
                       windows_error));
  }
  ~MockRequest() override = default;

 private:
  void DoCallback(WindowsSystemProxyResolutionRequest* callback_target,
                  const ProxyList& proxy_list,
                  WinHttpStatus winhttp_status,
                  int windows_error) {
    callback_target->ProxyResolutionComplete(proxy_list, winhttp_status,
                                             windows_error);
  }

  base::WeakPtrFactory<MockRequest> weak_ptr_factory_{this};
};

class MockWindowsSystemProxyResolver : public WindowsSystemProxyResolver {
 public:
  MockWindowsSystemProxyResolver() = default;
  ~MockWindowsSystemProxyResolver() override = default;

  void add_server_to_proxy_list(const ProxyServer& proxy_server) {
    proxy_list_.AddProxyServer(proxy_server);
  }

  void set_winhttp_status(WinHttpStatus winhttp_status) {
    winhttp_status_ = winhttp_status;
  }

  void set_windows_error(int windows_error) { windows_error_ = windows_error; }

  std::unique_ptr<Request> GetProxyForUrl(
      const GURL& url,
      WindowsSystemProxyResolutionRequest* callback_target) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return std::make_unique<MockRequest>(callback_target, proxy_list_,
                                         winhttp_status_, windows_error_);
  }

 private:
  ProxyList proxy_list_;
  WinHttpStatus winhttp_status_ = WinHttpStatus::kOk;
  int windows_error_ = 0;

  SEQUENCE_CHECKER(sequence_checker_);
};

}  // namespace

// These tests verify the behavior of the WindowsSystemProxyResolutionService in
// isolation by mocking out the WindowsSystemProxyResolver.
class WindowsSystemProxyResolutionServiceTest : public TestWithTaskEnvironment {
 public:
  void SetUp() override {
    testing::Test::SetUp();

    if (!WindowsSystemProxyResolutionService::IsSupported()) {
      GTEST_SKIP()
          << "Windows System Proxy Resolution is only supported on Windows 8+.";
    }

    auto proxy_resolver = std::make_unique<MockWindowsSystemProxyResolver>();
    proxy_resolver_ = proxy_resolver.get();
    proxy_resolution_service_ = WindowsSystemProxyResolutionService::Create(
        std::move(proxy_resolver), /*net_log=*/nullptr);
    ASSERT_TRUE(proxy_resolution_service_);
  }

  WindowsSystemProxyResolutionService* service() {
    return proxy_resolution_service_.get();
  }

  MockWindowsSystemProxyResolver* resolver() { return proxy_resolver_; }

  void ResetProxyResolutionService() { proxy_resolution_service_.reset(); }

  void DoResolveProxyTest(const ProxyList& expected_proxy_list) {
    ProxyInfo info;
    TestCompletionCallback callback;
    NetLogWithSource log;
    std::unique_ptr<ProxyResolutionRequest> request;
    int result = service()->ResolveProxy(kResourceUrl, std::string(),
                                         NetworkAnonymizationKey(), &info,
                                         callback.callback(), &request, log);

    ASSERT_THAT(result, IsError(ERR_IO_PENDING));
    ASSERT_NE(request, nullptr);

    // Wait for result to come back.
    EXPECT_THAT(callback.GetResult(result), IsOk());

    EXPECT_TRUE(expected_proxy_list.Equals(info.proxy_list()));
    EXPECT_NE(request, nullptr);
  }

 private:
  std::unique_ptr<WindowsSystemProxyResolutionService>
      proxy_resolution_service_;
  raw_ptr<MockWindowsSystemProxyResolver, DanglingUntriaged> proxy_resolver_;
};

TEST_F(WindowsSystemProxyResolutionServiceTest, CreateWithNullResolver) {
  std::unique_ptr<WindowsSystemProxyResolutionService>
      proxy_resolution_service = WindowsSystemProxyResolutionService::Create(
          /*windows_system_proxy_resolver=*/nullptr, /*net_log=*/nullptr);
  EXPECT_FALSE(proxy_resolution_service);
}

TEST_F(WindowsSystemProxyResolutionServiceTest, ResolveProxyFailed) {
  resolver()->set_winhttp_status(WinHttpStatus::kAborted);

  // Make sure there would be a proxy result on success.
  const ProxyServer proxy_server =
      PacResultElementToProxyServer("HTTPS foopy:8443");
  resolver()->add_server_to_proxy_list(proxy_server);

  ProxyInfo info;
  TestCompletionCallback callback;
  NetLogWithSource log;
  std::unique_ptr<ProxyResolutionRequest> request;
  int result = service()->ResolveProxy(kResourceUrl, std::string(),
                                       NetworkAnonymizationKey(), &info,
                                       callback.callback(), &request, log);

  ASSERT_THAT(result, IsError(ERR_IO_PENDING));
  ASSERT_NE(request, nullptr);

  // Wait for result to come back.
  EXPECT_THAT(callback.GetResult(result), IsOk());

  EXPECT_TRUE(info.is_direct());
  EXPECT_NE(request, nullptr);
}

TEST_F(WindowsSystemProxyResolutionServiceTest, ResolveProxyCancelled) {
  // Make sure there would be a proxy result on success.
  const ProxyServer proxy_server =
      PacResultElementToProxyServer("HTTPS foopy:8443");
  resolver()->add_server_to_proxy_list(proxy_server);

  ProxyInfo info;
  TestCompletionCallback callback;
  NetLogWithSource log;
  std::unique_ptr<ProxyResolutionRequest> request;
  int result = service()->ResolveProxy(kResourceUrl, std::string(),
                                       NetworkAnonymizationKey(), &info,
                                       callback.callback(), &request, log);

  ASSERT_THAT(result, IsError(ERR_IO_PENDING));
  ASSERT_NE(request, nullptr);

  // Cancel the request.
  request.reset();

  // The proxy shouldn't resolve.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(callback.have_result());
}

TEST_F(WindowsSystemProxyResolutionServiceTest, ResolveProxyEmptyResults) {
  ProxyList expected_proxy_list;
  DoResolveProxyTest(expected_proxy_list);
}

TEST_F(WindowsSystemProxyResolutionServiceTest, ResolveProxyWithResults) {
  ProxyList expected_proxy_list;
  const ProxyServer proxy_server =
      PacResultElementToProxyServer("HTTPS foopy:8443");
  resolver()->add_server_to_proxy_list(proxy_server);
  expected_proxy_list.AddProxyServer(proxy_server);

  DoResolveProxyTest(expected_proxy_list);
}

TEST_F(WindowsSystemProxyResolutionServiceTest,
       MultipleProxyResolutionRequests) {
  ProxyList expected_proxy_list;
  const ProxyServer proxy_server =
      PacResultElementToProxyServer("HTTPS foopy:8443");
  resolver()->add_server_to_proxy_list(proxy_server);
  expected_proxy_list.AddProxyServer(proxy_server);
  NetLogWithSource log;

  ProxyInfo first_proxy_info;
  TestCompletionCallback first_callback;
  std::unique_ptr<ProxyResolutionRequest> first_request;
  int result = service()->ResolveProxy(
      kResourceUrl, std::string(), NetworkAnonymizationKey(), &first_proxy_info,
      first_callback.callback(), &first_request, log);
  ASSERT_THAT(result, IsError(ERR_IO_PENDING));
  ASSERT_NE(first_request, nullptr);

  ProxyInfo second_proxy_info;
  TestCompletionCallback second_callback;
  std::unique_ptr<ProxyResolutionRequest> second_request;
  result = service()->ResolveProxy(
      kResourceUrl, std::string(), NetworkAnonymizationKey(), &second_proxy_info,
      second_callback.callback(), &second_request, log);
  ASSERT_THAT(result, IsError(ERR_IO_PENDING));
  ASSERT_NE(second_request, nullptr);

  // Wait for results to come back.
  EXPECT_THAT(first_callback.GetResult(result), IsOk());
  EXPECT_THAT(second_callback.GetResult(result), IsOk());

  EXPECT_TRUE(expected_proxy_list.Equals(first_proxy_info.proxy_list()));
  EXPECT_NE(first_request, nullptr);
  EXPECT_TRUE(expected_proxy_list.Equals(second_proxy_info.proxy_list()));
  EXPECT_NE(second_request, nullptr);
}

TEST_F(WindowsSystemProxyResolutionServiceTest,
       ProxyResolutionServiceDestructionWithInFlightRequests) {
  ProxyList expected_proxy_list;
  const ProxyServer proxy_server =
      PacResultElementToProxyServer("HTTPS foopy:8443");
  resolver()->add_server_to_proxy_list(proxy_server);
  expected_proxy_list.AddProxyServer(proxy_server);
  NetLogWithSource log;

  ProxyInfo first_proxy_info;
  TestCompletionCallback first_callback;
  std::unique_ptr<ProxyResolutionRequest> first_request;
  int result = service()->ResolveProxy(
      kResourceUrl, std::string(), NetworkAnonymizationKey(), &first_proxy_info,
      first_callback.callback(), &first_request, log);
  ASSERT_THAT(result, IsError(ERR_IO_PENDING));
  ASSERT_NE(first_request, nullptr);

  ProxyInfo second_proxy_info;
  TestCompletionCallback second_callback;
  std::unique_ptr<ProxyResolutionRequest> second_request;
  result = service()->ResolveProxy(
      kResourceUrl, std::string(), NetworkAnonymizationKey(), &second_proxy_info,
      second_callback.callback(), &second_request, log);
  ASSERT_THAT(result, IsError(ERR_IO_PENDING));
  ASSERT_NE(second_request, nullptr);

  // There are now 2 in-flight proxy resolution requests. Deleting the proxy
  // resolution service should call the callbacks immediately and do any
  // appropriate error handling.
  ResetProxyResolutionService();
  EXPECT_TRUE(first_callback.have_result());
  EXPECT_TRUE(second_callback.have_result());

  EXPECT_TRUE(first_proxy_info.is_direct());
  EXPECT_TRUE(second_proxy_info.is_direct());
}

TEST_F(WindowsSystemProxyResolutionServiceTest,
       CastToConfiguredProxyResolutionService) {
  auto configured_service = ConfiguredProxyResolutionService::CreateDirect();
  ConfiguredProxyResolutionService* casted_service = configured_service.get();
  EXPECT_FALSE(
      service()->CastToConfiguredProxyResolutionService(&casted_service));
  EXPECT_EQ(nullptr, casted_service);
}

}  // namespace net