chromium/chrome/browser/web_applications/commands/install_app_from_verified_manifest_command_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/web_applications/commands/install_app_from_verified_manifest_command.h"

#include <memory>

#include "base/containers/flat_set.h"
#include "base/files/file_util.h"
#include "base/strings/string_util.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/ui/web_applications/web_app_browsertest_base.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom-shared.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app_command_manager.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_icon_manager.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_params.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/skia/include/core/SkColor.h"
#include "url/gurl.h"

namespace web_app {

class InstallAppFromVerifiedManifestCommandTest : public WebAppBrowserTestBase {
 public:
  void SetUpOnMainThread() override {
    WebAppBrowserTestBase::SetUpOnMainThread();
    host_resolver()->AddRule("lh3.googleusercontent.com", "127.0.0.1");
    host_resolver()->AddRule("fonts.gstatic.com", "127.0.0.1");
    host_resolver()->AddRule("youtube.com", "127.0.0.1");
  }

  void TearDownOnMainThread() override {
    EXPECT_TRUE(test::UninstallAllWebApps(profile()));
    WebAppBrowserTestBase::TearDownOnMainThread();
  }

  bool IsShortcutCreated(const webapps::AppId& app_id,
                         const std::string& name) {
#if BUILDFLAG(IS_CHROMEOS)
    // Shortcuts are always created (through App Service) on ChromeOS.
    return true;
#else
    base::ScopedAllowBlockingForTesting allow_blocking;
    return os_integration_override().IsShortcutCreated(profile(), app_id, name);
#endif
  }

  std::string GetIconUrl() {
    return https_server()->GetURL("/web_apps/blue-192.png").spec();
  }

  // Returns a basic, installable manifest with "/" as the start URL and 1 icon.
  std::string GetBasicManifest() {
    const char kManifestTemplate[] = R"json({
      "start_url": "/",
      "name": "Test app",
      "icons": [{
        "src": "$1",
        "sizes": "192x192",
        "type": "image/png"
      }]
    })json";

    return base::ReplaceStringPlaceholders(kManifestTemplate, {GetIconUrl()},
                                           nullptr);
  }

  std::tuple<webapps::AppId, webapps::InstallResultCode> InstallAndAwaitResult(
      const GURL& document_url,
      const GURL& manifest_url,
      const std::string& manifest,
      webapps::AppId expected_id = "",
      webapps::WebappInstallSource install_source =
          webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
      bool is_diy_app = false,
      std::optional<WebAppInstallParams> params = std::nullopt) {
    base::test::TestFuture<const webapps::AppId&, webapps::InstallResultCode>
        result;
    provider().command_manager().ScheduleCommand(
        std::make_unique<InstallAppFromVerifiedManifestCommand>(
            install_source, document_url, manifest_url, manifest, expected_id,
            is_diy_app, std::move(params), result.GetCallback()));
    return result.Take();
  }
};

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       SuccessNewInstall) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kStartUrl("https://www.app.com/home");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifestTemplate[] = R"json({
    "start_url": "/home",
    "name": "Test app",
    "launch_handler": {
      "client_mode": "focus-existing"
    },
    "icons": [{
      "src": "$1",
      "sizes": "192x192",
      "type": "image/png"
    }]
  })json";

  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate, {GetIconUrl()}, nullptr);

  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kStartUrl);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, expected_id);

  EXPECT_EQ(result_id, expected_id);
  EXPECT_TRUE(webapps::IsSuccess(result_code));

  EXPECT_EQ(provider().registrar_unsafe().GetAppShortName(result_id),
            "Test app");
  EXPECT_EQ(provider()
                .registrar_unsafe()
                .GetAppById(result_id)
                ->launch_handler()
                ->client_mode,
            blink::Manifest::LaunchHandler::ClientMode::kFocusExisting);
  SkColor icon_color =
      IconManagerReadAppIconPixel(provider().icon_manager(), result_id, 96);
  EXPECT_EQ(icon_color, SK_ColorBLUE);

  EXPECT_TRUE(IsShortcutCreated(result_id, "Test app"));
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       SuccessCrossOriginManifest) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.cdn.com/app/manifest.json");
  const char kManifestTemplate[] = R"json({
    "start_url": "https://www.app.com/",
    "name": "Test app",
    "icons": [{
      "src": "$1",
      "sizes": "192x192",
      "type": "image/png"
    }]
  })json";

  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate, {GetIconUrl()}, nullptr);

  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kDocumentUrl);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, expected_id);

  EXPECT_EQ(result_id, expected_id);
  EXPECT_TRUE(webapps::IsSuccess(result_code));
  EXPECT_TRUE(IsShortcutCreated(result_id, "Test app"));
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       SuccessWithManifestId) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifestTemplate[] = R"json({
    "start_url": "/",
    "id": "/appid",
    "name": "Test app",
    "icons": [{
      "src": "$1",
      "sizes": "192x192",
      "type": "image/png"
    }]
  })json";

  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate, {GetIconUrl()}, nullptr);

  webapps::AppId expected_id = GenerateAppId("appid", kDocumentUrl);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, expected_id);

  EXPECT_EQ(result_id, expected_id);
  EXPECT_TRUE(webapps::IsSuccess(result_code));
  EXPECT_TRUE(IsShortcutCreated(result_id, "Test app"));

  EXPECT_EQ(provider().registrar_unsafe().GetAppShortName(result_id),
            "Test app");
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       SuccessWithExistingApp) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifestTemplate[] = R"json({
    "start_url": "/",
    "name": "Manifest installed app",
    "icons": [{
      "src": "$1",
      "sizes": "192x192",
      "type": "image/png"
    }]
  })json";
  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate, {GetIconUrl()}, nullptr);

  webapps::AppId existing_id =
      test::InstallDummyWebApp(profile(), "User installed app", kDocumentUrl);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, existing_id,
                            webapps::WebappInstallSource::EXTERNAL_DEFAULT);

  EXPECT_EQ(result_id, existing_id);
  EXPECT_TRUE(webapps::IsSuccess(result_code));

  // Existing install data should not be overwritten.
  EXPECT_EQ(provider().registrar_unsafe().GetAppShortName(result_id),
            "User installed app");
  /// Existing install should be updated with the new install source.
  EXPECT_TRUE(
      provider().registrar_unsafe().GetAppById(result_id)->IsPreinstalledApp());
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       SuccessWithMultipleIcons) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifestTemplate[] = R"json({
    "start_url": "/ ",
    "name": "Test app",
    "icons": [
      {
        "src": "$1",
        "sizes": "96x96",
        "type": "image/png"
      },
      {
        "src": "$2",
        "sizes": "192x192",
        "type": "image/png"
      }
    ],
    "shortcuts": [
      {
        "name": "Shortcut",
        "url": "/shortcut",
        "icons": [{
          "src": "$3",
          "sizes": "192x192",
          "type": "image/png"
        }]
      }
    ]
  })json";

  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate,
      {https_server()->GetURL("/banners/96x96-red.png").spec(),
       https_server()->GetURL("/banners/192x192-green.png").spec(),
       https_server()->GetURL("/web_apps/blue-192.png").spec()

      },
      nullptr);
  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kDocumentUrl);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, expected_id);

  EXPECT_TRUE(webapps::IsSuccess(result_code));

  SkColor small_icon_color =
      IconManagerReadAppIconPixel(provider().icon_manager(), result_id, 96);
  EXPECT_EQ(small_icon_color, SK_ColorRED);

  SkColor large_icon_color =
      IconManagerReadAppIconPixel(provider().icon_manager(), result_id, 192);
  EXPECT_EQ(large_icon_color, SK_ColorGREEN);

  base::test::TestFuture<ShortcutsMenuIconBitmaps> shortcut_future;
  provider().icon_manager().ReadAllShortcutsMenuIcons(
      result_id, shortcut_future.GetCallback());

  SkColor shortcut_icon_color =
      shortcut_future.Get()[0].any.at(192).getColor(0, 0);
  EXPECT_EQ(shortcut_icon_color, SK_ColorBLUE);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       FailureInvalidManifest) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifest[] = "notjson";

  auto [_, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, kManifest);

  EXPECT_EQ(result_code,
            webapps::InstallResultCode::kNotValidManifestForWebApp);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       FailureInvalidStartUrl) {
  // Installation will fail because there's no valid start URL.
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifest[] = R"json({
    "name": "Test app 2"
  })json";

  auto [_, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, kManifest);

  EXPECT_EQ(result_code,
            webapps::InstallResultCode::kNotValidManifestForWebApp);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       FailureInvalidName) {
  // Installation will fail because there's no valid name.
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifest[] = R"json({
    "start_url": "/"
  })json";

  auto [_, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, kManifest);

  EXPECT_EQ(result_code,
            webapps::InstallResultCode::kNotValidManifestForWebApp);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       FailureStartUrlOriginMismatch) {
  // Installation will fail because the start URL is a different origin to the
  // document URL.
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifest[] = R"json({
    "start_url": "https://www.not-app.com/",
    "name": "Test app 2",
    "icons": [{
      "src": "/icon/96.png",
      "sizes": "96x96",
      "type": "image/png"
    }]
  })json";

  auto [_, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, kManifest);

  EXPECT_EQ(result_code,
            webapps::InstallResultCode::kNotValidManifestForWebApp);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       FailureExpectedIdMismatch) {
  // Installation will fail because the expected ID doesn't match the ID in the
  // manifest.
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const webapps::AppId kExpectedId = GenerateAppId("/two", kDocumentUrl);
  const char kManifestTemplate[] = R"json({
    "start_url": "/",
    "id": "/one",
    "name": "Test app 2",
    "icons": [{
      "src": "$1",
      "sizes": "192x192",
      "type": "image/png"
    }]
  })json";
  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate, {GetIconUrl()}, nullptr);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, kExpectedId);

  EXPECT_EQ(result_code, webapps::InstallResultCode::kExpectedAppIdCheckFailed);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       FailureNoValidIcons) {
  // Installation will fail because there are no icons that match the allowlist.
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const webapps::AppId kExpectedId = GenerateAppId("/two", kDocumentUrl);
  const char kManifestTemplate[] = R"json({
    "start_url": "/",
    "name": "Test app",
    "icons": [
      {
        "src": "/icons/96.png",
        "sizes": "96x96",
        "type": "image/png"
      },
      {
        "src": "https://www.somesite.com/icons/48.png",
        "sizes": "48x48",
        "type": "image/png"
      },
      {
        "src": "https://notgoogleusercontent.com/icons/192.png",
        "sizes": "192x192",
        "type": "image/png"
      },
    ]
  })json";
  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate, {GetIconUrl()}, nullptr);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, kExpectedId);

  EXPECT_EQ(result_code,
            webapps::InstallResultCode::kNotValidManifestForWebApp);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       FailureAllIconsError) {
  // Installation will fail because all icons error when downloaded.
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const webapps::AppId kExpectedId = GenerateAppId("/two", kDocumentUrl);
  const char kManifestTemplate[] = R"json({
    "start_url": "/",
    "id": "/one",
    "name": "Test app 2",
    "icons": [
      {
        "src": "$1",
        "sizes": "96x96",
        "type": "image/png"
      },
      {
        "src": "$2",
        "sizes": "48x48",
        "type": "image/png"
      }
    ]
  })json";
  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate,
      {https_server()->GetURL("/404").spec(),
       https_server()->GetURL("/nocontent").spec()},
      nullptr);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, kExpectedId);

  EXPECT_EQ(result_code, webapps::InstallResultCode::kIconDownloadingFailed);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       SuccessIconsFromDifferentHosts) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifestTemplate[] = R"json({
    "start_url": "/ ",
    "name": "Test app",
    "icons": [
      {
        "src": "$1",
        "sizes": "96x96",
        "type": "image/png"
      },
      {
        "src": "$2",
        "sizes": "192x192",
        "type": "image/png"
      }
    ],
    "shortcuts": [
      {
        "name": "Shortcut",
        "url": "/shortcut",
        "icons": [{
            "src": "$3",
            "sizes": "192x192",
            "type": "image/png"
        }]
      }
    ]
  })json";

  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate,
      {https_server()
           ->GetURL("fonts.gstatic.com", "/banners/96x96-red.png")
           .spec(),
       https_server()
           ->GetURL("lh3.googleusercontent.com", "/banners/192x192-green.png")
           .spec(),
       https_server()->GetURL("youtube.com", "/web_apps/blue-192.png").spec()},
      nullptr);
  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kDocumentUrl);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, expected_id);

  EXPECT_TRUE(webapps::IsSuccess(result_code));

  SkColor small_icon_color =
      IconManagerReadAppIconPixel(provider().icon_manager(), result_id, 96);
  EXPECT_EQ(small_icon_color, SK_ColorRED);

  SkColor large_icon_color =
      IconManagerReadAppIconPixel(provider().icon_manager(), result_id, 192);
  EXPECT_EQ(large_icon_color, SK_ColorGREEN);

  base::test::TestFuture<ShortcutsMenuIconBitmaps> shortcut_future;
  provider().icon_manager().ReadAllShortcutsMenuIcons(
      result_id, shortcut_future.GetCallback());

  SkColor shortcut1_icon_color =
      shortcut_future.Get()[0].any.at(192).getColor(0, 0);
  EXPECT_EQ(shortcut1_icon_color, SK_ColorBLUE);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       FailureInvalidIconHost) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kStartUrl("https://www.app.com/home");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifestTemplate[] = R"json({
    "start_url": "/home",
    "name": "Test app",
    "launch_handler": {
      "client_mode": "focus-existing"
    },
    "icons": [
      {
        "src": "$1",
        "sizes": "96x96",
        "type": "image/png"
      },
      {
        "src": "$2",
        "sizes": "192x192",
        "type": "image/png"
      }
    ]
  })json";

  std::string manifest = base::ReplaceStringPlaceholders(
      kManifestTemplate,
      {"https://googleusercontent.com.evil.com/icons/192.png",
       "https://evilgoogleusercontent.com/icons/192.png"},
      nullptr);

  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kStartUrl);
  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, expected_id);

  EXPECT_EQ(result_code, webapps::InstallResultCode::kNoValidIconsInManifest);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       FailureNoIcons) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kStartUrl("https://www.app.com/home");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  const char kManifest[] = R"json({
    "start_url": "/home",
    "name": "Test app",
    "launch_handler": {
      "client_mode": "focus-existing"
    }
  })json";

  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kStartUrl);
  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, kManifest, expected_id);

  EXPECT_EQ(result_code, webapps::InstallResultCode::kNoValidIconsInManifest);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       DefaultToStandaloneUserDisplay) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");

  std::string manifest = GetBasicManifest();
  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kDocumentUrl);

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, expected_id);

  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(result_id)->user_display_mode(),
      mojom::UserDisplayMode::kStandalone);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       OverrideUserDisplayModeWithParams) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  std::string manifest = GetBasicManifest();
  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kDocumentUrl);

  WebAppInstallParams params;
  params.user_display_mode = mojom::UserDisplayMode::kBrowser;

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, expected_id,
                            webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
                            /*is_diy_app=*/false, params);

  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(result_id)->user_display_mode(),
      mojom::UserDisplayMode::kBrowser);
  EXPECT_EQ(provider().registrar_unsafe().GetAppEffectiveDisplayMode(result_id),
            DisplayMode::kBrowser);
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       SetAdditionalSearchTermsThroughParams) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  std::string manifest = GetBasicManifest();
  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kDocumentUrl);

  WebAppInstallParams params;
  params.additional_search_terms = {"chocolate", "vanilla", "strawberry"};

  auto [result_id, result_code] =
      InstallAndAwaitResult(kDocumentUrl, kManifestUrl, manifest, expected_id,
                            webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
                            /*is_diy_app=*/false, params);

  EXPECT_THAT(provider()
                  .registrar_unsafe()
                  .GetAppById(result_id)
                  ->additional_search_terms(),
              testing::ElementsAre("chocolate", "vanilla", "strawberry"));
}

IN_PROC_BROWSER_TEST_F(InstallAppFromVerifiedManifestCommandTest,
                       InstallAsDiyApp) {
  const GURL kDocumentUrl("https://www.app.com/");
  const GURL kManifestUrl("https://www.app.com/manifest.json");
  std::string manifest = GetBasicManifest();
  webapps::AppId expected_id =
      GenerateAppId(/*manifest_id=*/std::nullopt, kDocumentUrl);

  auto [result_id, result_code] = InstallAndAwaitResult(
      kDocumentUrl, kManifestUrl, manifest, expected_id,
      webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON, /*is_diy_app=*/true);

  EXPECT_TRUE(provider().registrar_unsafe().IsDiyApp(result_id));
}

}  // namespace web_app