// Copyright 2018 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/web_applications/externally_managed_app_manager.h"

#include <algorithm>
#include <iterator>
#include <optional>
#include <sstream>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/publishers/app_publisher.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/externally_managed_app_manager.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/fake_web_contents_manager.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_management_type.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/web_contents/web_app_url_loader.h"
#include "components/webapps/common/web_page_metadata.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace web_app {

namespace {

int GetInstallCountsFromResult(
    const std::map<GURL, ExternallyManagedAppManager::InstallResult>&
        install_results,
    bool include_duplicates = false) {
  int count = 0;
  for (const auto& result : install_results) {
    if (result.second.code == webapps::InstallResultCode::kSuccessNewInstall ||
        (include_duplicates &&
         result.second.code ==
             webapps::InstallResultCode::kSuccessAlreadyInstalled)) {
      count++;
    }
  }
  return count;
}

}  // namespace

class ExternallyManagedAppManagerTest : public WebAppTest {
 public:
  ExternallyManagedAppManagerTest() = default;

 protected:
  void SetUp() override {
    WebAppTest::SetUp();
    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
  }

  void ForceSystemShutdown() { fake_provider().Shutdown(); }

  void Sync(const std::vector<GURL>& urls, bool include_duplicates = false) {
    ResetCounts();

    std::vector<ExternalInstallOptions> install_options_list;
    install_options_list.reserve(urls.size());
    for (const auto& url : urls) {
      install_options_list.emplace_back(
          url, mojom::UserDisplayMode::kStandalone,
          ExternalInstallSource::kInternalDefault);
    }

    base::RunLoop run_loop;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        std::move(install_options_list),
        ExternalInstallSource::kInternalDefault,
        base::BindLambdaForTesting(
            [&](std::map<GURL, ExternallyManagedAppManager::InstallResult>
                    install_results,
                std::map<GURL, webapps::UninstallResultCode>
                    uninstall_results) {
              install_count_ = GetInstallCountsFromResult(install_results,
                                                          include_duplicates);
              uninstall_count_ = uninstall_results.size();
              run_loop.Quit();
            }));
    // Wait for SynchronizeInstalledApps to finish.
    run_loop.Run();
  }

  void Expect(int install_count,
              int uninstall_count,
              const std::vector<GURL>& installed_app_urls) {
    EXPECT_EQ(install_count, install_count_);
    EXPECT_EQ(uninstall_count, uninstall_count_);
    base::flat_map<webapps::AppId, base::flat_set<GURL>> apps =
        app_registrar().GetExternallyInstalledApps(
            ExternalInstallSource::kInternalDefault);
    std::vector<GURL> urls;
    for (const auto& it : apps) {
      std::ranges::copy(it.second, std::back_inserter(urls));
    }

    std::sort(urls.begin(), urls.end());
    EXPECT_EQ(installed_app_urls, urls);
  }

  void ResetCounts() {
    install_count_ = 0;
    uninstall_count_ = 0;
  }

  WebAppRegistrar& app_registrar() {
    return fake_provider().registrar_unsafe();
  }

 private:
  int install_count_ = 0;
  int uninstall_count_ = 0;
};

// Test that destroying ExternallyManagedAppManager during a synchronize call
// that installs an app doesn't crash. Regression test for
// https://crbug.com/41458655
TEST_F(ExternallyManagedAppManagerTest, DestroyDuringInstallInSynchronize) {
  std::vector<ExternalInstallOptions> install_options_list;
  install_options_list.emplace_back(GURL("https://foo.example"),
                                    mojom::UserDisplayMode::kStandalone,
                                    ExternalInstallSource::kInternalDefault);
  install_options_list.emplace_back(GURL("https://bar.example"),
                                    mojom::UserDisplayMode::kStandalone,
                                    ExternalInstallSource::kInternalDefault);

  fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
      std::move(install_options_list), ExternalInstallSource::kInternalDefault,
      // ExternallyManagedAppManager gives no guarantees about whether its
      // pending callbacks will be run or not when it gets destroyed.
      base::DoNothing());
  ForceSystemShutdown();
  base::RunLoop().RunUntilIdle();
}

// Test that destroying ExternallyManagedAppManager during a synchronize call
// that uninstalls an app doesn't crash. Regression test for
// https://crbug.com/41458655
TEST_F(ExternallyManagedAppManagerTest, DestroyDuringUninstallInSynchronize) {
  // Install an app that will be uninstalled next.
  {
    std::vector<ExternalInstallOptions> install_options_list;
    install_options_list.emplace_back(GURL("https://foo.example"),
                                      mojom::UserDisplayMode::kStandalone,
                                      ExternalInstallSource::kInternalDefault);
    base::RunLoop run_loop;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        std::move(install_options_list),
        ExternalInstallSource::kInternalDefault,
        base::BindLambdaForTesting(
            [&](std::map<GURL, ExternallyManagedAppManager::InstallResult>
                    install_results,
                std::map<GURL, webapps::UninstallResultCode>
                    uninstall_results) { run_loop.Quit(); }));
    run_loop.Run();
  }

  fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
      std::vector<ExternalInstallOptions>(),
      ExternalInstallSource::kInternalDefault,
      // ExternallyManagedAppManager gives no guarantees about whether its
      // pending callbacks will be run or not when it gets destroyed.
      base::DoNothing());
  ForceSystemShutdown();
  base::RunLoop().RunUntilIdle();
}

TEST_F(ExternallyManagedAppManagerTest, SynchronizeInstalledApps) {
  GURL a("https://a.example.com/");
  GURL b("https://b.example.com/");
  GURL c("https://c.example.com/");
  GURL d("https://d.example.com/");
  GURL e("https://e.example.com/");

  FakeWebContentsManager& web_contents_manager =
      static_cast<FakeWebContentsManager&>(
          fake_provider().web_contents_manager());
  web_contents_manager.CreateBasicInstallPageState(a, a, a);
  web_contents_manager.CreateBasicInstallPageState(b, b, b);
  web_contents_manager.CreateBasicInstallPageState(c, c, c);
  web_contents_manager.CreateBasicInstallPageState(d, d, d);
  web_contents_manager.CreateBasicInstallPageState(e, e, e);

  Sync(std::vector<GURL>{a, b, d});
  Expect(3, 0, std::vector<GURL>{a, b, d});

  Sync(std::vector<GURL>{b, e});
  Expect(1, 2, std::vector<GURL>{b, e});

  Sync(std::vector<GURL>{e});
  Expect(0, 1, std::vector<GURL>{e});

  Sync(std::vector<GURL>{c});
  Expect(1, 1, std::vector<GURL>{c});

  Sync(std::vector<GURL>{e, a, d});
  Expect(3, 1, std::vector<GURL>{a, d, e});

  Sync(std::vector<GURL>{c, a, b, d, e});
  Expect(2, 0, std::vector<GURL>{a, b, c, d, e});

  Sync(std::vector<GURL>{});
  Expect(0, 5, std::vector<GURL>{});

  // The remaining code tests duplicate inputs.
  Sync(std::vector<GURL>{b, a, b, c}, /*include_duplicates=*/true);
  Expect(3, 0, std::vector<GURL>{a, b, c});

  Sync(std::vector<GURL>{e, a, e, e, e, a}, /*include_duplicates=*/true);
  Expect(2, 2, std::vector<GURL>{a, e});

  Sync(std::vector<GURL>{b, c, d}, /*include_duplicates=*/true);
  Expect(3, 2, std::vector<GURL>{b, c, d});

  Sync(std::vector<GURL>{a, a, a, a, a, a}, /*include_duplicates=*/true);
  Expect(1, 3, std::vector<GURL>{a});

  Sync(std::vector<GURL>{}, /*include_duplicates=*/true);
  Expect(0, 1, std::vector<GURL>{});
}

namespace {

using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;

std::unique_ptr<WebAppInstallInfo> GetWebAppInstallInfo(const GURL& url) {
  std::unique_ptr<WebAppInstallInfo> info =
      WebAppInstallInfo::CreateWithStartUrlForTesting(url);
  info->scope = url.GetWithoutFilename();
  info->title = u"Web App";
  return info;
}

// Test harness that keep the system as real as possible.
class ExternallyAppManagerTest : public WebAppTest {
 public:
  using InstallResults = std::map<GURL /*install_url*/,
                                  ExternallyManagedAppManager::InstallResult>;
  using UninstallResults =
      std::map<GURL /*install_url*/, webapps::UninstallResultCode>;
  using SynchronizeFuture =
      base::test::TestFuture<InstallResults, UninstallResults>;
  using InstallNowFuture =
      base::test::TestFuture<const GURL&,
                             ExternallyManagedAppManager::InstallResult>;

  ExternallyAppManagerTest() = default;

  void SetUp() override {
    WebAppTest::SetUp();
    // TODO(http://b/278922549): Disable the external management apps so we
    // don't compete with the policy app manager for our installs /
    // synchronization.
    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
  }

  std::vector<ExternalInstallOptions> CreateExternalInstallOptionsFromTemplate(
      std::vector<GURL> install_urls,
      ExternalInstallSource source,
      std::optional<ExternalInstallOptions> template_options = std::nullopt) {
    std::vector<ExternalInstallOptions> output;
    std::ranges::transform(
        install_urls, std::back_inserter(output),
        [source, &template_options](const GURL& install_url) {
          ExternalInstallOptions options = template_options.value_or(
              ExternalInstallOptions(install_url, std::nullopt, source));
          options.install_url = install_url;
          options.install_source = source;
          return options;
        });
    return output;
  }

  ExternalInstallOptions CreateExternalInstallOptionsWithAppInfo(
      GURL install_url,
      ExternalInstallSource source) {
    ExternalInstallOptions options(install_url,
                                   mojom::UserDisplayMode::kBrowser, source);
    options.app_info_factory =
        base::BindRepeating(&GetWebAppInstallInfo, install_url);
    return options;
  }

  WebAppRegistrar& app_registrar() {
    return fake_provider().registrar_unsafe();
  }

  ExternallyManagedAppManager& external_manager() {
    return fake_provider().externally_managed_app_manager();
  }

  FakeWebContentsManager& web_contents_manager() {
    return static_cast<FakeWebContentsManager&>(
        fake_provider().web_contents_manager());
  }
};

TEST_F(ExternallyAppManagerTest, NoNetworkNoPlaceholder) {
  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");

  // Not populating the `FakeWebContentsManager` means it treats the network as
  // non-functional / not available.

  SynchronizeFuture result;
  external_manager().SynchronizeInstalledApps(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl}, ExternalInstallSource::kExternalPolicy),
      ExternalInstallSource::kExternalPolicy, result.GetCallback());
  ASSERT_TRUE(result.Wait());

  // Empty uninstall results.
  EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty());

  // Install should have failed.
  std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
      result.Get<InstallResults>();
  EXPECT_THAT(install_results,
              ElementsAre(std::make_pair(
                  kInstallUrl,
                  ExternallyManagedAppManager::InstallResult(
                      webapps::InstallResultCode::kInstallURLLoadFailed))));
}

TEST_F(ExternallyAppManagerTest, SimpleInstall) {
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kInstallUrl =
      GURL("https://www.example.com/nested/install_url.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl, kStartUrl);

  SynchronizeFuture result;
  external_manager().SynchronizeInstalledApps(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl}, ExternalInstallSource::kExternalPolicy),
      ExternalInstallSource::kExternalPolicy, result.GetCallback());
  ASSERT_TRUE(result.Wait());

  // Empty uninstall results.
  EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty());

  // Install should succeed.
  std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
      result.Get<InstallResults>();
  EXPECT_THAT(
      install_results,
      ElementsAre(std::make_pair(
          kInstallUrl,
          ExternallyManagedAppManager::InstallResult(
              webapps::InstallResultCode::kSuccessNewInstall, app_id))));
}

// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_SimpleInstallWebAppInfo DISABLED_SimpleInstallWebAppInfo
#else
#define MAYBE_SimpleInstallWebAppInfo SimpleInstallWebAppInfo
#endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_SimpleInstallWebAppInfo) {
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kInstallUrl =
      GURL("https://www.example.com/nested/install_url.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl, kStartUrl);

  InstallNowFuture future;
  external_manager().InstallNow(
      CreateExternalInstallOptionsWithAppInfo(
          kInstallUrl, ExternalInstallSource::kExternalPolicy),
      future.GetCallback());
  ASSERT_TRUE(future.Wait());

  // Install should succeed.
  ExternallyManagedAppManager::InstallResult result =
      future.Get<ExternallyManagedAppManager::InstallResult>();
  EXPECT_EQ(result,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id));
}

// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_MultipleInstallWebAppInfoInstallUrl \
  DISABLED_MultipleInstallWebAppInfoInstallUrl
#else
#define MAYBE_MultipleInstallWebAppInfoInstallUrl \
  MultipleInstallWebAppInfoInstallUrl
#endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_MultipleInstallWebAppInfoInstallUrl) {
  const GURL kStartUrl1 = GURL("https://www.example1.com/index.html");
  const GURL kInstallUrl1 =
      GURL("https://www.example1.com/nested/install_url.html");
  const GURL kManifestUrl1 = GURL("https://www.example1.com/manifest.json");

  const GURL kStartUrl2 = GURL("https://www.example2.com/index.html");
  const GURL kInstallUrl2 =
      GURL("https://www.example2.com/nested/install_url.html");
  const GURL kManifestUrl2 = GURL("https://www.example2.com/manifest.json");

  webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl1, kManifestUrl1, kStartUrl1);

  webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl2, kManifestUrl2, kStartUrl2);

  InstallNowFuture future1;
  InstallNowFuture future2;
  external_manager().InstallNow(
      CreateExternalInstallOptionsWithAppInfo(
          kInstallUrl1, ExternalInstallSource::kExternalDefault),
      future1.GetCallback());
  external_manager().InstallNow(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl2}, ExternalInstallSource::kExternalPolicy)[0],
      future2.GetCallback());
  ASSERT_TRUE(future1.Wait());
  ASSERT_TRUE(future2.Wait());

  // Both installs should succeed.
  ExternallyManagedAppManager::InstallResult result1 =
      future1.Get<ExternallyManagedAppManager::InstallResult>();
  ExternallyManagedAppManager::InstallResult result2 =
      future2.Get<ExternallyManagedAppManager::InstallResult>();

  EXPECT_EQ(result1,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id1));
  EXPECT_EQ(result2,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id2));
}

// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_InstallWebAppInfoTwiceAlreadyInstalled \
  DISABLED_InstallWebAppInfoTwiceAlreadyInstalled
#else
#define MAYBE_InstallWebAppInfoTwiceAlreadyInstalled \
  InstallWebAppInfoTwiceAlreadyInstalled
#endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_InstallWebAppInfoTwiceAlreadyInstalled) {
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kInstallUrl =
      GURL("https://www.example.com/nested/install_url.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl, kStartUrl);

  InstallNowFuture future1;
  InstallNowFuture future2;
  external_manager().Install(
      CreateExternalInstallOptionsWithAppInfo(
          kInstallUrl, ExternalInstallSource::kExternalDefault),
      future1.GetCallback());
  external_manager().Install(
      CreateExternalInstallOptionsWithAppInfo(
          kInstallUrl, ExternalInstallSource::kExternalDefault),
      future2.GetCallback());
  ASSERT_TRUE(future1.Wait());
  ASSERT_TRUE(future2.Wait());

  // Both installs should succeed, with the 2nd one returning a
  // `kSuccessAlreadyInstalled` result.
  ExternallyManagedAppManager::InstallResult result1 =
      future1.Get<ExternallyManagedAppManager::InstallResult>();
  ExternallyManagedAppManager::InstallResult result2 =
      future2.Get<ExternallyManagedAppManager::InstallResult>();

  EXPECT_EQ(result1,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id));
  EXPECT_EQ(result2,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessAlreadyInstalled, app_id));
}

// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_InstallOptionsTwiceForReinstall \
  DISABLED_InstallOptionsTwiceForReinstall
#else
#define MAYBE_InstallOptionsTwiceForReinstall InstallOptionsTwiceForReinstall
#endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_InstallOptionsTwiceForReinstall) {
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kInstallUrl =
      GURL("https://www.example.com/nested/install_url.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl, kStartUrl);

  ExternalInstallOptions options = CreateExternalInstallOptionsFromTemplate(
      {kInstallUrl}, ExternalInstallSource::kExternalPolicy)[0];
  options.force_reinstall = true;

  InstallNowFuture future1;
  InstallNowFuture future2;
  external_manager().Install(options, future1.GetCallback());
  external_manager().Install(options, future2.GetCallback());
  ASSERT_TRUE(future1.Wait());
  ASSERT_TRUE(future2.Wait());

  // Both installs should succeed, with the 2nd one returning a
  // `kSuccessNewInstall` result because of the `force_reinstall` flag.
  ExternallyManagedAppManager::InstallResult result1 =
      future1.Get<ExternallyManagedAppManager::InstallResult>();
  ExternallyManagedAppManager::InstallResult result2 =
      future2.Get<ExternallyManagedAppManager::InstallResult>();

  EXPECT_EQ(result1,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id));
  EXPECT_EQ(result2,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id));
}

// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_InstallReentrantCallback DISABLED_InstallReentrantCallback
#else
#define MAYBE_InstallReentrantCallback InstallReentrantCallback
#endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_InstallReentrantCallback) {
  const GURL kStartUrl1 = GURL("https://www.example1.com/index.html");
  const GURL kInstallUrl1 =
      GURL("https://www.example1.com/nested/install_url.html");
  const GURL kManifestUrl1 = GURL("https://www.example1.com/manifest.json");

  const GURL kStartUrl2 = GURL("https://www.example2.com/index.html");
  const GURL kInstallUrl2 =
      GURL("https://www.example2.com/nested/install_url.html");
  const GURL kManifestUrl2 = GURL("https://www.example2.com/manifest.json");

  webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl1, kManifestUrl1, kStartUrl1);

  webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl2, kManifestUrl2, kStartUrl2);

  InstallNowFuture inside_future;
  ExternallyManagedAppManager::InstallResult external_result;
  external_manager().InstallNow(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl1}, ExternalInstallSource::kExternalPolicy)[0],
      base::BindLambdaForTesting(
          [&](const GURL& url,
              ExternallyManagedAppManager::InstallResult result) {
            // Verify the first installation has succeeded.
            EXPECT_EQ(
                result,
                ExternallyManagedAppManager::InstallResult(
                    webapps::InstallResultCode::kSuccessNewInstall, app_id1));
            external_manager().InstallNow(
                CreateExternalInstallOptionsFromTemplate(
                    {kInstallUrl2}, ExternalInstallSource::kExternalPolicy)[0],
                inside_future.GetCallback());
          }));
  ASSERT_TRUE(inside_future.Wait());

  // The intermediary install inside the callback should succeed.
  ExternallyManagedAppManager::InstallResult result =
      inside_future.Get<ExternallyManagedAppManager::InstallResult>();
  EXPECT_EQ(result,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id2));
}

// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_MultipleAppsInstallSerial DISABLED_MultipleAppsInstallSerial
#else
#define MAYBE_MultipleAppsInstallSerial MultipleAppsInstallSerial
#endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_MultipleAppsInstallSerial) {
  const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
  const GURL kInstallUrl1 =
      GURL("https://www.example.com/nested/install_url1.html");
  const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");

  webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl1, kManifestUrl1, kStartUrl1);

  SynchronizeFuture result1;
  external_manager().SynchronizeInstalledApps(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl1}, ExternalInstallSource::kExternalPolicy),
      ExternalInstallSource::kExternalPolicy, result1.GetCallback());
  ASSERT_TRUE(result1.Wait());

  // Empty uninstall results.
  EXPECT_THAT(result1.Get<UninstallResults>(), IsEmpty());

  // Install should succeed.
  std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results1 =
      result1.Get<InstallResults>();
  EXPECT_THAT(
      install_results1,
      ElementsAre(std::make_pair(
          kInstallUrl1,
          ExternallyManagedAppManager::InstallResult(
              webapps::InstallResultCode::kSuccessNewInstall, app_id1))));

  const GURL kStartUrl2 = GURL("https://www.example2.com/index.html");
  const GURL kInstallUrl2 =
      GURL("https://www.example2.com/nested/install_url.html");
  const GURL kManifestUrl2 = GURL("https://www.example2.com/manifest.json");

  webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl2, kManifestUrl2, kStartUrl2);

  SynchronizeFuture result2;
  external_manager().SynchronizeInstalledApps(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl2}, ExternalInstallSource::kExternalPolicy),
      ExternalInstallSource::kExternalPolicy, result2.GetCallback());
  ASSERT_TRUE(result2.Wait());

  std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results2 =
      result2.Get<InstallResults>();
  EXPECT_THAT(
      install_results2,
      ElementsAre(std::make_pair(
          kInstallUrl2,
          ExternallyManagedAppManager::InstallResult(
              webapps::InstallResultCode::kSuccessNewInstall, app_id2))));
}

// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_PlaceholderAppWindowsClosed DISABLED_PlaceholderAppWindowsClosed
#else
#define MAYBE_PlaceholderAppWindowsClosed PlaceholderAppWindowsClosed
#endif  // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_PlaceholderAppWindowsClosed) {
  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
  ExternalInstallOptions template_options(
      kInstallUrl, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);
  template_options.install_placeholder = true;

  SynchronizeFuture result;
  external_manager().SynchronizeInstalledApps(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl}, ExternalInstallSource::kExternalPolicy,
          template_options),
      ExternalInstallSource::kExternalPolicy, result.GetCallback());
  ASSERT_TRUE(result.Wait());

  // The webapps::AppId should be created from the install url.
  webapps::AppId app_id =
      GenerateAppId(/*manifest_id_path=*/std::nullopt, kInstallUrl);

  // Install should succeed.
  std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
      result.Get<InstallResults>();
  EXPECT_THAT(
      install_results,
      ElementsAre(std::make_pair(
          kInstallUrl,
          ExternallyManagedAppManager::InstallResult(
              webapps::InstallResultCode::kSuccessNewInstall, app_id))));

  const WebApp* app = fake_provider().registrar_unsafe().GetAppById(app_id);

  ASSERT_TRUE(app);
  EXPECT_THAT(app->management_to_external_config_map(),
              ElementsAre(std::make_pair(WebAppManagement::kPolicy,
                                         WebApp::ExternalManagementConfig(
                                             /*is_placeholder=*/true,
                                             /*install_urls=*/{kInstallUrl},
                                             /*additional_policy_ids=*/{}))));
}

TEST_F(ExternallyAppManagerTest, TwoInstallUrlsSameApp) {
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kInstallUrl1 =
      GURL("https://www.example.com/nested/install_url.html");
  const GURL kInstallUrl2 =
      GURL("https://www.example.com/nested/install_url2.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl1, kManifestUrl, kStartUrl);
  webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl2, kManifestUrl, kStartUrl);
  EXPECT_EQ(app_id, app_id2);

  SynchronizeFuture result;
  external_manager().SynchronizeInstalledApps(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl1, kInstallUrl2}, ExternalInstallSource::kExternalPolicy),
      ExternalInstallSource::kExternalPolicy, result.GetCallback());
  ASSERT_TRUE(result.Wait());

  // Empty uninstall results.
  EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty());

  // Installs should have both succeeded.
  std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
      result.Get<InstallResults>();
  EXPECT_THAT(
      install_results,
      UnorderedElementsAre(
          std::make_pair(
              kInstallUrl1,
              ExternallyManagedAppManager::InstallResult(
                  webapps::InstallResultCode::kSuccessNewInstall, app_id)),
          std::make_pair(
              kInstallUrl2,
              ExternallyManagedAppManager::InstallResult(
                  webapps::InstallResultCode::kSuccessNewInstall, app_id))));

  EXPECT_EQ(app_registrar().GetAppIds().size(), 1ul);
  const WebApp* app = app_registrar().GetAppById(app_id);
  ASSERT_TRUE(app);
  EXPECT_THAT(app->management_to_external_config_map(),
              ElementsAre(std::make_pair(
                  WebAppManagement::kPolicy,
                  WebApp::ExternalManagementConfig(
                      /*is_placeholder=*/false,
                      /*install_urls=*/{kInstallUrl1, kInstallUrl2},
                      /*additional_policy_ids=*/{}))));
}

TEST_F(ExternallyAppManagerTest, RemovingInstallUrlsFromSource) {
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kInstallUrl1 =
      GURL("https://www.example.com/nested/install_url.html");
  const GURL kInstallUrl2 =
      GURL("https://www.example.com/nested/install_url2.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl1, kManifestUrl, kStartUrl);
  webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl2, kManifestUrl, kStartUrl);
  EXPECT_EQ(app_id, app_id2);

  // Synchronize with 2 install URLs.
  {
    SynchronizeFuture result;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl1, kInstallUrl2},
            ExternalInstallSource::kExternalPolicy),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());

    // Empty uninstall results.
    EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty());

    // Installs should have both succeeded.
    EXPECT_THAT(
        result.Get<InstallResults>(),
        UnorderedElementsAre(
            std::make_pair(
                kInstallUrl1,
                ExternallyManagedAppManager::InstallResult(
                    webapps::InstallResultCode::kSuccessNewInstall, app_id)),
            std::make_pair(
                kInstallUrl2,
                ExternallyManagedAppManager::InstallResult(
                    webapps::InstallResultCode::kSuccessNewInstall, app_id))));

    EXPECT_EQ(app_registrar().GetAppIds().size(), 1ul);
    const WebApp* app = app_registrar().GetAppById(app_id);
    ASSERT_TRUE(app);
    EXPECT_THAT(app->management_to_external_config_map(),
                ElementsAre(std::make_pair(
                    WebAppManagement::kPolicy,
                    WebApp::ExternalManagementConfig(
                        /*is_placeholder=*/false,
                        /*install_urls=*/{kInstallUrl1, kInstallUrl2},
                        /*additional_policy_ids=*/{}))));
  }

  // Synchronize with 1 install URL.
  {
    SynchronizeFuture result;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl1}, ExternalInstallSource::kExternalPolicy),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());

    // Empty install results.
    EXPECT_THAT(result.Get<InstallResults>(),
                UnorderedElementsAre(std::make_pair(
                    kInstallUrl1,
                    ExternallyManagedAppManager::InstallResult(
                        webapps::InstallResultCode::kSuccessAlreadyInstalled,
                        app_id))));

    // One install URL uninstalled.
    EXPECT_THAT(
        result.Get<UninstallResults>(),
        UnorderedElementsAre(std::make_pair(
            kInstallUrl2, webapps::UninstallResultCode::kInstallUrlRemoved)));

    EXPECT_EQ(app_registrar().GetAppIds().size(), 1ul);
    const WebApp* app = app_registrar().GetAppById(app_id);
    ASSERT_TRUE(app);
    EXPECT_THAT(app->management_to_external_config_map(),
                ElementsAre(std::make_pair(WebAppManagement::kPolicy,
                                           WebApp::ExternalManagementConfig(
                                               /*is_placeholder=*/false,
                                               /*install_urls=*/{kInstallUrl1},
                                               /*additional_policy_ids=*/{}))));
  }

  // Synchronize with 0 install URLs.
  {
    SynchronizeFuture result;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {}, ExternalInstallSource::kExternalPolicy),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());

    // Empty install results.
    EXPECT_THAT(result.Get<InstallResults>(), IsEmpty());

    // One install URL uninstalled.
    EXPECT_THAT(result.Get<UninstallResults>(),
                UnorderedElementsAre(std::make_pair(
                    kInstallUrl1, webapps::UninstallResultCode::kAppRemoved)));

    // App should be cleaned up.
    EXPECT_EQ(app_registrar().GetAppIds().size(), 0ul);
    const WebApp* app = app_registrar().GetAppById(app_id);
    ASSERT_FALSE(app);
  }
}

TEST_F(ExternallyAppManagerTest, InstallUrlChanges) {
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kInstallUrl =
      GURL("https://www.example.com/nested/install_url.html");
  const GURL kInstallUrl2 =
      GURL("https://www.example.com/nested/install_url2.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl, kStartUrl);
  webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl2, kManifestUrl, kStartUrl);
  EXPECT_EQ(app_id, app_id2);

  // First synchronize will install the app.
  {
    SynchronizeFuture result;
    external_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl}, ExternalInstallSource::kExternalPolicy),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());

    std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
        result.Get<InstallResults>();
    EXPECT_THAT(
        install_results,
        ElementsAre(std::make_pair(
            kInstallUrl,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id))));
  }

  // Second synchronize with a different install url should succeed and update
  // the install urls correctly.
  {
    SynchronizeFuture result;
    external_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl2}, ExternalInstallSource::kExternalPolicy),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());
    std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
        result.Get<InstallResults>();
    EXPECT_THAT(
        install_results,
        ElementsAre(std::make_pair(
            kInstallUrl2,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id))));

    ASSERT_THAT(result.Get<UninstallResults>(),
                testing::UnorderedElementsAre(std::make_pair(
                    kInstallUrl, webapps::UninstallResultCode::kAppRemoved)));
  }

  const WebApp* app = fake_provider().registrar_unsafe().GetAppById(app_id);
  ASSERT_TRUE(app);
  EXPECT_THAT(app->management_to_external_config_map(),
              ElementsAre(std::make_pair(WebAppManagement::kPolicy,
                                         WebApp::ExternalManagementConfig(
                                             /*is_placeholder=*/false,
                                             /*install_urls=*/{kInstallUrl2},
                                             /*additional_policy_ids=*/{}))));
}

TEST_F(ExternallyAppManagerTest, PolicyAppOverridesUserInstalledApp) {
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kInstallUrl =
      GURL("https://www.example.com/nested/install_url.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl, kStartUrl);

  {
    // Install user app
    auto& install_page_state =
        web_contents_manager().GetOrCreatePageState(kInstallUrl);
    install_page_state.manifest_before_default_processing->short_name =
        u"Test user app";

    auto install_info =
        WebAppInstallInfo::CreateWithStartUrlForTesting(kStartUrl);
    install_info->title = u"Test user app";
    std::optional<webapps::AppId> user_app_id =
        test::InstallWebApp(profile(), std::move(install_info));

    ASSERT_TRUE(user_app_id.has_value());
    ASSERT_EQ(user_app_id.value(), app_id);
    ASSERT_TRUE(
        app_registrar().AppMatches(app_id, WebAppFilter::InstalledByUser()));
    ASSERT_TRUE(app_registrar()
                    .GetAppById(app_id)
                    ->management_to_external_config_map()
                    .empty());
    ASSERT_EQ("Test user app", app_registrar().GetAppShortName(app_id));
  }
  {
    // Install policy app
    auto& install_page_state =
        web_contents_manager().GetOrCreatePageState(kInstallUrl);
    install_page_state.manifest_before_default_processing->name =
        u"Test policy app";
    install_page_state.manifest_before_default_processing->short_name =
        u"Test policy app";

    SynchronizeFuture result;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl}, ExternalInstallSource::kExternalPolicy),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());
    std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
        result.Get<InstallResults>();
    EXPECT_THAT(
        install_results,
        ElementsAre(std::make_pair(
            kInstallUrl,
            ExternallyManagedAppManager::InstallResult(
                webapps::InstallResultCode::kSuccessNewInstall, app_id))));
    ASSERT_EQ("Test policy app", app_registrar().GetAppShortName(app_id));
  }
}

TEST_F(ExternallyAppManagerTest, NoNetworkWithPlaceholder) {
  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
  ExternalInstallOptions template_options(
      kInstallUrl, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);
  template_options.install_placeholder = true;

  SynchronizeFuture result;
  external_manager().SynchronizeInstalledApps(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl}, ExternalInstallSource::kExternalPolicy,
          template_options),
      ExternalInstallSource::kExternalPolicy, result.GetCallback());
  ASSERT_TRUE(result.Wait());

  // The webapps::AppId should be created from the install url.
  webapps::AppId app_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl);

  // Install should succeed.
  std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
      result.Get<InstallResults>();
  EXPECT_THAT(
      install_results,
      ElementsAre(std::make_pair(
          kInstallUrl,
          ExternallyManagedAppManager::InstallResult(
              webapps::InstallResultCode::kSuccessNewInstall, app_id))));

  const WebApp* app = fake_provider().registrar_unsafe().GetAppById(app_id);

  ASSERT_TRUE(app);
  EXPECT_THAT(app->management_to_external_config_map(),
              ElementsAre(std::make_pair(WebAppManagement::kPolicy,
                                         WebApp::ExternalManagementConfig(
                                             /*is_placeholder=*/true,
                                             /*install_urls=*/{kInstallUrl},
                                             /*additional_policy_ids=*/{}))));
}

TEST_F(ExternallyAppManagerTest, RedirectInstallUrlPlaceholder) {
  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
  const GURL kRedirectToUrl =
      GURL("https://www.otherorigin.com/redirected.html");
  ExternalInstallOptions template_options(
      kInstallUrl, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);
  template_options.install_placeholder = true;

  // Verify that a redirection causes a placeholder app to be installed.

  auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl);
  page_state.redirection_url = kRedirectToUrl;

  SynchronizeFuture result;
  external_manager().SynchronizeInstalledApps(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl}, ExternalInstallSource::kExternalPolicy,
          template_options),
      ExternalInstallSource::kExternalPolicy, result.GetCallback());
  ASSERT_TRUE(result.Wait());

  // The webapps::AppId should be created from the install url.
  webapps::AppId app_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl);

  // Install should succeed.
  std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
      result.Get<InstallResults>();
  EXPECT_THAT(
      install_results,
      ElementsAre(std::make_pair(
          kInstallUrl,
          ExternallyManagedAppManager::InstallResult(
              webapps::InstallResultCode::kSuccessNewInstall, app_id))));

  const WebApp* app = fake_provider().registrar_unsafe().GetAppById(app_id);
  ASSERT_TRUE(app);
  EXPECT_THAT(app->management_to_external_config_map(),
              ElementsAre(std::make_pair(WebAppManagement::kPolicy,
                                         WebApp::ExternalManagementConfig(
                                             /*is_placeholder=*/true,
                                             /*install_urls=*/{kInstallUrl},
                                             /*additional_policy_ids=*/{}))));
}

TEST_F(ExternallyAppManagerTest, PlaceholderResolvedFromSynchronize) {
  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
  const GURL kRedirectToUrl =
      GURL("https://www.otherorigin.com/redirected.html");
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  ExternalInstallOptions template_options(
      kInstallUrl, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);
  template_options.install_placeholder = true;

  auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl);
  page_state.redirection_url = kRedirectToUrl;
  {
    SynchronizeFuture result;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl}, ExternalInstallSource::kExternalPolicy,
            template_options),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());
  }

  webapps::AppId placeholder_app_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl);

  auto app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids, ElementsAre(placeholder_app_id));

  // Replace the redirect with an app that resolves.
  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl, kStartUrl);

  // The placeholder app should be uninstalled & the real one installed.
  {
    SynchronizeFuture result;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl}, ExternalInstallSource::kExternalPolicy,
            template_options),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());
  }

  app_ids = fake_provider().registrar_unsafe().GetAppIds();

  EXPECT_THAT(app_ids, ElementsAre(app_id));
}

TEST_F(ExternallyAppManagerTest, PlaceholderResolvedFromInstallNow) {
  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
  const GURL kRedirectToUrl =
      GURL("https://www.otherorigin.com/redirected.html");
  const GURL kStartUrl = GURL("https://www.example.com/index.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");

  ExternalInstallOptions template_options(
      kInstallUrl, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);
  template_options.install_placeholder = true;

  auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl);
  page_state.redirection_url = kRedirectToUrl;
  {
    SynchronizeFuture result;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl}, ExternalInstallSource::kExternalPolicy,
            template_options),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());
  }

  webapps::AppId placeholder_app_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl);

  auto app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids, ElementsAre(placeholder_app_id));

  // Replace the redirect with an app that resolves.
  webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl, kStartUrl);

  ExternalInstallOptions options = template_options;
  options.install_url = kInstallUrl;
  options.placeholder_resolution_behavior =
      PlaceholderResolutionBehavior::kClose;
  InstallNowFuture install_future;
  fake_provider().externally_managed_app_manager().InstallNow(
      std::move(options), install_future.GetCallback());
  ASSERT_TRUE(install_future.Wait());

  app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids, ElementsAre(app_id));
}

TEST_F(ExternallyAppManagerTest, TwoAppsSameInstallUrlSameSourceInstallNow) {
  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
  const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
  const GURL kStartUrl2 = GURL("https://www.example.com/index2.html");
  const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");
  const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json");

  ExternalInstallOptions template_options(
      kInstallUrl, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);

  webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl1, kStartUrl1);

  {
    ExternalInstallOptions options = template_options;
    options.install_url = kInstallUrl;
    base::test::TestFuture<const GURL&,
                           ExternallyManagedAppManager::InstallResult>
        install_future;
    fake_provider().externally_managed_app_manager().InstallNow(
        std::move(options), install_future.GetCallback());
    ASSERT_TRUE(install_future.Wait());
  }

  auto app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids, ElementsAre(app_id1));

  webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl2, kStartUrl2);

  {
    ExternalInstallOptions options = template_options;
    options.install_url = kInstallUrl;
    InstallNowFuture install_future;
    fake_provider().externally_managed_app_manager().InstallNow(
        std::move(options), install_future.GetCallback());
    ASSERT_TRUE(install_future.Wait());
  }

  // TODO(crbug.com/40264854): This keeps the original app, but perhaps
  // should install app_id2.
  app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids, ElementsAre(app_id1));
}

TEST_F(ExternallyAppManagerTest, TwoAppsSameInstallUrlTwoSourcesInstallNow) {
  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
  const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
  const GURL kStartUrl2 = GURL("https://www.example.com/index2.html");
  const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");
  const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json");

  ExternalInstallOptions template_options(
      kInstallUrl, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);

  webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl1, kStartUrl1);

  {
    ExternalInstallOptions options = template_options;
    options.install_url = kInstallUrl;
    base::test::TestFuture<const GURL&,
                           ExternallyManagedAppManager::InstallResult>
        install_future;
    fake_provider().externally_managed_app_manager().InstallNow(
        std::move(options), install_future.GetCallback());
    ASSERT_TRUE(install_future.Wait());
  }

  auto app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids, ElementsAre(app_id1));

  webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl2, kStartUrl2);

  {
    ExternalInstallOptions options = template_options;
    options.install_url = kInstallUrl;
    options.install_source = ExternalInstallSource::kInternalDefault;
    InstallNowFuture install_future;
    fake_provider().externally_managed_app_manager().InstallNow(
        std::move(options), install_future.GetCallback());
    ASSERT_TRUE(install_future.Wait());
  }

  // TODO(crbug.com/40264854): Currently, this keeps the original app,
  // but we should eventually resolve all apps to app_id2.
  app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids, ElementsAre(app_id1));
}

TEST_F(ExternallyAppManagerTest, TwoAppsSameInstallUrlTwoSourcesSynchronize) {
  const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
  const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
  const GURL kStartUrl2 = GURL("https://www.example.com/index2.html");
  const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");
  const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json");

  ExternalInstallOptions template_options(
      kInstallUrl, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);

  webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl1, kStartUrl1);

  {
    SynchronizeFuture result;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl}, ExternalInstallSource::kExternalPolicy,
            template_options),
        ExternalInstallSource::kExternalPolicy, result.GetCallback());
    ASSERT_TRUE(result.Wait());
  }

  auto app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids, ElementsAre(app_id1));

  webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
      kInstallUrl, kManifestUrl2, kStartUrl2);

  {
    SynchronizeFuture result;
    fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
        CreateExternalInstallOptionsFromTemplate(
            {kInstallUrl}, ExternalInstallSource::kInternalDefault,
            template_options),
        ExternalInstallSource::kInternalDefault, result.GetCallback());
    ASSERT_TRUE(result.Wait());
  }

  // TODO(crbug.com/40264854): Currently this resolves to app_id1, but
  // should probably eventually resolve to app_id2.
  app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids, UnorderedElementsAre(app_id1));
}

TEST_F(ExternallyAppManagerTest, PlaceholderFixedBySecondInstallUrlInstallNow) {
  const GURL kInstallUrl1 = GURL("https://www.example.com/install_url1.html");
  const GURL kInstallUrl2 = GURL("https://www.example.com/install_url2.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest1.json");

  ExternalInstallOptions template_options(
      kInstallUrl1, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);
  template_options.install_placeholder = true;

  // The first install creates a placeholder at kInstallUrl1, as that redirects.
  // The second install doesn't redirect, and the app at kInstallUrl2 points to
  // kInstallUrl1 as it's start_url (basically - it's identity conflicts with
  // the placeholder app's default identity).

  auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl1);
  page_state.redirection_url =
      GURL("https://www.otherorigin.com/redirect.html");

  webapps::AppId app_at_install_url =
      web_contents_manager().CreateBasicInstallPageState(
          kInstallUrl2, kManifestUrl, kInstallUrl1);

  {
    ExternalInstallOptions options = template_options;
    options.install_url = kInstallUrl1;
    InstallNowFuture install_future;
    fake_provider().externally_managed_app_manager().InstallNow(
        std::move(options), install_future.GetCallback());
    ASSERT_TRUE(install_future.Wait());
    EXPECT_THAT(
        install_future.Get(),
        Eq(std::make_tuple(kInstallUrl1,
                           ExternallyManagedAppManager::InstallResult(
                               webapps::InstallResultCode::kSuccessNewInstall,
                               app_at_install_url))));
  }

  {
    ExternalInstallOptions options = template_options;
    options.install_url = kInstallUrl2;
    base::test::TestFuture<const GURL&,
                           ExternallyManagedAppManager::InstallResult>
        install_future;
    fake_provider().externally_managed_app_manager().InstallNow(
        std::move(options), install_future.GetCallback());
    ASSERT_TRUE(install_future.Wait());
    EXPECT_THAT(
        install_future.Get(),
        Eq(std::make_tuple(kInstallUrl2,
                           ExternallyManagedAppManager::InstallResult(
                               webapps::InstallResultCode::kSuccessNewInstall,
                               app_at_install_url))));
  }

  // The current implementation records placeholder information per-source, so
  // when the second install succeeds, it overrides the `is_placeholder` to
  // `false`, and thus the first install is considered fully installed now too.
  // This is in contrast to the behavior if the two installs are different
  // sources, which will (correctly?) evaluate the manifest served by the
  // install url of the first install, which could be a different app identity.
  const WebApp* app =
      fake_provider().registrar_unsafe().GetAppById(app_at_install_url);
  ASSERT_TRUE(app);
  EXPECT_THAT(app->management_to_external_config_map(),
              ElementsAre(std::make_pair(
                  WebAppManagement::kPolicy,
                  WebApp::ExternalManagementConfig(
                      /*is_placeholder=*/false,
                      /*install_urls=*/{kInstallUrl1, kInstallUrl2},
                      /*additional_policy_ids=*/{}))));
}

TEST_F(ExternallyAppManagerTest,
       PlaceholderFixedBySecondInstallUrlSynchronize) {
  const GURL kInstallUrl1 = GURL("https://www.example.com/install_url1.html");
  const GURL kInstallUrl2 = GURL("https://www.example.com/install_url2.html");
  const GURL kManifestUrl = GURL("https://www.example.com/manifest1.json");

  // The first install creates a placeholder at kInstallUrl1, as that redirects.
  // The second install doesn't redirect, and the app at kInstallUrl2 points to
  // kInstallUrl1 as it's start_url (basically - it's identity conflicts with
  // the placeholder app's default identity).

  ExternalInstallOptions template_options(
      kInstallUrl1, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);
  template_options.install_placeholder = true;

  auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl1);
  page_state.redirection_url =
      GURL("https://www.otherorigin.com/redirect.html");

  webapps::AppId app_at_install_url =
      web_contents_manager().CreateBasicInstallPageState(
          kInstallUrl2, kManifestUrl, /*start_url=*/kInstallUrl1);

  SynchronizeFuture result;
  fake_provider().externally_managed_app_manager().SynchronizeInstalledApps(
      CreateExternalInstallOptionsFromTemplate(
          {kInstallUrl1, kInstallUrl2}, ExternalInstallSource::kExternalPolicy,
          template_options),
      ExternalInstallSource::kExternalPolicy, result.GetCallback());
  ASSERT_TRUE(result.Wait());

  // Install should succeed.
  std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
      result.Get<InstallResults>();
  EXPECT_THAT(
      install_results,
      UnorderedElementsAre(
          std::make_pair(kInstallUrl1,
                         ExternallyManagedAppManager::InstallResult(
                             webapps::InstallResultCode::kSuccessNewInstall,
                             app_at_install_url)),
          std::make_pair(kInstallUrl2,
                         ExternallyManagedAppManager::InstallResult(
                             webapps::InstallResultCode::kSuccessNewInstall,
                             app_at_install_url))));

  // The current implementation records placeholder information per-source, so
  // when the second install succeeds, it overrides the `is_placeholder` to
  // `false`, and thus the first install is considered fully installed now too.
  const WebApp* app =
      fake_provider().registrar_unsafe().GetAppById(app_at_install_url);
  ASSERT_TRUE(app);
  EXPECT_THAT(app->management_to_external_config_map(),
              ElementsAre(std::make_pair(
                  WebAppManagement::kPolicy,
                  WebApp::ExternalManagementConfig(
                      /*is_placeholder=*/false,
                      /*install_urls=*/{kInstallUrl1, kInstallUrl2},
                      /*additional_policy_ids=*/{}))));
}

TEST_F(ExternallyAppManagerTest, PlaceholderFullInstallConflictCanUpdate) {
  // This test exists to test what happens when a placeholder install conflicts
  // with a another install from a different source, and then resolves to a
  // separate app entirely after the placeholder state is fixed.

  // Phase 1 configuration with redirect:
  // - kInstallUrl1 will redirect & generate a placeholder
  // - kInstallUrl2 will serve a manifest with kInstallUrl1 as the start_url

  // Phase 2 configuration - redirect removed
  // - kInstallUrl1 will serve a manifest with kStartUrl as the start_url.

  // What happens when resolving the placeholder at kInstallUrl1? Currently this
  // updates so that kStartUrl1 is installed.

  const GURL kInstallUrl1 = GURL("https://www.example.com/install_url1.html");
  const GURL kInstallUrl2 = GURL("https://www.example.com/install_url2.html");
  const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
  const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");
  const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json");

  ExternalInstallOptions template_options(
      kInstallUrl1, mojom::UserDisplayMode::kStandalone,
      ExternalInstallSource::kExternalPolicy);
  template_options.install_placeholder = true;

  // Phase 1 state
  auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl1);
  page_state.redirection_url =
      GURL("https://www.otherorigin.com/redirect.html");

  webapps::AppId app_at_install_url =
      web_contents_manager().CreateBasicInstallPageState(
          kInstallUrl2, kManifestUrl1, kInstallUrl1);

  {
    ExternalInstallOptions options = template_options;
    options.install_url = kInstallUrl1;
    base::test::TestFuture<const GURL&,
                           ExternallyManagedAppManager::InstallResult>
        install_future;
    fake_provider().externally_managed_app_manager().InstallNow(
        std::move(options), install_future.GetCallback());
    ASSERT_TRUE(install_future.Wait());
    EXPECT_THAT(
        install_future.Get(),
        Eq(std::make_tuple(kInstallUrl1,
                           ExternallyManagedAppManager::InstallResult(
                               webapps::InstallResultCode::kSuccessNewInstall,
                               app_at_install_url))));
  }

  {
    ExternalInstallOptions options(kInstallUrl2,
                                   mojom::UserDisplayMode::kStandalone,
                                   ExternalInstallSource::kInternalDefault);
    base::test::TestFuture<const GURL&,
                           ExternallyManagedAppManager::InstallResult>
        install_future;
    fake_provider().externally_managed_app_manager().InstallNow(
        std::move(options), install_future.GetCallback());
    ASSERT_TRUE(install_future.Wait());
    EXPECT_THAT(
        install_future.Get(),
        Eq(std::make_tuple(kInstallUrl2,
                           ExternallyManagedAppManager::InstallResult(
                               webapps::InstallResultCode::kSuccessNewInstall,
                               app_at_install_url))));
  }

  const WebApp* app =
      fake_provider().registrar_unsafe().GetAppById(app_at_install_url);
  ASSERT_TRUE(app);
  EXPECT_THAT(
      app->management_to_external_config_map(),
      UnorderedElementsAre(std::make_pair(WebAppManagement::kPolicy,
                                          WebApp::ExternalManagementConfig(
                                              /*is_placeholder=*/true,
                                              /*install_urls=*/{kInstallUrl1},
                                              /*additional_policy_ids=*/{})),
                           std::make_pair(WebAppManagement::kDefault,
                                          WebApp::ExternalManagementConfig(
                                              /*is_placeholder=*/false,
                                              /*install_urls=*/{kInstallUrl2},
                                              /*additional_policy_ids=*/{}))));
  // Phase 2 - undo the redirection, and point to start url.
  webapps::AppId app_at_start_url =
      web_contents_manager().CreateBasicInstallPageState(
          kInstallUrl1, kManifestUrl2, kStartUrl1);

  {
    ExternalInstallOptions options = template_options;
    options.install_url = kInstallUrl1;
    base::test::TestFuture<const GURL&,
                           ExternallyManagedAppManager::InstallResult>
        install_future;
    fake_provider().externally_managed_app_manager().InstallNow(
        std::move(options), install_future.GetCallback());
    ASSERT_TRUE(install_future.Wait());

    EXPECT_THAT(
        install_future.Get(),
        Eq(std::make_tuple(kInstallUrl1,
                           ExternallyManagedAppManager::InstallResult(
                               webapps::InstallResultCode::kSuccessNewInstall,
                               app_at_start_url))));
  }

  // This is the current behavior, but it could change if we decide that
  // 'placeholder' is a per-app state instead of a per-app-and-source state.
  auto app_ids = fake_provider().registrar_unsafe().GetAppIds();
  EXPECT_THAT(app_ids,
              UnorderedElementsAre(app_at_install_url, app_at_start_url));
}

}  // namespace
}  // namespace web_app
