chromium/chrome/browser/lacros/app_mode/web_kiosk_installer_lacros_unittest.cc

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

#include "chrome/browser/lacros/app_mode/web_kiosk_installer_lacros.h"

#include <memory>
#include <optional>
#include <tuple>

#include "base/test/test_future.h"
#include "chrome/browser/apps/app_service/app_service_test.h"
#include "chrome/browser/lacros/app_mode/kiosk_session_service_lacros.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/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_registrar.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 "chromeos/crosapi/mojom/web_kiosk_service.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "components/webapps/browser/web_contents/web_app_url_loader.h"
#include "components/webapps/common/web_app_id.h"
#include "components/webapps/common/web_page_metadata.mojom.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::test::TestFuture;
using WebKioskInstallState = crosapi::mojom::WebKioskInstallState;

namespace {

const char kAppInstallUrl[] = "https://example.com";
const char kAppLaunchUrl[] = "https://example.com/launch";
const char kManifestUrl[] = "https://example.com/manifest.json";

std::optional<webapps::AppId> app_id() {
  return web_app::GenerateAppId(/*manifest_id=*/std::nullopt,
                                GURL(kAppLaunchUrl));
}

class FakeWebKioskService : public crosapi::mojom::WebKioskService {
 public:
  using WebKioskInstallState = crosapi::mojom::WebKioskInstallState;
  using GetWebKioskInstallStateCallback =
      crosapi::mojom::WebKioskInstaller::GetWebKioskInstallStateCallback;
  using InstallWebKioskCallback =
      crosapi::mojom::WebKioskInstaller::InstallWebKioskCallback;

  FakeWebKioskService() = default;
  ~FakeWebKioskService() override = default;

  mojo::PendingRemote<crosapi::mojom::WebKioskService>
  BindNewPipeAndPassRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  void BindInstaller(mojo::PendingRemote<crosapi::mojom::WebKioskInstaller>
                         installer) override {
    installer_.Bind(std::move(installer));
    bound_future_.SetValue();
  }

  void WaitUntilBound() { ASSERT_TRUE(bound_future_.Wait()); }

  std::tuple<WebKioskInstallState, std::optional<webapps::AppId>>
  GetWebKioskInstallState(const GURL& url) {
    TestFuture<WebKioskInstallState, const std::optional<webapps::AppId>&>
        future;

    installer_->GetWebKioskInstallState(url, future.GetCallback());

    return future.Get();
  }

  std::optional<webapps::AppId> InstallWebKiosk(const GURL& url) {
    TestFuture<const std::optional<webapps::AppId>&> future;
    installer_->InstallWebKiosk(url, future.GetCallback());
    return future.Get();
  }

 private:
  TestFuture<void> bound_future_;
  mojo::Receiver<crosapi::mojom::WebKioskService> receiver_{this};
  mojo::Remote<crosapi::mojom::WebKioskInstaller> installer_;
};

}  // namespace

class WebKioskInstallerLacrosTest : public testing::Test {
 public:
  void SetUp() override {
    ASSERT_TRUE(testing_profile_manager_.SetUp());
    profile_ = testing_profile_manager_.CreateTestingProfile(
        "Default", /*is_main_profile=*/true);

    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile_);
  }

 protected:
  TestingProfile& profile() { return *profile_; }
  FakeWebKioskService& web_kiosk_service() { return web_kiosk_service_; }

  void CreateInstallerAndWaitUntilBound() {
    BindFakeWebKioskService();
    installer_ = std::make_unique<WebKioskInstallerLacros>(profile());
    web_kiosk_service().WaitUntilBound();
  }

  void InstallAppAsPlaceholder() {
    InstallAppInternal(/*install_app_as_placeholder=*/true);
  }

  void InstallApp() {
    CreateWebAppWithManifest();
    InstallAppInternal(/*install_app_as_placeholder=*/false);
  }

  webapps::AppId CreateWebAppWithManifest() {
    const GURL install_url = GURL(kAppInstallUrl);
    const GURL manifest_url = GURL(kManifestUrl);
    const GURL start_url = GURL(kAppLaunchUrl);

    auto& install_page_state =
        web_contents_manager().GetOrCreatePageState(install_url);
    install_page_state.url_load_result =
        webapps::WebAppUrlLoaderResult::kUrlLoaded;
    install_page_state.redirection_url = std::nullopt;

    install_page_state.opt_metadata =
        web_app::FakeWebContentsManager::CreateMetadataWithTitle(
            u"Basic app title");

    install_page_state.manifest_url = manifest_url;
    install_page_state.valid_manifest_for_web_app = true;

    install_page_state.manifest_before_default_processing =
        blink::mojom::Manifest::New();
    install_page_state.manifest_before_default_processing->start_url =
        start_url;
    install_page_state.manifest_before_default_processing->id =
        web_app::GenerateManifestIdFromStartUrlOnly(start_url);
    install_page_state.manifest_before_default_processing->display =
        blink::mojom::DisplayMode::kStandalone;
    install_page_state.manifest_before_default_processing->short_name =
        u"Basic app name";

    return web_app::GenerateAppId(/*manifest_id=*/std::nullopt, start_url);
  }

  bool IsAppInstalledAsPlaceholder() {
    return web_app_provider()
        .registrar_unsafe()
        .LookupPlaceholderAppId(GURL(kAppInstallUrl),
                                web_app::WebAppManagement::Type::kKiosk)
        .has_value();
  }

 private:
  void BindFakeWebKioskService() {
    chromeos::LacrosService::Get()->InjectRemoteForTesting(
        web_kiosk_service_.BindNewPipeAndPassRemote());
  }

  web_app::WebAppProvider& web_app_provider() {
    return *web_app::WebAppProvider::GetForTest(profile_);
  }

  web_app::FakeWebContentsManager& web_contents_manager() {
    return static_cast<web_app::FakeWebContentsManager&>(
        web_app_provider().web_contents_manager());
  }

  void InstallAppInternal(bool install_app_as_placeholder) {
    TestFuture<const GURL&, web_app::ExternallyManagedAppManager::InstallResult>
        install_result;
    web_app::ExternalInstallOptions install_options(
        GURL(kAppInstallUrl), web_app::mojom::UserDisplayMode::kStandalone,
        web_app::ExternalInstallSource::kKiosk);
    install_options.install_placeholder = install_app_as_placeholder;

    web_app_provider().externally_managed_app_manager().Install(
        install_options, install_result.GetCallback());
    ASSERT_TRUE(webapps::IsSuccess(install_result.Get<1>().code));
  }

  content::BrowserTaskEnvironment task_environment_;

  apps::AppServiceTest app_service_test_;

  FakeWebKioskService web_kiosk_service_;
  TestingProfileManager testing_profile_manager_{
      TestingBrowserProcess::GetGlobal()};
  std::unique_ptr<WebKioskInstallerLacros> installer_;
  raw_ptr<TestingProfile> profile_;
  KioskSessionServiceLacros kiosk_session_service_lacros_;
};

TEST_F(WebKioskInstallerLacrosTest, CreatingUnboundInstallerShouldNotCrash) {
  // We're not calling CreateInstallerAndWaitUntilBound to construct an unbound
  // installer
  auto installer = std::make_unique<WebKioskInstallerLacros>(profile());
}

TEST_F(WebKioskInstallerLacrosTest, GetInstallStateShouldWorkForNotInstalled) {
  CreateInstallerAndWaitUntilBound();

  EXPECT_EQ(web_kiosk_service().GetWebKioskInstallState(GURL(kAppInstallUrl)),
            std::make_tuple(WebKioskInstallState::kNotInstalled, std::nullopt));
}

TEST_F(WebKioskInstallerLacrosTest,
       GetInstallStateShouldWorkForPlaceholderInstall) {
  CreateInstallerAndWaitUntilBound();
  InstallAppAsPlaceholder();

  EXPECT_EQ(web_kiosk_service().GetWebKioskInstallState(GURL(kAppInstallUrl)),
            std::make_tuple(WebKioskInstallState::kPlaceholderInstalled,
                            std::nullopt));
}

TEST_F(WebKioskInstallerLacrosTest, GetInstallStateShouldWorkForInstalled) {
  CreateInstallerAndWaitUntilBound();
  InstallApp();

  EXPECT_EQ(web_kiosk_service().GetWebKioskInstallState(GURL(kAppInstallUrl)),
            std::make_tuple(WebKioskInstallState::kInstalled, app_id()));
}

TEST_F(WebKioskInstallerLacrosTest, InstallAppShouldFullyInstallApp) {
  CreateInstallerAndWaitUntilBound();
  CreateWebAppWithManifest();

  auto result_app_id =
      web_kiosk_service().InstallWebKiosk(GURL(kAppInstallUrl));

  EXPECT_EQ(result_app_id, app_id());
  EXPECT_FALSE(IsAppInstalledAsPlaceholder());
}

TEST_F(WebKioskInstallerLacrosTest, InstallAppShouldUpgradePlaceholder) {
  CreateInstallerAndWaitUntilBound();
  InstallAppAsPlaceholder();
  ASSERT_TRUE(IsAppInstalledAsPlaceholder());

  CreateWebAppWithManifest();

  auto result_app_id =
      web_kiosk_service().InstallWebKiosk(GURL(kAppInstallUrl));

  EXPECT_EQ(result_app_id, app_id());
  EXPECT_FALSE(IsAppInstalledAsPlaceholder());
}

TEST_F(WebKioskInstallerLacrosTest,
       InstallAppShouldCreatePlaceholderIfAppDoesntExist) {
  CreateInstallerAndWaitUntilBound();

  auto result_app_id =
      web_kiosk_service().InstallWebKiosk(GURL(kAppInstallUrl));

  EXPECT_TRUE(result_app_id.has_value());
  EXPECT_TRUE(IsAppInstalledAsPlaceholder());
}