fcm_invalidation_listener_unittest.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <map>
  5. #include <memory>
  6. #include <string>
  7. #include <vector>
  8. #include "base/memory/ptr_util.h"
  9. #include "base/memory/raw_ptr.h"
  10. #include "base/strings/string_number_conversions.h"
  11. #include "base/test/task_environment.h"
  12. #include "components/invalidation/impl/fcm_invalidation_listener.h"
  13. #include "components/invalidation/impl/per_user_topic_subscription_manager.h"
  14. #include "components/invalidation/public/invalidation_util.h"
  15. #include "components/invalidation/public/invalidator_state.h"
  16. #include "components/invalidation/public/topic_invalidation_map.h"
  17. #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
  18. #include "testing/gmock/include/gmock/gmock.h"
  19. #include "testing/gtest/include/gtest/gtest.h"
  20. namespace invalidation {
  21. namespace {
  22. const char kPayload1[] = "payload1";
  23. const char kPayload2[] = "payload2";
  24. const int64_t kVersion1 = 1LL;
  25. const int64_t kVersion2 = 2LL;
  26. class TestFCMSyncNetworkChannel : public FCMSyncNetworkChannel {
  27. public:
  28. void StartListening() override {}
  29. void StopListening() override {}
  30. void RequestDetailedStatus(
  31. const base::RepeatingCallback<void(base::Value::Dict)>& callback)
  32. override {}
  33. using FCMSyncNetworkChannel::DeliverIncomingMessage;
  34. using FCMSyncNetworkChannel::DeliverToken;
  35. using FCMSyncNetworkChannel::NotifyChannelStateChange;
  36. };
  37. // Fake delegate that keeps track of invalidation counts, payloads,
  38. // and state.
  39. class FakeDelegate : public FCMInvalidationListener::Delegate {
  40. public:
  41. explicit FakeDelegate(FCMInvalidationListener* listener)
  42. : state_(TRANSIENT_INVALIDATION_ERROR) {}
  43. ~FakeDelegate() override = default;
  44. size_t GetInvalidationCount(const Topic& topic) const {
  45. auto it = invalidations_.find(topic);
  46. if (it == invalidations_.end()) {
  47. return 0;
  48. } else {
  49. return it->second.size();
  50. }
  51. }
  52. int64_t GetVersion(const Topic& topic) const {
  53. auto it = invalidations_.find(topic);
  54. if (it == invalidations_.end()) {
  55. ADD_FAILURE() << "No invalidations for topic " << topic;
  56. return 0;
  57. } else {
  58. return it->second.back().version();
  59. }
  60. }
  61. std::string GetPayload(const Topic& topic) const {
  62. auto it = invalidations_.find(topic);
  63. if (it == invalidations_.end()) {
  64. ADD_FAILURE() << "No invalidations for topic " << topic;
  65. return "";
  66. } else {
  67. return it->second.back().payload();
  68. }
  69. }
  70. bool IsUnknownVersion(const Topic& topic) const {
  71. auto it = invalidations_.find(topic);
  72. if (it == invalidations_.end()) {
  73. ADD_FAILURE() << "No invalidations for topic " << topic;
  74. return false;
  75. } else {
  76. return it->second.back().is_unknown_version();
  77. }
  78. }
  79. InvalidatorState GetInvalidatorState() const { return state_; }
  80. // FCMInvalidationListener::Delegate implementation.
  81. void OnInvalidate(const TopicInvalidationMap& invalidation_map) override {
  82. TopicSet topics = invalidation_map.GetTopics();
  83. for (const auto& topic : topics) {
  84. const SingleTopicInvalidationSet& incoming =
  85. invalidation_map.ForTopic(topic);
  86. List& list = invalidations_[topic];
  87. list.insert(list.end(), incoming.begin(), incoming.end());
  88. }
  89. }
  90. void OnInvalidatorStateChange(InvalidatorState state) override {
  91. state_ = state;
  92. }
  93. private:
  94. typedef std::vector<Invalidation> List;
  95. typedef std::map<Topic, List> Map;
  96. typedef std::map<Topic, Invalidation> DropMap;
  97. Map invalidations_;
  98. InvalidatorState state_;
  99. DropMap dropped_invalidations_map_;
  100. };
  101. class MockSubscriptionManager : public PerUserTopicSubscriptionManager {
  102. public:
  103. MockSubscriptionManager()
  104. : PerUserTopicSubscriptionManager(nullptr /* identity_provider */,
  105. nullptr /* pref_service */,
  106. nullptr /* loader_factory */,
  107. "fake_sender_id",
  108. false) {
  109. ON_CALL(*this, LookupSubscribedPublicTopicByPrivateTopic)
  110. .WillByDefault(testing::ReturnArg<0>());
  111. }
  112. ~MockSubscriptionManager() override = default;
  113. MOCK_METHOD2(UpdateSubscribedTopics,
  114. void(const Topics& topics, const std::string& token));
  115. MOCK_METHOD0(Init, void());
  116. MOCK_CONST_METHOD1(LookupSubscribedPublicTopicByPrivateTopic,
  117. absl::optional<Topic>(const std::string& private_topic));
  118. };
  119. class FCMInvalidationListenerTest : public testing::Test {
  120. protected:
  121. FCMInvalidationListenerTest()
  122. : kBookmarksTopic_("BOOKMARK"),
  123. kPreferencesTopic_("PREFERENCE"),
  124. kExtensionsTopic_("EXTENSION"),
  125. kAppsTopic_("APP"),
  126. fcm_sync_network_channel_(new TestFCMSyncNetworkChannel()),
  127. listener_(base::WrapUnique(fcm_sync_network_channel_.get())),
  128. fake_delegate_(&listener_) {}
  129. void SetUp() override {
  130. StartListener();
  131. Topics initial_topics;
  132. initial_topics.emplace(kBookmarksTopic_, TopicMetadata{false});
  133. initial_topics.emplace(kPreferencesTopic_, TopicMetadata{true});
  134. listener_.UpdateInterestedTopics(initial_topics);
  135. }
  136. void TearDown() override {}
  137. void StartListener() {
  138. auto mock_subscription_manager =
  139. std::make_unique<MockSubscriptionManager>();
  140. subscription_manager_ = mock_subscription_manager.get();
  141. listener_.Start(&fake_delegate_, std::move(mock_subscription_manager));
  142. }
  143. size_t GetInvalidationCount(const Topic& topic) const {
  144. return fake_delegate_.GetInvalidationCount(topic);
  145. }
  146. std::string GetPayload(const Topic& topic) const {
  147. return fake_delegate_.GetPayload(topic);
  148. }
  149. int64_t GetVersion(const Topic& topic) const {
  150. return fake_delegate_.GetVersion(topic);
  151. }
  152. bool IsUnknownVersion(const Topic& topic) const {
  153. return fake_delegate_.IsUnknownVersion(topic);
  154. }
  155. InvalidatorState GetInvalidatorState() {
  156. return fake_delegate_.GetInvalidatorState();
  157. }
  158. void FireInvalidate(const Topic& topic,
  159. int64_t version,
  160. const std::string& payload) {
  161. fcm_sync_network_channel_->DeliverIncomingMessage(payload, topic, topic,
  162. version);
  163. }
  164. void EnableNotifications() {
  165. fcm_sync_network_channel_->NotifyChannelStateChange(
  166. FcmChannelState::ENABLED);
  167. fcm_sync_network_channel_->DeliverToken("token");
  168. }
  169. void DisableNotifications(FcmChannelState state) {
  170. fcm_sync_network_channel_->NotifyChannelStateChange(state);
  171. }
  172. const Topic kBookmarksTopic_;
  173. const Topic kPreferencesTopic_;
  174. const Topic kExtensionsTopic_;
  175. const Topic kAppsTopic_;
  176. private:
  177. base::test::SingleThreadTaskEnvironment task_environment_;
  178. data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
  179. raw_ptr<TestFCMSyncNetworkChannel> fcm_sync_network_channel_;
  180. raw_ptr<MockSubscriptionManager> subscription_manager_;
  181. protected:
  182. // Tests need to access these directly.
  183. FCMInvalidationListener listener_;
  184. private:
  185. FakeDelegate fake_delegate_;
  186. };
  187. // Invalidation tests.
  188. // Fire an invalidation without a payload. It should be processed,
  189. // the payload should remain empty, and the version should be updated.
  190. TEST_F(FCMInvalidationListenerTest, InvalidateNoPayload) {
  191. const Topic& topic = kBookmarksTopic_;
  192. FireInvalidate(topic, kVersion1, std::string());
  193. ASSERT_EQ(1U, GetInvalidationCount(topic));
  194. ASSERT_FALSE(IsUnknownVersion(topic));
  195. EXPECT_EQ(kVersion1, GetVersion(topic));
  196. EXPECT_EQ("", GetPayload(topic));
  197. }
  198. // Fire an invalidation with an empty payload. It should be
  199. // processed, the payload should remain empty, and the version should
  200. // be updated.
  201. TEST_F(FCMInvalidationListenerTest, InvalidateEmptyPayload) {
  202. const Topic& topic = kBookmarksTopic_;
  203. FireInvalidate(topic, kVersion1, std::string());
  204. ASSERT_EQ(1U, GetInvalidationCount(topic));
  205. ASSERT_FALSE(IsUnknownVersion(topic));
  206. EXPECT_EQ(kVersion1, GetVersion(topic));
  207. EXPECT_EQ("", GetPayload(topic));
  208. }
  209. // Fire an invalidation with a payload. It should be processed, and
  210. // both the payload and the version should be updated.
  211. TEST_F(FCMInvalidationListenerTest, InvalidateWithPayload) {
  212. const Topic& topic = kPreferencesTopic_;
  213. FireInvalidate(topic, kVersion1, kPayload1);
  214. ASSERT_EQ(1U, GetInvalidationCount(topic));
  215. ASSERT_FALSE(IsUnknownVersion(topic));
  216. EXPECT_EQ(kVersion1, GetVersion(topic));
  217. EXPECT_EQ(kPayload1, GetPayload(topic));
  218. }
  219. // Fire ten invalidations in a row. All should be received.
  220. TEST_F(FCMInvalidationListenerTest, ManyInvalidations_NoDrop) {
  221. const int kRepeatCount = 10;
  222. const Topic& topic = kPreferencesTopic_;
  223. int64_t initial_version = kVersion1;
  224. for (int64_t i = initial_version; i < initial_version + kRepeatCount; ++i) {
  225. FireInvalidate(topic, i, kPayload1);
  226. }
  227. ASSERT_EQ(static_cast<size_t>(kRepeatCount), GetInvalidationCount(topic));
  228. ASSERT_FALSE(IsUnknownVersion(topic));
  229. EXPECT_EQ(kPayload1, GetPayload(topic));
  230. EXPECT_EQ(initial_version + kRepeatCount - 1, GetVersion(topic));
  231. }
  232. // Fire an invalidation for an unregistered topic with a payload. It should
  233. // still be processed, and both the payload and the version should be updated.
  234. TEST_F(FCMInvalidationListenerTest, InvalidateBeforeRegistration_Simple) {
  235. const Topic kUnregisteredId = "unregistered";
  236. const Topic& topic = kUnregisteredId;
  237. Topics topics;
  238. topics.emplace(topic, TopicMetadata{false});
  239. EXPECT_EQ(0U, GetInvalidationCount(topic));
  240. FireInvalidate(topic, kVersion1, kPayload1);
  241. ASSERT_EQ(0U, GetInvalidationCount(topic));
  242. EnableNotifications();
  243. listener_.UpdateInterestedTopics(topics);
  244. ASSERT_EQ(1U, GetInvalidationCount(topic));
  245. ASSERT_FALSE(IsUnknownVersion(topic));
  246. EXPECT_EQ(kVersion1, GetVersion(topic));
  247. EXPECT_EQ(kPayload1, GetPayload(topic));
  248. }
  249. // Fire ten invalidations before an topics registers. Some invalidations will
  250. // be dropped an replaced with an unknown version invalidation.
  251. TEST_F(FCMInvalidationListenerTest, InvalidateBeforeRegistration_Drop) {
  252. const int kRepeatCount =
  253. UnackedInvalidationSet::kMaxBufferedInvalidations + 1;
  254. const Topic kUnregisteredId("unregistered");
  255. const Topic& topic = kUnregisteredId;
  256. Topics topics;
  257. topics.emplace(topic, TopicMetadata{false});
  258. EXPECT_EQ(0U, GetInvalidationCount(topic));
  259. int64_t initial_version = kVersion1;
  260. for (int64_t i = initial_version; i < initial_version + kRepeatCount; ++i) {
  261. FireInvalidate(topic, i, kPayload1);
  262. }
  263. EnableNotifications();
  264. listener_.UpdateInterestedTopics(topics);
  265. ASSERT_EQ(UnackedInvalidationSet::kMaxBufferedInvalidations,
  266. GetInvalidationCount(topic));
  267. }
  268. // Fire an invalidation, then fire another one with a lower version. Both
  269. // should be received.
  270. TEST_F(FCMInvalidationListenerTest, InvalidateVersion) {
  271. const Topic& topic = kPreferencesTopic_;
  272. FireInvalidate(topic, kVersion2, kPayload2);
  273. ASSERT_EQ(1U, GetInvalidationCount(topic));
  274. ASSERT_FALSE(IsUnknownVersion(topic));
  275. EXPECT_EQ(kVersion2, GetVersion(topic));
  276. EXPECT_EQ(kPayload2, GetPayload(topic));
  277. FireInvalidate(topic, kVersion1, kPayload1);
  278. ASSERT_EQ(2U, GetInvalidationCount(topic));
  279. ASSERT_FALSE(IsUnknownVersion(topic));
  280. EXPECT_EQ(kVersion1, GetVersion(topic));
  281. EXPECT_EQ(kPayload1, GetPayload(topic));
  282. }
  283. // Test a simple scenario for multiple IDs.
  284. TEST_F(FCMInvalidationListenerTest, InvalidateMultipleIds) {
  285. FireInvalidate(kBookmarksTopic_, 3, std::string());
  286. ASSERT_EQ(1U, GetInvalidationCount(kBookmarksTopic_));
  287. ASSERT_FALSE(IsUnknownVersion(kBookmarksTopic_));
  288. EXPECT_EQ(3, GetVersion(kBookmarksTopic_));
  289. EXPECT_EQ("", GetPayload(kBookmarksTopic_));
  290. // kExtensionId is not registered, so the invalidation should not get through.
  291. FireInvalidate(kExtensionsTopic_, 2, std::string());
  292. ASSERT_EQ(0U, GetInvalidationCount(kExtensionsTopic_));
  293. }
  294. // Disable notifications, then enable them.
  295. TEST_F(FCMInvalidationListenerTest, ReEnableNotifications) {
  296. DisableNotifications(FcmChannelState::NO_INSTANCE_ID_TOKEN);
  297. EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());
  298. EnableNotifications();
  299. EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
  300. }
  301. } // namespace
  302. } // namespace invalidation