chromium/chrome/browser/sync/test/integration/single_client_app_list_sync_test.cc

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

#include <stddef.h>

#include "ash/constants/ash_features.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ash/app_list/chrome_app_list_item.h"
#include "chrome/browser/ash/app_list/internal_app/internal_app_metadata.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/sync/test/integration/apps_helper.h"
#include "chrome/browser/sync/test/integration/status_change_checker.h"
#include "chrome/browser/sync/test/integration/sync_app_list_helper.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "content/public/test/browser_test.h"

using syncer::UserSelectableOsTypeSet;
using syncer::UserSelectableTypeSet;

namespace {

bool AllProfilesHaveSameAppList() {
  return SyncAppListHelper::GetInstance()->AllProfilesHaveSameAppList();
}

// Returns true if sync items from |service| all have non-empty names.
bool SyncItemsHaveNames(const app_list::AppListSyncableService* service) {
  for (const auto& [item_id, item] : service->sync_items()) {
    if (item->item_name.empty()) {
      return false;
    }
  }
  return true;
}

// Returns true if sync items from |service1| match to sync items in |service2|.
bool SyncItemsMatch(const app_list::AppListSyncableService* service1,
                    const app_list::AppListSyncableService* service2) {
  if (service1->sync_items().size() != service2->sync_items().size()) {
    return false;
  }

  for (const auto& [item_id, item1] : service1->sync_items()) {
    const app_list::AppListSyncableService::SyncItem* item2 =
        service2->GetSyncItem(item_id);
    if (!item2) {
      return false;
    }
    if (item1->item_id != item2->item_id ||
        item1->item_type != item2->item_type ||
        item1->item_name != item2->item_name ||
        item1->parent_id != item2->parent_id ||
        !item1->item_ordinal.EqualsOrBothInvalid(item2->item_ordinal) ||
        !item1->item_pin_ordinal.EqualsOrBothInvalid(item2->item_pin_ordinal)) {
      return false;
    }
  }
  return true;
}

class AppListSyncUpdateWaiter
    : public StatusChangeChecker,
      public app_list::AppListSyncableService::Observer {
 public:
  explicit AppListSyncUpdateWaiter(app_list::AppListSyncableService* service) {
    observer_.Observe(service);
  }

  AppListSyncUpdateWaiter(const AppListSyncUpdateWaiter&) = delete;
  AppListSyncUpdateWaiter& operator=(const AppListSyncUpdateWaiter&) = delete;

  ~AppListSyncUpdateWaiter() override = default;

  // StatusChangeChecker:
  bool IsExitConditionSatisfied(std::ostream* os) override {
    *os << "AwaitAppListSyncUpdated";
    return service_updated_;
  }

  // app_list::AppListSyncableService::Observer:
  void OnSyncModelUpdated() override {
    service_updated_ = true;
    CheckExitCondition();
  }

 private:
  base::ScopedObservation<app_list::AppListSyncableService,
                          app_list::AppListSyncableService::Observer>
      observer_{this};
  bool service_updated_ = false;
};

}  // namespace

class SingleClientAppListSyncTest : public SyncTest {
 public:
  SingleClientAppListSyncTest() : SyncTest(SINGLE_CLIENT) {}
  ~SingleClientAppListSyncTest() override = default;

  // SyncTest
  bool SetupClients() override {
    if (!SyncTest::SetupClients()) {
      return false;
    }

    // Init SyncAppListHelper to ensure that the extension system is initialized
    // for each Profile.
    SyncAppListHelper::GetInstance();
    return true;
  }
};

class SingleClientAppListSyncTestWithVerifier
    : public SingleClientAppListSyncTest {
 public:
  SingleClientAppListSyncTestWithVerifier() = default;
  ~SingleClientAppListSyncTestWithVerifier() override = default;

  bool UseVerifier() override {
    // TODO(crbug.com/40724974): rewrite tests to not use verifier.
    return true;
  }
};

IN_PROC_BROWSER_TEST_F(SingleClientAppListSyncTestWithVerifier, AppListEmpty) {
  ASSERT_TRUE(SetupSync());

  ASSERT_TRUE(AllProfilesHaveSameAppList());
}

IN_PROC_BROWSER_TEST_F(SingleClientAppListSyncTestWithVerifier,
                       AppListSomeApps) {
  ASSERT_TRUE(SetupSync());

  const size_t kNumApps = 5;
  for (int i = 0; i < static_cast<int>(kNumApps); ++i) {
    apps_helper::InstallHostedApp(GetProfile(0), i);
    apps_helper::InstallHostedApp(verifier(), i);
  }

  // Allow async callbacks to run, such as App Service Mojo calls.
  base::RunLoop().RunUntilIdle();

  app_list::AppListSyncableService* service =
      app_list::AppListSyncableServiceFactory::GetForProfile(verifier());

  // Default apps: chrome + web store + internal apps .
  const size_t kNumDefaultApps =
      2u + app_list::GetNumberOfInternalAppsShowInLauncherForTest(
               /*apps_name=*/nullptr, GetProfile(0));
  ASSERT_EQ(kNumApps + kNumDefaultApps, service->sync_items().size());

  ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
  ASSERT_TRUE(AllProfilesHaveSameAppList());
}

IN_PROC_BROWSER_TEST_F(SingleClientAppListSyncTest, LocalStorage) {
  ASSERT_TRUE(SetupSync());

  Profile* profile = GetProfile(0);
  app_list::AppListSyncableService* service =
      app_list::AppListSyncableServiceFactory::GetForProfile(profile);
  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForProfile(profile);

  const size_t kNumApps = 7;
  syncer::StringOrdinal pin_position =
      syncer::StringOrdinal::CreateInitialOrdinal();
  std::vector<std::string> app_ids;
  for (int i = 0; i < static_cast<int>(kNumApps); ++i) {
    app_ids.push_back(apps_helper::InstallHostedApp(profile, i));
  }

  // Allow async callbacks to run, such as App Service Mojo calls.
  base::RunLoop().RunUntilIdle();

  for (const std::string& app_id : app_ids) {
    service->SetPinPosition(app_id, pin_position, /*pinned_by_policy=*/false);
    pin_position = pin_position.CreateAfter();
  }
  EXPECT_TRUE(SyncItemsHaveNames(service));

  auto folder_item1 = std::make_unique<ChromeAppListItem>(
      profile, "folder1", service->GetModelUpdater());
  folder_item1->SetChromeIsFolder(true);
  ChromeAppListItem::TestApi(folder_item1.get()).SetPosition(pin_position);
  pin_position = pin_position.CreateAfter();
  ChromeAppListItem::TestApi(folder_item1.get()).SetName("Folder 1");
  service->AddItem(std::move(folder_item1));

  auto folder_item2 = std::make_unique<ChromeAppListItem>(
      profile, "folder2", service->GetModelUpdater());
  folder_item2->SetChromeIsFolder(true);
  ChromeAppListItem::TestApi(folder_item2.get()).SetPosition(pin_position);
  ChromeAppListItem::TestApi(folder_item2.get()).SetName("Folder 2");
  service->AddItem(std::move(folder_item2));

  // Ensure that one folder has more than one child. Otherwise, the folder could
  // be deleted.
  SyncAppListHelper::GetInstance()->MoveAppToFolder(profile, app_ids[2],
                                                    "folder1");
  SyncAppListHelper::GetInstance()->MoveAppToFolder(profile, app_ids[3],
                                                    "folder2");
  SyncAppListHelper::GetInstance()->MoveAppToFolder(profile, app_ids[5],
                                                    "folder1");
  SyncAppListHelper::GetInstance()->MoveAppToFolder(profile, app_ids[6],
                                                    "folder2");

  app_list::AppListSyncableService compare_service(profile);

  // Make sure that that on start, when sync has not been started yet, model
  // content is filled from local prefs and it matches latest state.
  EXPECT_TRUE(SyncItemsMatch(service, &compare_service));

  ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());

  // Disable app sync by disabling all user-selectable types.
  sync_service->GetUserSettings()->SetSelectedOsTypes(
      /*sync_all_os_types=*/false, syncer::UserSelectableOsTypeSet());

  // Change data when sync is off.
  for (const std::string& app_id : app_ids) {
    service->SetPinPosition(app_id, pin_position, /*pinned_by_policy=*/false);
    pin_position = pin_position.CreateAfter();
  }
  SyncAppListHelper::GetInstance()->MoveAppFromFolder(profile, app_ids[0],
                                                      "folder1");
  SyncAppListHelper::GetInstance()->MoveAppFromFolder(profile, app_ids[0],
                                                      "folder2");

  EXPECT_FALSE(SyncItemsMatch(service, &compare_service));

  // Restore app sync and sync data should override local changes.
  sync_service->GetUserSettings()->SetSelectedOsTypes(
      /*sync_all_os_types=*/true, syncer::UserSelectableOsTypeSet());
  EXPECT_TRUE(AppListSyncUpdateWaiter(service).Wait());
  EXPECT_TRUE(SyncItemsMatch(service, &compare_service));
}

class SingleClientAppListOsSyncTest : public SyncTest {
 public:
  SingleClientAppListOsSyncTest() : SyncTest(SINGLE_CLIENT) {}
  ~SingleClientAppListOsSyncTest() override = default;
};

IN_PROC_BROWSER_TEST_F(SingleClientAppListOsSyncTest,
                       AppListSyncedByOsSettings) {
  ASSERT_TRUE(SetupSync());
  syncer::SyncService* service = GetSyncService(0);
  syncer::SyncUserSettings* settings = service->GetUserSettings();

  // Initially app list is enabled.
  ASSERT_TRUE(settings->IsSyncEverythingEnabled());
  ASSERT_TRUE(settings->IsSyncAllOsTypesEnabled());
  ASSERT_TRUE(service->GetActiveDataTypes().Has(syncer::APP_LIST));

  // Disable all browser types.
  settings->SetSelectedTypes(false, UserSelectableTypeSet());
  ASSERT_TRUE(GetClient(0)->AwaitSyncSetupCompletion());

  // APP_LIST is still synced because it is an OS setting.
  EXPECT_TRUE(service->GetActiveDataTypes().Has(syncer::APP_LIST));

  // Disable OS types.
  settings->SetSelectedOsTypes(false, UserSelectableOsTypeSet());
  ASSERT_TRUE(GetClient(0)->AwaitSyncSetupCompletion());

  // APP_LIST is not synced.
  EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::APP_LIST));
}