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

#include "components/metrics/net/net_metrics_log_uploader.h"

#include <memory>

#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/encrypted_messages/encrypted_message.pb.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/reporting_info.pb.h"
#include "third_party/zlib/google/compression_utils.h"
#include "url/gurl.h"

namespace metrics {

class NetMetricsLogUploaderTest : public testing::Test {
 public:
  NetMetricsLogUploaderTest()
      : on_upload_complete_count_(0),
        test_shared_url_loader_factory_(
            base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
                &test_url_loader_factory_)) {
    test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting(
        [&](const network::ResourceRequest& request) {
          upload_data_ = network::GetUploadData(request);
          headers_ = request.headers;
          loop_.Quit();
        }));
  }

  NetMetricsLogUploaderTest(const NetMetricsLogUploaderTest&) = delete;
  NetMetricsLogUploaderTest& operator=(const NetMetricsLogUploaderTest&) =
      delete;

  void CreateAndOnUploadCompleteReuseUploader() {
    ReportingInfo reporting_info;
    reporting_info.set_attempt_count(10);
    uploader_ = std::make_unique<NetMetricsLogUploader>(
        test_shared_url_loader_factory_, GURL("https://dummy_server"),
        "dummy_mime", MetricsLogUploader::UMA,
        base::BindRepeating(
            &NetMetricsLogUploaderTest::OnUploadCompleteReuseUploader,
            base::Unretained(this)));
    uploader_->UploadLog("initial_dummy_data", LogMetadata(),
                         "initial_dummy_hash", "initial_dummy_signature",
                         reporting_info);
  }

  void CreateUploaderAndUploadToSecureURL(const std::string& url) {
    ReportingInfo dummy_reporting_info;
    uploader_ = std::make_unique<NetMetricsLogUploader>(
        test_shared_url_loader_factory_, GURL(url), "dummy_mime",
        MetricsLogUploader::UMA,
        base::BindRepeating(&NetMetricsLogUploaderTest::DummyOnUploadComplete,
                            base::Unretained(this)));
    uploader_->UploadLog("dummy_data", LogMetadata(), "dummy_hash",
                         "dummy_signature", dummy_reporting_info);
  }

  void CreateUploaderAndUploadToInsecureURL() {
    ReportingInfo dummy_reporting_info;
    uploader_ = std::make_unique<NetMetricsLogUploader>(
        test_shared_url_loader_factory_, GURL("http://dummy_insecure_server"),
        "dummy_mime", MetricsLogUploader::UMA,
        base::BindRepeating(&NetMetricsLogUploaderTest::DummyOnUploadComplete,
                            base::Unretained(this)));
    std::string compressed_message;
    // Compress the data since the encryption code expects a compressed log,
    // and tries to decompress it before encrypting it.
    compression::GzipCompress("dummy_data", &compressed_message);
    uploader_->UploadLog(compressed_message, LogMetadata(), "dummy_hash",
                         "dummy_signature", dummy_reporting_info);
  }

  void DummyOnUploadComplete(int response_code,
                             int error_code,
                             bool was_https,
                             bool force_discard,
                             base::StringPiece force_discard_reason) {
    log_was_force_discarded_ = force_discard;
  }

  void OnUploadCompleteReuseUploader(int response_code,
                                     int error_code,
                                     bool was_https,
                                     bool force_discard,
                                     base::StringPiece force_discard_reason) {
    ++on_upload_complete_count_;
    if (on_upload_complete_count_ == 1) {
      ReportingInfo reporting_info;
      reporting_info.set_attempt_count(20);
      uploader_->UploadLog("dummy_data", LogMetadata(), "dummy_hash",
                           "dummy_signature", reporting_info);
    }
    log_was_force_discarded_ = force_discard;
  }

  int on_upload_complete_count() const {
    return on_upload_complete_count_;
  }

  network::TestURLLoaderFactory* test_url_loader_factory() {
    return &test_url_loader_factory_;
  }

  const net::HttpRequestHeaders& last_request_headers() { return headers_; }

  const std::string& last_upload_data() { return upload_data_; }

  void WaitForRequest() { loop_.Run(); }

  bool log_was_force_discarded() { return log_was_force_discarded_; }

 private:
  std::unique_ptr<NetMetricsLogUploader> uploader_;
  int on_upload_complete_count_;

  network::TestURLLoaderFactory test_url_loader_factory_;
  scoped_refptr<network::SharedURLLoaderFactory>
      test_shared_url_loader_factory_;

  base::test::TaskEnvironment task_environment_;

  base::RunLoop loop_;
  std::string upload_data_;
  net::HttpRequestHeaders headers_;
  bool log_was_force_discarded_ = false;
};

void CheckReportingInfoHeader(net::HttpRequestHeaders headers,
                              int expected_attempt_count) {
  std::string reporting_info_base64;
  EXPECT_TRUE(
      headers.GetHeader("X-Chrome-UMA-ReportingInfo", &reporting_info_base64));
  std::string reporting_info_string;
  EXPECT_TRUE(
      base::Base64Decode(reporting_info_base64, &reporting_info_string));
  ReportingInfo reporting_info;
  EXPECT_TRUE(reporting_info.ParseFromString(reporting_info_string));
  EXPECT_EQ(reporting_info.attempt_count(), expected_attempt_count);
}

TEST_F(NetMetricsLogUploaderTest, OnUploadCompleteReuseUploader) {
  CreateAndOnUploadCompleteReuseUploader();
  WaitForRequest();

  // Mimic the initial fetcher callback.
  CheckReportingInfoHeader(last_request_headers(), 10);
  auto* pending_request_0 = test_url_loader_factory()->GetPendingRequest(0);
  test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList(
      pending_request_0, "");

  // Mimic the second fetcher callback.
  CheckReportingInfoHeader(last_request_headers(), 20);
  auto* pending_request_1 = test_url_loader_factory()->GetPendingRequest(1);
  test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList(
      pending_request_1, "");

  EXPECT_EQ(on_upload_complete_count(), 2);
  EXPECT_FALSE(log_was_force_discarded());
}

// Verifies that when no server URLs are specified, the logs are forcibly
// discarded.
TEST_F(NetMetricsLogUploaderTest, ForceDiscard) {
  CreateUploaderAndUploadToSecureURL(/*url=*/"");
  WaitForRequest();

  // Mimic the initial fetcher callback.
  auto* pending_request_0 = test_url_loader_factory()->GetPendingRequest(0);
  test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList(
      pending_request_0, "");

  EXPECT_TRUE(log_was_force_discarded());
}

// Test that attempting to upload to an HTTP URL results in an encrypted
// message.
TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPIsEncrypted) {
  CreateUploaderAndUploadToInsecureURL();
  WaitForRequest();
  encrypted_messages::EncryptedMessage message;
  EXPECT_TRUE(message.ParseFromString(last_upload_data()));
}

// Test that attempting to upload to an HTTPS URL results in an unencrypted
// message.
TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPSIsNotEncrypted) {
  CreateUploaderAndUploadToSecureURL("https://dummy_secure_server");
  WaitForRequest();
  EXPECT_EQ(last_upload_data(), "dummy_data");
}

// Test that attempting to upload to localhost over http results in an
// unencrypted message.
TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPLocalhostIsNotEncrypted) {
  CreateUploaderAndUploadToSecureURL("http://localhost");
  WaitForRequest();
  EXPECT_EQ(last_upload_data(), "dummy_data");
}

}  // namespace metrics
