|  | // 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/invalidation/impl/per_user_topic_registration_manager.h" | 
|  |  | 
|  | #include "base/json/json_string_value_serializer.h" | 
|  | #include "base/json/json_writer.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/test/scoped_task_environment.h" | 
|  | #include "base/values.h" | 
|  | #include "components/invalidation/impl/json_unsafe_parser.h" | 
|  | #include "components/invalidation/impl/profile_identity_provider.h" | 
|  | #include "components/invalidation/public/invalidation_util.h" | 
|  | #include "components/prefs/testing_pref_service.h" | 
|  | #include "net/http/http_status_code.h" | 
|  | #include "services/identity/public/cpp/identity_test_environment.h" | 
|  | #include "services/network/test/test_url_loader_factory.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace syncer { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | size_t kInvalidationObjectIdsCount = 5; | 
|  |  | 
|  | const char kInvalidationRegistrationScope[] = | 
|  | "https://firebaseperusertopics-pa.googleapis.com"; | 
|  |  | 
|  | const char kProjectId[] = "8181035976"; | 
|  |  | 
|  | const char kTypeRegisteredForInvalidation[] = | 
|  | "invalidation.registered_for_invalidation"; | 
|  |  | 
|  | const char kActiveRegistrationToken[] = | 
|  | "invalidation.active_registration_token"; | 
|  |  | 
|  | const char kFakeInstanceIdToken[] = "fake_instance_id_token"; | 
|  |  | 
|  | std::string IndexToName(size_t index) { | 
|  | char name[2] = "a"; | 
|  | name[0] += static_cast<char>(index); | 
|  | return name; | 
|  | } | 
|  |  | 
|  | TopicSet GetSequenceOfTopicsStartingAt(size_t start, size_t count) { | 
|  | TopicSet ids; | 
|  | for (size_t i = start; i < start + count; ++i) | 
|  | ids.insert(IndexToName(i)); | 
|  | return ids; | 
|  | } | 
|  |  | 
|  | TopicSet GetSequenceOfTopics(size_t count) { | 
|  | return GetSequenceOfTopicsStartingAt(0, count); | 
|  | } | 
|  |  | 
|  | network::ResourceResponseHead CreateHeadersForTest(int responce_code) { | 
|  | network::ResourceResponseHead head; | 
|  | head.headers = new net::HttpResponseHeaders(base::StringPrintf( | 
|  | "HTTP/1.1 %d OK\nContent-type: text/html\n\n", responce_code)); | 
|  | head.mime_type = "text/html"; | 
|  | return head; | 
|  | } | 
|  |  | 
|  | GURL FullSubscriptionUrl(const std::string& token) { | 
|  | return GURL(base::StringPrintf( | 
|  | "%s/v1/perusertopics/%s/rel/topics/?subscriber_token=%s", | 
|  | kInvalidationRegistrationScope, kProjectId, token.c_str())); | 
|  | } | 
|  |  | 
|  | GURL FullUnSubscriptionUrlForTopic(const std::string& topic) { | 
|  | return GURL(base::StringPrintf( | 
|  | "%s/v1/perusertopics/%s/rel/topics/%s?subscriber_token=%s", | 
|  | kInvalidationRegistrationScope, kProjectId, topic.c_str(), | 
|  | kFakeInstanceIdToken)); | 
|  | } | 
|  |  | 
|  | network::URLLoaderCompletionStatus CreateStatusForTest( | 
|  | int status, | 
|  | const std::string& response_body) { | 
|  | network::URLLoaderCompletionStatus response_status(status); | 
|  | response_status.decoded_body_length = response_body.size(); | 
|  | return response_status; | 
|  | } | 
|  |  | 
|  | };  // namespace | 
|  |  | 
|  | class PerUserTopicRegistrationManagerTest : public testing::Test { | 
|  | protected: | 
|  | PerUserTopicRegistrationManagerTest() {} | 
|  |  | 
|  | ~PerUserTopicRegistrationManagerTest() override {} | 
|  |  | 
|  | void SetUp() override { | 
|  | PerUserTopicRegistrationManager::RegisterProfilePrefs( | 
|  | pref_service_.registry()); | 
|  | AccountInfo account = | 
|  | identity_test_env_.MakePrimaryAccountAvailable("example@gmail.com"); | 
|  | identity_test_env_.SetAutomaticIssueOfAccessTokens(true); | 
|  | identity_provider_ = | 
|  | std::make_unique<invalidation::ProfileIdentityProvider>( | 
|  | identity_test_env_.identity_manager()); | 
|  | identity_provider_->SetActiveAccountId(account.account_id); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<PerUserTopicRegistrationManager> BuildRegistrationManager() { | 
|  | auto reg_manager = std::make_unique<PerUserTopicRegistrationManager>( | 
|  | identity_provider_.get(), &pref_service_, url_loader_factory(), | 
|  | base::BindRepeating(&syncer::JsonUnsafeParser::Parse)); | 
|  | reg_manager->Init(); | 
|  | return reg_manager; | 
|  | } | 
|  |  | 
|  | network::TestURLLoaderFactory* url_loader_factory() { | 
|  | return &url_loader_factory_; | 
|  | } | 
|  |  | 
|  | TestingPrefServiceSimple* pref_service() { return &pref_service_; } | 
|  |  | 
|  | void AddCorrectSubscriptionResponce( | 
|  | const std::string& private_topic = std::string(), | 
|  | const std::string& token = kFakeInstanceIdToken) { | 
|  | std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue()); | 
|  | value->SetString("privateTopicName", | 
|  | private_topic.empty() ? "test-pr" : private_topic.c_str()); | 
|  | std::string serialized_response; | 
|  | JSONStringValueSerializer serializer(&serialized_response); | 
|  | serializer.Serialize(*value); | 
|  | url_loader_factory()->AddResponse( | 
|  | FullSubscriptionUrl(token), CreateHeadersForTest(net::HTTP_OK), | 
|  | serialized_response, CreateStatusForTest(net::OK, serialized_response)); | 
|  | } | 
|  |  | 
|  | void AddCorrectUnSubscriptionResponceForTopic(const std::string& topic) { | 
|  | url_loader_factory()->AddResponse( | 
|  | FullUnSubscriptionUrlForTopic(topic), | 
|  | CreateHeadersForTest(net::HTTP_OK), std::string() /* response_body */, | 
|  | CreateStatusForTest(net::OK, std::string() /* response_body */)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | base::test::ScopedTaskEnvironment task_environment_; | 
|  | network::TestURLLoaderFactory url_loader_factory_; | 
|  | TestingPrefServiceSimple pref_service_; | 
|  |  | 
|  | identity::IdentityTestEnvironment identity_test_env_; | 
|  | std::unique_ptr<invalidation::ProfileIdentityProvider> identity_provider_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(PerUserTopicRegistrationManagerTest); | 
|  | }; | 
|  |  | 
|  | TEST_F(PerUserTopicRegistrationManagerTest, | 
|  | EmptyPrivateTopicShouldNotUpdateRegisteredTopics) { | 
|  | TopicSet ids = GetSequenceOfTopics(kInvalidationObjectIdsCount); | 
|  |  | 
|  | auto per_user_topic_registration_manager = BuildRegistrationManager(); | 
|  |  | 
|  | EXPECT_TRUE(per_user_topic_registration_manager->GetRegisteredIds().empty()); | 
|  |  | 
|  | // Empty response body should result in no succesfull registrations. | 
|  | std::string response_body; | 
|  |  | 
|  | url_loader_factory()->AddResponse( | 
|  | FullSubscriptionUrl(kFakeInstanceIdToken), | 
|  | CreateHeadersForTest(net::HTTP_OK), response_body, | 
|  | CreateStatusForTest(net::OK, response_body)); | 
|  |  | 
|  | per_user_topic_registration_manager->UpdateRegisteredTopics( | 
|  | ids, kFakeInstanceIdToken); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  |  | 
|  | // The response didn't contain non-empty topic name. So nothing was | 
|  | // registered. | 
|  | EXPECT_TRUE(per_user_topic_registration_manager->GetRegisteredIds().empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(PerUserTopicRegistrationManagerTest, ShouldUpdateRegisteredTopics) { | 
|  | TopicSet ids = GetSequenceOfTopics(kInvalidationObjectIdsCount); | 
|  |  | 
|  | auto per_user_topic_registration_manager = BuildRegistrationManager(); | 
|  |  | 
|  | EXPECT_TRUE(per_user_topic_registration_manager->GetRegisteredIds().empty()); | 
|  |  | 
|  | AddCorrectSubscriptionResponce(); | 
|  |  | 
|  | per_user_topic_registration_manager->UpdateRegisteredTopics( | 
|  | ids, kFakeInstanceIdToken); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  |  | 
|  | EXPECT_EQ(ids, per_user_topic_registration_manager->GetRegisteredIds()); | 
|  |  | 
|  | for (const auto& id : ids) { | 
|  | const base::DictionaryValue* topics = | 
|  | pref_service()->GetDictionary(kTypeRegisteredForInvalidation); | 
|  | const base::Value* private_topic_value = | 
|  | topics->FindKeyOfType(id, base::Value::Type::STRING); | 
|  | ASSERT_NE(private_topic_value, nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(PerUserTopicRegistrationManagerTest, | 
|  | ShouldDisableIdsAndDeleteFromPrefs) { | 
|  | TopicSet ids = GetSequenceOfTopics(kInvalidationObjectIdsCount); | 
|  |  | 
|  | AddCorrectSubscriptionResponce(); | 
|  |  | 
|  | auto per_user_topic_registration_manager = BuildRegistrationManager(); | 
|  | EXPECT_TRUE(per_user_topic_registration_manager->GetRegisteredIds().empty()); | 
|  |  | 
|  | per_user_topic_registration_manager->UpdateRegisteredTopics( | 
|  | ids, kFakeInstanceIdToken); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(ids, per_user_topic_registration_manager->GetRegisteredIds()); | 
|  |  | 
|  | // Disable some ids. | 
|  | TopicSet disabled_ids = GetSequenceOfTopics(3); | 
|  | TopicSet enabled_ids = | 
|  | GetSequenceOfTopicsStartingAt(3, kInvalidationObjectIdsCount - 3); | 
|  | for (const auto& id : disabled_ids) | 
|  | AddCorrectUnSubscriptionResponceForTopic(id); | 
|  |  | 
|  | per_user_topic_registration_manager->UpdateRegisteredTopics( | 
|  | enabled_ids, kFakeInstanceIdToken); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  |  | 
|  | // ids were disabled, check that they're not in the prefs. | 
|  | for (const auto& id : disabled_ids) { | 
|  | const base::DictionaryValue* topics = | 
|  | pref_service()->GetDictionary(kTypeRegisteredForInvalidation); | 
|  | const base::Value* private_topic_value = topics->FindKey(id); | 
|  | ASSERT_EQ(private_topic_value, nullptr); | 
|  | } | 
|  |  | 
|  | // Check that enable ids are still in the prefs. | 
|  | for (const auto& id : enabled_ids) { | 
|  | const base::DictionaryValue* topics = | 
|  | pref_service()->GetDictionary(kTypeRegisteredForInvalidation); | 
|  | const base::Value* private_topic_value = | 
|  | topics->FindKeyOfType(id, base::Value::Type::STRING); | 
|  | ASSERT_NE(private_topic_value, nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(PerUserTopicRegistrationManagerTest, | 
|  | ShouldDropSavedTopicsOnTokenChange) { | 
|  | TopicSet ids = GetSequenceOfTopics(kInvalidationObjectIdsCount); | 
|  |  | 
|  | auto per_user_topic_registration_manager = BuildRegistrationManager(); | 
|  |  | 
|  | EXPECT_TRUE(per_user_topic_registration_manager->GetRegisteredIds().empty()); | 
|  |  | 
|  | AddCorrectSubscriptionResponce("old-token-topic"); | 
|  |  | 
|  | per_user_topic_registration_manager->UpdateRegisteredTopics( | 
|  | ids, kFakeInstanceIdToken); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  |  | 
|  | EXPECT_EQ(ids, per_user_topic_registration_manager->GetRegisteredIds()); | 
|  |  | 
|  | for (const auto& id : ids) { | 
|  | const base::DictionaryValue* topics = | 
|  | pref_service()->GetDictionary(kTypeRegisteredForInvalidation); | 
|  | const base::Value* private_topic_value = | 
|  | topics->FindKeyOfType(id, base::Value::Type::STRING); | 
|  | ASSERT_NE(private_topic_value, nullptr); | 
|  | std::string private_topic; | 
|  | private_topic_value->GetAsString(&private_topic); | 
|  | EXPECT_EQ(private_topic, "old-token-topic"); | 
|  | } | 
|  |  | 
|  | EXPECT_EQ(kFakeInstanceIdToken, | 
|  | pref_service()->GetString(kActiveRegistrationToken)); | 
|  |  | 
|  | std::string token = "new-fake-token"; | 
|  | AddCorrectSubscriptionResponce("new-token-topic", token); | 
|  |  | 
|  | per_user_topic_registration_manager->UpdateRegisteredTopics(ids, token); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  |  | 
|  | EXPECT_EQ(token, pref_service()->GetString(kActiveRegistrationToken)); | 
|  | EXPECT_EQ(ids, per_user_topic_registration_manager->GetRegisteredIds()); | 
|  |  | 
|  | for (const auto& id : ids) { | 
|  | const base::DictionaryValue* topics = | 
|  | pref_service()->GetDictionary(kTypeRegisteredForInvalidation); | 
|  | const base::Value* private_topic_value = | 
|  | topics->FindKeyOfType(id, base::Value::Type::STRING); | 
|  | ASSERT_NE(private_topic_value, nullptr); | 
|  | std::string private_topic; | 
|  | private_topic_value->GetAsString(&private_topic); | 
|  | EXPECT_EQ(private_topic, "new-token-topic"); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace syncer |