| // Copyright 2020 The Fuchsia 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 "src/cobalt/bin/app/user_consent_watcher.h" |
| |
| #include <fuchsia/settings/cpp/fidl.h> |
| #include <lib/inspect/cpp/inspect.h> |
| #include <lib/inspect/testing/cpp/inspect.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include "sdk/lib/sys/cpp/testing/service_directory_provider.h" |
| #include "src/lib/testing/loop_fixture/test_loop_fixture.h" |
| |
| namespace cobalt { |
| |
| using fuchsia::settings::Error; |
| using fuchsia::settings::PrivacySettings; |
| using inspect::testing::ChildrenMatch; |
| using inspect::testing::IntIs; |
| using inspect::testing::NameMatches; |
| using inspect::testing::NodeMatches; |
| using inspect::testing::PropertyList; |
| using inspect::testing::StringIs; |
| using ::testing::UnorderedElementsAre; |
| |
| PrivacySettings MakePrivacySettings(const std::optional<bool> user_data_sharing_consent) { |
| PrivacySettings settings; |
| if (user_data_sharing_consent.has_value()) { |
| settings.set_user_data_sharing_consent(user_data_sharing_consent.value()); |
| } |
| return settings; |
| } |
| |
| class FakePrivacy : public fuchsia::settings::Privacy { |
| public: |
| fidl::InterfaceRequestHandler<fuchsia::settings::Privacy> GetHandler() { |
| return [this](fidl::InterfaceRequest<fuchsia::settings::Privacy> request) { |
| first_call_ = true; |
| binding_ = |
| std::make_unique<fidl::Binding<fuchsia::settings::Privacy>>(this, std::move(request)); |
| }; |
| } |
| |
| void Watch(WatchCallback callback) override { |
| if (!first_call_) { |
| watchers_.push_back(std::move(callback)); |
| return; |
| } |
| |
| fuchsia::settings::PrivacySettings settings; |
| settings_.Clone(&settings); |
| callback(std::move(settings)); |
| first_call_ = false; |
| } |
| |
| void Set(fuchsia::settings::PrivacySettings settings, SetCallback callback) override { |
| settings_ = std::move(settings); |
| callback(fpromise::ok()); |
| |
| NotifyWatchers(); |
| } |
| |
| protected: |
| void CloseConnection() { |
| if (binding_) { |
| binding_->Unbind(); |
| } |
| } |
| |
| private: |
| void NotifyWatchers() { |
| for (const auto& watcher : watchers_) { |
| fuchsia::settings::PrivacySettings settings; |
| settings_.Clone(&settings); |
| watcher(std::move(settings)); |
| } |
| watchers_.clear(); |
| } |
| |
| std::unique_ptr<fidl::Binding<fuchsia::settings::Privacy>> binding_; |
| fuchsia::settings::PrivacySettings settings_; |
| bool first_call_ = true; |
| std::vector<WatchCallback> watchers_; |
| }; |
| |
| class FakePrivacyClosesConnection : public FakePrivacy { |
| public: |
| void Watch(WatchCallback callback) { CloseConnection(); } |
| }; |
| |
| class FakePrivacyClosesConnectionOnce : public FakePrivacy { |
| public: |
| void Watch(WatchCallback callback) { |
| if (!has_closed_once_) { |
| has_closed_once_ = true; |
| CloseConnection(); |
| return; |
| } |
| |
| FakePrivacy::Watch(std::move(callback)); |
| } |
| |
| private: |
| bool has_closed_once_ = false; |
| }; |
| |
| class UserConsentWatcherTest : public gtest::TestLoopFixture, |
| public testing::WithParamInterface<std::optional<bool>> { |
| public: |
| UserConsentWatcherTest() |
| : gtest::TestLoopFixture(), |
| service_directory_provider_(dispatcher()), |
| watcher_(dispatcher(), inspector_.GetRoot().CreateChild("user_consent_watcher"), |
| service_directory_provider_.service_directory(), |
| [this](const CobaltServiceInterface::DataCollectionPolicy& policy) { |
| Callback(policy); |
| }) {} |
| |
| void Callback(const CobaltServiceInterface::DataCollectionPolicy& policy) { |
| current_policy_ = policy; |
| } |
| |
| void SetPrivacyProvider(std::unique_ptr<FakePrivacy> privacy_provider) { |
| privacy_provider_ = std::move(privacy_provider); |
| ASSERT_EQ(service_directory_provider_.AddService(privacy_provider_->GetHandler()), ZX_OK); |
| } |
| |
| void CreatePrivacyProvider() { SetPrivacyProvider(std::make_unique<FakePrivacy>()); } |
| |
| void SetPrivacySetting(const std::optional<bool>& setting) { |
| fpromise::result<void, Error> set_result; |
| privacy_provider_->Set( |
| MakePrivacySettings(setting), |
| [&set_result](fpromise::result<void, Error> result) { set_result = std::move(result); }); |
| EXPECT_TRUE(set_result.is_ok()); |
| } |
| |
| protected: |
| sys::testing::ServiceDirectoryProvider service_directory_provider_; |
| inspect::Inspector inspector_; |
| UserConsentWatcher watcher_; |
| CobaltServiceInterface::DataCollectionPolicy current_policy_; |
| PrivacySettings last_settings_; |
| std::unique_ptr<FakePrivacy> privacy_provider_; |
| }; |
| |
| std::string PrettyPrintConsentStates(const testing::TestParamInfo<std::optional<bool>>& info) { |
| if (info.param.has_value()) { |
| if (info.param.value()) { |
| return "UserConsented"; |
| } else { |
| return "UserDidNotConsent"; |
| } |
| } else { |
| return "NoConsentState"; |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(WithVariousConsentStates, UserConsentWatcherTest, |
| ::testing::Values(true, false, std::nullopt), &PrettyPrintConsentStates); |
| |
| TEST_F(UserConsentWatcherTest, CallbackCalledIfServerNotAvailable) { |
| watcher_.StartWatching(); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(watcher_.IsConnected()); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD); |
| EXPECT_TRUE(watcher_.privacy_settings().IsEmpty()); |
| } |
| |
| TEST_P(UserConsentWatcherTest, ConsentResetIfServerClosesConnection) { |
| SetPrivacyProvider(std::make_unique<FakePrivacyClosesConnection>()); |
| SetPrivacySetting(GetParam()); |
| |
| watcher_.StartWatching(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(watcher_.IsConnected()); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD); |
| EXPECT_TRUE(watcher_.privacy_settings().IsEmpty()); |
| EXPECT_THAT( |
| inspect::ReadFromVmo(inspector_.DuplicateVmo()).take_value(), |
| AllOf(NodeMatches(NameMatches("root")), |
| ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf( |
| NameMatches("user_consent_watcher"), |
| PropertyList(UnorderedElementsAre( |
| IntIs("successful_watches", 0), IntIs("watch_errors", 1), |
| IntIs("data_collection_policy", |
| static_cast<int>( |
| CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD)))))))))); |
| } |
| |
| TEST_P(UserConsentWatcherTest, WatcherReconnectsIfServerClosesConnection) { |
| SetPrivacyProvider(std::make_unique<FakePrivacyClosesConnectionOnce>()); |
| SetPrivacySetting(GetParam()); |
| |
| watcher_.StartWatching(); |
| RunLoopFor(zx::msec(200)); |
| |
| EXPECT_TRUE(watcher_.IsConnected()); |
| if (GetParam().has_value()) { |
| EXPECT_FALSE(watcher_.privacy_settings().IsEmpty()); |
| if (GetParam().value()) { |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::COLLECT_AND_UPLOAD); |
| } else { |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::DO_NOT_COLLECT); |
| } |
| } else { |
| EXPECT_TRUE(watcher_.privacy_settings().IsEmpty()); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD); |
| } |
| } |
| |
| TEST_F(UserConsentWatcherTest, ConsentStateDefaultsToDoNotUpload) { |
| CreatePrivacyProvider(); |
| |
| watcher_.StartWatching(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(watcher_.IsConnected()); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD); |
| EXPECT_TRUE(watcher_.privacy_settings().IsEmpty()); |
| EXPECT_THAT( |
| inspect::ReadFromVmo(inspector_.DuplicateVmo()).take_value(), |
| AllOf(NodeMatches(NameMatches("root")), |
| ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf( |
| NameMatches("user_consent_watcher"), |
| PropertyList(UnorderedElementsAre( |
| IntIs("successful_watches", 1), IntIs("watch_errors", 0), |
| IntIs("data_collection_policy", |
| static_cast<int>( |
| CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD)))))))))); |
| } |
| |
| TEST_P(UserConsentWatcherTest, SettingWorks) { |
| CreatePrivacyProvider(); |
| SetPrivacySetting(GetParam()); |
| |
| watcher_.StartWatching(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(watcher_.IsConnected()); |
| if (GetParam().has_value()) { |
| EXPECT_FALSE(watcher_.privacy_settings().IsEmpty()); |
| } |
| } |
| |
| TEST_P(UserConsentWatcherTest, ContinuesWatching) { |
| CreatePrivacyProvider(); |
| SetPrivacySetting(GetParam()); |
| |
| watcher_.StartWatching(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(watcher_.IsConnected()); |
| if (GetParam().has_value()) { |
| EXPECT_FALSE(watcher_.privacy_settings().IsEmpty()); |
| EXPECT_EQ(watcher_.privacy_settings().user_data_sharing_consent(), GetParam().value()); |
| } |
| |
| SetPrivacySetting(false); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watcher_.privacy_settings().user_data_sharing_consent(), false); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::DO_NOT_COLLECT); |
| EXPECT_THAT( |
| inspect::ReadFromVmo(inspector_.DuplicateVmo()).take_value(), |
| AllOf( |
| NodeMatches(NameMatches("root")), |
| ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf( |
| NameMatches("user_consent_watcher"), |
| PropertyList(UnorderedElementsAre( |
| IntIs("successful_watches", 2), IntIs("watch_errors", 0), |
| IntIs("data_collection_policy", |
| static_cast<int>( |
| CobaltServiceInterface::DataCollectionPolicy::DO_NOT_COLLECT)))))))))); |
| |
| SetPrivacySetting(true); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watcher_.privacy_settings().user_data_sharing_consent(), true); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::COLLECT_AND_UPLOAD); |
| EXPECT_THAT(inspect::ReadFromVmo(inspector_.DuplicateVmo()).take_value(), |
| AllOf(NodeMatches(NameMatches("root")), |
| ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf( |
| NameMatches("user_consent_watcher"), |
| PropertyList(UnorderedElementsAre( |
| IntIs("successful_watches", 3), IntIs("watch_errors", 0), |
| IntIs("data_collection_policy", |
| static_cast<int>(CobaltServiceInterface::DataCollectionPolicy:: |
| COLLECT_AND_UPLOAD)))))))))); |
| |
| SetPrivacySetting(std::nullopt); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watcher_.privacy_settings().has_user_data_sharing_consent(), false); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD); |
| |
| SetPrivacySetting(GetParam()); |
| RunLoopUntilIdle(); |
| if (GetParam().has_value()) { |
| EXPECT_FALSE(watcher_.privacy_settings().IsEmpty()); |
| EXPECT_EQ(watcher_.privacy_settings().user_data_sharing_consent(), GetParam().value()); |
| } |
| |
| SetPrivacySetting(false); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watcher_.privacy_settings().user_data_sharing_consent(), false); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::DO_NOT_COLLECT); |
| |
| SetPrivacySetting(true); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watcher_.privacy_settings().user_data_sharing_consent(), true); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::COLLECT_AND_UPLOAD); |
| EXPECT_THAT(inspect::ReadFromVmo(inspector_.DuplicateVmo()).take_value(), |
| AllOf(NodeMatches(NameMatches("root")), |
| ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf( |
| NameMatches("user_consent_watcher"), |
| PropertyList(UnorderedElementsAre( |
| IntIs("successful_watches", 7), IntIs("watch_errors", 0), |
| IntIs("data_collection_policy", |
| static_cast<int>(CobaltServiceInterface::DataCollectionPolicy:: |
| COLLECT_AND_UPLOAD)))))))))); |
| |
| SetPrivacySetting(std::nullopt); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watcher_.privacy_settings().has_user_data_sharing_consent(), false); |
| EXPECT_EQ(current_policy_, CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD); |
| |
| SetPrivacySetting(GetParam()); |
| RunLoopUntilIdle(); |
| if (GetParam().has_value()) { |
| EXPECT_FALSE(watcher_.privacy_settings().IsEmpty()); |
| EXPECT_EQ(watcher_.privacy_settings().user_data_sharing_consent(), GetParam().value()); |
| } |
| } |
| |
| } // namespace cobalt |