// Copyright (c) 2013 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/net_log/net_export_file_writer.h"

#include <stdint.h>

#include <memory>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/scoped_task_environment.h"
#include "base/values.h"
#include "build/build_config.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "net/base/network_change_notifier.h"
#include "net/base/test_completion_callback.h"
#include "net/log/net_log_capture_mode.h"
#include "net/log/net_log_event_type.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/network_context.h"
#include "services/network/network_service.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/test/test_network_context.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

const char kChannelString[] = "SomeChannel";
const size_t kMaxLogSizeBytes = 100 * 1024 * 1024;  // 100MiB

// Keep this in sync with kLogRelativePath defined in net_export_file_writer.cc.
base::FilePath::CharType kLogRelativePath[] =
    FILE_PATH_LITERAL("net-export/chrome-net-export-log.json");

const char kCaptureModeDefaultString[] = "STRIP_PRIVATE_DATA";
const char kCaptureModeIncludeSensitiveString[] = "NORMAL";
const char kCaptureModeIncludeEverythingString[] = "LOG_BYTES";

const char kStateUninitializedString[] = "UNINITIALIZED";
const char kStateInitializingString[] = "INITIALIZING";
const char kStateNotLoggingString[] = "NOT_LOGGING";
const char kStateStartingLogString[] = "STARTING_LOG";
const char kStateLoggingString[] = "LOGGING";
const char kStateStoppingLogString[] = "STOPPING_LOG";
}  // namespace

namespace net_log {

class FakeNetLogExporter : public network::mojom::NetLogExporter {
 public:
  FakeNetLogExporter() {}
  ~FakeNetLogExporter() override {}

  void Start(base::File destination,
             base::Value extra_constants,
             net::NetLogCaptureMode capture_mode,
             uint64_t max_file_size,
             StartCallback callback) override {
    std::move(callback).Run(net::OK);
  }

  void Stop(base::Value polled_values, StopCallback callback) override {
    std::move(callback).Run(net::OK);
  }
};

class FakeNetworkContext : public network::TestNetworkContext {
 public:
  void CreateNetLogExporter(
      network::mojom::NetLogExporterRequest request) override {
    binding_ = mojo::StrongBinding<network::mojom::NetLogExporter>::Create(
        std::make_unique<FakeNetLogExporter>(), std::move(request));
  }

  void Disconnect() { binding_->Close(); }

 private:
  mojo::StrongBindingPtr<network::mojom::NetLogExporter> binding_;
};

// Sets |path| to |path_to_return| and always returns true. This function is
// used to override NetExportFileWriter's usual getter for the default log base
// directory.
bool SetPathToGivenAndReturnTrue(const base::FilePath& path_to_return,
                                 base::FilePath* path) {
  *path = path_to_return;
  return true;
}

// Checks the "state" string of a NetExportFileWriter state.
WARN_UNUSED_RESULT ::testing::AssertionResult VerifyState(
    std::unique_ptr<base::DictionaryValue> state,
    const std::string& expected_state_string) {
  std::string actual_state_string;
  if (!state->GetString("state", &actual_state_string)) {
    return ::testing::AssertionFailure()
           << "State is missing \"state\" string.";
  }
  if (actual_state_string != expected_state_string) {
    return ::testing::AssertionFailure()
           << "\"state\" string of state does not match expected." << std::endl
           << "    Actual: " << actual_state_string << std::endl
           << "  Expected: " << expected_state_string;
  }
  return ::testing::AssertionSuccess();
}

// Checks all fields of a NetExportFileWriter state except possibly the
// "captureMode" string; that field is only checked if
// |expected_log_capture_mode_known| is true.
WARN_UNUSED_RESULT ::testing::AssertionResult VerifyState(
    std::unique_ptr<base::DictionaryValue> state,
    const std::string& expected_state_string,
    bool expected_log_exists,
    bool expected_log_capture_mode_known,
    const std::string& expected_log_capture_mode_string) {
  base::DictionaryValue expected_state;
  expected_state.SetString("state", expected_state_string);
  expected_state.SetBoolean("logExists", expected_log_exists);
  expected_state.SetBoolean("logCaptureModeKnown",
                            expected_log_capture_mode_known);
  if (expected_log_capture_mode_known) {
    expected_state.SetString("captureMode", expected_log_capture_mode_string);
  } else {
    state->Remove("captureMode", nullptr);
  }

  // Remove "file" field which is only added in debug mode.
  state->Remove("file", nullptr);

  std::string expected_state_json_string;
  JSONStringValueSerializer expected_state_serializer(
      &expected_state_json_string);
  expected_state_serializer.Serialize(expected_state);

  std::string actual_state_json_string;
  JSONStringValueSerializer actual_state_serializer(&actual_state_json_string);
  actual_state_serializer.Serialize(*state);

  if (actual_state_json_string != expected_state_json_string) {
    return ::testing::AssertionFailure()
           << "State (possibly excluding \"captureMode\") does not match "
              "expected."
           << std::endl
           << "    Actual: " << actual_state_json_string << std::endl
           << "  Expected: " << expected_state_json_string;
  }
  return ::testing::AssertionSuccess();
}

WARN_UNUSED_RESULT ::testing::AssertionResult ReadCompleteLogFile(
    const base::FilePath& log_path,
    std::unique_ptr<base::DictionaryValue>* root) {
  DCHECK(!log_path.empty());

  if (!base::PathExists(log_path)) {
    return ::testing::AssertionFailure()
           << log_path.value() << " does not exist.";
  }
  // Parse log file contents into a dictionary
  std::string log_string;
  if (!base::ReadFileToString(log_path, &log_string)) {
    return ::testing::AssertionFailure()
           << log_path.value() << " could not be read.";
  }
  *root =
      base::DictionaryValue::From(base::JSONReader::ReadDeprecated(log_string));
  if (!*root) {
    return ::testing::AssertionFailure()
           << "Contents of " << log_path.value()
           << " do not form valid JSON dictionary.";
  }
  // Make sure the "constants" section exists
  base::DictionaryValue* constants;
  if (!(*root)->GetDictionary("constants", &constants)) {
    root->reset();
    return ::testing::AssertionFailure()
           << log_path.value() << " is missing constants.";
  }
  // Make sure the "events" section exists
  base::ListValue* events;
  if (!(*root)->GetList("events", &events)) {
    root->reset();
    return ::testing::AssertionFailure()
           << log_path.value() << " is missing events list.";
  }
  return ::testing::AssertionSuccess();
}

// An implementation of NetExportFileWriter::StateObserver that allows waiting
// until it's notified of a new state.
class TestStateObserver : public NetExportFileWriter::StateObserver {
 public:
  // NetExportFileWriter::StateObserver implementation
  void OnNewState(const base::DictionaryValue& state) override {
    test_closure_.closure().Run();
    result_state_ = state.CreateDeepCopy();
  }

  std::unique_ptr<base::DictionaryValue> WaitForNewState() {
    test_closure_.WaitForResult();
    DCHECK(result_state_);
    return std::move(result_state_);
  }

 private:
  net::TestClosure test_closure_;
  std::unique_ptr<base::DictionaryValue> result_state_;
};

// A class that wraps around TestClosure. Provides the ability to wait on a
// file path callback and retrieve the result.
class TestFilePathCallback {
 public:
  TestFilePathCallback()
      : callback_(base::Bind(&TestFilePathCallback::SetResultThenNotify,
                             base::Unretained(this))) {}

  const base::Callback<void(const base::FilePath&)>& callback() const {
    return callback_;
  }

  const base::FilePath& WaitForResult() {
    test_closure_.WaitForResult();
    return result_;
  }

 private:
  void SetResultThenNotify(const base::FilePath& result) {
    result_ = result;
    test_closure_.closure().Run();
  }

  net::TestClosure test_closure_;
  base::FilePath result_;
  base::Callback<void(const base::FilePath&)> callback_;
};

class NetExportFileWriterTest : public ::testing::Test {
 public:
  NetExportFileWriterTest()
      : scoped_task_environment_(
            base::test::ScopedTaskEnvironment::MainThreadType::IO),
        network_change_notifier_(net::NetworkChangeNotifier::CreateMock()),
        network_service_(network::NetworkService::CreateForTesting()) {}

  // ::testing::Test implementation
  void SetUp() override {
    ASSERT_TRUE(log_temp_dir_.CreateUniqueTempDir());
    network::mojom::NetworkContextParamsPtr params =
        network::mojom::NetworkContextParams::New();
    // Use a fixed proxy config, to avoid dependencies on local network
    // configuration.
    params->initial_proxy_config =
        net::ProxyConfigWithAnnotation::CreateDirect();
    network_context_ = std::make_unique<network::NetworkContext>(
        network_service_.get(), mojo::MakeRequest(&network_context_ptr_),
        std::move(params));

    // Override |file_writer_|'s default-log-base-directory-getter to
    // a getter that returns the temp dir created for the test.
    file_writer_.SetDefaultLogBaseDirectoryGetterForTest(
        base::Bind(&SetPathToGivenAndReturnTrue, log_temp_dir_.GetPath()));

    default_log_path_ = log_temp_dir_.GetPath().Append(kLogRelativePath);

    file_writer_.AddObserver(&test_state_observer_);

    ASSERT_TRUE(VerifyState(file_writer_.GetState(), kStateUninitializedString,
                            false, false, ""));
  }

  // ::testing::Test implementation
  void TearDown() override {
    file_writer_.RemoveObserver(&test_state_observer_);
    ASSERT_TRUE(log_temp_dir_.Delete());
  }

  base::FilePath FileWriterGetFilePathToCompletedLog() {
    TestFilePathCallback test_callback;
    file_writer_.GetFilePathToCompletedLog(test_callback.callback());
    return test_callback.WaitForResult();
  }

  WARN_UNUSED_RESULT ::testing::AssertionResult InitializeThenVerifyNewState(
      bool expected_initialize_success,
      bool expected_log_exists) {
    file_writer_.Initialize();
    std::unique_ptr<base::DictionaryValue> state =
        test_state_observer_.WaitForNewState();
    ::testing::AssertionResult result =
        VerifyState(std::move(state), kStateInitializingString);
    if (!result) {
      return ::testing::AssertionFailure()
             << "First state after Initialize() does not match expected:"
             << std::endl
             << result.message();
    }

    state = test_state_observer_.WaitForNewState();
    result =
        VerifyState(std::move(state),
                    expected_initialize_success ? kStateNotLoggingString
                                                : kStateUninitializedString,
                    expected_log_exists, false, "");
    if (!result) {
      return ::testing::AssertionFailure()
             << "Second state after Initialize() does not match expected:"
             << std::endl
             << result.message();
    }

    return ::testing::AssertionSuccess();
  }

  // If |custom_log_path| is empty path, |file_writer_| will use its
  // default log path, which is cached in |default_log_path_|.
  WARN_UNUSED_RESULT::testing::AssertionResult StartThenVerifyNewState(
      const base::FilePath& custom_log_path,
      net::NetLogCaptureMode capture_mode,
      const std::string& expected_capture_mode_string,
      network::mojom::NetworkContext* network_context) {
    file_writer_.StartNetLog(custom_log_path, capture_mode, kMaxLogSizeBytes,
                             base::CommandLine::StringType(), kChannelString,
                             network_context);
    std::unique_ptr<base::DictionaryValue> state =
        test_state_observer_.WaitForNewState();
    ::testing::AssertionResult result =
        VerifyState(std::move(state), kStateStartingLogString);
    if (!result) {
      return ::testing::AssertionFailure()
             << "First state after StartNetLog() does not match expected:"
             << std::endl
             << result.message();
    }

    state = test_state_observer_.WaitForNewState();
    result = VerifyState(std::move(state), kStateLoggingString, true, true,
                         expected_capture_mode_string);
    if (!result) {
      return ::testing::AssertionFailure()
             << "Second state after StartNetLog() does not match expected:"
             << std::endl
             << result.message();
    }

    // Make sure GetFilePath() returns empty path when logging.
    base::FilePath actual_log_path = FileWriterGetFilePathToCompletedLog();
    if (!actual_log_path.empty()) {
      return ::testing::AssertionFailure()
             << "GetFilePath() should return empty path after logging starts."
             << " Actual: " << ::testing::PrintToString(actual_log_path);
    }

    return ::testing::AssertionSuccess();
  }

  // If |custom_log_path| is empty path, it's assumed the log file with be at
  // |default_log_path_|.
  WARN_UNUSED_RESULT ::testing::AssertionResult StopThenVerifyNewStateAndFile(
      const base::FilePath& custom_log_path,
      std::unique_ptr<base::DictionaryValue> polled_data,
      const std::string& expected_capture_mode_string) {
    file_writer_.StopNetLog(std::move(polled_data));
    std::unique_ptr<base::DictionaryValue> state =
        test_state_observer_.WaitForNewState();
    ::testing::AssertionResult result =
        VerifyState(std::move(state), kStateStoppingLogString);
    if (!result) {
      return ::testing::AssertionFailure()
             << "First state after StopNetLog() does not match expected:"
             << std::endl
             << result.message();
    }

    state = test_state_observer_.WaitForNewState();
    result = VerifyState(std::move(state), kStateNotLoggingString, true, true,
                         expected_capture_mode_string);
    if (!result) {
      return ::testing::AssertionFailure()
             << "Second state after StopNetLog() does not match expected:"
             << std::endl
             << result.message();
    }

    // Make sure GetFilePath() returns the expected path.
    const base::FilePath& expected_log_path =
        custom_log_path.empty() ? default_log_path_ : custom_log_path;
    base::FilePath actual_log_path = FileWriterGetFilePathToCompletedLog();
    if (actual_log_path != expected_log_path) {
      return ::testing::AssertionFailure()
             << "GetFilePath() returned incorrect path after logging stopped."
             << std::endl
             << "    Actual: " << ::testing::PrintToString(actual_log_path)
             << std::endl
             << "  Expected: " << ::testing::PrintToString(expected_log_path);
    }

    // Make sure the generated log file is valid.
    std::unique_ptr<base::DictionaryValue> root;
    result = ReadCompleteLogFile(expected_log_path, &root);
    if (!result) {
      return ::testing::AssertionFailure()
             << "Log file after logging stopped is not valid:" << std::endl
             << result.message();
    }

    return ::testing::AssertionSuccess();
  }

  net::NetLog* net_log() { return network_service_->net_log(); }

  NetExportFileWriter* file_writer() { return &file_writer_; }

  const base::FilePath& GetLogTempDirPath() const {
    return log_temp_dir_.GetPath();
  }

  const base::FilePath& default_log_path() const { return default_log_path_; }

  TestStateObserver* test_state_observer() { return &test_state_observer_; }

  network::mojom::NetworkContext* network_context() {
    return network_context_ptr_.get();
  }

 private:
  base::test::ScopedTaskEnvironment scoped_task_environment_;
  // Use a mock NetworkChangeNotifier so the real one can't add any logging.
  std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_;
  std::unique_ptr<network::NetworkService> network_service_;

  network::mojom::NetworkContextPtr network_context_ptr_;
  std::unique_ptr<network::NetworkContext> network_context_;

  // |file_writer_| is initialized after |network_context_ptr_| since
  // |file_writer_| destructor can talk to mojo objects owned by
  // |network_context_|.
  NetExportFileWriter file_writer_;

  base::ScopedTempDir log_temp_dir_;

  // The default log path that |file_writer_| will use is cached here.
  base::FilePath default_log_path_;

  TestStateObserver test_state_observer_;
};

TEST_F(NetExportFileWriterTest, InitFail) {
  // Override file_writer_'s default log base directory getter to always
  // fail.
  file_writer()->SetDefaultLogBaseDirectoryGetterForTest(
      base::Bind([](base::FilePath* path) -> bool { return false; }));

  // Initialization should fail due to the override.
  ASSERT_TRUE(InitializeThenVerifyNewState(false, false));

  // NetExportFileWriter::GetFilePath() should return empty path if
  // uninitialized.
  EXPECT_TRUE(FileWriterGetFilePathToCompletedLog().empty());
}

TEST_F(NetExportFileWriterTest, InitWithoutExistingLog) {
  ASSERT_TRUE(InitializeThenVerifyNewState(true, false));

  // NetExportFileWriter::GetFilePathToCompletedLog() should return empty path
  // when no log file exists.
  EXPECT_TRUE(FileWriterGetFilePathToCompletedLog().empty());
}

TEST_F(NetExportFileWriterTest, InitWithExistingLog) {
  // Create and close an empty log file to simulate existence of a previous log
  // file.
  ASSERT_TRUE(
      base::CreateDirectoryAndGetError(default_log_path().DirName(), nullptr));
  base::ScopedFILE empty_file(base::OpenFile(default_log_path(), "w"));
  ASSERT_TRUE(empty_file.get());
  empty_file.reset();

  ASSERT_TRUE(InitializeThenVerifyNewState(true, true));

  EXPECT_EQ(default_log_path(), FileWriterGetFilePathToCompletedLog());
}

TEST_F(NetExportFileWriterTest, StartAndStopWithAllCaptureModes) {
  const net::NetLogCaptureMode capture_modes[3] = {
      net::NetLogCaptureMode::kDefault,
      net::NetLogCaptureMode::kIncludeSensitive,
      net::NetLogCaptureMode::kEverything};

  const std::string capture_mode_strings[3] = {
      kCaptureModeDefaultString, kCaptureModeIncludeSensitiveString,
      kCaptureModeIncludeEverythingString};

  ASSERT_TRUE(InitializeThenVerifyNewState(true, false));

  // For each capture mode, start and stop |file_writer_| in that mode.
  for (int i = 0; i < 3; ++i) {
    // StartNetLog(), should result in state change.
    ASSERT_TRUE(StartThenVerifyNewState(base::FilePath(), capture_modes[i],
                                        capture_mode_strings[i],
                                        network_context()));

    // Calling StartNetLog() again should be a no-op. Try doing StartNetLog()
    // with various capture modes; they should all be ignored and result in no
    // state change.
    file_writer()->StartNetLog(
        base::FilePath(), capture_modes[i], kMaxLogSizeBytes,
        base::CommandLine::StringType(), kChannelString, network_context());
    file_writer()->StartNetLog(
        base::FilePath(), capture_modes[(i + 1) % 3], kMaxLogSizeBytes,
        base::CommandLine::StringType(), kChannelString, network_context());
    file_writer()->StartNetLog(
        base::FilePath(), capture_modes[(i + 2) % 3], kMaxLogSizeBytes,
        base::CommandLine::StringType(), kChannelString, network_context());

    // StopNetLog(), should result in state change. The capture mode should
    // match that of the first StartNetLog() call (called by
    // StartThenVerifyNewState()).
    ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr,
                                              capture_mode_strings[i]));

    // Stopping a second time should be a no-op.
    file_writer()->StopNetLog(nullptr);
  }

  // Start and stop one more time just to make sure the last StopNetLog() call
  // was properly ignored and left |file_writer_| in a valid state.
  ASSERT_TRUE(StartThenVerifyNewState(base::FilePath(), capture_modes[0],
                                      capture_mode_strings[0],
                                      network_context()));

  ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr,
                                            capture_mode_strings[0]));
}

// Verify the file sizes after two consecutive starts/stops are the same (even
// if some junk data is added in between).
TEST_F(NetExportFileWriterTest, StartClearsFile) {
  ASSERT_TRUE(InitializeThenVerifyNewState(true, false));

  ASSERT_TRUE(StartThenVerifyNewState(
      base::FilePath(), net::NetLogCaptureMode::kDefault,
      kCaptureModeDefaultString, network_context()));

  ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr,
                                            kCaptureModeDefaultString));

  int64_t stop_file_size;
  EXPECT_TRUE(base::GetFileSize(default_log_path(), &stop_file_size));

  // Add some junk at the end of the file.
  std::string junk_data("Hello");
  EXPECT_TRUE(base::AppendToFile(default_log_path(), junk_data.c_str(),
                                 junk_data.size()));

  int64_t junk_file_size;
  EXPECT_TRUE(base::GetFileSize(default_log_path(), &junk_file_size));
  EXPECT_GT(junk_file_size, stop_file_size);

  // Start and stop again and make sure the file is back to the size it was
  // before adding the junk data.
  ASSERT_TRUE(StartThenVerifyNewState(
      base::FilePath(), net::NetLogCaptureMode::kDefault,
      kCaptureModeDefaultString, network_context()));

  ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr,
                                            kCaptureModeDefaultString));

  int64_t new_stop_file_size;
  EXPECT_TRUE(base::GetFileSize(default_log_path(), &new_stop_file_size));

  EXPECT_EQ(stop_file_size, new_stop_file_size);
}

// Adds an event to the log file, then checks that the file is larger than
// the file created without that event.
TEST_F(NetExportFileWriterTest, AddEvent) {
  ASSERT_TRUE(InitializeThenVerifyNewState(true, false));

  ASSERT_TRUE(StartThenVerifyNewState(
      base::FilePath(), net::NetLogCaptureMode::kDefault,
      kCaptureModeDefaultString, network_context()));

  ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr,
                                            kCaptureModeDefaultString));

  // Get file size without the event.
  int64_t stop_file_size;
  EXPECT_TRUE(base::GetFileSize(default_log_path(), &stop_file_size));

  ASSERT_TRUE(StartThenVerifyNewState(
      base::FilePath(), net::NetLogCaptureMode::kDefault,
      kCaptureModeDefaultString, network_context()));

  net_log()->AddGlobalEntry(net::NetLogEventType::CANCELLED);

  ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr,
                                            kCaptureModeDefaultString));

  // Get file size after adding the event and make sure it's larger than before.
  int64_t new_stop_file_size;
  EXPECT_TRUE(base::GetFileSize(default_log_path(), &new_stop_file_size));
  EXPECT_GE(new_stop_file_size, stop_file_size);
}

// Using a custom path to make sure logging can still occur when the path has
// changed.
TEST_F(NetExportFileWriterTest, AddEventCustomPath) {
  ASSERT_TRUE(InitializeThenVerifyNewState(true, false));

  base::FilePath::CharType kCustomRelativePath[] =
      FILE_PATH_LITERAL("custom/custom/chrome-net-export-log.json");
  base::FilePath custom_log_path =
      GetLogTempDirPath().Append(kCustomRelativePath);
  EXPECT_TRUE(
      base::CreateDirectoryAndGetError(custom_log_path.DirName(), nullptr));

  ASSERT_TRUE(
      StartThenVerifyNewState(custom_log_path, net::NetLogCaptureMode::kDefault,
                              kCaptureModeDefaultString, network_context()));

  ASSERT_TRUE(StopThenVerifyNewStateAndFile(custom_log_path, nullptr,
                                            kCaptureModeDefaultString));

  // Get file size without the event.
  int64_t stop_file_size;
  EXPECT_TRUE(base::GetFileSize(custom_log_path, &stop_file_size));

  ASSERT_TRUE(
      StartThenVerifyNewState(custom_log_path, net::NetLogCaptureMode::kDefault,
                              kCaptureModeDefaultString, network_context()));

  net_log()->AddGlobalEntry(net::NetLogEventType::CANCELLED);

  ASSERT_TRUE(StopThenVerifyNewStateAndFile(custom_log_path, nullptr,
                                            kCaptureModeDefaultString));

  // Get file size after adding the event and make sure it's larger than before.
  int64_t new_stop_file_size;
  EXPECT_TRUE(base::GetFileSize(custom_log_path, &new_stop_file_size));
  EXPECT_GE(new_stop_file_size, stop_file_size);
}

TEST_F(NetExportFileWriterTest, StopWithPolledData) {
  ASSERT_TRUE(InitializeThenVerifyNewState(true, false));

  // Create dummy polled data
  const char kDummyPolledDataPath[] = "dummy_path";
  const char kDummyPolledDataString[] = "dummy_info";
  std::unique_ptr<base::DictionaryValue> dummy_polled_data =
      std::make_unique<base::DictionaryValue>();
  dummy_polled_data->SetString(kDummyPolledDataPath, kDummyPolledDataString);

  ASSERT_TRUE(StartThenVerifyNewState(
      base::FilePath(), net::NetLogCaptureMode::kDefault,
      kCaptureModeDefaultString, network_context()));

  ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(),
                                            std::move(dummy_polled_data),
                                            kCaptureModeDefaultString));

  // Read polledData from log file.
  std::unique_ptr<base::DictionaryValue> root;
  ASSERT_TRUE(ReadCompleteLogFile(default_log_path(), &root));
  base::DictionaryValue* polled_data;
  ASSERT_TRUE(root->GetDictionary("polledData", &polled_data));

  // Check that it contains the field from the polled data that was passed in.
  std::string dummy_string;
  ASSERT_TRUE(polled_data->GetString(kDummyPolledDataPath, &dummy_string));
  EXPECT_EQ(kDummyPolledDataString, dummy_string);

  // Check that it also contains something from net::GetNetInfo.
  base::DictionaryValue* http_cache_info;
  ASSERT_TRUE(polled_data->GetDictionary("httpCacheInfo", &http_cache_info));
}

// Test with requests in flight. This is done by going through a sequence of a
// redirect --- at which point the log is started --- and then a fetch of
// destination that's blocked on an event in EmbeddedTestServer.
TEST_F(NetExportFileWriterTest, StartWithNetworkContextActive) {
  net::EmbeddedTestServer test_server;
  net::test_server::RegisterDefaultHandlers(&test_server);

  base::WaitableEvent block_fetch(
      base::WaitableEvent::ResetPolicy::MANUAL,
      base::WaitableEvent::InitialState::NOT_SIGNALED);

  test_server.RegisterRequestHandler(base::BindRepeating(
      [](base::WaitableEvent* block_fetch,
         const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        if (request.relative_url == "/block")
          block_fetch->Wait();
        return nullptr;
      },
      &block_fetch));

  ASSERT_TRUE(test_server.Start());

  ASSERT_TRUE(InitializeThenVerifyNewState(true, false));

  network::mojom::URLLoaderFactoryPtr url_loader_factory;
  auto url_loader_factory_params =
      network::mojom::URLLoaderFactoryParams::New();
  url_loader_factory_params->process_id = network::mojom::kBrowserProcessId;
  url_loader_factory_params->is_corb_enabled = false;
  network_context()->CreateURLLoaderFactory(
      mojo::MakeRequest(&url_loader_factory),
      std::move(url_loader_factory_params));

  const char kRedirectURL[] = "/server-redirect?/block";
  std::unique_ptr<network::ResourceRequest> request =
      std::make_unique<network::ResourceRequest>();
  request->url = test_server.GetURL(kRedirectURL);

  std::unique_ptr<network::SimpleURLLoader> simple_loader =
      network::SimpleURLLoader::Create(std::move(request),
                                       TRAFFIC_ANNOTATION_FOR_TESTS);
  base::RunLoop run_loop, run_loop2;
  simple_loader->SetOnRedirectCallback(base::BindRepeating(
      [](base::RepeatingClosure notify_log,
         const net::RedirectInfo& redirect_info,
         const network::ResourceResponseHead& response_head,
         std::vector<std::string>* to_be_removed_headers) { notify_log.Run(); },
      run_loop.QuitClosure()));
  simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
      url_loader_factory.get(),
      base::BindOnce(
          [](base::OnceClosure quit_closure,
             std::unique_ptr<std::string> response_body) {
            std::move(quit_closure).Run();
          },
          run_loop2.QuitClosure()));

  // Wait for fetch to get some bytes accross. It will not be the entire
  // thing since the post-redirect URL will get blocked by the custom handler.
  run_loop.Run();
  ASSERT_TRUE(StartThenVerifyNewState(
      base::FilePath(), net::NetLogCaptureMode::kDefault,
      kCaptureModeDefaultString, network_context()));

  ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr,
                                            kCaptureModeDefaultString));
  // Read events from log file.
  std::unique_ptr<base::DictionaryValue> root;
  ASSERT_TRUE(ReadCompleteLogFile(default_log_path(), &root));
  base::ListValue* events;
  ASSERT_TRUE(root->GetList("events", &events));

  // Check there is at least one event as a result of the ongoing request.
  ASSERT_GE(events->GetSize(), 1u);

  // Check the URL in the params of the first event.
  base::DictionaryValue* event;
  EXPECT_TRUE(events->GetDictionary(0, &event));
  base::DictionaryValue* event_params;
  EXPECT_TRUE(event->GetDictionary("params", &event_params));
  std::string event_url;
  EXPECT_TRUE(event_params->GetString("url", &event_url));
  EXPECT_EQ(test_server.GetURL(kRedirectURL), event_url);

  block_fetch.Signal();
  run_loop2.Run();
}

TEST_F(NetExportFileWriterTest, ReceiveStartWhileInitializing) {
  // Trigger initialization of |file_writer_|.
  file_writer()->Initialize();

  // Before running the main message loop, tell |file_writer_| to start
  // logging. Not running the main message loop prevents the initialization
  // process from completing, so this ensures that StartNetLog() is received
  // before |file_writer_| finishes initialization, which means this
  // should be a no-op.
  file_writer()->StartNetLog(base::FilePath(), net::NetLogCaptureMode::kDefault,
                             kMaxLogSizeBytes, base::CommandLine::StringType(),
                             kChannelString, network_context());

  // Now run the main message loop. Make sure StartNetLog() was ignored by
  // checking that the next two states are "initializing" followed by
  // "not-logging".
  std::unique_ptr<base::DictionaryValue> state =
      test_state_observer()->WaitForNewState();
  ASSERT_TRUE(VerifyState(std::move(state), kStateInitializingString));
  state = test_state_observer()->WaitForNewState();
  ASSERT_TRUE(
      VerifyState(std::move(state), kStateNotLoggingString, false, false, ""));
}

TEST_F(NetExportFileWriterTest, ReceiveStartWhileStoppingLog) {
  ASSERT_TRUE(InitializeThenVerifyNewState(true, false));

  // Call StartNetLog() on |file_writer_| and wait for the state change.
  ASSERT_TRUE(StartThenVerifyNewState(
      base::FilePath(), net::NetLogCaptureMode::kEverything,
      kCaptureModeIncludeEverythingString, network_context()));

  // Tell |file_writer_| to stop logging.
  file_writer()->StopNetLog(nullptr);

  // Before running the main message loop, tell |file_writer_| to start
  // logging. Not running the main message loop prevents the stopping process
  // from completing, so this ensures StartNetLog() is received before
  // |file_writer_| finishes stopping, which means this should be a
  // no-op.
  file_writer()->StartNetLog(base::FilePath(), net::NetLogCaptureMode::kDefault,
                             kMaxLogSizeBytes, base::CommandLine::StringType(),
                             kChannelString, network_context());

  // Now run the main message loop. Make sure the last StartNetLog() was
  // ignored by checking that the next two states are "stopping-log" followed by
  // "not-logging". Also make sure the capture mode matches that of the first
  // StartNetLog() call (called by StartThenVerifyState()).
  std::unique_ptr<base::DictionaryValue> state =
      test_state_observer()->WaitForNewState();
  ASSERT_TRUE(VerifyState(std::move(state), kStateStoppingLogString));
  state = test_state_observer()->WaitForNewState();
  ASSERT_TRUE(VerifyState(std::move(state), kStateNotLoggingString, true, true,
                          kCaptureModeIncludeEverythingString));
}

TEST_F(NetExportFileWriterTest, HandleCrash) {
  FakeNetworkContext fake_network_context;

  ASSERT_TRUE(InitializeThenVerifyNewState(true, false));
  ASSERT_TRUE(StartThenVerifyNewState(
      base::FilePath(), net::NetLogCaptureMode::kEverything,
      kCaptureModeIncludeEverythingString, &fake_network_context));

  // Break the pipe, as if network service crashed.
  fake_network_context.Disconnect();

  std::unique_ptr<base::DictionaryValue> state =
      test_state_observer()->WaitForNewState();
  ASSERT_TRUE(VerifyState(std::move(state), kStateNotLoggingString));
}

}  // namespace net_log
