910e62b5创建于 1月15日历史提交
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/consent_auditor/consent_sync_bridge_impl.h"

#include <map>
#include <set>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "components/sync/model/data_batch.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/user_consent_specifics.pb.h"
#include "components/sync/test/data_type_store_test_util.h"
#include "components/sync/test/mock_data_type_local_change_processor.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace consent_auditor {
namespace {

using sync_pb::UserConsentSpecifics;
using syncer::DataBatch;
using syncer::DataTypeStore;
using syncer::DataTypeStoreTestUtil;
using syncer::DataTypeSyncBridge;
using syncer::EntityChange;
using syncer::EntityChangeList;
using syncer::EntityData;
using syncer::MetadataChangeList;
using syncer::MockDataTypeLocalChangeProcessor;
using syncer::OnceDataTypeStoreFactory;
using testing::_;
using testing::ElementsAre;
using testing::Eq;
using testing::InvokeWithoutArgs;
using testing::IsEmpty;
using testing::IsNull;
using testing::NotNull;
using testing::Pair;
using testing::Pointee;
using testing::Property;
using testing::Return;
using testing::SaveArg;
using testing::SizeIs;
using testing::UnorderedElementsAre;
using testing::WithArg;
using WriteBatch = DataTypeStore::WriteBatch;

constexpr GaiaId::Literal kDefaultGaiaId("gaia_id");

MATCHER_P(MatchesUserConsent, expected, "") {
  if (!arg.has_user_consent()) {
    *result_listener << "which is not a user consent";
    return false;
  }
  const UserConsentSpecifics& actual = arg.user_consent();
  if (actual.client_consent_time_usec() !=
      expected.client_consent_time_usec()) {
    return false;
  }
  return true;
}

UserConsentSpecifics CreateSpecifics(int64_t client_consent_time_usec) {
  UserConsentSpecifics specifics;
  specifics.set_client_consent_time_usec(client_consent_time_usec);
  specifics.set_obfuscated_gaia_id(kDefaultGaiaId.ToString());
  return specifics;
}

std::unique_ptr<UserConsentSpecifics> SpecificsUniquePtr(
    int64_t client_consent_time_usec) {
  return std::make_unique<UserConsentSpecifics>(
      CreateSpecifics(client_consent_time_usec));
}

class ConsentSyncBridgeImplTest : public testing::Test {
 protected:
  ConsentSyncBridgeImplTest() { ResetBridge(); }

  void ResetBridge() {
    OnceDataTypeStoreFactory store_factory;
    if (bridge_) {
      // Carry over the underlying store from previous bridge instances.
      std::unique_ptr<DataTypeStore> store = bridge_->StealStoreForTest();
      bridge_.reset();
      store_factory =
          DataTypeStoreTestUtil::MoveStoreToFactory(std::move(store));
    } else {
      store_factory = DataTypeStoreTestUtil::FactoryForInMemoryStoreForTest();
    }

    bridge_ = std::make_unique<ConsentSyncBridgeImpl>(
        std::move(store_factory), mock_processor_.CreateForwardingProcessor());
  }

  void WaitUntilModelReadyToSync(const GaiaId& gaia_id) {
    base::RunLoop loop;
    base::RepeatingClosure quit_closure = loop.QuitClosure();
    // Let the bridge initialize fully, which should run ModelReadyToSync().
    ON_CALL(*processor(), ModelReadyToSync(_))
        .WillByDefault(InvokeWithoutArgs([=]() { quit_closure.Run(); }));
    loop.Run();
    ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(true));
    ON_CALL(*processor(), TrackedGaiaId()).WillByDefault(Return(gaia_id));
  }

  static std::string GetStorageKey(const UserConsentSpecifics& specifics) {
    return ConsentSyncBridgeImpl::GetStorageKeyFromSpecificsForTest(specifics);
  }

  ConsentSyncBridgeImpl* bridge() { return bridge_.get(); }
  MockDataTypeLocalChangeProcessor* processor() { return &mock_processor_; }

  std::map<std::string, sync_pb::EntitySpecifics> GetAllDataForDebugging() {
    std::unique_ptr<DataBatch> batch = bridge_->GetAllDataForDebugging();
    EXPECT_NE(nullptr, batch);

    std::map<std::string, sync_pb::EntitySpecifics> storage_key_to_specifics;
    if (batch != nullptr) {
      while (batch->HasNext()) {
        const syncer::KeyAndData& pair = batch->Next();
        storage_key_to_specifics[pair.first] = pair.second->specifics;
      }
    }
    return storage_key_to_specifics;
  }

  std::unique_ptr<sync_pb::EntitySpecifics> GetDataForCommit(
      const std::string& storage_key) {
    std::unique_ptr<DataBatch> batch = bridge_->GetDataForCommit({storage_key});
    EXPECT_NE(nullptr, batch);

    std::unique_ptr<sync_pb::EntitySpecifics> specifics;
    if (batch != nullptr && batch->HasNext()) {
      const syncer::KeyAndData& pair = batch->Next();
      specifics =
          std::make_unique<sync_pb::EntitySpecifics>(pair.second->specifics);
      EXPECT_FALSE(batch->HasNext());
    }
    return specifics;
  }

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;
  testing::NiceMock<MockDataTypeLocalChangeProcessor> mock_processor_;
  std::unique_ptr<ConsentSyncBridgeImpl> bridge_;
};

TEST_F(ConsentSyncBridgeImplTest, ShouldCallModelReadyToSyncOnStartup) {
  EXPECT_CALL(*processor(), ModelReadyToSync(NotNull()));
  WaitUntilModelReadyToSync(kDefaultGaiaId);
}

TEST_F(ConsentSyncBridgeImplTest, ShouldGetDataForCommit) {
  WaitUntilModelReadyToSync(kDefaultGaiaId);
  const UserConsentSpecifics specifics(
      CreateSpecifics(/*client_consent_time_usec=*/1u));
  std::string storage_key;
  EXPECT_CALL(*processor(), Put(_, _, _))
      .WillOnce(WithArg<0>(SaveArg<0>(&storage_key)));
  bridge()->RecordConsent(std::make_unique<UserConsentSpecifics>(specifics));

  // Existing specifics should be returned.
  EXPECT_THAT(GetDataForCommit(storage_key),
              Pointee(MatchesUserConsent(specifics)));
  // GetDataForCommit() should handle arbitrary storage key.
  EXPECT_THAT(GetDataForCommit("bogus"), IsNull());
}

TEST_F(ConsentSyncBridgeImplTest, ShouldRecordSingleConsent) {
  WaitUntilModelReadyToSync(kDefaultGaiaId);
  const UserConsentSpecifics specifics(
      CreateSpecifics(/*client_consent_time_usec=*/1u));
  std::string storage_key;
  EXPECT_CALL(*processor(), Put(_, _, _))
      .WillOnce(WithArg<0>(SaveArg<0>(&storage_key)));
  bridge()->RecordConsent(std::make_unique<UserConsentSpecifics>(specifics));

  EXPECT_THAT(GetDataForCommit(storage_key),
              Pointee(MatchesUserConsent(specifics)));
  EXPECT_THAT(GetAllDataForDebugging(),
              ElementsAre(Pair(storage_key, MatchesUserConsent(specifics))));
}

TEST_F(ConsentSyncBridgeImplTest, ShouldNotDeleteConsentsWhenSyncIsDisabled) {
  WaitUntilModelReadyToSync(kDefaultGaiaId);
  UserConsentSpecifics user_consent_specifics(
      CreateSpecifics(/*client_consent_time_usec=*/2u));
  bridge()->RecordConsent(
      std::make_unique<UserConsentSpecifics>(user_consent_specifics));
  ASSERT_THAT(GetAllDataForDebugging(), SizeIs(1));

  bridge()->ApplyDisableSyncChanges(WriteBatch::CreateMetadataChangeList());
  // The bridge may asynchronously query the store to choose what to delete.
  base::RunLoop().RunUntilIdle();

  // User consent specific must be persisted when sync is disabled.
  EXPECT_THAT(GetAllDataForDebugging(),
              ElementsAre(Pair(_, MatchesUserConsent(user_consent_specifics))));
}

TEST_F(ConsentSyncBridgeImplTest,
       ShouldRecordMultipleConsentsAndDeduplicateByTime) {
  WaitUntilModelReadyToSync(kDefaultGaiaId);
  std::set<std::string> unique_storage_keys;
  EXPECT_CALL(*processor(), Put(_, _, _))
      .Times(4)
      .WillRepeatedly(
          [&unique_storage_keys](const std::string& storage_key,
                                 std::unique_ptr<EntityData> entity_data,
                                 MetadataChangeList* metadata_change_list) {
            unique_storage_keys.insert(storage_key);
          });

  bridge()->RecordConsent(SpecificsUniquePtr(/*client_consent_time_usec=*/1u));
  bridge()->RecordConsent(SpecificsUniquePtr(/*client_consent_time_usec=*/1u));
  bridge()->RecordConsent(SpecificsUniquePtr(/*client_consent_time_usec=*/1u));
  bridge()->RecordConsent(SpecificsUniquePtr(/*client_consent_time_usec=*/2u));

  EXPECT_EQ(2u, unique_storage_keys.size());
  EXPECT_THAT(GetAllDataForDebugging(), SizeIs(2));
}

TEST_F(ConsentSyncBridgeImplTest,
       ShouldDeleteCommitedConsentsAfterApplyIncrementalSyncChanges) {
  WaitUntilModelReadyToSync(kDefaultGaiaId);
  std::string first_storage_key;
  std::string second_storage_key;
  EXPECT_CALL(*processor(), Put(_, _, _))
      .WillOnce(WithArg<0>(SaveArg<0>(&first_storage_key)))
      .WillOnce(WithArg<0>(SaveArg<0>(&second_storage_key)));

  bridge()->RecordConsent(SpecificsUniquePtr(/*client_consent_time_usec=*/1u));
  bridge()->RecordConsent(SpecificsUniquePtr(/*client_consent_time_usec=*/2u));
  ASSERT_THAT(GetAllDataForDebugging(), SizeIs(2));

  syncer::EntityChangeList entity_change_list;
  entity_change_list.push_back(
      EntityChange::CreateDelete(first_storage_key, syncer::EntityData()));
  auto error_on_delete = bridge()->ApplyIncrementalSyncChanges(
      bridge()->CreateMetadataChangeList(), std::move(entity_change_list));
  EXPECT_FALSE(error_on_delete);
  EXPECT_THAT(GetAllDataForDebugging(), SizeIs(1));
  EXPECT_THAT(GetDataForCommit(first_storage_key), IsNull());
  EXPECT_THAT(GetDataForCommit(second_storage_key), NotNull());
}

TEST_F(ConsentSyncBridgeImplTest, ShouldRecordConsentsBeforeSyncEnabled) {
  WaitUntilModelReadyToSync(GaiaId());
  // The consent must be recorded, but not propagated anywhere while the
  // initialization is in progress and sync is still disabled.
  EXPECT_CALL(*processor(), Put(_, _, _)).Times(0);
  bridge()->RecordConsent(SpecificsUniquePtr(/*client_consent_time_usec=*/1u));
  // When sync is enabled, the consent should be reported to the processor.
  ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(true));
  ON_CALL(*processor(), TrackedGaiaId()).WillByDefault(Return(kDefaultGaiaId));
  EXPECT_CALL(*processor(), Put(_, _, _));
  bridge()->MergeFullSyncData(WriteBatch::CreateMetadataChangeList(),
                              EntityChangeList());
  base::RunLoop().RunUntilIdle();
}

// User consents should be buffered if the store and processor is not fully
// initialized.
TEST_F(ConsentSyncBridgeImplTest,
       ShouldSubmitBufferedConsentsWhenStoreIsInitialized) {
  EXPECT_CALL(*processor(), ModelReadyToSync(_)).Times(0);

  UserConsentSpecifics first_consent =
      CreateSpecifics(/*client_consent_time_usec=*/1u);
  first_consent.set_obfuscated_gaia_id(kDefaultGaiaId.ToString());
  UserConsentSpecifics second_consent =
      CreateSpecifics(/*client_consent_time_usec=*/2u);
  second_consent.set_obfuscated_gaia_id(kDefaultGaiaId.ToString());

  // Record consent before the store is initialized (ModelReadyToSync() not
  // called yet).
  bridge()->RecordConsent(
      std::make_unique<UserConsentSpecifics>(first_consent));

  // Wait until the store is initialized.
  EXPECT_CALL(*processor(), ModelReadyToSync(NotNull()));
  WaitUntilModelReadyToSync(kDefaultGaiaId);

  // Record consent after initializaiton is done.
  bridge()->RecordConsent(
      std::make_unique<UserConsentSpecifics>(second_consent));

  // Both the pre-initialization and post-initialization consents must be
  // handled after initialization as usual.
  EXPECT_THAT(GetAllDataForDebugging(),
              UnorderedElementsAre(Pair(GetStorageKey(first_consent),
                                        MatchesUserConsent(first_consent)),
                                   Pair(GetStorageKey(second_consent),
                                        MatchesUserConsent(second_consent))));
}

TEST_F(ConsentSyncBridgeImplTest,
       ShouldReportPreviouslyPersistedConsentsWhenSyncIsReenabled) {
  WaitUntilModelReadyToSync(kDefaultGaiaId);

  UserConsentSpecifics consent =
      CreateSpecifics(/*client_consent_time_usec=*/1u);
  consent.set_obfuscated_gaia_id(kDefaultGaiaId.ToString());

  bridge()->RecordConsent(std::make_unique<UserConsentSpecifics>(consent));

  // User disables sync, hovewer, the consent hasn't been submitted yet. It is
  // preserved to be submitted when sync is re-enabled.
  bridge()->ApplyDisableSyncChanges(WriteBatch::CreateMetadataChangeList());
  // The bridge may asynchronously query the store to choose what to delete.
  base::RunLoop().RunUntilIdle();

  ASSERT_THAT(GetAllDataForDebugging(), SizeIs(1));

  // Reenable sync.
  EXPECT_CALL(*processor(), Put(GetStorageKey(consent), _, _));
  ON_CALL(*processor(), TrackedGaiaId()).WillByDefault(Return(kDefaultGaiaId));
  bridge()->MergeFullSyncData(WriteBatch::CreateMetadataChangeList(),
                              EntityChangeList());

  // The bridge may asynchronously query the store to choose what to resubmit.
  base::RunLoop().RunUntilIdle();
}

TEST_F(ConsentSyncBridgeImplTest,
       ShouldReportPersistedConsentsOnStartupWithSyncAlreadyEnabled) {
  // Persist a consent while sync is enabled.
  WaitUntilModelReadyToSync(kDefaultGaiaId);
  UserConsentSpecifics consent =
      CreateSpecifics(/*client_consent_time_usec=*/1u);
  consent.set_obfuscated_gaia_id(kDefaultGaiaId.ToString());
  bridge()->RecordConsent(std::make_unique<UserConsentSpecifics>(consent));
  base::RunLoop().RunUntilIdle();
  ASSERT_THAT(GetAllDataForDebugging(), SizeIs(1));

  // Restart the bridge, mimic-ing a browser restart.
  EXPECT_CALL(*processor(), Put(GetStorageKey(consent), _, _));
  ResetBridge();

  // The bridge may asynchronously query the store to choose what to resubmit.
  base::RunLoop().RunUntilIdle();
}

TEST_F(ConsentSyncBridgeImplTest, ShouldReportPersistedConsentsOnSyncEnabled) {
  // Persist a consent before sync is enabled.
  WaitUntilModelReadyToSync(GaiaId());
  UserConsentSpecifics consent =
      CreateSpecifics(/*client_consent_time_usec=*/1u);
  consent.set_obfuscated_gaia_id(kDefaultGaiaId.ToString());
  bridge()->RecordConsent(std::make_unique<UserConsentSpecifics>(consent));
  base::RunLoop().RunUntilIdle();
  ASSERT_THAT(GetAllDataForDebugging(), SizeIs(1));

  // Restart the bridge, mimic-ing a browser restart. We expect no Put()
  // until sync is enabled.
  EXPECT_CALL(*processor(), Put(_, _, _)).Times(0);
  ResetBridge();
  WaitUntilModelReadyToSync(GaiaId());

  // Enable sync.
  EXPECT_CALL(*processor(), Put(GetStorageKey(consent), _, _));
  ON_CALL(*processor(), TrackedGaiaId()).WillByDefault(Return(kDefaultGaiaId));
  bridge()->MergeFullSyncData(WriteBatch::CreateMetadataChangeList(),
                              EntityChangeList());
  base::RunLoop().RunUntilIdle();
}

TEST_F(ConsentSyncBridgeImplTest,
       ShouldResubmitPersistedConsentOnlyIfSameAccount) {
  const GaiaId kFirstGaiaId("first_gaia_id");
  const GaiaId kSecondGaiaId("second_gaia_id");

  WaitUntilModelReadyToSync(kFirstGaiaId);
  UserConsentSpecifics user_consent_specifics(
      CreateSpecifics(/*client_consent_time_usec=*/2u));
  user_consent_specifics.set_obfuscated_gaia_id(kFirstGaiaId.ToString());
  bridge()->RecordConsent(
      std::make_unique<UserConsentSpecifics>(user_consent_specifics));
  ASSERT_THAT(GetAllDataForDebugging(), SizeIs(1));

  bridge()->ApplyDisableSyncChanges(WriteBatch::CreateMetadataChangeList());
  // The bridge may asynchronously query the store to choose what to delete.
  base::RunLoop().RunUntilIdle();

  ASSERT_THAT(GetAllDataForDebugging(),
              ElementsAre(Pair(_, MatchesUserConsent(user_consent_specifics))));

  // A new user signs in and enables sync.
  // The previous account consent should not be resubmited, because the new sync
  // account is different.
  EXPECT_CALL(*processor(), Put(_, _, _)).Times(0);
  ON_CALL(*processor(), TrackedGaiaId()).WillByDefault(Return(kSecondGaiaId));
  bridge()->MergeFullSyncData(WriteBatch::CreateMetadataChangeList(),
                              EntityChangeList());
  base::RunLoop().RunUntilIdle();

  bridge()->ApplyDisableSyncChanges(WriteBatch::CreateMetadataChangeList());
  base::RunLoop().RunUntilIdle();

  // This time their consent should be resubmitted, because it is for the same
  // account.
  EXPECT_CALL(*processor(), Put(GetStorageKey(user_consent_specifics), _, _));
  ON_CALL(*processor(), TrackedGaiaId()).WillByDefault(Return(kFirstGaiaId));
  bridge()->MergeFullSyncData(WriteBatch::CreateMetadataChangeList(),
                              EntityChangeList());
  // The bridge may asynchronously query the store to choose what to resubmit.
  base::RunLoop().RunUntilIdle();
}

}  // namespace

}  // namespace consent_auditor