// 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.

#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_metrics.h"

#include <memory>
#include <vector>

#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config_test_utils.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params_test_utils.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_server.h"
#include "net/base/load_flags.h"
#include "net/base/proxy_server.h"
#include "net/log/net_log_source_type.h"
#include "net/log/net_log_with_source.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "net/socket/socket_test_util.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

using net::MockRead;

namespace data_reduction_proxy {

TEST(ChromeNetworkDailyDataSavingMetricsTest,
     GetDataReductionProxyRequestType) {
  base::MessageLoopForIO message_loop;
  std::unique_ptr<DataReductionProxyTestContext> test_context =
      DataReductionProxyTestContext::Builder()
          .Build();
  TestDataReductionProxyConfig* config = test_context->config();
  config->test_params()->UseNonSecureProxiesForHttp();

  net::ProxyServer origin =
      config->test_params()->proxies_for_http().front().proxy_server();
  net::ProxyConfig data_reduction_proxy_config;
  data_reduction_proxy_config.proxy_rules().ParseFromString(
      "http=" + origin.host_port_pair().ToString() + ",direct://");
  data_reduction_proxy_config.proxy_rules().bypass_rules.ParseFromString(
      "localbypass.com");

  struct TestCase {
    GURL url;
    net::ProxyServer proxy_server;
    base::TimeDelta bypass_duration;  // 0 indicates not bypassed.
    int load_flags;
    const char* response_headers;
    DataReductionProxyRequestType expected_request_type;
  };
  const TestCase test_cases[] = {
      {
          GURL("http://foo.com"), origin, base::TimeDelta(), net::LOAD_NORMAL,
          "HTTP/1.1 200 OK\r\nVia: 1.1 Chrome-Compression-Proxy\r\n\r\n",
          VIA_DATA_REDUCTION_PROXY,
      },
      {
          GURL("https://foo.com"), net::ProxyServer::Direct(),
          base::TimeDelta(), net::LOAD_NORMAL, "HTTP/1.1 200 OK\r\n\r\n", HTTPS,
      },
      {
          GURL("http://foo.com"), net::ProxyServer::Direct(),
          base::TimeDelta::FromSeconds(1), net::LOAD_NORMAL,
          "HTTP/1.1 200 OK\r\n\r\n", DIRECT_HTTP,
      },
      {
          GURL("http://foo.com"), net::ProxyServer::Direct(),
          base::TimeDelta::FromSeconds(1), net::LOAD_NORMAL,
          "HTTP/1.1 304 Not Modified\r\n\r\n", DIRECT_HTTP,
      },
      {
          GURL("http://foo.com"), net::ProxyServer::Direct(),
          base::TimeDelta::FromMinutes(60), net::LOAD_NORMAL,
          "HTTP/1.1 200 OK\r\n\r\n", DIRECT_HTTP,
      },
      {
          GURL("http://foo.com"), net::ProxyServer::Direct(),
          base::TimeDelta::FromMinutes(60), net::LOAD_NORMAL,
          "HTTP/1.1 304 Not Modified\r\n\r\n", DIRECT_HTTP,
      },
      {
          GURL("http://foo.com"), origin, base::TimeDelta::FromSeconds(1),
          net::LOAD_NORMAL, "HTTP/1.1 200 OK\r\n\r\n", SHORT_BYPASS,
      },
      {
          GURL("http://foo.com"), origin, base::TimeDelta::FromSeconds(1),
          net::LOAD_NORMAL, "HTTP/1.1 304 Not Modified\r\n\r\n", SHORT_BYPASS,
      },
      {
          GURL("http://foo.com"), origin, base::TimeDelta::FromMinutes(60),
          net::LOAD_NORMAL, "HTTP/1.1 200 OK\r\n\r\n", LONG_BYPASS,
      },
      {
          GURL("http://foo.com"), origin, base::TimeDelta::FromMinutes(60),
          net::LOAD_NORMAL, "HTTP/1.1 304 Not Modified\r\n\r\n", LONG_BYPASS,
      },  // Requests with LOAD_BYPASS_PROXY (e.g. block-once) should be
          // classified
      // as SHORT_BYPASS.
      {
          GURL("http://foo.com"), net::ProxyServer::Direct(), base::TimeDelta(),
          net::LOAD_BYPASS_PROXY, "HTTP/1.1 200 OK\r\n\r\n", SHORT_BYPASS,
      },
      // Another proxy overriding the Data Reduction Proxy should be classified
      // as SHORT_BYPASS.
      {
          GURL("http://foo.com"),
          net::ProxyServer::FromPacString("PROXY otherproxy.net:80"),
          base::TimeDelta(), net::LOAD_NORMAL, "HTTP/1.1 200 OK\r\n\r\n",
          SHORT_BYPASS,
      },
      // Bypasses due to local bypass rules should be classified as
      // SHORT_BYPASS.
      {
          GURL("http://localbypass.com"), net::ProxyServer::Direct(),
          base::TimeDelta(), net::LOAD_NORMAL, "HTTP/1.1 200 OK\r\n\r\n",
          SHORT_BYPASS,
      },
      {
          GURL("http://foo.com"), net::ProxyServer::Direct(), base::TimeDelta(),
          net::LOAD_NORMAL, "HTTP/1.1 200 OK\r\n\r\n", DIRECT_HTTP,
      },
      // Responses that seem like they should have come through the Data
      // Reduction Proxy, but did not, should be classified as UNKNOWN_TYPE.
      {
          GURL("http://foo.com"), origin, base::TimeDelta(), net::LOAD_NORMAL,
          "HTTP/1.1 200 OK\r\n\r\n", UNKNOWN_TYPE,
      },
      // The proxy is currently bypassed, but the response still came through
      // the Data Reduction Proxy, so it should be classified as
      // VIA_DATA_REDUCTION_PROXY.
      {
          GURL("http://foo.com"), origin, base::TimeDelta::FromMinutes(60),
          net::LOAD_NORMAL,
          "HTTP/1.1 200 OK\r\nVia: 1.1 Chrome-Compression-Proxy\r\n\r\n",
          VIA_DATA_REDUCTION_PROXY,
      },
      // The request was sent over a direct connection, but the response still
      // has the Data Reduction Proxy Via header, so it should be classified as
      // VIA_DATA_REDUCTION_PROXY.
      {
          GURL("http://foo.com"), net::ProxyServer::Direct(), base::TimeDelta(),
          net::LOAD_NORMAL,
          "HTTP/1.1 200 OK\r\nVia: 1.1 Chrome-Compression-Proxy\r\n\r\n",
          VIA_DATA_REDUCTION_PROXY,
      },
      // A 304 response with a DRP Via header should be classified as
      // VIA_DATA_REDUCTION_PROXY.
      {
          GURL("http://foo.com"), net::ProxyServer::Direct(), base::TimeDelta(),
          net::LOAD_NORMAL,
          "HTTP/1.1 304 Not Modified\r\n"
          "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
          VIA_DATA_REDUCTION_PROXY,
      },
      // A 304 response without a DRP Via header where the request was sent
      // through the DRP should be classified as VIA_DATA_REDUCTION_PROXY.
      {
          GURL("http://foo.com"), origin, base::TimeDelta(), net::LOAD_NORMAL,
          "HTTP/1.1 304 Not Modified\r\n\r\n", VIA_DATA_REDUCTION_PROXY,
      },
  };

  for (const TestCase& test_case : test_cases) {
    net::TestURLRequestContext context(true);
    net::MockClientSocketFactory mock_socket_factory;
    context.set_client_socket_factory(&mock_socket_factory);
    // Set the |proxy_resolution_service| to use |test_case.proxy_server| for requests.
    std::unique_ptr<net::ProxyResolutionService> proxy_resolution_service(
        net::ProxyResolutionService::CreateFixedFromPacResult(
            test_case.proxy_server.ToPacString(),
            TRAFFIC_ANNOTATION_FOR_TESTS));
    context.set_proxy_resolution_service(proxy_resolution_service.get());
    context.Init();

    // Create a fake URLRequest and fill it with the appropriate response
    // headers and proxy server by executing it with fake socket data.
    net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
    if (test_case.url.SchemeIsCryptographic())
      mock_socket_factory.AddSSLSocketDataProvider(&ssl_socket_data_provider);
    MockRead mock_reads[] = {
        MockRead(test_case.response_headers),
        MockRead("hello world"),
        MockRead(net::SYNCHRONOUS, net::OK),
    };
    net::StaticSocketDataProvider socket_data_provider(
        mock_reads, base::span<net::MockWrite>());
    mock_socket_factory.AddSocketDataProvider(&socket_data_provider);

    net::TestDelegate delegate;
    std::unique_ptr<net::URLRequest> request = context.CreateRequest(
        test_case.url, net::IDLE, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS);
    request->SetLoadFlags(test_case.load_flags);
    request->Start();
    test_context->RunUntilIdle();

    // Mark the Data Reduction Proxy as bad if the test specifies to.
    if (test_case.bypass_duration > base::TimeDelta()) {
      net::ProxyInfo proxy_info;
      proxy_info.UseProxyList(
          data_reduction_proxy_config.proxy_rules().proxies_for_http);
      EXPECT_TRUE(context.proxy_resolution_service()->MarkProxiesAsBadUntil(
          proxy_info, test_case.bypass_duration,
          std::vector<net::ProxyServer>(),
          net::NetLogWithSource::Make(context.net_log(),
                                      net::NetLogSourceType::NONE)));
    }

    EXPECT_EQ(test_case.expected_request_type,
              GetDataReductionProxyRequestType(
                  *request, data_reduction_proxy_config,
                  *test_context->config()));
  }
}

}  // namespace data_reduction_proxy
