1 // Copyright 2014 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 
5 // This class defines tests that implementations of InvalidationService should
6 // pass in order to be conformant.  Here's how you use it to test your
7 // implementation.
8 //
9 // Say your class is called MyInvalidationService.  Then you need to define a
10 // class called MyInvalidationServiceTestDelegate in
11 // my_invalidation_frontend_unittest.cc like this:
12 //
13 //   class MyInvalidationServiceTestDelegate {
14 //    public:
15 //     MyInvalidationServiceTestDelegate() ...
16 //
17 //     ~MyInvalidationServiceTestDelegate() {
18 //       // DestroyInvalidator() may not be explicitly called by tests.
19 //       DestroyInvalidator();
20 //     }
21 //
22 //     // Create the InvalidationService implementation with the given params.
23 //     void CreateInvalidationService() {
24 //       ...
25 //     }
26 //
27 //     // Should return the InvalidationService implementation.  Only called
28 //     // after CreateInvalidator and before DestroyInvalidator.
29 //     MyInvalidationService* GetInvalidationService() {
30 //       ...
31 //     }
32 //
33 //     // Destroy the InvalidationService implementation.
34 //     void DestroyInvalidationService() {
35 //       ...
36 //     }
37 //
38 //     // The Trigger* functions below should block until the effects of
39 //     // the call are visible on the current thread.
40 //
41 //     // Should cause OnInvalidatorStateChange() to be called on all
42 //     // observers of the InvalidationService implementation with the given
43 //     // parameters.
44 //     void TriggerOnInvalidatorStateChange(InvalidatorState state) {
45 //       ...
46 //     }
47 //
48 //     // Should cause OnIncomingInvalidation() to be called on all
49 //     // observers of the InvalidationService implementation with the given
50 //     // parameters.
51 //     void TriggerOnIncomingInvalidation(
52 //         const TopicInvalidationMap& invalidation_map) {
53 //       ...
54 //     }
55 //   };
56 //
57 // The InvalidationServiceTest test harness will have a member variable of
58 // this delegate type and will call its functions in the various
59 // tests.
60 //
61 // Then you simply #include this file as well as gtest.h and add the
62 // following statement to my_sync_notifier_unittest.cc:
63 //
64 //   INSTANTIATE_TYPED_TEST_SUITE_P(
65 //       MyInvalidationService,
66 //       InvalidationServiceTest,
67 //       MyInvalidatorTestDelegate);
68 //
69 // Easy!
70 
71 #ifndef COMPONENTS_INVALIDATION_IMPL_INVALIDATION_SERVICE_TEST_TEMPLATE_H_
72 #define COMPONENTS_INVALIDATION_IMPL_INVALIDATION_SERVICE_TEST_TEMPLATE_H_
73 
74 #include "base/compiler_specific.h"
75 #include "base/macros.h"
76 #include "components/invalidation/impl/fake_invalidation_handler.h"
77 #include "components/invalidation/impl/topic_invalidation_map_test_util.h"
78 #include "components/invalidation/public/ack_handle.h"
79 #include "components/invalidation/public/invalidation.h"
80 #include "components/invalidation/public/invalidation_service.h"
81 #include "components/invalidation/public/invalidation_util.h"
82 #include "components/invalidation/public/topic_invalidation_map.h"
83 #include "testing/gtest/include/gtest/gtest.h"
84 
85 template <typename InvalidatorTestDelegate>
86 class InvalidationServiceTest : public testing::Test {
87  protected:
88   InvalidationServiceTest() = default;
89 
90   invalidation::InvalidationService*
CreateAndInitializeInvalidationService()91   CreateAndInitializeInvalidationService() {
92     this->delegate_.CreateInvalidationService();
93     return this->delegate_.GetInvalidationService();
94   }
95 
96   InvalidatorTestDelegate delegate_;
97 
98   const syncer::Topic topic1 = "BOOKMARK";
99   const syncer::Topic topic2 = "PREFERENCE";
100   const syncer::Topic topic3 = "AUTOFILL";
101   const syncer::Topic topic4 = "PUSH_MESSAGE";
102 };
103 
104 TYPED_TEST_SUITE_P(InvalidationServiceTest);
105 
106 // Initialize the invalidator, register a handler, register some IDs for that
107 // handler, and then unregister the handler, dispatching invalidations in
108 // between.  The handler should only see invalidations when its registered and
109 // its IDs are registered.
TYPED_TEST_P(InvalidationServiceTest,Basic)110 TYPED_TEST_P(InvalidationServiceTest, Basic) {
111   invalidation::InvalidationService* const invalidator =
112       this->CreateAndInitializeInvalidationService();
113 
114   syncer::FakeInvalidationHandler handler;
115 
116   invalidator->RegisterInvalidationHandler(&handler);
117 
118   syncer::TopicInvalidationMap invalidation_map;
119   invalidation_map.Insert(syncer::Invalidation::Init(this->topic1, 1, "1"));
120   invalidation_map.Insert(syncer::Invalidation::Init(this->topic2, 2, "2"));
121   invalidation_map.Insert(syncer::Invalidation::Init(this->topic3, 3, "3"));
122 
123   // Should be ignored since no IDs are registered to |handler|.
124   this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
125   EXPECT_EQ(0, handler.GetInvalidationCount());
126 
127   syncer::TopicSet topics;
128   topics.insert(this->topic1);
129   topics.insert(this->topic2);
130   EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler, topics));
131 
132   this->delegate_.TriggerOnInvalidatorStateChange(
133       syncer::INVALIDATIONS_ENABLED);
134   EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler.GetInvalidatorState());
135 
136   syncer::TopicInvalidationMap expected_invalidations;
137   expected_invalidations.Insert(
138       syncer::Invalidation::Init(this->topic1, 1, "1"));
139   expected_invalidations.Insert(
140       syncer::Invalidation::Init(this->topic2, 2, "2"));
141 
142   this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
143   EXPECT_EQ(1, handler.GetInvalidationCount());
144   EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap()));
145 
146   topics.erase(this->topic1);
147   topics.insert(this->topic3);
148   EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler, topics));
149 
150   expected_invalidations = syncer::TopicInvalidationMap();
151   expected_invalidations.Insert(
152       syncer::Invalidation::Init(this->topic2, 2, "2"));
153   expected_invalidations.Insert(
154       syncer::Invalidation::Init(this->topic3, 3, "3"));
155 
156   // Removed Topics should not be notified, newly-added ones should.
157   this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
158   EXPECT_EQ(2, handler.GetInvalidationCount());
159   EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap()));
160 
161   this->delegate_.TriggerOnInvalidatorStateChange(
162       syncer::TRANSIENT_INVALIDATION_ERROR);
163   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
164             handler.GetInvalidatorState());
165 
166   this->delegate_.TriggerOnInvalidatorStateChange(
167       syncer::INVALIDATIONS_ENABLED);
168   EXPECT_EQ(syncer::INVALIDATIONS_ENABLED,
169             handler.GetInvalidatorState());
170 
171   invalidator->UnregisterInvalidationHandler(&handler);
172 
173   // Should be ignored since |handler| isn't registered anymore.
174   this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
175   EXPECT_EQ(2, handler.GetInvalidationCount());
176 }
177 
178 // Register handlers and some topics for those handlers, register a handler
179 // with no topics, and register a handler with some topics but unregister it.
180 // Then, dispatch some invalidations and invalidations.  Handlers that are
181 // registered should get invalidations, and the ones that have registered
182 // topics should receive invalidations for those topics.
TYPED_TEST_P(InvalidationServiceTest,MultipleHandlers)183 TYPED_TEST_P(InvalidationServiceTest, MultipleHandlers) {
184   invalidation::InvalidationService* const invalidator =
185       this->CreateAndInitializeInvalidationService();
186 
187   syncer::FakeInvalidationHandler handler1;
188   syncer::FakeInvalidationHandler handler2;
189   syncer::FakeInvalidationHandler handler3;
190   syncer::FakeInvalidationHandler handler4;
191 
192   invalidator->RegisterInvalidationHandler(&handler1);
193   invalidator->RegisterInvalidationHandler(&handler2);
194   invalidator->RegisterInvalidationHandler(&handler3);
195   invalidator->RegisterInvalidationHandler(&handler4);
196 
197   {
198     syncer::TopicSet topics;
199     topics.insert(this->topic1);
200     topics.insert(this->topic2);
201     EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler1, topics));
202   }
203 
204   {
205     syncer::TopicSet topics;
206     topics.insert(this->topic3);
207     EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler2, topics));
208   }
209 
210   // Don't register any topics for handler3.
211 
212   {
213     syncer::TopicSet topics;
214     topics.insert(this->topic4);
215     EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler4, topics));
216   }
217 
218   invalidator->UnregisterInvalidationHandler(&handler4);
219 
220   this->delegate_.TriggerOnInvalidatorStateChange(
221       syncer::INVALIDATIONS_ENABLED);
222   EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler1.GetInvalidatorState());
223   EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler2.GetInvalidatorState());
224   EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler3.GetInvalidatorState());
225   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
226             handler4.GetInvalidatorState());
227 
228   {
229     syncer::TopicInvalidationMap invalidation_map;
230     invalidation_map.Insert(syncer::Invalidation::Init(this->topic1, 1, "1"));
231     invalidation_map.Insert(syncer::Invalidation::Init(this->topic2, 2, "2"));
232     invalidation_map.Insert(syncer::Invalidation::Init(this->topic3, 3, "3"));
233     invalidation_map.Insert(syncer::Invalidation::Init(this->topic4, 4, "4"));
234     this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
235 
236     syncer::TopicInvalidationMap expected_invalidations;
237     expected_invalidations.Insert(
238         syncer::Invalidation::Init(this->topic1, 1, "1"));
239     expected_invalidations.Insert(
240         syncer::Invalidation::Init(this->topic2, 2, "2"));
241 
242     EXPECT_EQ(1, handler1.GetInvalidationCount());
243     EXPECT_THAT(expected_invalidations, Eq(handler1.GetLastInvalidationMap()));
244 
245     expected_invalidations = syncer::TopicInvalidationMap();
246     expected_invalidations.Insert(
247         syncer::Invalidation::Init(this->topic3, 3, "3"));
248 
249     EXPECT_EQ(1, handler2.GetInvalidationCount());
250     EXPECT_THAT(expected_invalidations, Eq(handler2.GetLastInvalidationMap()));
251 
252     EXPECT_EQ(0, handler3.GetInvalidationCount());
253     EXPECT_EQ(0, handler4.GetInvalidationCount());
254   }
255 
256   this->delegate_.TriggerOnInvalidatorStateChange(
257       syncer::TRANSIENT_INVALIDATION_ERROR);
258   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
259             handler1.GetInvalidatorState());
260   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
261             handler2.GetInvalidatorState());
262   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
263             handler3.GetInvalidatorState());
264   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
265             handler4.GetInvalidatorState());
266 
267   invalidator->UnregisterInvalidationHandler(&handler3);
268   invalidator->UnregisterInvalidationHandler(&handler2);
269   invalidator->UnregisterInvalidationHandler(&handler1);
270 }
271 
272 // Multiple registrations by different handlers on the same Topic should return
273 // false.
TYPED_TEST_P(InvalidationServiceTest,MultipleRegistrations)274 TYPED_TEST_P(InvalidationServiceTest, MultipleRegistrations) {
275   invalidation::InvalidationService* const invalidator =
276       this->CreateAndInitializeInvalidationService();
277 
278   syncer::FakeInvalidationHandler handler1;
279   syncer::FakeInvalidationHandler handler2;
280 
281   invalidator->RegisterInvalidationHandler(&handler1);
282   invalidator->RegisterInvalidationHandler(&handler2);
283 
284   // Registering both handlers for the same topic. First call should succeed,
285   // second should fail.
286   syncer::TopicSet topics;
287   topics.insert(this->topic1);
288   EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler1, topics));
289   EXPECT_FALSE(invalidator->UpdateInterestedTopics(&handler2, topics));
290 
291   invalidator->UnregisterInvalidationHandler(&handler2);
292   invalidator->UnregisterInvalidationHandler(&handler1);
293 }
294 
295 // Make sure that passing an empty set to UpdateInterestedTopics clears
296 // the corresponding entries for the handler.
TYPED_TEST_P(InvalidationServiceTest,EmptySetUnregisters)297 TYPED_TEST_P(InvalidationServiceTest, EmptySetUnregisters) {
298   invalidation::InvalidationService* const invalidator =
299       this->CreateAndInitializeInvalidationService();
300 
301   syncer::FakeInvalidationHandler handler1;
302 
303   // Control observer.
304   syncer::FakeInvalidationHandler handler2;
305 
306   invalidator->RegisterInvalidationHandler(&handler1);
307   invalidator->RegisterInvalidationHandler(&handler2);
308 
309   {
310     syncer::TopicSet topics;
311     topics.insert(this->topic1);
312     topics.insert(this->topic2);
313     EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler1, topics));
314   }
315 
316   {
317     syncer::TopicSet topics;
318     topics.insert(this->topic3);
319     EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler2, topics));
320   }
321 
322   // Unregister the topics for the first observer. It should not receive any
323   // further invalidations.
324   EXPECT_TRUE(
325       invalidator->UpdateInterestedTopics(&handler1, syncer::TopicSet()));
326 
327   this->delegate_.TriggerOnInvalidatorStateChange(
328       syncer::INVALIDATIONS_ENABLED);
329   EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler1.GetInvalidatorState());
330   EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler2.GetInvalidatorState());
331 
332   {
333     syncer::TopicInvalidationMap invalidation_map;
334     invalidation_map.Insert(syncer::Invalidation::Init(this->topic1, 1, "1"));
335     invalidation_map.Insert(syncer::Invalidation::Init(this->topic2, 2, "2"));
336     invalidation_map.Insert(syncer::Invalidation::Init(this->topic3, 3, "3"));
337     this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
338     EXPECT_EQ(0, handler1.GetInvalidationCount());
339     EXPECT_EQ(1, handler2.GetInvalidationCount());
340   }
341 
342   this->delegate_.TriggerOnInvalidatorStateChange(
343       syncer::TRANSIENT_INVALIDATION_ERROR);
344   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
345             handler1.GetInvalidatorState());
346   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
347             handler2.GetInvalidatorState());
348 
349   invalidator->UnregisterInvalidationHandler(&handler2);
350   invalidator->UnregisterInvalidationHandler(&handler1);
351 }
352 
353 namespace internal {
354 
355 // A FakeInvalidationHandler that is "bound" to a specific
356 // InvalidationService.  This is for cross-referencing state information with
357 // the bound InvalidationService.
358 class BoundFakeInvalidationHandler : public syncer::FakeInvalidationHandler {
359  public:
360   explicit BoundFakeInvalidationHandler(
361       const invalidation::InvalidationService& invalidator);
362   ~BoundFakeInvalidationHandler() override;
363 
364   // Returns the last return value of GetInvalidatorState() on the
365   // bound invalidator from the last time the invalidator state
366   // changed.
367   syncer::InvalidatorState GetLastRetrievedState() const;
368 
369   // InvalidationHandler implementation.
370   void OnInvalidatorStateChange(syncer::InvalidatorState state) override;
371 
372  private:
373   const invalidation::InvalidationService& invalidator_;
374   syncer::InvalidatorState last_retrieved_state_;
375 
376   DISALLOW_COPY_AND_ASSIGN(BoundFakeInvalidationHandler);
377 };
378 
379 }  // namespace internal
380 
TYPED_TEST_P(InvalidationServiceTest,GetInvalidatorStateAlwaysCurrent)381 TYPED_TEST_P(InvalidationServiceTest, GetInvalidatorStateAlwaysCurrent) {
382   invalidation::InvalidationService* const invalidator =
383       this->CreateAndInitializeInvalidationService();
384 
385   internal::BoundFakeInvalidationHandler handler(*invalidator);
386   invalidator->RegisterInvalidationHandler(&handler);
387 
388   this->delegate_.TriggerOnInvalidatorStateChange(
389       syncer::INVALIDATIONS_ENABLED);
390   EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler.GetInvalidatorState());
391   EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler.GetLastRetrievedState());
392 
393   this->delegate_.TriggerOnInvalidatorStateChange(
394       syncer::TRANSIENT_INVALIDATION_ERROR);
395   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
396             handler.GetInvalidatorState());
397   EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR,
398             handler.GetLastRetrievedState());
399 
400   invalidator->UnregisterInvalidationHandler(&handler);
401 }
402 
403 REGISTER_TYPED_TEST_SUITE_P(InvalidationServiceTest,
404                             Basic,
405                             MultipleHandlers,
406                             MultipleRegistrations,
407                             EmptySetUnregisters,
408                             GetInvalidatorStateAlwaysCurrent);
409 
410 #endif  // COMPONENTS_INVALIDATION_IMPL_INVALIDATION_SERVICE_TEST_TEMPLATE_H_
411