blob: ddb107166d79a289050fd525854b22a7875a6a3c [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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_auditor_impl.h"
#include <map>
#include <memory>
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "components/consent_auditor/pref_names.h"
#include "components/prefs/testing_pref_service.h"
#include "components/sync/driver/sync_driver_switches.h"
#include "components/sync/model/fake_model_type_controller_delegate.h"
#include "components/sync/user_events/fake_user_event_service.h"
#include "components/variations/variations_params_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
using sync_pb::UserConsentSpecifics;
using sync_pb::UserEventSpecifics;
namespace consent_auditor {
namespace {
const char kLocalConsentDescriptionKey[] = "description";
const char kLocalConsentConfirmationKey[] = "confirmation";
const char kLocalConsentVersionKey[] = "version";
const char kLocalConsentLocaleKey[] = "locale";
// Fake product version for testing.
const char kCurrentAppVersion[] = "1.2.3.4";
const char kCurrentAppLocale[] = "en-US";
// Fake account ID for testing.
const char kAccountId[] = "testing_account_id";
// A helper function to load the |description|, |confirmation|, |version|,
// and |locale|, in that order, from a record for the |feature| in
// the |consents| dictionary.
void LoadEntriesFromLocalConsentRecord(const base::Value* consents,
const std::string& feature,
std::string* description,
std::string* confirmation,
std::string* version,
std::string* locale) {
SCOPED_TRACE(::testing::Message() << "|feature| = " << feature);
const base::Value* record =
consents->FindKeyOfType(feature, base::Value::Type::DICTIONARY);
ASSERT_TRUE(record);
SCOPED_TRACE(::testing::Message() << "|record| = " << record);
const base::Value* description_entry =
record->FindKey(kLocalConsentDescriptionKey);
const base::Value* confirmation_entry =
record->FindKey(kLocalConsentConfirmationKey);
const base::Value* version_entry = record->FindKey(kLocalConsentVersionKey);
const base::Value* locale_entry = record->FindKey(kLocalConsentLocaleKey);
ASSERT_TRUE(description_entry);
ASSERT_TRUE(confirmation_entry);
ASSERT_TRUE(version_entry);
ASSERT_TRUE(locale_entry);
*description = description_entry->GetString();
*confirmation = confirmation_entry->GetString();
*version = version_entry->GetString();
*locale = locale_entry->GetString();
}
class FakeConsentSyncBridge : public syncer::ConsentSyncBridge {
public:
~FakeConsentSyncBridge() override = default;
// ConsentSyncBridge implementation.
void RecordConsent(
std::unique_ptr<sync_pb::UserConsentSpecifics> specifics) override {
DCHECK(specifics);
recorded_user_consents_.push_back(*specifics);
}
base::WeakPtr<syncer::ModelTypeControllerDelegate>
GetControllerDelegateOnUIThread() override {
return delegate_;
}
// Fake methods.
void SetControllerDelegateOnUIThread(
base::WeakPtr<syncer::ModelTypeControllerDelegate> delegate) {
delegate_ = delegate;
}
const std::vector<UserConsentSpecifics>& GetRecordedUserConsents() const {
return recorded_user_consents_;
}
private:
base::WeakPtr<syncer::ModelTypeControllerDelegate> delegate_;
std::vector<UserConsentSpecifics> recorded_user_consents_;
};
} // namespace
class ConsentAuditorImplTest : public testing::Test {
public:
void SetUp() override {
pref_service_ = std::make_unique<TestingPrefServiceSimple>();
user_event_service_ = std::make_unique<syncer::FakeUserEventService>();
ConsentAuditorImpl::RegisterProfilePrefs(pref_service_->registry());
app_version_ = kCurrentAppVersion;
app_locale_ = kCurrentAppLocale;
BuildConsentAuditorImpl();
}
// TODO(vitaliii): Add a real builder class instead.
void BuildConsentAuditorImpl() {
consent_auditor_ = std::make_unique<ConsentAuditorImpl>(
pref_service_.get(), std::move(consent_sync_bridge_),
user_event_service_.get(), app_version_, app_locale_);
}
// These have no effect before |BuildConsentAuditorImpl|.
void SetAppVersion(const std::string& new_app_version) {
app_version_ = new_app_version;
}
void SetAppLocale(const std::string& new_app_locale) {
app_locale_ = new_app_locale;
}
void SetConsentSyncBridge(std::unique_ptr<syncer::ConsentSyncBridge> bridge) {
consent_sync_bridge_ = std::move(bridge);
}
void SetUserEventService(
std::unique_ptr<syncer::FakeUserEventService> service) {
user_event_service_ = std::move(service);
}
void SetIsSeparateConsentTypeEnabledFeature(bool new_value) {
// VariationParamsManager supports only one
// |SetVariationParamsWithFeatureAssociations| at a time, so we clear
// previous settings first to make this explicit.
params_manager_.ClearAllVariationParams();
if (new_value) {
params_manager_.SetVariationParamsWithFeatureAssociations(
/*trial_name=*/switches::kSyncUserConsentSeparateType.name,
/*param_values=*/std::map<std::string, std::string>(),
{switches::kSyncUserConsentSeparateType.name});
}
}
ConsentAuditorImpl* consent_auditor() { return consent_auditor_.get(); }
PrefService* pref_service() const { return pref_service_.get(); }
syncer::FakeUserEventService* user_event_service() {
return user_event_service_.get();
}
private:
std::unique_ptr<ConsentAuditorImpl> consent_auditor_;
std::unique_ptr<TestingPrefServiceSimple> pref_service_;
std::unique_ptr<syncer::FakeUserEventService> user_event_service_;
std::string app_version_;
std::string app_locale_;
std::unique_ptr<syncer::ConsentSyncBridge> consent_sync_bridge_;
variations::testing::VariationParamsManager params_manager_;
};
TEST_F(ConsentAuditorImplTest, LocalConsentPrefRepresentation) {
SetAppVersion(kCurrentAppVersion);
SetAppLocale(kCurrentAppLocale);
BuildConsentAuditorImpl();
// No consents are written at first.
EXPECT_FALSE(pref_service()->HasPrefPath(prefs::kLocalConsentsDictionary));
// Record a consent and check that it appears in the prefs.
const std::string kFeature1Description = "This will enable feature 1.";
const std::string kFeature1Confirmation = "OK.";
consent_auditor()->RecordLocalConsent("feature1", kFeature1Description,
kFeature1Confirmation);
ASSERT_TRUE(pref_service()->HasPrefPath(prefs::kLocalConsentsDictionary));
const base::DictionaryValue* consents =
pref_service()->GetDictionary(prefs::kLocalConsentsDictionary);
ASSERT_TRUE(consents);
std::string description;
std::string confirmation;
std::string version;
std::string locale;
LoadEntriesFromLocalConsentRecord(consents, "feature1", &description,
&confirmation, &version, &locale);
EXPECT_EQ(kFeature1Description, description);
EXPECT_EQ(kFeature1Confirmation, confirmation);
EXPECT_EQ(kCurrentAppVersion, version);
EXPECT_EQ(kCurrentAppLocale, locale);
// Do the same for another feature.
const std::string kFeature2Description = "Enable feature 2?";
const std::string kFeature2Confirmation = "Yes.";
consent_auditor()->RecordLocalConsent("feature2", kFeature2Description,
kFeature2Confirmation);
LoadEntriesFromLocalConsentRecord(consents, "feature2", &description,
&confirmation, &version, &locale);
EXPECT_EQ(kFeature2Description, description);
EXPECT_EQ(kFeature2Confirmation, confirmation);
EXPECT_EQ(kCurrentAppVersion, version);
EXPECT_EQ(kCurrentAppLocale, locale);
// They are two separate records; the latter did not overwrite the former.
EXPECT_EQ(2u, consents->size());
EXPECT_TRUE(
consents->FindKeyOfType("feature1", base::Value::Type::DICTIONARY));
// Overwrite an existing consent, this time use a different product version
// and a different locale.
const std::string kFeature2NewDescription = "Re-enable feature 2?";
const std::string kFeature2NewConfirmation = "Yes again.";
const std::string kFeature2NewAppVersion = "5.6.7.8";
const std::string kFeature2NewAppLocale = "de";
SetAppVersion(kFeature2NewAppVersion);
SetAppLocale(kFeature2NewAppLocale);
// We rebuild consent auditor to emulate restarting Chrome. This is the only
// way to change app version or app locale.
BuildConsentAuditorImpl();
consent_auditor()->RecordLocalConsent("feature2", kFeature2NewDescription,
kFeature2NewConfirmation);
LoadEntriesFromLocalConsentRecord(consents, "feature2", &description,
&confirmation, &version, &locale);
EXPECT_EQ(kFeature2NewDescription, description);
EXPECT_EQ(kFeature2NewConfirmation, confirmation);
EXPECT_EQ(kFeature2NewAppVersion, version);
EXPECT_EQ(kFeature2NewAppLocale, locale);
// We still have two records.
EXPECT_EQ(2u, consents->size());
}
TEST_F(ConsentAuditorImplTest, RecordingEnabled) {
SetIsSeparateConsentTypeEnabledFeature(false);
consent_auditor()->RecordGaiaConsent(kAccountId, Feature::CHROME_SYNC, {}, 0,
ConsentStatus::GIVEN);
auto& events = user_event_service()->GetRecordedUserEvents();
EXPECT_EQ(1U, events.size());
}
TEST_F(ConsentAuditorImplTest, RecordingDisabled) {
SetIsSeparateConsentTypeEnabledFeature(false);
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(switches::kSyncUserConsentEvents);
consent_auditor()->RecordGaiaConsent(kAccountId, Feature::CHROME_SYNC, {}, 0,
ConsentStatus::GIVEN);
auto& events = user_event_service()->GetRecordedUserEvents();
EXPECT_EQ(0U, events.size());
}
TEST_F(ConsentAuditorImplTest, RecordGaiaConsentAsUserEvent) {
SetIsSeparateConsentTypeEnabledFeature(false);
SetConsentSyncBridge(nullptr);
SetAppVersion(kCurrentAppVersion);
SetAppLocale(kCurrentAppLocale);
BuildConsentAuditorImpl();
std::vector<int> kDescriptionMessageIds = {12, 37, 42};
int kConfirmationMessageId = 47;
base::Time t1 = base::Time::Now();
consent_auditor()->RecordGaiaConsent(
kAccountId, Feature::CHROME_SYNC, kDescriptionMessageIds,
kConfirmationMessageId, ConsentStatus::GIVEN);
base::Time t2 = base::Time::Now();
auto& events = user_event_service()->GetRecordedUserEvents();
EXPECT_EQ(1U, events.size());
EXPECT_LE(t1.since_origin().InMicroseconds(), events[0].event_time_usec());
EXPECT_GE(t2.since_origin().InMicroseconds(), events[0].event_time_usec());
EXPECT_FALSE(events[0].has_navigation_id());
EXPECT_TRUE(events[0].has_user_consent());
auto& consent = events[0].user_consent();
EXPECT_EQ(kAccountId, consent.account_id());
EXPECT_EQ(UserEventSpecifics::UserConsent::CHROME_SYNC, consent.feature());
EXPECT_EQ(3, consent.description_grd_ids_size());
EXPECT_EQ(kDescriptionMessageIds[0], consent.description_grd_ids(0));
EXPECT_EQ(kDescriptionMessageIds[1], consent.description_grd_ids(1));
EXPECT_EQ(kDescriptionMessageIds[2], consent.description_grd_ids(2));
EXPECT_EQ(kConfirmationMessageId, consent.confirmation_grd_id());
EXPECT_EQ(kCurrentAppLocale, consent.locale());
}
TEST_F(ConsentAuditorImplTest, RecordGaiaConsentAsUserConsent) {
SetIsSeparateConsentTypeEnabledFeature(true);
auto wrapped_fake_bridge = std::make_unique<FakeConsentSyncBridge>();
FakeConsentSyncBridge* fake_bridge = wrapped_fake_bridge.get();
SetIsSeparateConsentTypeEnabledFeature(true);
SetConsentSyncBridge(std::move(wrapped_fake_bridge));
SetUserEventService(nullptr);
SetAppVersion(kCurrentAppVersion);
SetAppLocale(kCurrentAppLocale);
BuildConsentAuditorImpl();
std::vector<int> kDescriptionMessageIds = {12, 37, 42};
int kConfirmationMessageId = 47;
// TODO(vitaliii): Inject a fake clock instead.
base::Time time_before = base::Time::Now();
consent_auditor()->RecordGaiaConsent(
kAccountId, Feature::CHROME_SYNC, kDescriptionMessageIds,
kConfirmationMessageId, ConsentStatus::GIVEN);
base::Time time_after = base::Time::Now();
std::vector<UserConsentSpecifics> consents =
fake_bridge->GetRecordedUserConsents();
ASSERT_EQ(1U, consents.size());
UserConsentSpecifics consent = consents[0];
EXPECT_LE(time_before.since_origin().InMicroseconds(),
consent.client_consent_time_usec());
EXPECT_GE(time_after.since_origin().InMicroseconds(),
consent.client_consent_time_usec());
EXPECT_EQ(kAccountId, consent.account_id());
EXPECT_EQ(UserConsentSpecifics::CHROME_SYNC, consent.feature());
EXPECT_EQ(3, consent.description_grd_ids_size());
EXPECT_EQ(kDescriptionMessageIds[0], consent.description_grd_ids(0));
EXPECT_EQ(kDescriptionMessageIds[1], consent.description_grd_ids(1));
EXPECT_EQ(kDescriptionMessageIds[2], consent.description_grd_ids(2));
EXPECT_EQ(kConfirmationMessageId, consent.confirmation_grd_id());
EXPECT_EQ(kCurrentAppLocale, consent.locale());
}
TEST_F(ConsentAuditorImplTest, ShouldReturnNoSyncDelegateWhenNoBridge) {
SetIsSeparateConsentTypeEnabledFeature(false);
SetConsentSyncBridge(nullptr);
BuildConsentAuditorImpl();
// There is no bridge (i.e. separate sync type for consents is disabled),
// thus, there should be no delegate as well.
EXPECT_EQ(nullptr, consent_auditor()->GetControllerDelegateOnUIThread());
}
TEST_F(ConsentAuditorImplTest, ShouldReturnSyncDelegateWhenBridgePresent) {
SetIsSeparateConsentTypeEnabledFeature(true);
auto fake_bridge = std::make_unique<FakeConsentSyncBridge>();
syncer::FakeModelTypeControllerDelegate fake_delegate(
syncer::ModelType::USER_CONSENTS);
auto expected_delegate_ptr = fake_delegate.GetWeakPtr();
DCHECK(expected_delegate_ptr);
fake_bridge->SetControllerDelegateOnUIThread(expected_delegate_ptr);
SetConsentSyncBridge(std::move(fake_bridge));
SetUserEventService(nullptr);
BuildConsentAuditorImpl();
// There is a bridge (i.e. separate sync type for consents is enabled), thus,
// there should be a delegate as well.
EXPECT_EQ(expected_delegate_ptr.get(),
consent_auditor()->GetControllerDelegateOnUIThread().get());
}
} // namespace consent_auditor