chromium/chrome/browser/apps/app_preload_service/app_preload_almanac_endpoint_unittest.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_preload_service/app_preload_almanac_endpoint.h"

#include <optional>
#include <tuple>

#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chrome/browser/apps/almanac_api_client/device_info_manager.h"
#include "chrome/browser/apps/almanac_api_client/proto/client_context.pb.h"
#include "chrome/browser/apps/app_preload_service/app_preload_service.h"
#include "chrome/browser/apps/app_preload_service/preload_app_definition.h"
#include "chrome/browser/apps/app_preload_service/proto/app_preload.pb.h"
#include "content/public/test/browser_task_environment.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace apps {

class AppPreloadAlmanacEndpointTest : public testing::Test {
 public:
  AppPreloadAlmanacEndpointTest()
      : test_shared_loader_factory_(
            base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
                &url_loader_factory_)) {
    feature_list_.InitAndDisableFeature(kAppPreloadServiceEnableTestApps);
  }

 protected:
  network::TestURLLoaderFactory url_loader_factory_;
  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
  base::HistogramTester histograms_;
  base::test::ScopedFeatureList feature_list_;

 private:
  content::BrowserTaskEnvironment task_environment_;
};

TEST_F(AppPreloadAlmanacEndpointTest, GetAppsForFirstLoginRequest) {
  // We only set enough fields to verify that context protos are attached to the
  // request.
  DeviceInfo device_info;
  device_info.board = "brya";
  device_info.user_type = "unmanaged";

  std::string method;
  std::optional<std::string> method_override_header;
  std::optional<std::string> content_type;
  std::string body;

  url_loader_factory_.SetInterceptor(
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        content_type =
            request.headers.GetHeader(net::HttpRequestHeaders::kContentType);
        method_override_header =
            request.headers.GetHeader("X-HTTP-Method-Override");
        method = request.method;
        body = network::GetUploadData(request);
      }));

  app_preload_almanac_endpoint::GetAppsForFirstLogin(
      device_info, *test_shared_loader_factory_, base::DoNothing());

  EXPECT_EQ(method, "POST");
  EXPECT_EQ(method_override_header, "GET");
  EXPECT_EQ(content_type, "application/x-protobuf");

  proto::AppPreloadListRequest request;
  ASSERT_TRUE(request.ParseFromString(body));

  EXPECT_EQ(request.device_context().board(), "brya");
  EXPECT_EQ(request.user_context().user_type(),
            apps::proto::ClientUserContext::USERTYPE_UNMANAGED);
}

TEST_F(AppPreloadAlmanacEndpointTest, GetAppsForFirstLoginSuccessfulResponse) {
  PackageId chrome_app =
      *PackageId::FromString("chromeapp:mgndgikekgjfcpckkfioiadnlibdjbkf");
  PackageId lacros_app = *PackageId::FromString("system:lacros-chrome");
  PackageId web_app1 = *PackageId::FromString("web:http://example.com/app1");
  PackageId android_app1 = *PackageId::FromString("android:com.example.app1");
  PackageId web_app2 = *PackageId::FromString("web:http://example.com/app2");
  PackageId web_app3 = *PackageId::FromString("web:http://example.com/app3");
  PackageId web_app4 = *PackageId::FromString("web:http://example.com/app4");

  auto type_chrome =
      proto::AppPreloadListResponse_LauncherType_LAUNCHER_TYPE_CHROME;
  auto type_app = proto::AppPreloadListResponse_LauncherType_LAUNCHER_TYPE_APP;
  auto type_folder =
      proto::AppPreloadListResponse_LauncherType_LAUNCHER_TYPE_FOLDER;
  proto::AppPreloadListResponse response;
  auto* app = response.add_apps_to_install();
  app->set_name("Peanut Types");

  // Add chrome with no package.
  auto* launcher_config_item = response.add_launcher_config();
  launcher_config_item->set_type(type_chrome);
  launcher_config_item->set_order(1);

  launcher_config_item = response.add_launcher_config();
  launcher_config_item->set_type(type_app);
  launcher_config_item->set_order(2);
  launcher_config_item->add_package_id(web_app1.ToString());
  launcher_config_item->add_package_id(android_app1.ToString());

  launcher_config_item = response.add_launcher_config();
  launcher_config_item->set_type(type_app);
  launcher_config_item->set_order(3);
  launcher_config_item->set_feature_flag("unknown");
  launcher_config_item->add_package_id(web_app2.ToString());

  launcher_config_item = response.add_launcher_config();
  launcher_config_item->set_type(type_app);
  launcher_config_item->set_order(4);
  launcher_config_item->set_feature_flag("AppPreloadServiceEnableTestApps");
  launcher_config_item->add_package_id(web_app3.ToString());

  // Add folder with web_app4.
  launcher_config_item = response.add_launcher_config();
  launcher_config_item->set_type(type_folder);
  launcher_config_item->set_order(5);
  launcher_config_item->set_folder_name("other-folder");
  auto* folder_item = launcher_config_item->add_child_config();
  folder_item->set_type(type_app);
  folder_item->set_order(1);
  folder_item->add_package_id(web_app4.ToString());

  auto* shelf_config_item = response.add_shelf_config();
  shelf_config_item->set_order(1);
  shelf_config_item->add_package_id(web_app1.ToString());
  shelf_config_item->add_package_id(android_app1.ToString());

  shelf_config_item = response.add_shelf_config();
  shelf_config_item->set_order(2);
  shelf_config_item->set_feature_flag("unknown");
  shelf_config_item->add_package_id(web_app2.ToString());

  shelf_config_item = response.add_shelf_config();
  shelf_config_item->set_order(3);
  shelf_config_item->set_feature_flag("AppPreloadServiceEnableTestApps");
  shelf_config_item->add_package_id(web_app3.ToString());

  url_loader_factory_.AddResponse(
      app_preload_almanac_endpoint::GetServerUrl().spec(),
      response.SerializeAsString());

  base::test::TestFuture<std::optional<std::vector<PreloadAppDefinition>>,
                         LauncherOrdering, ShelfPinOrdering>
      test_callback;
  app_preload_almanac_endpoint::GetAppsForFirstLogin(
      DeviceInfo(), *test_shared_loader_factory_, test_callback.GetCallback());

  auto apps = std::get<0>(test_callback.Get());
  EXPECT_TRUE(apps.has_value());
  EXPECT_EQ(apps->size(), 1u);
  EXPECT_EQ(apps.value()[0].GetName(), "Peanut Types");

  auto launcher_ordering = std::get<1>(test_callback.Get());
  EXPECT_EQ(launcher_ordering.size(), 2u);
  auto root_folder = launcher_ordering[""];
  EXPECT_EQ(root_folder.size(), 5u);
  EXPECT_EQ(root_folder[chrome_app].type, type_chrome);
  EXPECT_EQ(root_folder[chrome_app].order, 1u);
  EXPECT_EQ(root_folder[lacros_app].type, type_chrome);
  EXPECT_EQ(root_folder[lacros_app].order, 1u);
  EXPECT_EQ(root_folder[web_app1].type, type_app);
  EXPECT_EQ(root_folder[web_app1].order, 2u);
  EXPECT_EQ(root_folder[android_app1].type, type_app);
  EXPECT_EQ(root_folder[android_app1].order, 2u);
  EXPECT_EQ(root_folder["other-folder"].type, type_folder);
  EXPECT_EQ(root_folder["other-folder"].order, 5u);
  auto other_folder = launcher_ordering["other-folder"];
  EXPECT_EQ(other_folder.size(), 1u);
  EXPECT_EQ(other_folder[web_app4].type, type_app);
  EXPECT_EQ(other_folder[web_app4].order, 1u);

  auto shelf_pin_ordering = std::get<2>(test_callback.Get());
  EXPECT_EQ(shelf_pin_ordering.size(), 2u);
  EXPECT_EQ(shelf_pin_ordering[web_app1], 1u);
  EXPECT_EQ(shelf_pin_ordering[android_app1], 1u);
}

TEST_F(AppPreloadAlmanacEndpointTest, GetAppsForFirstLoginServerError) {
  url_loader_factory_.AddResponse(
      app_preload_almanac_endpoint::GetServerUrl().spec(), /*content=*/"",
      net::HTTP_INTERNAL_SERVER_ERROR);

  base::test::TestFuture<std::optional<std::vector<PreloadAppDefinition>>,
                         LauncherOrdering, ShelfPinOrdering>
      result;
  app_preload_almanac_endpoint::GetAppsForFirstLogin(
      DeviceInfo(), *test_shared_loader_factory_, result.GetCallback());
  EXPECT_FALSE(std::get<0>(result.Get()).has_value());
}

TEST_F(AppPreloadAlmanacEndpointTest, GetAppsForFirstLoginNetworkError) {
  url_loader_factory_.AddResponse(
      app_preload_almanac_endpoint::GetServerUrl(),
      network::mojom::URLResponseHead::New(), /*content=*/"",
      network::URLLoaderCompletionStatus(net::ERR_TIMED_OUT));

  base::test::TestFuture<std::optional<std::vector<PreloadAppDefinition>>,
                         LauncherOrdering, ShelfPinOrdering>
      result;
  app_preload_almanac_endpoint::GetAppsForFirstLogin(
      DeviceInfo(), *test_shared_loader_factory_, result.GetCallback());
  EXPECT_FALSE(std::get<0>(result.Get()).has_value());
}

}  // namespace apps