chromium/chrome/browser/sync/test/integration/two_client_workspace_desk_sync_test.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 "ash/constants/ash_features.h"
#include "ash/public/cpp/desk_template.h"
#include "base/test/bind.h"
#include "base/test/simple_test_clock.h"
#include "base/uuid.h"
#include "chrome/browser/sync/desk_sync_service_factory.h"
#include "chrome/browser/sync/test/integration/sync_integration_test_util.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/workspace_desk_helper.h"
#include "chrome/browser/ui/browser.h"
#include "components/desks_storage/core/desk_model.h"
#include "components/desks_storage/core/desk_sync_service.h"
#include "components/sync/base/time.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/workspace_desk_specifics.pb.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace {

using ash::DeskTemplate;
using ash::DeskTemplateSource;
using ash::DeskTemplateType;
using desks_storage::DeskModel;
using desks_storage::DeskSyncService;
using sync_pb::WorkspaceDeskSpecifics;
using testing::Contains;

constexpr char kUuidFormat[] = "9e186d5a-502e-49ce-9ee1-00000000000%d";
constexpr char kNameFormat[] = "template %d";

WorkspaceDeskSpecifics CreateWorkspaceDeskSpecifics(int templateIndex,
                                                    base::Time created_time) {
  WorkspaceDeskSpecifics specifics;
  specifics.set_uuid(base::StringPrintf(kUuidFormat, templateIndex));
  specifics.set_name(base::StringPrintf(kNameFormat, templateIndex));
  specifics.set_created_time_windows_epoch_micros(
      created_time.ToDeltaSinceWindowsEpoch().InMicroseconds());
  return specifics;
}

// Waits for kUpToDate download status for WORKSPACE_DESK data type.
class DownloadStatusChecker : public SingleClientStatusChangeChecker {
 public:
  explicit DownloadStatusChecker(syncer::SyncServiceImpl* sync_service)
      : SingleClientStatusChangeChecker(sync_service) {}
  ~DownloadStatusChecker() override = default;

  bool IsExitConditionSatisfied(std::ostream* os) override {
    *os << "Waiting for download status kUpToDate for WORKSPACE_DESK.";

    return service()->GetDownloadStatusFor(syncer::WORKSPACE_DESK) ==
           syncer::SyncService::DataTypeDownloadStatus::kUpToDate;
  }
};

class TwoClientWorkspaceDeskSyncTest : public SyncTest {
 public:
  TwoClientWorkspaceDeskSyncTest() : SyncTest(TWO_CLIENT) {}

  TwoClientWorkspaceDeskSyncTest(const TwoClientWorkspaceDeskSyncTest&) =
      delete;
  TwoClientWorkspaceDeskSyncTest& operator=(
      const TwoClientWorkspaceDeskSyncTest&) = delete;
  ~TwoClientWorkspaceDeskSyncTest() override = default;

  base::Time AdvanceAndGetTime(base::TimeDelta delta = base::Milliseconds(10)) {
    clock_.Advance(delta);
    return clock_.Now();
  }

 private:
  base::SimpleTestClock clock_;
};

IN_PROC_BROWSER_TEST_F(
    TwoClientWorkspaceDeskSyncTest,
    PRE_DownloadDeskTemplateWhenUpToDateFromOneClientToAnother) {
  ASSERT_TRUE(SetupSync());
}

IN_PROC_BROWSER_TEST_F(TwoClientWorkspaceDeskSyncTest,
                       DownloadDeskTemplateWhenUpToDateFromOneClientToAnother) {
  // Inject a test desk template to Sync.
  sync_pb::EntitySpecifics specifics;
  WorkspaceDeskSpecifics* desk = specifics.mutable_workspace_desk();
  desk->CopyFrom(CreateWorkspaceDeskSpecifics(1, AdvanceAndGetTime()));
  base::Uuid desk_1_uuid =
      base::Uuid::ParseCaseInsensitive(base::StringPrintf(kUuidFormat, 1));
  base::Uuid desk_2_uuid =
      base::Uuid::ParseCaseInsensitive(base::StringPrintf(kUuidFormat, 2));
  fake_server_->InjectEntity(
      syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
          "desk_1", desk_1_uuid.AsLowercaseString(), specifics,
          /*creation_time=*/syncer::TimeToProtoTime(AdvanceAndGetTime()),
          /*last_modified_time=*/syncer::TimeToProtoTime(AdvanceAndGetTime())));

  ASSERT_TRUE(SetupClients());
  // Checks that client 1 was able to get the entry via the on `OnStateChanged`
  // from sync service.
  ASSERT_TRUE(DownloadStatusChecker(GetSyncService(0)).Wait());
  // Verify that the update has been actually downloaded.
  desks_storage::DeskModel* c1_desk_model =
      DeskSyncServiceFactory::GetForProfile(GetProfile(0))->GetDeskModel();
  EXPECT_THAT(c1_desk_model->GetAllEntryUuids(), Contains(desk_1_uuid));

  // Client 1 adds an entry and client 2 receives the entry correctly.
  desks_storage::DeskModel* c1_model =
      DeskSyncServiceFactory::GetForProfile(GetProfile(0))->GetDeskModel();
  ASSERT_TRUE(c1_model->IsSyncing());
  base::RunLoop loop;
  c1_model->AddOrUpdateEntry(
      std::make_unique<DeskTemplate>(desk_2_uuid, DeskTemplateSource::kUser,
                                     "desk_2", AdvanceAndGetTime(),
                                     DeskTemplateType::kTemplate),
      base::BindLambdaForTesting(
          [&](DeskModel::AddOrUpdateEntryStatus status,
              std::unique_ptr<ash::DeskTemplate> new_entry) {
            EXPECT_EQ(DeskModel::AddOrUpdateEntryStatus::kOk, status);
            loop.Quit();
          }));
  loop.Run();
  EXPECT_TRUE(
      workspace_desk_helper::DeskUuidChecker(
          DeskSyncServiceFactory::GetForProfile(GetProfile(0)), desk_2_uuid)
          .Wait());
  // Verify that the update has been actually downloaded on client 2.
  EXPECT_TRUE(
      workspace_desk_helper::DeskUuidChecker(
          DeskSyncServiceFactory::GetForProfile(GetProfile(1)), desk_2_uuid)
          .Wait());
}

IN_PROC_BROWSER_TEST_F(TwoClientWorkspaceDeskSyncTest,
                       DeleteDeskTemplateIsSyncedAcrossBothClients) {
  sync_pb::EntitySpecifics specifics;
  WorkspaceDeskSpecifics* desk = specifics.mutable_workspace_desk();
  desk->CopyFrom(CreateWorkspaceDeskSpecifics(1, AdvanceAndGetTime()));
  base::Uuid desk_1_uuid =
      base::Uuid::ParseCaseInsensitive(base::StringPrintf(kUuidFormat, 1));
  fake_server_->InjectEntity(
      syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
          "desk_1", desk_1_uuid.AsLowercaseString(), specifics,
          /*creation_time=*/syncer::TimeToProtoTime(AdvanceAndGetTime()),
          /*last_modified_time=*/syncer::TimeToProtoTime(AdvanceAndGetTime())));

  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
  // Make sure the template is on both client.
  ASSERT_TRUE(
      workspace_desk_helper::DeskUuidChecker(
          DeskSyncServiceFactory::GetForProfile(GetProfile(0)), desk_1_uuid)
          .Wait());

  ASSERT_TRUE(
      workspace_desk_helper::DeskUuidChecker(
          DeskSyncServiceFactory::GetForProfile(GetProfile(1)), desk_1_uuid)
          .Wait());

  desks_storage::DeskModel* model =
      DeskSyncServiceFactory::GetForProfile(GetProfile(0))->GetDeskModel();

  // Delete template 1.
  base::RunLoop loop;
  model->DeleteEntry(
      desk_1_uuid,
      base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
        EXPECT_EQ(DeskModel::DeleteEntryStatus::kOk, status);
        loop.Quit();
      }));
  loop.Run();

  EXPECT_TRUE(
      workspace_desk_helper::DeskUuidDeletedChecker(
          DeskSyncServiceFactory::GetForProfile(GetProfile(0)), desk_1_uuid)
          .Wait());
  EXPECT_TRUE(
      workspace_desk_helper::DeskUuidDeletedChecker(
          DeskSyncServiceFactory::GetForProfile(GetProfile(1)), desk_1_uuid)
          .Wait());
}

}  // namespace