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

#include "content/browser/tracing/traces_internals/traces_internals_handler.h"

#include "base/base_paths.h"
#include "base/functional/callback_helpers.h"
#include "base/path_service.h"
#include "base/strings/string_view_util.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/test_proto_loader.h"
#include "base/token.h"
#include "content/browser/tracing/background_tracing_manager_impl.h"
#include "content/browser/tracing/test_tracing_session.h"
#include "content/browser/tracing/traces_internals/traces_internals.mojom.h"
#include "content/public/browser/tracing_delegate.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/tracing/public/cpp/background_tracing/background_tracing_manager.h"
#include "services/tracing/public/cpp/background_tracing/trace_upload_list.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

using testing::_;
using tracing::ClientTraceReport;
using tracing::ReportUploadState;
using tracing::SkipUploadReason;

perfetto::protos::gen::TraceConfig ParseTraceConfigFromText(
    const std::string& proto_text) {
  base::TestProtoLoader config_loader(
      base::PathService::CheckedGet(base::DIR_GEN_TEST_DATA_ROOT)
          .Append(FILE_PATH_LITERAL("third_party/perfetto/protos/perfetto/"
                                    "config/config.descriptor")),
      "perfetto.protos.TraceConfig");
  std::string serialized_message;
  config_loader.ParseFromText(proto_text, serialized_message);
  perfetto::protos::gen::TraceConfig destination;
  destination.ParseFromString(serialized_message);
  return destination;
}

class FakeTraceUploadList : public tracing::TraceUploadList {
 public:
  // Functions we want to intercept.
  MOCK_METHOD(void, OpenDatabaseIfExists, (), (override));
  MOCK_METHOD(void, GetAllTraceReports, (GetReportsCallback), (override));
  MOCK_METHOD(void,
              DeleteSingleTrace,
              (const base::Token&, FinishedProcessingCallback),
              (override));
  MOCK_METHOD(void, DeleteAllTraces, (FinishedProcessingCallback), (override));
  MOCK_METHOD(void,
              UserUploadSingleTrace,
              (const base::Token&, FinishedProcessingCallback),
              (override));
  MOCK_METHOD(void,
              DownloadTrace,
              (const base::Token&, GetProtoCallback),
              (override));
};

class MockTracePage : public traces_internals::mojom::Page {
 public:
  MockTracePage() = default;
  ~MockTracePage() override = default;

  mojo::PendingRemote<traces_internals::mojom::Page> BindAndGetRemote() {
    CHECK(!receiver_.is_bound());
    return receiver_.BindNewPipeAndPassRemote();
  }

  MOCK_METHOD(void,
              OnTraceComplete,
              (std::optional<mojo_base::BigBuffer>,
               const std::optional<base::Token>&),
              (override));

  mojo::Receiver<traces_internals::mojom::Page> receiver_{this};
};

class MockTracingDelegate : public TracingDelegate {
 public:
  MOCK_METHOD(bool,
              IsRecordingAllowed,
              (bool, base::TimeTicks),
              (const, override));
  MOCK_METHOD(bool, ShouldSaveUnuploadedTrace, (), (const, override));
#if BUILDFLAG(IS_WIN)
  MOCK_METHOD(void,
              GetSystemTracingState,
              (base::OnceCallback<void(bool, bool)>),
              (override));
  MOCK_METHOD(void,
              EnableSystemTracing,
              (base::OnceCallback<void(bool)>),
              (override));
  MOCK_METHOD(void,
              DisableSystemTracing,
              (base::OnceCallback<void(bool)>),
              (override));
#endif
};

class TracesInternalsHandlerForTesting : public TracesInternalsHandler {
 public:
  TracesInternalsHandlerForTesting(
      mojo::PendingReceiver<traces_internals::mojom::PageHandler> receiver,
      mojo::PendingRemote<traces_internals::mojom::Page> page,
      tracing::TraceUploadList& trace_upload_list,
      BackgroundTracingManagerImpl& background_tracing_manager,
      TracingDelegate* tracing_delegate)
      : TracesInternalsHandler(std::move(receiver),
                               std::move(page),
                               trace_upload_list,
                               background_tracing_manager,
                               tracing_delegate) {}

 protected:
  std::unique_ptr<perfetto::TracingSession> CreateTracingSession() override {
    return std::make_unique<TestTracingSession>();
  }
};

// A fixture to test TracesInternalsHandler.
class TracesInternalsHandlerTest : public testing::Test {
 public:
  TracesInternalsHandlerTest() = default;
  ~TracesInternalsHandlerTest() override = default;

  void SetUp() override {
    background_tracing_manager_ =
        std::make_unique<BackgroundTracingManagerImpl>(&mock_tracing_delegate_);
    // Expect the Database to be opened before executing each test.
    EXPECT_CALL(fake_trace_upload_list_, OpenDatabaseIfExists());
    handler_ = std::make_unique<TracesInternalsHandlerForTesting>(
        mojo::PendingReceiver<traces_internals::mojom::PageHandler>(),
        mock_page_.BindAndGetRemote(), fake_trace_upload_list_,
        *background_tracing_manager_, &mock_tracing_delegate_);
  }

 protected:
  BrowserTaskEnvironment task_environment_;
  std::unique_ptr<BackgroundTracingManagerImpl> background_tracing_manager_;
  testing::StrictMock<FakeTraceUploadList> fake_trace_upload_list_;
  testing::NiceMock<MockTracePage> mock_page_;
  testing::NiceMock<MockTracingDelegate> mock_tracing_delegate_;
  std::unique_ptr<TracesInternalsHandler> handler_;
};

TEST_F(TracesInternalsHandlerTest, TracingStartStop) {
  auto trace_config =
      ParseTraceConfigFromText(R"pb(
        data_sources: { config: { name: "org.chromium.trace_metadata2" } }
      )pb")
          .SerializeAsString();
  base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
      start_callback;
  handler_->StartTraceSession(
      mojo_base::BigBuffer(
          base::as_bytes(base::span<const char>(trace_config))),
      /*enable_privacy_filters=*/false, start_callback.Get());

  {
    base::RunLoop run_loop_start;
    EXPECT_CALL(start_callback, Run(true))
        .WillOnce(base::test::RunOnceClosure(run_loop_start.QuitClosure()));
    run_loop_start.Run();
  }

  base::MockCallback<TracesInternalsHandler::StopTraceSessionCallback>
      stop_callback;
  handler_->StopTraceSession(stop_callback.Get());
  {
    base::RunLoop run_loop_stop;
    EXPECT_CALL(stop_callback, Run(true)).Times(1);

    EXPECT_CALL(mock_page_,
                OnTraceComplete(
                    testing::Truly(
                        [](const std::optional<mojo_base::BigBuffer>& trace) {
                          return base::as_string_view(base::as_chars(
                                     base::span(*trace))) == "this is a trace";
                        }),
                    _))
        .WillOnce(base::test::RunOnceClosure(run_loop_stop.QuitClosure()));

    run_loop_stop.Run();
  }
}

TEST_F(TracesInternalsHandlerTest, TracingTimer) {
  auto trace_config = ParseTraceConfigFromText(R"pb(
                        data_sources: { config: { name: "Stop" } }
                      )pb")
                          .SerializeAsString();
  handler_->StartTraceSession(
      mojo_base::BigBuffer(
          base::as_bytes(base::span<const char>(trace_config))),
      /*enable_privacy_filters=*/false, base::DoNothing());

  base::RunLoop run_loop;

  EXPECT_CALL(
      mock_page_,
      OnTraceComplete(
          testing::Truly([](const std::optional<mojo_base::BigBuffer>& trace) {
            return base::as_string_view(base::as_chars(base::span(*trace))) ==
                   "this is a trace";
          }),
          _))
      .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
  run_loop.Run();
}

TEST_F(TracesInternalsHandlerTest, TracingStartFail) {
  auto trace_config = ParseTraceConfigFromText(R"pb(
                        data_sources: { config: { name: "Invalid" } }
                      )pb")
                          .SerializeAsString();
  base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
      start_callback;
  handler_->StartTraceSession(
      mojo_base::BigBuffer(
          base::as_bytes(base::span<const char>(trace_config))),
      /*enable_privacy_filters=*/false, start_callback.Get());

  {
    base::RunLoop run_loop_stop;
    EXPECT_CALL(start_callback, Run(false))
        .WillOnce(base::test::RunOnceClosure(run_loop_stop.QuitClosure()));
    run_loop_stop.Run();
  }
}

TEST_F(TracesInternalsHandlerTest, TracingClone) {
  auto trace_config =
      ParseTraceConfigFromText(R"pb(
        data_sources: { config: { name: "org.chromium.trace_metadata2" } }
      )pb")
          .SerializeAsString();
  base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
      start_callback;
  handler_->StartTraceSession(
      mojo_base::BigBuffer(
          base::as_bytes(base::span<const char>(trace_config))),
      /*enable_privacy_filters=*/false, start_callback.Get());

  {
    base::RunLoop run_loop_start;
    EXPECT_CALL(start_callback, Run(true))
        .WillOnce(base::test::RunOnceClosure(run_loop_start.QuitClosure()));
    run_loop_start.Run();
  }

  base::MockCallback<TracesInternalsHandler::CloneTraceSessionCallback>
      clone_callback;
  handler_->CloneTraceSession(clone_callback.Get());
  {
    base::RunLoop run_loop_clone;

    EXPECT_CALL(clone_callback,
                Run(testing::Truly(
                        [](const std::optional<mojo_base::BigBuffer>& trace) {
                          return base::as_string_view(base::as_chars(
                                     base::span(*trace))) == "this is a trace";
                        }),
                    _))
        .WillOnce(base::test::RunOnceClosure(run_loop_clone.QuitClosure()));

    run_loop_clone.Run();
  }
}

TEST_F(TracesInternalsHandlerTest, TracingBufferUsage) {
  auto trace_config =
      ParseTraceConfigFromText(R"pb(
        data_sources: { config: { name: "org.chromium.trace_metadata2" } }
      )pb")
          .SerializeAsString();
  base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
      start_callback;
  handler_->StartTraceSession(
      mojo_base::BigBuffer(
          base::as_bytes(base::span<const char>(trace_config))),
      /*enable_privacy_filters=*/false, start_callback.Get());

  {
    base::RunLoop run_loop_start;
    EXPECT_CALL(start_callback, Run(true))
        .WillOnce(base::test::RunOnceClosure(run_loop_start.QuitClosure()));
    run_loop_start.Run();
  }

  base::MockCallback<TracesInternalsHandler::GetBufferUsageCallback>
      buffer_callback;
  handler_->GetBufferUsage(buffer_callback.Get());
  {
    base::RunLoop run_loop_buffer;

    EXPECT_CALL(buffer_callback, Run(true, _, _))
        .WillOnce(base::test::RunOnceClosure(run_loop_buffer.QuitClosure()));

    run_loop_buffer.Run();
  }
}

TEST_F(TracesInternalsHandlerTest, GetAllTraceReports) {
  base::MockCallback<TracesInternalsHandler::GetAllTraceReportsCallback>
      callback;

  EXPECT_CALL(fake_trace_upload_list_, GetAllTraceReports)
      .WillOnce([](FakeTraceUploadList::GetReportsCallback callback) {
        std::vector<ClientTraceReport> trace_reports;

        for (int report = 0; report < 2; ++report) {
          ClientTraceReport test_report;
          test_report.uuid = base::Token::CreateRandom();
          test_report.creation_time = base::Time::Now();
          test_report.scenario_name = "scenario1";
          test_report.upload_rule_name = "rules1";
          test_report.total_size = 23192873129873128;
          test_report.skip_reason = SkipUploadReason::kNoSkip;
          test_report.upload_state = ReportUploadState::kNotUploaded;
          test_report.upload_time = base::Time::Now();
          trace_reports.push_back(test_report);
        }

        std::move(callback).Run(trace_reports);
      });

  EXPECT_CALL(callback, Run)
      .WillOnce([](std::vector<traces_internals::mojom::ClientTraceReportPtr>
                       all_reports) { EXPECT_EQ(all_reports.size(), 2u); });
  handler_->GetAllTraceReports(callback.Get());
}

TEST_F(TracesInternalsHandlerTest, DeleteAllTraces) {
  base::MockCallback<TracesInternalsHandler::DeleteAllTracesCallback> callback;

  EXPECT_CALL(fake_trace_upload_list_, DeleteAllTraces)
      .WillOnce([](FakeTraceUploadList::FinishedProcessingCallback callback) {
        std::move(callback).Run(true);
      });
  EXPECT_CALL(callback, Run(true));
  handler_->DeleteAllTraces(callback.Get());
}

TEST_F(TracesInternalsHandlerTest, DeleteSingleTrace) {
  auto uuid = base::Token::CreateRandom();
  base::MockCallback<TracesInternalsHandler::DeleteSingleTraceCallback>
      callback;

  EXPECT_CALL(fake_trace_upload_list_, DeleteSingleTrace)
      .WillOnce(
          [&uuid](const base::Token passed_uuid,
                  FakeTraceUploadList::FinishedProcessingCallback callback) {
            EXPECT_EQ(uuid, passed_uuid);
            std::move(callback).Run(false);
          });
  EXPECT_CALL(callback, Run(false));
  handler_->DeleteSingleTrace(uuid, callback.Get());
}

TEST_F(TracesInternalsHandlerTest, UserUploadSingleTrace) {
  auto uuid = base::Token::CreateRandom();
  base::MockCallback<TracesInternalsHandler::UserUploadSingleTraceCallback>
      callback;

  EXPECT_CALL(fake_trace_upload_list_, UserUploadSingleTrace)
      .WillOnce(
          [&uuid](const base::Token passed_uuid,
                  FakeTraceUploadList::FinishedProcessingCallback callback) {
            EXPECT_EQ(uuid, passed_uuid);
            std::move(callback).Run(true);
          });
  EXPECT_CALL(callback, Run(true));
  handler_->UserUploadSingleTrace(uuid, callback.Get());
}

TEST_F(TracesInternalsHandlerTest, DownloadTrace) {
  auto uuid = base::Token::CreateRandom();
  base::MockCallback<TracesInternalsHandler::DownloadTraceCallback> callback;

  const auto result = std::optional<base::span<const char>>("PROTO RESULT");

  EXPECT_CALL(fake_trace_upload_list_, DownloadTrace)
      .WillOnce(
          [&result, &uuid](const base::Token passed_uuid,
                           FakeTraceUploadList::GetProtoCallback callback) {
            EXPECT_EQ(uuid, passed_uuid);
            std::move(callback).Run(result);
          });
  EXPECT_CALL(callback, Run)
      .WillOnce([&result](std::optional<mojo_base::BigBuffer> converted_value) {
        EXPECT_EQ(
            std::string_view(reinterpret_cast<char*>(converted_value->data()),
                             converted_value->size()),
            std::string_view(result->data(), result->size()));
      });
  handler_->DownloadTrace(uuid, callback.Get());
}

TEST_F(TracesInternalsHandlerTest, GetTrackEventCategories) {
  base::MockCallback<
      traces_internals::mojom::PageHandler::GetTrackEventCategoriesCallback>
      callback;
  base::RunLoop run_loop;
  EXPECT_CALL(callback, Run)
      .WillOnce(
          [&run_loop](std::vector<traces_internals::mojom::TraceCategoryPtr>
                          categories) {
            ASSERT_EQ(categories.size(), 1u);
            EXPECT_EQ(categories[0]->name, "test_cat");
            EXPECT_EQ(categories[0]->description, "test description");
            run_loop.Quit();
          });
  handler_->GetTrackEventCategories(callback.Get());
  run_loop.Run();
}

#if BUILDFLAG(IS_WIN)
// Tests that TracesInternalsHandler delegates GetSystemTracingState to the
// TracingDelegate.
TEST_F(TracesInternalsHandlerTest, GetSystemTracingState) {
  EXPECT_CALL(mock_tracing_delegate_, GetSystemTracingState(testing::_));
  handler_->GetSystemTracingState({});
}

// Tests that TracesInternalsHandler delegates EnableSystemTracing to the
// TracingDelegate.
TEST_F(TracesInternalsHandlerTest, EnableSystemTracing) {
  EXPECT_CALL(mock_tracing_delegate_, EnableSystemTracing(testing::_));
  handler_->EnableSystemTracing({});
}

// Tests that TracesInternalsHandler delegates DisableSystemTracing to the
// TracingDelegate.
TEST_F(TracesInternalsHandlerTest, DisableSystemTracing) {
  EXPECT_CALL(mock_tracing_delegate_, DisableSystemTracing(testing::_));
  handler_->DisableSystemTracing({});
}
#endif  // BUILDFLAG(IS_WIN)

}  // namespace content
