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

#include "chrome/browser/enterprise/data_protection/paste_allowed_request.h"

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h"
#include "chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.h"
#include "chrome/browser/enterprise/connectors/test/fake_clipboard_request_handler.h"
#include "chrome/browser/enterprise/connectors/test/fake_content_analysis_delegate.h"
#include "chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/enterprise/common/proto/synced/browser_events.pb.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/clipboard_request_handler.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/common.h"
#include "components/enterprise/connectors/core/content_analysis_delegate_base.h"
#include "components/enterprise/data_controls/core/browser/test_utils.h"
#include "content/public/browser/clipboard_types.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/clipboard/clipboard_monitor.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/clipboard/test/test_clipboard.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/services/network_config/public/cpp/cros_network_config_test_helper.h"
#endif

namespace enterprise_data_protection {

namespace {

constexpr char kScanId[] = "scan_id";

enterprise_connectors::ContentAnalysisResponse::Result CreateResult(
    enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
        Action action) {
  enterprise_connectors::ContentAnalysisResponse::Result result;
  result.set_tag("dlp");
  result.set_status(
      enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);

  if (action != enterprise_connectors::ContentAnalysisResponse::Result::
                    TriggeredRule::ACTION_UNSPECIFIED) {
    auto* rule = result.add_triggered_rules();
    rule->set_rule_name("paste_rule_name");
    rule->set_action(action);
  }
  return result;
}

enterprise_connectors::ContentAnalysisResponse CreateResponse(
    enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
        Action action) {
  enterprise_connectors::ContentAnalysisResponse response;
  response.set_request_token(kScanId);

  auto* result = response.add_results();
  *result = CreateResult(action);
  return response;
}

class TestClipboardRequestHandler
    : public enterprise_connectors::test::FakeClipboardRequestHandler {
 public:
  static std::unique_ptr<ClipboardRequestHandler> Create(
      enterprise_connectors::ContentAnalysisInfoBase* content_analysis_info,
      enterprise_connectors::BinaryUploadService* upload_service,
      enterprise_connectors::ReportingEventRouter* router,
      GURL url,
      Type type,
      enterprise_connectors::DeepScanAccessPoint access_point,
      enterprise_connectors::ContentMetaData::CopiedTextSource clipboard_source,
      std::string source_content_area_email,
      std::string content_transfer_method,
      std::string data,
      CompletionCallback callback,
      enterprise_connectors::BinaryUploadRequest::BrowserPolicyConnectorGetter
          policy_getter) {
    return base::WrapUnique(new TestClipboardRequestHandler(
        content_analysis_info, upload_service, router, std::move(url), type,
        access_point, std::move(clipboard_source),
        std::move(source_content_area_email),
        std::move(content_transfer_method), std::move(data),
        std::move(callback), std::move(policy_getter)));
  }

 protected:
  using FakeClipboardRequestHandler::FakeClipboardRequestHandler;

 private:
  void UploadForDeepScanning(
      std::unique_ptr<enterprise_connectors::ClipboardAnalysisRequest> request)
      override {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &TestClipboardRequestHandler::OnContentAnalysisResponse,
            base::Unretained(this),
            enterprise_connectors::ScanRequestUploadResult::kSuccess,
            CreateResponse(enterprise_connectors::ContentAnalysisResponse::
                               Result::TriggeredRule::BLOCK)));
  }
};

class PasteAllowedRequestTest : public testing::Test {
 public:
  PasteAllowedRequestTest()
      : profile_manager_(TestingBrowserProcess::GetGlobal()) {
    EXPECT_TRUE(profile_manager_.SetUp());
    profile_ = profile_manager_.CreateTestingProfile("test-user-1");
  }

  void SetUp() override {
    PasteAllowedRequest::CleanupRequestsForTesting();
    ui::TestClipboard::CreateForCurrentThread();

#if BUILDFLAG(IS_CHROMEOS)
    network_config_helper_ =
        std::make_unique<ash::network_config::CrosNetworkConfigTestHelper>();
    ash::NetworkHandler::Initialize();
#endif
  }

  void TearDown() override {
#if BUILDFLAG(IS_CHROMEOS)
    ash::NetworkHandler::Shutdown();
#endif
  }

  content::WebContents* main_web_contents() {
    if (!main_web_contents_) {
      content::WebContents::CreateParams params(profile_);
      main_web_contents_ = content::WebContents::Create(params);
    }
    return main_web_contents_.get();
  }

  content::RenderFrameHost& main_rfh() {
    return *main_web_contents()->GetPrimaryMainFrame();
  }

  content::ClipboardEndpoint main_endpoint() {
    return content::ClipboardEndpoint(
        ui::DataTransferEndpoint(GURL("https://google.com")),
        base::BindLambdaForTesting([this]() -> content::BrowserContext* {
          return static_cast<content::BrowserContext*>(profile_);
        }),
        main_rfh());
  }

  content::RenderFrameHost& child_rfh() {
    if (!child_rfh_) {
      content::RenderFrameHostTester::For(&main_rfh())
          ->InitializeRenderFrameIfNeeded();
      child_rfh_ = content::RenderFrameHostTester::For(&main_rfh())
                       ->AppendChild("child");
    }
    return *child_rfh_;
  }

  content::ClipboardEndpoint child_endpoint() {
    return content::ClipboardEndpoint(
        ui::DataTransferEndpoint(GURL("https://google.com")),
        base::BindLambdaForTesting([this]() -> content::BrowserContext* {
          return static_cast<content::BrowserContext*>(profile_);
        }),
        child_rfh());
  }

  content::WebContents* secondary_web_contents() {
    if (!secondary_web_contents_) {
      content::WebContents::CreateParams params(profile_);
      secondary_web_contents_ = content::WebContents::Create(params);
    }
    return secondary_web_contents_.get();
  }

  content::RenderFrameHost& secondary_rfh() {
    return *secondary_web_contents()->GetPrimaryMainFrame();
  }

  content::ClipboardEndpoint secondary_endpoint() {
    return content::ClipboardEndpoint(
        ui::DataTransferEndpoint(GURL("https://google.com")),
        base::BindLambdaForTesting([this]() -> content::BrowserContext* {
          return static_cast<content::BrowserContext*>(profile_);
        }),
        secondary_rfh());
  }

 protected:
  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  content::RenderViewHostTestEnabler rvh_test_enabler_;
  TestingProfileManager profile_manager_;
  raw_ptr<TestingProfile> profile_;
  std::unique_ptr<content::WebContents> main_web_contents_;
  std::unique_ptr<content::WebContents> secondary_web_contents_;
  raw_ptr<content::RenderFrameHost> child_rfh_ = nullptr;
#if BUILDFLAG(IS_CHROMEOS)
  std::unique_ptr<ash::network_config::CrosNetworkConfigTestHelper>
      network_config_helper_;
#endif
};

class PasteAllowedRequestScanningTest : public PasteAllowedRequestTest {
 public:
  PasteAllowedRequestScanningTest() {
    enterprise_connectors::ContentAnalysisDelegate::DisableUIForTesting();
  }

  void SetUp() override {
    PasteAllowedRequestTest::SetUp();

    helper_ = std::make_unique<
        enterprise_connectors::test::EventReportValidatorHelper>(profile_);

    enterprise_connectors::test::SetAnalysisConnector(
        profile_->GetPrefs(), enterprise_connectors::BULK_DATA_ENTRY,
        R"({
          "service_provider": "google",
          "block_until_verdict": 1,
          "minimum_data_size": 1,
          "enable": [
            {
              "url_list": ["*"],
              "tags": ["dlp"]
            }
          ]
        })");
  }

 protected:
  std::unique_ptr<enterprise_connectors::test::EventReportValidatorHelper>
      helper_;
};

}  // namespace

TEST_F(PasteAllowedRequestTest, AddCallbacksAndComplete) {
  PasteAllowedRequest request;
  content::ClipboardPasteData clipboard_paste_data_1;
  clipboard_paste_data_1.text = u"text";
  clipboard_paste_data_1.png = {1, 2, 3, 4, 5};
  content::ClipboardPasteData clipboard_paste_data_2;
  clipboard_paste_data_2.text = u"other text";
  clipboard_paste_data_2.png = {6, 7, 8, 9, 10};

  int count = 0;

  // Add a callback.  It should not fire right away.
  request.AddCallback(base::BindLambdaForTesting(
      [&count, clipboard_paste_data_1](
          std::optional<content::ClipboardPasteData> clipboard_paste_data) {
        ++count;
        ASSERT_EQ(clipboard_paste_data->text, clipboard_paste_data_1.text);
        ASSERT_EQ(clipboard_paste_data->png, clipboard_paste_data_1.png);
      }));
  EXPECT_EQ(0, count);

  // Complete the request.  Callback should fire.  Whether paste is allowed
  // or not is not important.
  request.Complete(clipboard_paste_data_1);
  EXPECT_EQ(1, count);

  // Add a second callback.  It should not fire right away.
  request.AddCallback(base::BindLambdaForTesting(
      [&count, clipboard_paste_data_2](
          std::optional<content::ClipboardPasteData> clipboard_paste_data) {
        ++count;
        ASSERT_EQ(clipboard_paste_data->text, clipboard_paste_data_2.text);
        ASSERT_EQ(clipboard_paste_data->png, clipboard_paste_data_2.png);
      }));
  EXPECT_EQ(1, count);

  // Calling `Complete()` again will call the second callback.
  request.Complete(clipboard_paste_data_2);
  EXPECT_EQ(2, count);
}

TEST_F(PasteAllowedRequestTest, IsObsolete) {
  PasteAllowedRequest request;
  content::ClipboardPasteData clipboard_paste_data;
  clipboard_paste_data.text = u"data";

  // A request is obsolete once it is too old and completed.
  // Whether paste is allowed or not is not important.
  request.Complete(clipboard_paste_data);
  EXPECT_TRUE(
      request.IsObsolete(request.completed_time() +
                         PasteAllowedRequest::kIsPasteAllowedRequestTooOld +
                         base::Microseconds(1)));
}

TEST_F(PasteAllowedRequestTest, SameDestinationSource) {
  auto seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
      ui::ClipboardBuffer::kCopyPaste);

  const std::u16string kText = u"text";
  content::ClipboardPasteData clipboard_paste_data;
  clipboard_paste_data.text = kText;

  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ main_endpoint(), /*destination*/ main_endpoint(),
      {.seqno = seqno}, clipboard_paste_data, future.GetCallback());

  ASSERT_TRUE(future.Get());
  ASSERT_EQ(future.Get()->text, kText);

  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest, SameDestinationSource_AfterReplacement) {
  data_controls::SetDataControls(profile_->GetPrefs(), {
                                                           R"({
                    "sources": {
                      "urls": ["google.com"]
                    },
                    "destinations": {
                      "os_clipboard": true
                    },
                    "restrictions": [
                      {"class": "CLIPBOARD", "level": "BLOCK"}
                    ]
                  })"});

  const std::u16string kText = u"text";
  content::ClipboardPasteData copied_data;
  copied_data.text = kText;
  base::test::TestFuture<const ui::ClipboardFormatType&,
                         const content::ClipboardPasteData&,
                         std::optional<std::u16string>>
      copy_future;

  IsClipboardCopyAllowedByPolicy(main_endpoint(), {}, copied_data,
                                 copy_future.GetCallback());
  auto replacement = copy_future.Get<std::optional<std::u16string>>();
  EXPECT_TRUE(replacement);
  EXPECT_EQ(*replacement,
            u"Pasting this content here is blocked by your administrator.");

  // This triggers the clipboard observer started by the
  // `IsClipboardCopyAllowedByPolicy` calls so that they're aware of the new
  // seqno.
  ui::ClipboardMonitor::GetInstance()->NotifyClipboardDataChanged();

  auto seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
      ui::ClipboardBuffer::kCopyPaste);

  // After the data was replaced when initially copied, it should be put back
  // when pasting in the same tab.
  content::ClipboardPasteData pasted_data;
  pasted_data.html = u"to be replaced";
  base::test::TestFuture<std::optional<content::ClipboardPasteData>>
      paste_future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ main_endpoint(), /*destination*/ main_endpoint(),
      {.seqno = seqno}, pasted_data, paste_future.GetCallback());

  ASSERT_TRUE(paste_future.Get());
  ASSERT_EQ(paste_future.Get()->text, kText);
  ASSERT_TRUE(paste_future.Get()->html.empty());

  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest, DifferentDestinationSource) {
  auto seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
      ui::ClipboardBuffer::kCopyPaste);

  const std::u16string kText = u"text";
  content::ClipboardPasteData clipboard_paste_data;
  clipboard_paste_data.text = kText;

  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ secondary_endpoint(), /*destination*/ main_endpoint(),
      {.seqno = seqno}, clipboard_paste_data, future.GetCallback());

  ASSERT_TRUE(future.Get());
  ASSERT_EQ(future.Get()->text, kText);

  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest,
       DifferentDestinationSource_AllowedWithCachedRequest) {
  const std::u16string kText = u"text";
  auto seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
      ui::ClipboardBuffer::kCopyPaste);

  content::ClipboardPasteData clipboard_paste_data;
  clipboard_paste_data.text = kText;
  PasteAllowedRequest request;
  request.Complete(clipboard_paste_data);
  PasteAllowedRequest::AddRequestToCacheForTesting(main_rfh().GetGlobalId(),
                                                   seqno, std::move(request));
  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());

  // Attempting to start a new request when there is an allowed cached request
  // with the same seqno should allow again, but not by making a new request.
  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ secondary_endpoint(), /*destination*/ main_endpoint(),
      {.seqno = seqno}, clipboard_paste_data, future.GetCallback());

  ASSERT_TRUE(future.Wait());
  ASSERT_TRUE(future.Get());
  ASSERT_EQ(future.Get()->text, kText);

  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest,
       DifferentDestinationSource_BlockedWithCachedRequest) {
  auto seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
      ui::ClipboardBuffer::kCopyPaste);

  PasteAllowedRequest request;
  request.Complete(std::nullopt);
  PasteAllowedRequest::AddRequestToCacheForTesting(main_rfh().GetGlobalId(),
                                                   seqno, std::move(request));
  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());

  const std::u16string kText = u"text";
  content::ClipboardPasteData clipboard_paste_data;
  clipboard_paste_data.text = kText;

  // Attempting to start a new request when there is a blocked cached request
  // with the same seqno should block again, but not by making a new request.
  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ secondary_endpoint(), /*destination*/ main_endpoint(),
      {.seqno = seqno}, clipboard_paste_data, future.GetCallback());

  ASSERT_TRUE(future.Wait());
  ASSERT_FALSE(future.Get());

  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest, ChildFrameDestinationCreatesSeparateRequest) {
  auto seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
      ui::ClipboardBuffer::kCopyPaste);

  // Seed a completed request for the main frame so that a paste targeting it
  // would resolve to `kCachedText`.
  const std::u16string kCachedText = u"cached";
  content::ClipboardPasteData cached_data;
  cached_data.text = kCachedText;
  PasteAllowedRequest cached_request;
  cached_request.Complete(cached_data);
  PasteAllowedRequest::AddRequestToCacheForTesting(
      main_rfh().GetGlobalId(), seqno, std::move(cached_request));
  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());

  // A paste into a child frame of the same page must not be coalesced into the
  // main frame's request, so it should be evaluated independently and resolve
  // to its own data rather than the main frame's cached result.
  const std::u16string kChildText = u"child";
  content::ClipboardPasteData child_data;
  child_data.text = kChildText;

  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ secondary_endpoint(), /*destination*/ child_endpoint(),
      {.seqno = seqno}, child_data, future.GetCallback());

  ASSERT_TRUE(future.Get());
  EXPECT_EQ(future.Get()->text, kChildText);

  EXPECT_EQ(2u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest, UnknownSource) {
  auto seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
      ui::ClipboardBuffer::kCopyPaste);

  const std::u16string kText = u"text";
  content::ClipboardPasteData clipboard_paste_data;
  clipboard_paste_data.text = kText;

  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ content::ClipboardEndpoint(std::nullopt),
      /*destination*/ main_endpoint(), {.seqno = seqno}, clipboard_paste_data,
      future.GetCallback());

  ASSERT_TRUE(future.Get());
  ASSERT_EQ(future.Get()->text, kText);

  // When a document reads from the clipboard, but the clipboard was written
  // from an unknown source, content checks should not be skipped.
  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest, EmptyData) {
  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ secondary_endpoint(),
      /*destination*/ main_endpoint(),
      {
          .seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
              ui::ClipboardBuffer::kCopyPaste),
      },
      content::ClipboardPasteData(), future.GetCallback());

  ASSERT_TRUE(future.Get());
  ASSERT_TRUE(future.Get()->empty());

  // When data is empty, a request is still made in case the data needs to be
  // replaced later.
  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest, EmptyData_SameSourceReplaced) {
  data_controls::SetDataControls(profile_->GetPrefs(), {
                                                           R"({
                    "destinations": {
                      "os_clipboard": true
                    },
                    "restrictions": [
                      {"class": "CLIPBOARD", "level": "BLOCK"}
                    ]
                  })"});

  const std::u16string kText = u"text";
  content::ClipboardPasteData copied_data;
  copied_data.text = kText;
  base::test::TestFuture<const ui::ClipboardFormatType&,
                         const content::ClipboardPasteData&,
                         std::optional<std::u16string>>
      copy_future;

  IsClipboardCopyAllowedByPolicy(main_endpoint(), {}, copied_data,
                                 copy_future.GetCallback());
  auto replacement = copy_future.Get<std::optional<std::u16string>>();
  EXPECT_TRUE(replacement);
  EXPECT_EQ(*replacement,
            u"Pasting this content here is blocked by your administrator.");

  // This triggers the clipboard observer started by the
  // `IsClipboardCopyAllowedByPolicy` calls so that they're aware of the new
  // seqno.
  ui::ClipboardMonitor::GetInstance()->NotifyClipboardDataChanged();

  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ secondary_endpoint(),
      /*destination*/ main_endpoint(),
      {
          .seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
              ui::ClipboardBuffer::kCopyPaste),
      },
      content::ClipboardPasteData(), future.GetCallback());

  ASSERT_TRUE(future.Get());
  ASSERT_EQ(future.Get()->text, kText);

  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest, EmptyData_DifferentSourceReplaced) {
  data_controls::SetDataControls(profile_->GetPrefs(), {
                                                           R"({
                    "destinations": {
                      "os_clipboard": true
                    },
                    "restrictions": [
                      {"class": "CLIPBOARD", "level": "BLOCK"}
                    ]
                  })"});

  const std::u16string kText = u"text";
  content::ClipboardPasteData copied_data;
  copied_data.text = kText;
  base::test::TestFuture<const ui::ClipboardFormatType&,
                         const content::ClipboardPasteData&,
                         std::optional<std::u16string>>
      copy_future;

  IsClipboardCopyAllowedByPolicy(main_endpoint(), {}, copied_data,
                                 copy_future.GetCallback());
  auto replacement = copy_future.Get<std::optional<std::u16string>>();
  EXPECT_TRUE(replacement);
  EXPECT_EQ(*replacement,
            u"Pasting this content here is blocked by your administrator.");

  // This triggers the clipboard observer started by the
  // `IsClipboardCopyAllowedByPolicy` calls so that they're aware of the new
  // seqno.
  ui::ClipboardMonitor::GetInstance()->NotifyClipboardDataChanged();

  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ main_endpoint(),
      /*destination*/ secondary_endpoint(),
      {
          .seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
              ui::ClipboardBuffer::kCopyPaste),
      },
      content::ClipboardPasteData(), future.GetCallback());

  ASSERT_TRUE(future.Get());
  ASSERT_EQ(future.Get()->text, kText);

  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestTest, CleanupObsoleteScanRequests) {
  auto seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
      ui::ClipboardBuffer::kCopyPaste);

  const std::u16string kText = u"text";
  content::ClipboardPasteData clipboard_paste_data;
  clipboard_paste_data.text = kText;

  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ secondary_endpoint(), /*destination*/ main_endpoint(),
      {.seqno = seqno}, clipboard_paste_data, future.GetCallback());

  ASSERT_TRUE(future.Get());
  ASSERT_EQ(future.Get()->text, kText);
  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());

  // Make sure an appropriate amount of time passes to make the request old.
  // It should be cleaned up.
  task_environment_.FastForwardBy(
      PasteAllowedRequest::kIsPasteAllowedRequestTooOld +
      base::Microseconds(1));
  PasteAllowedRequest::CleanupObsoleteRequests();
  EXPECT_EQ(0u, PasteAllowedRequest::requests_count_for_testing());
}

TEST_F(PasteAllowedRequestScanningTest, DifferentDestinationSource) {
  enterprise_connectors::ClipboardRequestHandler::SetFactoryForTesting(
      base::BindRepeating(TestClipboardRequestHandler::Create));

  auto validator = helper_->CreateValidator();
  base::RunLoop run_loop;
  validator.SetDoneClosure(run_loop.QuitClosure());

  chrome::cros::reporting::proto::DlpSensitiveDataEvent expected_event;

  expected_event.set_url("");
  expected_event.set_tab_url("");
  expected_event.set_source("https://google.com/");
  expected_event.set_destination("");
  expected_event.set_file_name("Text data");
  expected_event.set_content_type("text/plain");
  expected_event.set_content_size(4);
  expected_event.set_scan_id(kScanId);
  expected_event.set_event_result(
      chrome::cros::reporting::proto::EventResult::EVENT_RESULT_BLOCKED);
  expected_event.set_clicked_through(false);
  expected_event.set_trigger(chrome::cros::reporting::proto::
                                 DataTransferEventTrigger::WEB_CONTENT_UPLOAD);

  chrome::cros::reporting::proto::TriggeredRuleInfo triggered_rule;
  triggered_rule.set_action(
      chrome::cros::reporting::proto::TriggeredRuleInfo::BLOCK);
  triggered_rule.set_rule_name("paste_rule_name");

  *expected_event.add_triggered_rule_info() = triggered_rule;

  expected_event.set_profile_identifier(profile_->GetPath().AsUTF8Unsafe());
  expected_event.set_profile_user_name("test-user@chromium.org");

  validator.ExpectSensitiveDataEvent(std::move(expected_event));

  auto seqno = ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
      ui::ClipboardBuffer::kCopyPaste);

  const std::u16string kText = u"text";
  content::ClipboardPasteData clipboard_paste_data;
  clipboard_paste_data.text = kText;

  base::test::TestFuture<std::optional<content::ClipboardPasteData>> future;
  PasteAllowedRequest::StartPasteAllowedRequest(
      /*source*/ secondary_endpoint(), /*destination*/ main_endpoint(),
      {.seqno = seqno}, clipboard_paste_data, future.GetCallback());

  ASSERT_TRUE(future.Wait());
  ASSERT_FALSE(future.Get().has_value());

  EXPECT_EQ(1u, PasteAllowedRequest::requests_count_for_testing());
  run_loop.Run();
}

}  // namespace enterprise_data_protection
