chromium/chrome/browser/apps/app_service/app_install/app_install_service_ash_browsertest.cc

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

#include "chrome/browser/apps/app_service/app_install/app_install_service_ash.h"

#include <optional>

#include "ash/constants/ash_switches.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_future.h"
#include "chrome/browser/apps/app_service/app_install/app_install.pb.h"
#include "chrome/browser/apps/app_service/app_install/test_app_install_server.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/ui/browser.h"
#include "chrome/browser/ui/webui/ash/app_install/app_install_dialog_test_helpers.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/services/app_service/public/cpp/package_id.h"
#include "components/user_manager/user_names.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_navigation_observer.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace apps {

class AppInstallServiceAshBrowserTest : public InProcessBrowserTest {
 public:
  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    ASSERT_TRUE(app_install_server_.SetUp());
  }

  TestAppInstallServer* app_install_server() { return &app_install_server_; }

 private:
  TestAppInstallServer app_install_server_;
};

IN_PROC_BROWSER_TEST_F(AppInstallServiceAshBrowserTest,
                       InstallArcAppOpensPlayStore) {
  GURL play_install_url(
      "https://play.google.com/store/apps/details?id=com.android.chrome");
  app_install_server()->SetUpInstallUrlResponse(
      PackageId(PackageType::kArc, "com.android.chrome"), play_install_url);

  base::HistogramTester histogram_tester;

  content::TestNavigationObserver navigation_observer(play_install_url);
  navigation_observer.StartWatchingNewWebContents();

  AppServiceProxyFactory::GetForProfile(browser()->profile())
      ->AppInstallService()
      .InstallApp(AppInstallSurface::kAppInstallUriUnknown,
                  PackageId(PackageType::kArc, "com.android.chrome"),
                  /*anchor_window=*/std::nullopt, base::DoNothing());

  navigation_observer.Wait();

  AppInstallResult expected_result = AppInstallResult::kUnknown;
  histogram_tester.ExpectUniqueSample("Apps.AppInstallService.AppInstallResult",
                                      expected_result, 1);
  histogram_tester.ExpectUniqueSample(
      "Apps.AppInstallService.AppInstallResult.AppInstallUriUnknown",
      expected_result, 1);
}

IN_PROC_BROWSER_TEST_F(AppInstallServiceAshBrowserTest, InstallGfnAppOpensGfn) {
  GURL gfn_install_url("https://play.geforcenow.com/games?game-id=test");
  app_install_server()->SetUpInstallUrlResponse(
      PackageId(PackageType::kGeForceNow, "test"), gfn_install_url);

  base::HistogramTester histogram_tester;

  content::TestNavigationObserver navigation_observer(gfn_install_url);
  navigation_observer.StartWatchingNewWebContents();

  AppServiceProxyFactory::GetForProfile(browser()->profile())
      ->AppInstallService()
      .InstallApp(AppInstallSurface::kAppInstallUriUnknown,
                  PackageId(PackageType::kGeForceNow, "test"),
                  /*anchor_window=*/std::nullopt, base::DoNothing());

  navigation_observer.Wait();

  AppInstallResult expected_result = AppInstallResult::kUnknown;
  histogram_tester.ExpectUniqueSample("Apps.AppInstallService.AppInstallResult",
                                      expected_result, 1);
  histogram_tester.ExpectUniqueSample(
      "Apps.AppInstallService.AppInstallResult.AppInstallUriUnknown",
      expected_result, 1);
}

IN_PROC_BROWSER_TEST_F(AppInstallServiceAshBrowserTest,
                       InstallAndroidWithNoInstallUrlShowsError) {
  std::string test_package_id = "android:com.android.chrome";
  base::HistogramTester histograms;

  // Set up an invalid payload -- an Android app response with no Install URL.
  proto::AppInstallResponse response;
  proto::AppInstallResponse_AppInstance& instance =
      *response.mutable_app_instance();
  instance.set_package_id(test_package_id);
  instance.set_name("Test app");

  app_install_server()->SetUpResponse(test_package_id, response);

  base::test::TestFuture<void> completion_future;

  content::TestNavigationObserver navigation_observer_dialog(
      (GURL(chrome::kChromeUIAppInstallDialogURL)));
  navigation_observer_dialog.StartWatchingNewWebContents();

  AppServiceProxyFactory::GetForProfile(browser()->profile())
      ->AppInstallService()
      .InstallApp(AppInstallSurface::kAppInstallUriUnknown,
                  PackageId::FromString(test_package_id).value(),
                  /*anchor_window=*/std::nullopt,
                  completion_future.GetCallback());

  navigation_observer_dialog.Wait();
  content::WebContents* contents = ash::app_install::GetWebContentsFromDialog();
  ASSERT_TRUE(contents);
  EXPECT_EQ(ash::app_install::GetDialogTitle(contents), "App not available");

  ASSERT_TRUE(completion_future.Wait());
  histograms.ExpectUniqueSample(
      "Apps.AppInstallService.AppInstallResult.AppInstallUriUnknown",
      AppInstallResult::kAppDataCorrupted, 1);
}

IN_PROC_BROWSER_TEST_F(AppInstallServiceAshBrowserTest,
                       InstallWebsiteShowsInstallDialog) {
  auto [app_id, package_id] = app_install_server()->SetUpWebsiteResponse();

  content::TestNavigationObserver navigation_observer(
      (GURL(chrome::kChromeUIAppInstallDialogURL)));
  navigation_observer.StartWatchingNewWebContents();

  AppServiceProxyFactory::GetForProfile(browser()->profile())
      ->AppInstallService()
      .InstallApp(AppInstallSurface::kAppInstallUriMall, package_id,
                  /*anchor_window=*/std::nullopt, base::DoNothing());

  navigation_observer.Wait();

  EXPECT_THAT(ash::app_install::GetDialogTitle(
                  ash::app_install::GetWebContentsFromDialog()),
              testing::StartsWith("Install app"));
}

IN_PROC_BROWSER_TEST_F(AppInstallServiceAshBrowserTest,
                       InstallWebsiteFindsInstalledWebApp) {
  auto [app_id, package_id] = app_install_server()->SetUpWebsiteResponse();

  web_app::test::InstallDummyWebApp(browser()->profile(), "Test app",
                                    GURL(package_id.identifier()));

  content::TestNavigationObserver navigation_observer(
      (GURL(chrome::kChromeUIAppInstallDialogURL)));
  navigation_observer.StartWatchingNewWebContents();

  AppServiceProxyFactory::GetForProfile(browser()->profile())
      ->AppInstallService()
      .InstallApp(AppInstallSurface::kAppInstallUriMall, package_id,
                  /*anchor_window=*/std::nullopt, base::DoNothing());

  navigation_observer.Wait();

  content::WebContents* web_contents =
      ash::app_install::GetWebContentsFromDialog();

  // The dialog should recognize that the app is already installed, even though
  // the installed app is an "app", and we are requesting to install a
  // "shortcut".
  EXPECT_EQ(ash::app_install::GetDialogTitle(web_contents),
            "App is already installed");
}

class AppInstallServiceAshGuestBrowserTest
    : public InProcessBrowserTest,
      public testing::WithParamInterface<bool> {
 public:
  static std::string ParamToString(testing::TestParamInfo<bool> param) {
    return param.param ? "Guest" : "NonGuest";
  }

  AppInstallServiceAshGuestBrowserTest() = default;

  bool is_guest() const { return GetParam(); }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    InProcessBrowserTest::SetUpCommandLine(command_line);

    if (is_guest()) {
      command_line->AppendSwitch(::switches::kIncognito);
      command_line->AppendSwitch(ash::switches::kGuestSession);
      command_line->AppendSwitchASCII(ash::switches::kLoginProfile, "user");
      command_line->AppendSwitchASCII(ash::switches::kLoginUser,
                                      user_manager::kGuestUserName);
    }
  }
};

INSTANTIATE_TEST_SUITE_P(All,
                         AppInstallServiceAshGuestBrowserTest,
                         testing::Bool(),
                         AppInstallServiceAshGuestBrowserTest::ParamToString);

IN_PROC_BROWSER_TEST_P(AppInstallServiceAshGuestBrowserTest, InstallApp) {
  base::HistogramTester histogram_tester;
  base::RunLoop run_loop;
  AppServiceProxyFactory::GetForProfile(browser()->profile())
      ->AppInstallService()
      .InstallApp(AppInstallSurface::kAppInstallUriUnknown,
                  PackageId(PackageType::kWeb, "test"),
                  /*anchor_window=*/std::nullopt, run_loop.QuitClosure());
  run_loop.Run();

  AppInstallResult expected_result =
      is_guest() ? AppInstallResult::kUserTypeNotPermitted
                 // Check that the install is attempted and not discarded for
                 // the non-guest user. No Almanac mock has been set up so it
                 // will fail at the app data fetching stage.
                 : AppInstallResult::kAlmanacFetchFailed;
  histogram_tester.ExpectUniqueSample("Apps.AppInstallService.AppInstallResult",
                                      expected_result, 1);
  histogram_tester.ExpectUniqueSample(
      "Apps.AppInstallService.AppInstallResult.AppInstallUriUnknown",
      expected_result, 1);
}

}  // namespace apps