// Copyright 2013 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/connectors/analysis/content_analysis_delegate.h"

#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <vector>

#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/scoped_refptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/enterprise/connectors/connectors_service.h"
#include "chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.h"
#include "chrome/browser/enterprise/connectors/test/fake_content_analysis_delegate.h"
#include "chrome/browser/policy/dm_token_utils.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
#include "chrome/common/chrome_paths.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/buildflags/buildflags.h"
#include "components/enterprise/common/proto/connectors.pb.h"
#include "components/enterprise/connectors/core/analysis_settings.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/binary_upload_service.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/common.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/deep_scanning_utils.h"
#include "components/enterprise/connectors/core/features.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/prefs/testing_pref_service.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/test/fake_content_analysis_sdk_manager.h"  // nogncheck
#endif

namespace enterprise_connectors {

namespace {

constexpr char kDmToken[] = "dm_token";
constexpr char kTestUrl[] = "http://example.com/";

constexpr char kBlockingScansForDlpAndMalware[] = R"(
{
  "service_provider": "google",
  "enable": [
    {
      "url_list": ["*"],
      "tags": ["dlp", "malware"]
    }
  ],
  "block_until_verdict": 1
})";

constexpr char kBlockingScansForDlp[] = R"(
{
  "service_provider": "google",
  "enable": [
    {
      "url_list": ["*"],
      "tags": ["dlp"]
    }
  ],
  "block_until_verdict": 1
})";

#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
constexpr char kBlockingScansForLocalDlp[] = R"(
{
  "service_provider": "local_user_agent",
  "enable": [
    {
      "url_list": ["*"],
      "tags": ["dlp"]
    }
  ],
  "block_until_verdict": 1
})";
#endif  // BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)

constexpr char kBlockingScansForMalware[] = R"(
{
  "service_provider": "google",
  "enable": [
    {
      "url_list": ["*"],
      "tags": ["dlp"]
    }
  ],
  "block_until_verdict": 1
})";

constexpr char kNothingEnabled[] = R"({ "service_provider": "google" })";

// Helpers to get text with sizes relative to the minimum required size of 100
// bytes for scans to trigger.
std::string large_text() {
  return std::string(100, 'a');
}

std::string small_text() {
  return "random small text";
}

base::ReadOnlySharedMemoryRegion create_page(size_t size) {
  base::MappedReadOnlyRegion page =
      base::ReadOnlySharedMemoryRegion::Create(size);
  std::ranges::fill(base::span(page.mapping), 'a');
  return std::move(page.region);
}

base::ReadOnlySharedMemoryRegion normal_page() {
  return create_page(1024);
}

class ScopedSetDMToken {
 public:
  explicit ScopedSetDMToken(const policy::DMToken& dm_token) {
    SetDMTokenForTesting(dm_token);
  }
  ~ScopedSetDMToken() {
    SetDMTokenForTesting(policy::DMToken::CreateEmptyToken());
  }
};

class BaseTest : public testing::Test {
 public:
  BaseTest() : profile_manager_(TestingBrowserProcess::GetGlobal()) {
    EXPECT_TRUE(profile_manager_.SetUp());
    profile_ = profile_manager_.CreateTestingProfile("test-user");
    ContentAnalysisDelegate::DisableUIForTesting();
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/
        {
            safe_browsing::kEnhancedFieldsForSecOps,
            kEnterpriseIframeDlpRulesSupport,
            kDlpScanPastedImages,
        },
        /*disabled_features=*/{});
  }

  void ScanUpload(
      content::WebContents* web_contents,
      ContentAnalysisDelegate::Data data,
      ContentAnalysisDelegate::CompletionCallback callback,
      DeepScanAccessPoint access_point = DeepScanAccessPoint::UPLOAD) {
    ContentAnalysisDelegate::CreateForWebContents(
        web_contents, std::move(data), std::move(callback), access_point);
  }

  void CreateFilesForTest(
      const std::vector<base::FilePath::StringType>& file_names,
      ContentAnalysisDelegate::Data* data,
      const std::string& content = "content") {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    for (const auto& file_name : file_names) {
      base::FilePath path = temp_dir_.GetPath().Append(file_name);
      base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
      file.WriteAtCurrentPos(base::as_byte_span(content));
      data->paths.emplace_back(path);
    }
  }

  Profile* profile() { return profile_; }

  content::WebContents* contents() {
    if (!web_contents_) {
      web_contents_ = content::WebContentsTester::CreateTestWebContents(
          profile(), content::SiteInstance::Create(profile()));
    }
    return web_contents_.get();
  }

  void RunUntilDone() { run_loop_.Run(); }

  void ValidateIsEnabled(const std::string& url,
                         bool expect_dlp,
                         bool expect_malware) {
    ContentAnalysisDelegate::Data data;
    EXPECT_EQ(expect_dlp || expect_malware,
              ContentAnalysisDelegate::IsEnabled(profile(), GURL(url), &data,
                                                 FILE_ATTACHED));
    const auto& tags = data.settings.tags;
    EXPECT_EQ(expect_dlp, tags.find("dlp") != tags.end());
    EXPECT_EQ(expect_malware, tags.find("malware") != tags.end());
  }

  void TearDown() override {
    web_contents_.reset();
    testing::Test::TearDown();
  }

 protected:
  content::BrowserTaskEnvironment task_environment_;
  content::RenderViewHostTestEnabler rvh_test_enabler_;
  base::test::ScopedFeatureList scoped_feature_list_;
  TestingPrefServiceSimple pref_service_;
  TestingProfileManager profile_manager_;
  raw_ptr<TestingProfile> profile_;
  base::ScopedTempDir temp_dir_;
  std::unique_ptr<content::WebContents> web_contents_;
  base::RunLoop run_loop_;
};

}  // namespace

using ContentAnalysisDelegateIsEnabledTest = BaseTest;

TEST_F(ContentAnalysisDelegateIsEnabledTest, NoDMTokenNoPref) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateInvalidToken());

  ContentAnalysisDelegate::Data data;
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                  FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, NoDMToken) {
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_ATTACHED, kBlockingScansForDlpAndMalware);
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateInvalidToken());

  ContentAnalysisDelegate::Data data;
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                  FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, DlpNoPref) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));

  ContentAnalysisDelegate::Data data;
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                  FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, DlpNoPref2) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_ATTACHED, kNothingEnabled);

  ContentAnalysisDelegate::Data data;
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                  FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, DlpNoPref3) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_DOWNLOADED, kBlockingScansForDlpAndMalware);

  ContentAnalysisDelegate::Data data;
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                  FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, DlpEnabled) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_ATTACHED, kBlockingScansForDlp);

  ContentAnalysisDelegate::Data data;
  EXPECT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                 FILE_ATTACHED));
  EXPECT_TRUE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, DlpEnabled2) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_ATTACHED, kBlockingScansForDlp);
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_DOWNLOADED, kBlockingScansForDlp);

  ContentAnalysisDelegate::Data data;
  EXPECT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                 FILE_ATTACHED));
  EXPECT_TRUE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, DlpEnabledWithUrl) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_ATTACHED, kBlockingScansForDlp);
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_DOWNLOADED, kBlockingScansForDlp);
  GURL url(kTestUrl);

  ContentAnalysisDelegate::Data data;
  EXPECT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));
  EXPECT_TRUE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
  EXPECT_EQ(kTestUrl, data.url);
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, DlpDisabledByList) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(profile_->GetPrefs(),
                                                    FILE_ATTACHED,
                                                    R"(
        {
          "service_provider": "google",
          "enable": [
            {
              "url_list": ["*"],
              "tags": ["dlp"]
            }
          ],
          "disable": [
            {
              "url_list": ["http://example.com/"],
              "tags": ["dlp"]
            }
          ],
          "block_until_verdict": 1
        })");

  ContentAnalysisDelegate::Data data;
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(kTestUrl),
                                                  &data, FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, DlpDisabledByListWithPatterns) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(profile_->GetPrefs(),
                                                    FILE_ATTACHED,
                                                    R"(
        {
          "service_provider": "google",
          "enable": [
            {
              "url_list": ["*"],
              "tags": ["dlp"]
            }
          ],
          "disable": [
            {
              "url_list": [
                "http://example.com/",
                "https://*",
                "devtools://*",
                "*/a/specific/path/",
                "*:1234",
                "*?q=5678"
              ],
              "tags": ["dlp"]
            }
          ],
          "block_until_verdict": 1
        })");

  ValidateIsEnabled("http://example.com", /*dlp*/ false, /*malware*/ false);
  ValidateIsEnabled("http://google.com", /*dlp*/ true, /*malware*/ false);
  ValidateIsEnabled("https://google.com", /*dlp*/ false, /*malware*/ false);
  ValidateIsEnabled("custom://google.com", /*dlp*/ true, /*malware*/ false);
  ValidateIsEnabled("custom://version", /*dlp*/ true, /*malware*/ false);
  ValidateIsEnabled("devtools://devtools/bundled/inspector.html", /*dlp*/ false,
                    /*malware*/ false);
  ValidateIsEnabled("custom://devtools/bundled/inspector.html", /*dlp*/ true,
                    /*malware*/ false);
  ValidateIsEnabled("http://google.com/a/specific/path/", /*dlp*/ false,
                    /*malware*/ false);
  ValidateIsEnabled("http://google.com/not/a/specific/path/", /*dlp*/ true,
                    /*malware*/ false);
  ValidateIsEnabled("http://google.com:1234", /*dlp*/ false, /*malware*/ false);
  ValidateIsEnabled("http://google.com:4321", /*dlp*/ true, /*malware*/ false);
  ValidateIsEnabled("http://google.com?q=5678", /*dlp*/ false,
                    /*malware*/ false);
  ValidateIsEnabled("http://google.com?q=8765", /*dlp*/ true,
                    /*malware*/ false);
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, MalwareNoPref) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));

  ContentAnalysisDelegate::Data data;
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                  FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, MalwareNoPref2) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_ATTACHED, kNothingEnabled);

  ContentAnalysisDelegate::Data data;
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                  FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, MalwareNoPref3) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_DOWNLOADED, kBlockingScansForDlpAndMalware);

  ContentAnalysisDelegate::Data data;
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(), &data,
                                                  FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_FALSE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, MalwareEnabled) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(profile_->GetPrefs(),
                                                    FILE_ATTACHED,
                                                    R"(
        {
          "service_provider": "google",
          "enable": [
            {
              "url_list": ["http://example.com/"],
              "tags": ["malware"]
            }
          ],
          "block_until_verdict": 1
        })");

  ContentAnalysisDelegate::Data data;
  EXPECT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), GURL(kTestUrl),
                                                 &data, FILE_ATTACHED));
  EXPECT_FALSE(data.settings.tags.count("dlp"));
  EXPECT_TRUE(data.settings.tags.count("malware"));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, NoScanInIncognito) {
  GURL url(kTestUrl);
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), FILE_ATTACHED, kBlockingScansForDlpAndMalware);

  ContentAnalysisDelegate::Data data;
  EXPECT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  // The same URL should not trigger a scan in incognito.
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(
      profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true), url, &data,
      FILE_ATTACHED));

  // The same URL should not trigger a scan in non-primary OTR profiles
  EXPECT_FALSE(ContentAnalysisDelegate::IsEnabled(
      profile()->GetOffTheRecordProfile(
          Profile::OTRProfileID::CreateUniqueForTesting(),
          /*create_if_needed=*/true),
      url, &data, FILE_ATTACHED));
}

TEST_F(ContentAnalysisDelegateIsEnabledTest, MalwareEnabledWithPatterns) {
  ScopedSetDMToken scoped_dm_token(policy::DMToken::CreateValidToken(kDmToken));
  enterprise_connectors::test::SetAnalysisConnector(profile_->GetPrefs(),
                                                    FILE_ATTACHED,
                                                    R"(
        {
          "service_provider": "google",
          "enable": [
            {
              "url_list": [
                "http://example.com/",
                "https://*",
                "devtools://*",
                "*/a/specific/path/",
                "*:1234",
                "*?q=5678"
              ],
              "tags": ["malware"]
            }
          ],
          "block_until_verdict": 1
        })");

  ContentAnalysisDelegate::Data data;

  ValidateIsEnabled("http://example.com", /*dlp*/ false, /*malware*/ true);
  ValidateIsEnabled("http://google.com", /*dlp*/ false, /*malware*/ false);
  ValidateIsEnabled("https://google.com", /*dlp*/ false, /*malware*/ true);
  ValidateIsEnabled("custom://google.com", /*dlp*/ false, /*malware*/ false);
  ValidateIsEnabled("custom://version", /*dlp*/ false, /*malware*/ false);
  ValidateIsEnabled("devtools://devtools/bundled/inspector.html", /*dlp*/ false,
                    /*malware*/ false);
  ValidateIsEnabled("custom://devtools/bundled/inspector.html", /*dlp*/ false,
                    /*malware*/ false);
  ValidateIsEnabled("http://google.com/a/specific/path/", /*dlp*/ false,
                    /*malware*/ true);
  ValidateIsEnabled("http://google.com/not/a/specific/path/", /*dlp*/ false,
                    /*malware*/ false);
  ValidateIsEnabled("http://google.com:1234", /*dlp*/ false, /*malware*/ true);
  ValidateIsEnabled("http://google.com:4321", /*dlp*/ false, /*malware*/ false);
  ValidateIsEnabled("http://google.com?q=5678", /*dlp*/ false,
                    /*malware*/ true);
  ValidateIsEnabled("http://google.com?q=8765", /*dlp*/ false,
                    /*malware*/ false);
}

class ContentAnalysisDelegateAuditOnlyTest : public BaseTest {
 public:
  ContentAnalysisDelegateAuditOnlyTest() = default;

 protected:
  void SetDLPResponse(ContentAnalysisResponse response) {
    dlp_response_ = std::move(response);
  }

  void PathFailsDeepScan(base::FilePath path,
                         ContentAnalysisResponse response) {
    failures_.insert({std::move(path), std::move(response)});
  }

  void SetScanPolicies(bool dlp, bool malware) {
    include_dlp_ = dlp;
    include_malware_ = malware;

    for (auto connector : {FILE_ATTACHED, BULK_DATA_ENTRY, PRINT}) {
      if (include_dlp_ && include_malware_) {
        enterprise_connectors::test::SetAnalysisConnector(
            profile_->GetPrefs(), connector, kBlockingScansForDlpAndMalware);
      } else if (include_dlp_) {
        enterprise_connectors::test::SetAnalysisConnector(
            profile_->GetPrefs(), connector, kBlockingScansForDlp);
      } else if (include_malware_) {
        enterprise_connectors::test::SetAnalysisConnector(
            profile_->GetPrefs(), connector, kBlockingScansForMalware);
      } else {
        enterprise_connectors::test::SetAnalysisConnector(
            profile_->GetPrefs(), connector, kNothingEnabled);
      }
    }
  }

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

    enterprise_connectors::test::SetAnalysisConnector(
        profile_->GetPrefs(), FILE_ATTACHED, kBlockingScansForDlpAndMalware);
    enterprise_connectors::test::SetAnalysisConnector(
        profile_->GetPrefs(), BULK_DATA_ENTRY, kBlockingScansForDlpAndMalware);
    enterprise_connectors::test::SetAnalysisConnector(
        profile_->GetPrefs(), PRINT, kBlockingScansForDlpAndMalware);

    ContentAnalysisDelegate::SetFactoryForTesting(base::BindRepeating(
        &test::FakeContentAnalysisDelegate::Create, run_loop_.QuitClosure(),
        base::BindRepeating(
            &ContentAnalysisDelegateAuditOnlyTest::ConnectorStatusCallback,
            base::Unretained(this)),
        kDmToken));
    test::FakeContentAnalysisDelegate::
        ResetStaticDialogFlagsAndTotalRequestsCount();
  }

  ContentAnalysisResponse ConnectorStatusCallback(const std::string& contents,
                                                  const base::FilePath& path) {
    // The path succeeds if it is not in the |failures_| maps.
    auto it = failures_.find(path);
    ContentAnalysisResponse response =
        it != failures_.end()
            ? it->second
            : test::FakeContentAnalysisDelegate::SuccessfulResponse([this]() {
                std::set<std::string> tags;
                if (include_dlp_ && !dlp_response_.has_value()) {
                  tags.insert("dlp");
                }
                if (include_malware_) {
                  tags.insert("malware");
                }
                return tags;
              }());

    if (include_dlp_ && dlp_response_.has_value()) {
      *response.add_results() = dlp_response_.value().results(0);
    }

    return response;
  }

 private:
  ScopedSetDMToken scoped_dm_token_{
      policy::DMToken::CreateValidToken(kDmToken)};
  bool include_dlp_ = true;
  bool include_malware_ = true;

  // Paths in this map will be consider to have failed deep scan checks.
  // The actual failure response is given for each path.
  std::map<base::FilePath, ContentAnalysisResponse> failures_;

  // DLP response to ovewrite in the callback if present.
  std::optional<ContentAnalysisResponse> dlp_response_;

#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
  // This installs a fake SDK manager that creates fake SDK clients when
  // its GetClient() method is called. This is needed so that calls to
  // ContentAnalysisSdkManager::Get()->GetClient() do not fail.
  FakeContentAnalysisSdkManager sdk_manager_;
#endif
};

TEST_F(ContentAnalysisDelegateAuditOnlyTest, Empty) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  // Keep |data| empty by not setting any text or paths.

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(0u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_EQ(0u, result.paths_results.size());
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(0,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, StringDataAndReportSuccess) {
  base::HistogramTester histogram_tester_;

  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(1u, data.text.size());
                   EXPECT_EQ(0u, data.paths.size());
                   ASSERT_EQ(1u, result.text_results.size());
                   EXPECT_EQ(0u, result.paths_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());

  // FakeContentAnalysisDelegate is constructed with UPLOAD by default here;
  // just verify a success histogram is recorded.
  histogram_tester_.ExpectTotalCount(
      "Enterprise.ContentAnalysis.Upload.Success.Duration", 1);
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, StringDataAndReportSuccess_Actor) {
  base::HistogramTester histogram_tester_;

  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());

  bool called = false;
  ScanUpload(
      contents(), std::move(data),
      base::BindOnce(
          [](bool* called, const ContentAnalysisDelegate::Data& data,
             ContentAnalysisDelegate::Result& result) { *called = true; },
          &called),
      enterprise_connectors::DeepScanAccessPoint::ACTOR);
  RunUntilDone();
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());

  histogram_tester_.ExpectTotalCount(
      "Enterprise.OnBulkDataEntry.Actor.DataSize", 1);
  histogram_tester_.ExpectTotalCount("Enterprise.OnBulkDataEntry.DataSize", 1);
  histogram_tester_.ExpectTotalCount(
      "Enterprise.ContentAnalysis.Actor.Success.Duration", 1);
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, StringData2) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());
  data.text.emplace_back(large_text());

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(2u, data.text.size());
                   EXPECT_EQ(0u, data.paths.size());
                   ASSERT_EQ(2u, result.text_results.size());
                   EXPECT_EQ(0u, result.paths_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   EXPECT_TRUE(result.text_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, StringData3) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  // Because the strings are small, they are exempt from scanning and will be
  // allowed even when a negative verdict is mocked.
  data.text.emplace_back(small_text());
  data.text.emplace_back(small_text());

  SetDLPResponse(test::FakeContentAnalysisDelegate::DlpResponse(
      ContentAnalysisResponse::Result::SUCCESS, "rule", TriggeredRule::BLOCK));

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(2u, data.text.size());
                   EXPECT_EQ(0u, data.paths.size());
                   ASSERT_EQ(2u, result.text_results.size());
                   EXPECT_EQ(0u, result.paths_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   EXPECT_TRUE(result.text_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  // Text too small, no analysis request is created.
  EXPECT_EQ(0,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, PagePrintAllowed) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data, PRINT));

  data.page = normal_page();
  ASSERT_TRUE(data.page.IsValid());

  bool called = false;
  ContentAnalysisDelegate::CreateForWebContents(
      contents(), std::move(data),
      base::BindOnce(
          [](bool* called, const ContentAnalysisDelegate::Data& data,
             ContentAnalysisDelegate::Result& result) {
            EXPECT_EQ(0u, data.text.size());
            EXPECT_EQ(0u, data.paths.size());
            // The page data should no longer be valid since it's moved
            // to be uploaded in a request.
            EXPECT_FALSE(data.page.IsValid());
            ASSERT_EQ(0u, result.text_results.size());
            EXPECT_EQ(0u, result.paths_results.size());
            EXPECT_TRUE(result.page_result);
            *called = true;
          },
          &called),
      DeepScanAccessPoint::PRINT);
  RunUntilDone();
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, PagePrintBlocked) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data, PRINT));

  data.page = normal_page();
  ASSERT_TRUE(data.page.IsValid());
  SetDLPResponse(test::FakeContentAnalysisDelegate::DlpResponse(
      ContentAnalysisResponse::Result::SUCCESS, "rule", TriggeredRule::BLOCK));

  bool called = false;
  ContentAnalysisDelegate::CreateForWebContents(
      contents(), std::move(data),
      base::BindOnce(
          [](bool* called, const ContentAnalysisDelegate::Data& data,
             ContentAnalysisDelegate::Result& result) {
            EXPECT_EQ(0u, data.text.size());
            EXPECT_EQ(0u, data.paths.size());
            // The page data should no longer be valid since it's moved
            // to be uploaded in a request.
            EXPECT_FALSE(data.page.IsValid());
            ASSERT_EQ(0u, result.text_results.size());
            EXPECT_EQ(0u, result.paths_results.size());
            EXPECT_FALSE(result.page_result);
            *called = true;
          },
          &called),
      DeepScanAccessPoint::PRINT);
  RunUntilDone();
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest,
       FileDataPositiveMalwareAndDlpVerdicts) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  CreateFilesForTest({FILE_PATH_LITERAL("foo.doc")}, &data);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(1u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   ASSERT_EQ(1u, result.paths_results.size());
                   EXPECT_TRUE(result.paths_results[0]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest,
       FileDataPositiveMalwareAndDlpVerdicts2) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  CreateFilesForTest(
      {FILE_PATH_LITERAL("foo.doc"), FILE_PATH_LITERAL("bar.doc")}, &data);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(2u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   ASSERT_EQ(2u, result.paths_results.size());
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_TRUE(result.paths_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(2,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, FileDataPositiveMalwareVerdict) {
  SetScanPolicies(/*dlp=*/false, /*malware=*/true);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  CreateFilesForTest(
      {FILE_PATH_LITERAL("good.doc"), FILE_PATH_LITERAL("good2.doc")}, &data);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(2u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_EQ(2u, result.paths_results.size());
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_TRUE(result.paths_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(2,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, FileIsEncrypted) {
  content::InProcessUtilityThreadHelper in_process_utility_thread_helper;

  enterprise_connectors::test::SetAnalysisConnector(profile_->GetPrefs(),
                                                    FILE_ATTACHED, R"(
    {
      "service_provider": "google",
      "enable": [
        {
          "url_list": ["*"],
          "tags": ["dlp", "malware"]
        }
      ],
      "block_until_verdict": 1,
      "block_password_protected": true
    })");
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  base::FilePath test_zip;
  EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_zip));
  test_zip = test_zip.AppendASCII("safe_browsing")
                 .AppendASCII("download_protection")
                 .AppendASCII("encrypted.zip");
  data.paths.emplace_back(test_zip);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(1u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_EQ(1u, result.paths_results.size());
                   EXPECT_FALSE(result.paths_results[0]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  // "FILE_ATTACHED" is exempt from scanning.
  EXPECT_EQ(0,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, FileIsEncrypted_PolicyAllows) {
  content::InProcessUtilityThreadHelper in_process_utility_thread_helper;

  enterprise_connectors::test::SetAnalysisConnector(profile_->GetPrefs(),
                                                    FILE_ATTACHED, R"(
    {
      "service_provider": "google",
      "enable": [
        {
          "url_list": ["*"],
          "tags": ["dlp", "malware"]
        }
      ],
      "block_until_verdict": 1,
      "block_password_protected": false
    })");
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  base::FilePath test_zip;
  EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_zip));
  test_zip = test_zip.AppendASCII("safe_browsing")
                 .AppendASCII("download_protection")
                 .AppendASCII("encrypted.zip");
  data.paths.emplace_back(test_zip);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(1u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_EQ(1u, result.paths_results.size());
                   EXPECT_TRUE(result.paths_results[0]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  // When resumable upload is in use and the policy does not block encrypted
  // files by default, the file's metadata is uploaded for scanning.
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, FileDataNegativeMalwareVerdict) {
  SetScanPolicies(/*dlp=*/false, /*malware=*/true);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  CreateFilesForTest(
      {FILE_PATH_LITERAL("good.doc"), FILE_PATH_LITERAL("bad.doc")}, &data);
  PathFailsDeepScan(
      data.paths[1],
      test::FakeContentAnalysisDelegate::MalwareResponse(TriggeredRule::BLOCK));

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(2u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_EQ(2u, result.paths_results.size());
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_FALSE(result.paths_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(2,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, FileDataPositiveDlpVerdict) {
  SetScanPolicies(/*dlp=*/true, /*malware=*/false);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  CreateFilesForTest(
      {FILE_PATH_LITERAL("good.doc"), FILE_PATH_LITERAL("good2.doc")}, &data);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(2u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_EQ(2u, result.paths_results.size());
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_TRUE(result.paths_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(2,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, FileDataNegativeDlpVerdict) {
  SetScanPolicies(/*dlp=*/true, /*malware=*/false);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  CreateFilesForTest(
      {FILE_PATH_LITERAL("good.doc"), FILE_PATH_LITERAL("bad.doc")}, &data);

  PathFailsDeepScan(data.paths[1],
                    test::FakeContentAnalysisDelegate::DlpResponse(
                        ContentAnalysisResponse::Result::SUCCESS, "rule",
                        TriggeredRule::BLOCK));

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(2u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_EQ(2u, result.paths_results.size());
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_FALSE(result.paths_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(2,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest,
       FileDataNegativeMalwareAndDlpVerdicts) {
  SetScanPolicies(/*dlp=*/true, /*malware=*/true);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  CreateFilesForTest(
      {FILE_PATH_LITERAL("good.doc"), FILE_PATH_LITERAL("bad.doc")}, &data);

  PathFailsDeepScan(
      data.paths[1],
      test::FakeContentAnalysisDelegate::MalwareAndDlpResponse(
          TriggeredRule::BLOCK, ContentAnalysisResponse::Result::SUCCESS,
          "rule", TriggeredRule::BLOCK));

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(2u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_EQ(2u, result.paths_results.size());
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_FALSE(result.paths_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(2,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, StringFileData) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());
  CreateFilesForTest(
      {FILE_PATH_LITERAL("foo.doc"), FILE_PATH_LITERAL("bar.doc")}, &data);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(1u, data.text.size());
                   EXPECT_EQ(2u, data.paths.size());
                   ASSERT_EQ(1u, result.text_results.size());
                   ASSERT_EQ(2u, result.paths_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_TRUE(result.paths_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(3,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, StringFileDataNoDLP) {
  // Enable malware scan so deep scanning still occurs.
  SetScanPolicies(/*dlp=*/false, /*malware=*/true);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());
  data.text.emplace_back(large_text());
  CreateFilesForTest(
      {FILE_PATH_LITERAL("foo.doc"), FILE_PATH_LITERAL("bar.doc")}, &data);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(2u, data.text.size());
                   EXPECT_EQ(2u, data.paths.size());
                   ASSERT_EQ(2u, result.text_results.size());
                   ASSERT_EQ(2u, result.paths_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   EXPECT_TRUE(result.text_results[1]);
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_TRUE(result.paths_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(3,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, ImageDataCloudScan) {
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), BULK_DATA_ENTRY, kBlockingScansForDlp);

  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.image = large_text();

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_TRUE(result.image_result);
                   *called = true;
                 },
                 &called));
  RunUntilDone();

  // There should be an image request made when the policy is set to do
  // cloud scanning.
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
TEST_F(ContentAnalysisDelegateAuditOnlyTest, ImageDataLocalScan) {
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), BULK_DATA_ENTRY, kBlockingScansForLocalDlp);

  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.image = large_text();

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_TRUE(result.image_result);
                   *called = true;
                 },
                 &called));
  RunUntilDone();

  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}
#endif  // BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)

TEST_F(ContentAnalysisDelegateAuditOnlyTest, TextAndImageDataCloudScan) {
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), BULK_DATA_ENTRY, kBlockingScansForDlp);

  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));
  data.text.emplace_back(large_text());
  data.image = large_text();

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(1u, result.text_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   EXPECT_TRUE(result.image_result);
                   *called = true;
                 },
                 &called));
  RunUntilDone();

  // Both text and image data are scanned for cloud scans.

  EXPECT_EQ(2,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
TEST_F(ContentAnalysisDelegateAuditOnlyTest, TextAndImageDataLocalScan) {
  enterprise_connectors::test::SetAnalysisConnector(
      profile_->GetPrefs(), BULK_DATA_ENTRY, kBlockingScansForLocalDlp);

  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));
  data.text.emplace_back(large_text());
  data.image = large_text();

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(1u, result.text_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   EXPECT_TRUE(result.image_result);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(2,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}
#endif  // BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)

TEST_F(ContentAnalysisDelegateAuditOnlyTest, StringFileDataFailedDLP) {
  SetScanPolicies(/*dlp=*/true, /*malware=*/false);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());
  data.text.emplace_back(large_text());

  SetDLPResponse(test::FakeContentAnalysisDelegate::DlpResponse(
      ContentAnalysisResponse::Result::SUCCESS, "rule", TriggeredRule::BLOCK));

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(2u, data.text.size());
                   EXPECT_EQ(0u, data.paths.size());
                   ASSERT_EQ(2u, result.text_results.size());
                   ASSERT_EQ(0u, result.paths_results.size());
                   EXPECT_FALSE(result.text_results[0]);
                   EXPECT_FALSE(result.text_results[1]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, StringFileDataPartialSuccess) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());
  CreateFilesForTest({FILE_PATH_LITERAL("foo.doc"),
                      FILE_PATH_LITERAL("foo_fail_malware_1.doc"),
                      FILE_PATH_LITERAL("foo_fail_malware_2.doc"),
                      FILE_PATH_LITERAL("foo_fail_dlp_status.doc"),
                      FILE_PATH_LITERAL("foo_fail_dlp_rule.doc")},
                     &data);

  // Mark some files with failed scans.
  PathFailsDeepScan(
      data.paths[1],
      test::FakeContentAnalysisDelegate::MalwareResponse(TriggeredRule::WARN));
  PathFailsDeepScan(
      data.paths[2],
      test::FakeContentAnalysisDelegate::MalwareResponse(TriggeredRule::BLOCK));
  PathFailsDeepScan(data.paths[3],
                    test::FakeContentAnalysisDelegate::DlpResponse(
                        ContentAnalysisResponse::Result::FAILURE, "",
                        TriggeredRule::REPORT_ONLY));
  PathFailsDeepScan(data.paths[4],
                    test::FakeContentAnalysisDelegate::DlpResponse(
                        ContentAnalysisResponse::Result::SUCCESS, "rule",
                        TriggeredRule::BLOCK));

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(1u, data.text.size());
                   EXPECT_EQ(5u, data.paths.size());
                   ASSERT_EQ(1u, result.text_results.size());
                   ASSERT_EQ(5u, result.paths_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_FALSE(result.paths_results[1]);
                   EXPECT_FALSE(result.paths_results[2]);
                   EXPECT_TRUE(result.paths_results[3]);
                   EXPECT_FALSE(result.paths_results[4]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(6,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, NoDelay) {
  enterprise_connectors::test::SetAnalysisConnector(profile_->GetPrefs(),
                                                    FILE_ATTACHED, R"(
    {
      "service_provider": "google",
      "enable": [
        {
          "url_list": ["*"],
          "tags": ["dlp", "malware"]
        }
      ],
      "block_until_verdict": 0
    })");
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  data.text.emplace_back("dlp_text");
  CreateFilesForTest({FILE_PATH_LITERAL("foo_fail_malware_0.doc"),
                      FILE_PATH_LITERAL("foo_fail_malware_1.doc"),
                      FILE_PATH_LITERAL("foo_fail_malware_2.doc"),
                      FILE_PATH_LITERAL("foo_fail_dlp_status.doc"),
                      FILE_PATH_LITERAL("foo_fail_dlp_rule.doc")},
                     &data);

  // Mark all files and text with failed scans.
  SetDLPResponse(test::FakeContentAnalysisDelegate::DlpResponse(
      ContentAnalysisResponse::Result::SUCCESS, "rule", TriggeredRule::BLOCK));
  PathFailsDeepScan(
      data.paths[0],
      test::FakeContentAnalysisDelegate::MalwareResponse(TriggeredRule::BLOCK));
  PathFailsDeepScan(
      data.paths[1],
      test::FakeContentAnalysisDelegate::MalwareResponse(TriggeredRule::WARN));
  PathFailsDeepScan(
      data.paths[2],
      test::FakeContentAnalysisDelegate::MalwareResponse(TriggeredRule::BLOCK));
  PathFailsDeepScan(data.paths[3],
                    test::FakeContentAnalysisDelegate::DlpResponse(
                        ContentAnalysisResponse::Result::FAILURE, "",
                        TriggeredRule::REPORT_ONLY));
  PathFailsDeepScan(data.paths[4],
                    test::FakeContentAnalysisDelegate::DlpResponse(
                        ContentAnalysisResponse::Result::SUCCESS, "rule",
                        TriggeredRule::BLOCK));

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(1u, data.text.size());
                   EXPECT_EQ(5u, data.paths.size());
                   EXPECT_EQ(1u, result.text_results.size());
                   EXPECT_EQ(5u, result.paths_results.size());

                   // All results are set to true since we are not blocking the
                   // user.
                   EXPECT_TRUE(result.text_results[0]);
                   EXPECT_TRUE(result.paths_results[0]);
                   EXPECT_TRUE(result.paths_results[1]);
                   EXPECT_TRUE(result.paths_results[2]);
                   EXPECT_TRUE(result.paths_results[3]);
                   EXPECT_TRUE(result.paths_results[4]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  // Text too small, only file analysis requests are created.
  EXPECT_EQ(5,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateAuditOnlyTest, EmptyWait) {
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(0u, data.paths.size());
                   ASSERT_EQ(0u, result.text_results.size());
                   ASSERT_EQ(0u, result.paths_results.size());
                   *called = true;
                 },
                 &called));
  RunUntilDone();
  EXPECT_EQ(0,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

// test params:
// 0: upload result from binary upload service.
// 1: whether an cloud analysis is done.
// 2: whether to fail open.
class ContentAnalysisDelegateResultHandlingTest
    : public BaseTest,
      public testing::WithParamInterface<
          std::tuple<ScanRequestUploadResult, bool, bool>> {
 public:
  ContentAnalysisDelegateResultHandlingTest() = default;

  void SetUp() override {
    BaseTest::SetUp();
    std::string pref = base::StringPrintf(R"(
    {
      "service_provider": "%s",
      "enable": [{"url_list": ["*"], "tags": ["dlp", "malware"]}],
      "block_until_verdict": 1,
      "default_action": "%s"
    })",
                                          service_provider_setting(),
                                          default_action_setting());
    enterprise_connectors::test::SetAnalysisConnector(profile_->GetPrefs(),
                                                      FILE_ATTACHED, pref);

    ContentAnalysisDelegate::SetFactoryForTesting(base::BindRepeating(
        &test::FakeContentAnalysisDelegate::Create, run_loop_.QuitClosure(),
        base::BindRepeating(
            &ContentAnalysisDelegateResultHandlingTest::ConnectorStatusCallback,
            base::Unretained(this)),
        kDmToken));
    test::FakeContentAnalysisDelegate::
        ResetStaticDialogFlagsAndTotalRequestsCount();
  }

  ScanRequestUploadResult result() const { return std::get<0>(GetParam()); }

  bool is_cloud() const { return std::get<1>(GetParam()); }

  const char* service_provider_setting() const {
    return is_cloud() ? "google" : "local_system_agent";
  }

  bool should_fail_closed() const { return std::get<2>(GetParam()); }

  const char* default_action_setting() const {
    return should_fail_closed() ? "block" : "allow";
  }

  ContentAnalysisResponse ConnectorStatusCallback(const std::string& contents,
                                                  const base::FilePath& path) {
    return test::FakeContentAnalysisDelegate::SuccessfulResponse(
        {"dlp", "malware"});
  }

 protected:
  ScopedSetDMToken scoped_dm_token_{
      policy::DMToken::CreateValidToken(kDmToken)};

  bool ResultIsFailClosed(ScanRequestUploadResult result) {
    return result == ScanRequestUploadResult::kUploadFailure ||
           result == ScanRequestUploadResult::kTimeout ||
           result == ScanRequestUploadResult::kFailedToGetToken ||
           result == ScanRequestUploadResult::kTooManyRequests ||
           result == ScanRequestUploadResult::kUnknown ||
           result == ScanRequestUploadResult::kIncompleteResponse;
  }

#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
  // This installs a fake SDK manager that creates fake SDK clients when
  // its GetClient() method is called. This is needed so that calls to
  // ContentAnalysisSdkManager::Get()->GetClient() do not fail.
  FakeContentAnalysisSdkManager sdk_manager_;
#endif
};

TEST_P(ContentAnalysisDelegateResultHandlingTest, Test) {
  // This is not a desktop platform don't try the non-cloud case since it
  // is not supported.
#if !BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
  if (!is_cloud()) {
    return;
  }
#endif

  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  test::FakeContentAnalysisDelegate::SetResponseResult(result());
  ASSERT_TRUE(
      ContentAnalysisDelegate::IsEnabled(profile(), url, &data, FILE_ATTACHED));

  CreateFilesForTest({FILE_PATH_LITERAL("foo.txt")}, &data);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindLambdaForTesting(
                 [this, &called](const ContentAnalysisDelegate::Data& data,
                                 ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(0u, data.text.size());
                   EXPECT_EQ(1u, data.paths.size());
                   EXPECT_EQ(0u, result.text_results.size());
                   EXPECT_EQ(1u, result.paths_results.size());

                   bool expected =
                       ResultShouldAllowDataUse(data.settings, this->result());
                   EXPECT_EQ(expected, result.paths_results[0]);
                   called = true;
                 }));
  RunUntilDone();
  EXPECT_TRUE(called);

  // Dialog should be shown for fail-close cases, regardless of local or cloud,
  // otherwise dialog should be hidden for local analysis.
  if (ResultIsFailClosed(result()) && should_fail_closed()) {
    EXPECT_TRUE(test::FakeContentAnalysisDelegate::WasDialogShown());
    EXPECT_FALSE(test::FakeContentAnalysisDelegate::WasDialogCanceled());
  } else {
    EXPECT_EQ(is_cloud(), test::FakeContentAnalysisDelegate::WasDialogShown());
    EXPECT_NE(is_cloud(),
              test::FakeContentAnalysisDelegate::WasDialogCanceled());
  }
}

INSTANTIATE_TEST_SUITE_P(
    ,
    ContentAnalysisDelegateResultHandlingTest,
    testing::Combine(testing::Values(ScanRequestUploadResult::kUnknown,
                                     ScanRequestUploadResult::kSuccess,
                                     ScanRequestUploadResult::kUploadFailure,
                                     ScanRequestUploadResult::kTimeout,
                                     ScanRequestUploadResult::kFileTooLarge,
                                     ScanRequestUploadResult::kFailedToGetToken,
                                     ScanRequestUploadResult::kUnauthorized,
                                     ScanRequestUploadResult::kFileEncrypted),
                     testing::Bool(),
                     testing::Bool()));

// The following tests should only be executed on the OS that support LCAC.
#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
class ContentAnalysisDelegateWithLocalClient : public BaseTest {
 public:
  ContentAnalysisDelegateWithLocalClient() = default;

 protected:
  FakeContentAnalysisSdkManager sdk_manager_;

  void SetLocalPolicies(bool should_fail_open) {
    std::string pref = base::StringPrintf(R"(
    {
      "service_provider": "local_system_agent",
      "enable": [{"url_list": ["*"], "tags": ["dlp", "malware"]}],
      "block_until_verdict": 1,
      "default_action": "%s"
    })",
                                          should_fail_open ? "allow" : "block");
    enterprise_connectors::test::SetAnalysisConnector(profile_->GetPrefs(),
                                                      BULK_DATA_ENTRY, pref);
  }

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

    ContentAnalysisDelegate::SetFactoryForTesting(base::BindRepeating(
        &test::FakeContentAnalysisDelegate::Create, run_loop_.QuitClosure(),
        base::BindRepeating(
            &ContentAnalysisDelegateWithLocalClient::ConnectorStatusCallback,
            base::Unretained(this)),
        kDmToken));
    test::FakeContentAnalysisDelegate::
        ResetStaticDialogFlagsAndTotalRequestsCount();
  }

  ContentAnalysisResponse ConnectorStatusCallback(const std::string& contents,
                                                  const base::FilePath& path) {
    return test::FakeContentAnalysisDelegate::SuccessfulResponse(
        {"dlp", "malware"});
  }

 private:
  ScopedSetDMToken scoped_dm_token_{
      policy::DMToken::CreateValidToken(kDmToken)};
};

TEST_F(ContentAnalysisDelegateWithLocalClient, StringDataWithValidClient) {
  SetLocalPolicies(/*should_fail_open=*/true);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());
  sdk_manager_.SetCreateClientAbility(true);

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(1u, data.text.size());
                   EXPECT_EQ(0u, data.paths.size());
                   ASSERT_EQ(1u, result.text_results.size());
                   EXPECT_EQ(0u, result.paths_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();

  EXPECT_FALSE(sdk_manager_.NoConnectionEstablished());
  EXPECT_EQ(1,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateWithLocalClient, FailOpen) {
  SetLocalPolicies(/*should_fail_open=*/true);
  sdk_manager_.SetCreateClientAbility(false);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(1u, data.text.size());
                   EXPECT_EQ(0u, data.paths.size());
                   ASSERT_EQ(1u, result.text_results.size());
                   EXPECT_EQ(0u, result.paths_results.size());
                   EXPECT_TRUE(result.text_results[0]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();

  EXPECT_TRUE(sdk_manager_.NoConnectionEstablished());
  // No local client found, should skip data analysis.
  EXPECT_EQ(0,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}

TEST_F(ContentAnalysisDelegateWithLocalClient, FailClosed) {
  SetLocalPolicies(/*should_fail_open=*/false);
  sdk_manager_.SetCreateClientAbility(false);
  GURL url(kTestUrl);
  ContentAnalysisDelegate::Data data;
  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(profile(), url, &data,
                                                 BULK_DATA_ENTRY));

  data.text.emplace_back(large_text());

  bool called = false;
  ScanUpload(contents(), std::move(data),
             base::BindOnce(
                 [](bool* called, const ContentAnalysisDelegate::Data& data,
                    ContentAnalysisDelegate::Result& result) {
                   EXPECT_EQ(1u, data.text.size());
                   EXPECT_EQ(0u, data.paths.size());
                   ASSERT_EQ(1u, result.text_results.size());
                   EXPECT_EQ(0u, result.paths_results.size());

                   bool expected_result = true;
    // Should only fail closed on Windows.
#if BUILDFLAG(IS_WIN)
                   expected_result = false;
#endif
                   EXPECT_EQ(expected_result, result.text_results[0]);
                   *called = true;
                 },
                 &called));
  RunUntilDone();

  EXPECT_TRUE(sdk_manager_.NoConnectionEstablished());
  // No local client found, should skip data analysis.
  EXPECT_EQ(0,
            test::FakeContentAnalysisDelegate::GetTotalAnalysisRequestsCount());
  EXPECT_TRUE(called);
}
#endif

using ContentAnalysisDelegateDeleteTest = BaseTest;

TEST_F(ContentAnalysisDelegateDeleteTest, RunsCallbackAndDeletes) {
  bool callback_ran = false;
  ContentAnalysisDelegate::Data data;
  auto delegate = test::FakeContentAnalysisDelegate::Create(
      run_loop_.QuitClosure(),
      base::BindRepeating([](const std::string&, const base::FilePath&) {
        return test::FakeContentAnalysisDelegate::SuccessfulResponse({"dlp"});
      }),
      kDmToken, contents(), std::move(data),
      base::BindLambdaForTesting([&](const ContentAnalysisDelegate::Data& data,
                                     ContentAnalysisDelegate::Result& result) {
        callback_ran = true;
      }),
      DeepScanAccessPoint::COPY);

  auto* delegate_ptr = delegate.release();
  delegate_ptr->Delete();
  RunUntilDone();

  EXPECT_TRUE(callback_ran);
}

TEST_F(ContentAnalysisDelegateDeleteTest, DoesNotRunCallbackIfAlreadyRun) {
  int callback_count = 0;
  ContentAnalysisDelegate::Data data;
  auto delegate = test::FakeContentAnalysisDelegate::Create(
      run_loop_.QuitClosure(),
      base::BindRepeating([](const std::string&, const base::FilePath&) {
        return test::FakeContentAnalysisDelegate::SuccessfulResponse({"dlp"});
      }),
      kDmToken, contents(), std::move(data),
      base::BindLambdaForTesting(
          [&](const ContentAnalysisDelegate::Data& data,
              ContentAnalysisDelegate::Result& result) { callback_count++; }),
      DeepScanAccessPoint::COPY);

  auto* delegate_ptr = delegate.release();
  delegate_ptr->BypassWarnings(std::nullopt);

  EXPECT_EQ(1, callback_count);

  delegate_ptr->Delete();
  RunUntilDone();

  EXPECT_EQ(1, callback_count);
}

using ContentAnalysisDelegateUpdateFinalResultTest = BaseTest;

class MinimalTestContentAnalysisDelegate : public ContentAnalysisDelegate {
 public:
  MinimalTestContentAnalysisDelegate(content::WebContents* web_contents,
                                     Data data)
      : ContentAnalysisDelegate(
            web_contents,
            std::move(data),
            base::BindOnce([](const Data& data, Result& result) {}),
            DeepScanAccessPoint::PASTE) {}
};

TEST_F(ContentAnalysisDelegateUpdateFinalResultTest, Precedence) {
  ContentAnalysisDelegate::Data data;
  data.url = GURL("https://example.com");

  // Create a minimal delegate just to call UpdateFinalResult on it.
  auto delegate = std::make_unique<MinimalTestContentAnalysisDelegate>(
      contents(), std::move(data));

  // Initial state should be SUCCESS.
  EXPECT_EQ(FinalContentAnalysisResult::SUCCESS, delegate->final_result_);

  // Overriding SUCCESS with KEPT_IN_MANAGED_CHROME should work.
  delegate->UpdateFinalResult(
      FinalContentAnalysisResult::KEPT_IN_MANAGED_CHROME, "dlp", {});
  EXPECT_EQ(FinalContentAnalysisResult::KEPT_IN_MANAGED_CHROME,
            delegate->final_result_);

  // Overriding KEPT_IN_MANAGED_CHROME with FAILURE should work.
  delegate->UpdateFinalResult(FinalContentAnalysisResult::FAILURE, "dlp", {});
  EXPECT_EQ(FinalContentAnalysisResult::FAILURE, delegate->final_result_);

  // Attempting to override FAILURE with WARNING should NOT work.
  delegate->UpdateFinalResult(FinalContentAnalysisResult::WARNING, "dlp", {});
  EXPECT_EQ(FinalContentAnalysisResult::FAILURE, delegate->final_result_);

  // Attempting to override FAILURE with KEPT_IN_MANAGED_CHROME should
  // NOT work.
  delegate->UpdateFinalResult(
      FinalContentAnalysisResult::KEPT_IN_MANAGED_CHROME, "dlp", {});
  EXPECT_EQ(FinalContentAnalysisResult::FAILURE, delegate->final_result_);
}

using ContentAnalysisDelegateFrameUrlChainTest = BaseTest;

TEST_F(ContentAnalysisDelegateFrameUrlChainTest,
       DefaultToFocusedFrameWhenInitiatingFrameOmitted) {
  content::WebContentsTester::For(contents())->NavigateAndCommit(
      GURL(kTestUrl));

  const GURL child_frame_url("https://subframe.example.com/");
  content::RenderFrameHostTester* rfh_tester =
      content::RenderFrameHostTester::For(
          contents()->GetPrimaryMainFrame());

  content::RenderFrameHost* child_frame =
      rfh_tester->AppendChild("child_frame");
  child_frame = content::NavigationSimulator::NavigateAndCommitFromDocument(
      child_frame_url, child_frame);

  // Focus the child frame.
  content::FocusWebContentsOnFrame(contents(), child_frame);

  ContentAnalysisDelegate::Data data;
  data.url = GURL("https://example.com");
  // data.initiating_frame_id is omitted (std::nullopt).

  auto delegate = std::make_unique<MinimalTestContentAnalysisDelegate>(
      contents(), std::move(data));

  google::protobuf::RepeatedPtrField<std::string> frame_urls =
      delegate->frame_url_chain();

  ASSERT_EQ(1, frame_urls.size());
  EXPECT_EQ(child_frame_url.spec(), frame_urls[0]);
}

TEST_F(ContentAnalysisDelegateFrameUrlChainTest,
       UsesInitiatingFrameWhenMainFrameFocused) {
  content::WebContentsTester::For(contents())->NavigateAndCommit(
      GURL(kTestUrl));

  const GURL child_frame_url("https://subframe.example.com/");
  content::RenderFrameHostTester* rfh_tester =
      content::RenderFrameHostTester::For(
          contents()->GetPrimaryMainFrame());

  content::RenderFrameHost* child_frame =
      rfh_tester->AppendChild("child_frame");
  child_frame = content::NavigationSimulator::NavigateAndCommitFromDocument(
      child_frame_url, child_frame);

  // Focus the main frame, simulating focus change away from the child frame.
  content::FocusWebContentsOnFrame(contents(),
                                   contents()->GetPrimaryMainFrame());

  ContentAnalysisDelegate::Data data;
  data.url = GURL("https://example.com");
  data.initiating_frame_id = child_frame->GetGlobalId();

  auto delegate = std::make_unique<MinimalTestContentAnalysisDelegate>(
      contents(), std::move(data));

  google::protobuf::RepeatedPtrField<std::string> frame_urls =
      delegate->frame_url_chain();

  ASSERT_EQ(1, frame_urls.size());
  EXPECT_EQ(child_frame_url.spec(), frame_urls[0]);
}

TEST_F(ContentAnalysisDelegateFrameUrlChainTest,
       UsesInitiatingFrameWhenSiblingFrameFocused) {
  content::WebContentsTester::For(contents())->NavigateAndCommit(
      GURL(kTestUrl));

  const GURL untrusted_frame_url("https://untrusted.example.com/");
  const GURL trusted_sibling_frame_url("https://trusted-sibling.example.com/");

  content::RenderFrameHostTester* rfh_tester =
      content::RenderFrameHostTester::For(
          contents()->GetPrimaryMainFrame());

  content::RenderFrameHost* untrusted_frame =
      rfh_tester->AppendChild("untrusted_frame");
  untrusted_frame = content::NavigationSimulator::NavigateAndCommitFromDocument(
      untrusted_frame_url, untrusted_frame);

  content::RenderFrameHost* trusted_sibling_frame =
      rfh_tester->AppendChild("trusted_sibling_frame");
  trusted_sibling_frame =
      content::NavigationSimulator::NavigateAndCommitFromDocument(
          trusted_sibling_frame_url, trusted_sibling_frame);

  // Focus the trusted sibling frame, simulating focus shift to a trusted sibling.
  content::FocusWebContentsOnFrame(contents(), trusted_sibling_frame);

  ContentAnalysisDelegate::Data data;
  data.url = GURL("https://example.com");
  data.initiating_frame_id = untrusted_frame->GetGlobalId();

  auto delegate = std::make_unique<MinimalTestContentAnalysisDelegate>(
      contents(), std::move(data));

  google::protobuf::RepeatedPtrField<std::string> frame_urls =
      delegate->frame_url_chain();

  // The chain must contain the untrusted initiating frame, NOT the focused sibling.
  ASSERT_EQ(1, frame_urls.size());
  EXPECT_EQ(untrusted_frame_url.spec(), frame_urls[0]);
}

}  // namespace enterprise_connectors
