// Copyright (c) 2012 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 #include "base/message_loop.h" #include "google/cacheinvalidation/include/invalidation-client.h" #include "google/cacheinvalidation/include/types.h" #include "google/cacheinvalidation/v2/types.pb.h" #include "jingle/notifier/listener/fake_push_client.h" #include "sync/internal_api/public/syncable/model_type.h" #include "sync/internal_api/public/syncable/model_type_payload_map.h" #include "sync/notifier/chrome_invalidation_client.h" #include "sync/notifier/mock_invalidation_state_tracker.h" #include "sync/util/weak_handle.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace sync_notifier { using ::testing::_; using ::testing::Return; using ::testing::StrictMock; namespace { const char kClientId[] = "client_id"; const char kClientInfo[] = "client_info"; const char kState[] = "state"; const char kNewState[] = "new_state"; class MockInvalidationClient : public invalidation::InvalidationClient { public: MOCK_METHOD0(Start, void()); MOCK_METHOD0(Stop, void()); MOCK_METHOD1(Register, void(const invalidation::ObjectId&)); MOCK_METHOD1(Register, void(const std::vector&)); MOCK_METHOD1(Unregister, void(const invalidation::ObjectId&)); MOCK_METHOD1(Unregister, void(const std::vector&)); MOCK_METHOD1(Acknowledge, void(const invalidation::AckHandle&)); }; class MockListener : public ChromeInvalidationClient::Listener { public: MOCK_METHOD1(OnInvalidate, void(const syncable::ModelTypePayloadMap&)); MOCK_METHOD1(OnSessionStatusChanged, void(bool)); }; } // namespace class ChromeInvalidationClientTest : public testing::Test { protected: ChromeInvalidationClientTest() : client_( scoped_ptr( new notifier::FakePushClient())) {} virtual void SetUp() { client_.Start(kClientId, kClientInfo, kState, InvalidationVersionMap(), browser_sync::MakeWeakHandle( mock_invalidation_state_tracker_.AsWeakPtr()), &mock_listener_); } virtual void TearDown() { // client_.Stop() stops the invalidation scheduler, which deletes any // pending tasks without running them. Some tasks "run and delete" another // task, so they must be run in order to avoid leaking the inner task. // client_.Stop() does not schedule any tasks, so it's both necessary and // sufficient to drain the task queue before calling it. message_loop_.RunAllPending(); client_.Stop(); } // |payload| can be NULL, but not |type_name|. void FireInvalidate(const char* type_name, int64 version, const char* payload) { const invalidation::ObjectId object_id( ipc::invalidation::ObjectSource::CHROME_SYNC, type_name); std::string payload_tmp = payload ? payload : ""; invalidation::Invalidation inv; if (payload) { inv = invalidation::Invalidation(object_id, version, payload); } else { inv = invalidation::Invalidation(object_id, version); } invalidation::AckHandle ack_handle("fakedata"); EXPECT_CALL(mock_invalidation_client_, Acknowledge(ack_handle)); client_.Invalidate(&mock_invalidation_client_, inv, ack_handle); // Pump message loop to trigger // InvalidationStateTracker::SetMaxVersion(). message_loop_.RunAllPending(); } // |payload| can be NULL, but not |type_name|. void FireInvalidateUnknownVersion(const char* type_name) { const invalidation::ObjectId object_id( ipc::invalidation::ObjectSource::CHROME_SYNC, type_name); invalidation::AckHandle ack_handle("fakedata"); EXPECT_CALL(mock_invalidation_client_, Acknowledge(ack_handle)); client_.InvalidateUnknownVersion(&mock_invalidation_client_, object_id, ack_handle); } void FireInvalidateAll() { invalidation::AckHandle ack_handle("fakedata"); EXPECT_CALL(mock_invalidation_client_, Acknowledge(ack_handle)); client_.InvalidateAll(&mock_invalidation_client_, ack_handle); } MessageLoop message_loop_; StrictMock mock_listener_; StrictMock mock_invalidation_state_tracker_; StrictMock mock_invalidation_client_; ChromeInvalidationClient client_; }; namespace { syncable::ModelTypePayloadMap MakeMap(syncable::ModelType model_type, const std::string& payload) { syncable::ModelTypePayloadMap type_payloads; type_payloads[model_type] = payload; return type_payloads; } syncable::ModelTypePayloadMap MakeMapFromSet(syncable::ModelTypeSet types, const std::string& payload) { return syncable::ModelTypePayloadMapFromEnumSet(types, payload); } } // namespace TEST_F(ChromeInvalidationClientTest, InvalidateBadObjectId) { syncable::ModelTypeSet types(syncable::BOOKMARKS, syncable::APPS); client_.RegisterTypes(types); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); FireInvalidate("bad", 1, NULL); message_loop_.RunAllPending(); } TEST_F(ChromeInvalidationClientTest, InvalidateNoPayload) { EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::BOOKMARKS, ""))); EXPECT_CALL(mock_invalidation_state_tracker_, SetMaxVersion(syncable::BOOKMARKS, 1)); FireInvalidate("BOOKMARK", 1, NULL); } TEST_F(ChromeInvalidationClientTest, InvalidateWithPayload) { EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::PREFERENCES, "payload"))); EXPECT_CALL(mock_invalidation_state_tracker_, SetMaxVersion(syncable::PREFERENCES, 1)); FireInvalidate("PREFERENCE", 1, "payload"); } TEST_F(ChromeInvalidationClientTest, WriteState) { EXPECT_CALL(mock_invalidation_state_tracker_, SetInvalidationState(kNewState)); client_.WriteState(kNewState); } TEST_F(ChromeInvalidationClientTest, InvalidateVersion) { using ::testing::Mock; EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::APPS, ""))); EXPECT_CALL(mock_invalidation_state_tracker_, SetMaxVersion(syncable::APPS, 1)); // Should trigger. FireInvalidate("APP", 1, NULL); Mock::VerifyAndClearExpectations(&mock_listener_); // Should be dropped. FireInvalidate("APP", 1, NULL); } TEST_F(ChromeInvalidationClientTest, InvalidateUnknownVersion) { EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))) .Times(2); // Should trigger twice. FireInvalidateUnknownVersion("EXTENSION"); FireInvalidateUnknownVersion("EXTENSION"); } TEST_F(ChromeInvalidationClientTest, InvalidateVersionMultipleTypes) { using ::testing::Mock; syncable::ModelTypeSet types(syncable::BOOKMARKS, syncable::APPS); client_.RegisterTypes(types); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::APPS, ""))); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))); EXPECT_CALL(mock_invalidation_state_tracker_, SetMaxVersion(syncable::APPS, 3)); EXPECT_CALL(mock_invalidation_state_tracker_, SetMaxVersion(syncable::EXTENSIONS, 2)); // Should trigger both. FireInvalidate("APP", 3, NULL); FireInvalidate("EXTENSION", 2, NULL); Mock::VerifyAndClearExpectations(&mock_listener_); Mock::VerifyAndClearExpectations(&mock_invalidation_state_tracker_); // Should both be dropped. FireInvalidate("APP", 1, NULL); FireInvalidate("EXTENSION", 1, NULL); Mock::VerifyAndClearExpectations(&mock_listener_); Mock::VerifyAndClearExpectations(&mock_invalidation_state_tracker_); // InvalidateAll shouldn't change any version state. EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); FireInvalidateAll(); Mock::VerifyAndClearExpectations(&mock_listener_); Mock::VerifyAndClearExpectations(&mock_invalidation_state_tracker_); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::PREFERENCES, ""))); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::APPS, ""))); EXPECT_CALL(mock_invalidation_state_tracker_, SetMaxVersion(syncable::PREFERENCES, 5)); EXPECT_CALL(mock_invalidation_state_tracker_, SetMaxVersion(syncable::EXTENSIONS, 3)); EXPECT_CALL(mock_invalidation_state_tracker_, SetMaxVersion(syncable::APPS, 4)); // Should trigger all three. FireInvalidate("PREFERENCE", 5, NULL); FireInvalidate("EXTENSION", 3, NULL); FireInvalidate("APP", 4, NULL); } TEST_F(ChromeInvalidationClientTest, InvalidateAll) { syncable::ModelTypeSet types(syncable::PREFERENCES, syncable::EXTENSIONS); client_.RegisterTypes(types); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); FireInvalidateAll(); } TEST_F(ChromeInvalidationClientTest, RegisterTypes) { syncable::ModelTypeSet types(syncable::PREFERENCES, syncable::EXTENSIONS); client_.RegisterTypes(types); // Registered types should be preserved across Stop/Start. TearDown(); SetUp(); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); FireInvalidateAll(); } // TODO(akalin): Flesh out unit tests. } // namespace sync_notifier