// Copyright (c) 2011 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 <string> #include "base/message_loop.h" #include "chrome/browser/sync/notifier/chrome_invalidation_client.h" #include "chrome/browser/sync/notifier/state_writer.h" #include "chrome/browser/sync/syncable/model_type.h" #include "chrome/browser/sync/syncable/model_type_payload_map.h" #include "jingle/notifier/base/fake_base_task.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"; static const int64 kUnknownVersion = invalidation::InvalidationListener::UNKNOWN_OBJECT_VERSION; class MockListener : public ChromeInvalidationClient::Listener { public: MOCK_METHOD1(OnInvalidate, void(const syncable::ModelTypePayloadMap&)); MOCK_METHOD1(OnSessionStatusChanged, void(bool)); }; class MockStateWriter : public StateWriter { public: MOCK_METHOD1(WriteState, void(const std::string&)); }; class MockCallback { public: MOCK_METHOD0(Run, void()); invalidation::Closure* MakeClosure() { return invalidation::NewPermanentCallback(this, &MockCallback::Run); } }; } // namespace class ChromeInvalidationClientTest : public testing::Test { protected: virtual void SetUp() { client_.Start(kClientId, kClientInfo, kState, &mock_listener_, &mock_state_writer_, fake_base_task_.AsWeakPtr()); } virtual void TearDown() { client_.Stop(); message_loop_.RunAllPending(); } // Simulates DoInformOutboundListener() from network-manager.cc. void SimulateInformOutboundListener() { // Explicitness hack here to work around broken callback // implementations. void (invalidation::NetworkCallback::*run_function)( invalidation::NetworkEndpoint* const&) = &invalidation::NetworkCallback::Run; client_.chrome_system_resources_.ScheduleOnListenerThread( invalidation::NewPermanentCallback( client_.handle_outbound_packet_callback_.get(), run_function, client_.invalidation_client_->network_endpoint())); } // |payload| can be NULL, but not |type_name|. void FireInvalidate(const char* type_name, int64 version, const char* payload) { const invalidation::ObjectId object_id( invalidation::ObjectSource::CHROME_SYNC, type_name); std::string payload_tmp = payload ? payload : ""; const invalidation::Invalidation invalidation( object_id, version, payload ? &payload_tmp : NULL, NULL); MockCallback mock_callback; EXPECT_CALL(mock_callback, Run()); client_.Invalidate(invalidation, mock_callback.MakeClosure()); } void FireInvalidateAll() { MockCallback mock_callback; EXPECT_CALL(mock_callback, Run()); client_.InvalidateAll(mock_callback.MakeClosure()); } MessageLoop message_loop_; StrictMock<MockListener> mock_listener_; StrictMock<MockStateWriter> mock_state_writer_; notifier::FakeBaseTask fake_base_task_; 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) { syncable::ModelTypePayloadMap type_payloads; for (syncable::ModelTypeSet::const_iterator it = types.begin(); it != types.end(); ++it) { type_payloads[*it] = payload; } return type_payloads; } } // namespace TEST_F(ChromeInvalidationClientTest, InvalidateBadObjectId) { syncable::ModelTypeSet types; types.insert(syncable::BOOKMARKS); types.insert(syncable::APPS); client_.RegisterTypes(types); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); FireInvalidate("bad", 1, NULL); } TEST_F(ChromeInvalidationClientTest, InvalidateNoPayload) { EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::BOOKMARKS, ""))); FireInvalidate("BOOKMARK", 1, NULL); } TEST_F(ChromeInvalidationClientTest, InvalidateWithPayload) { EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::PREFERENCES, "payload"))); FireInvalidate("PREFERENCE", 1, "payload"); } TEST_F(ChromeInvalidationClientTest, InvalidateVersion) { using ::testing::Mock; EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::APPS, ""))); // 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. FireInvalidate("EXTENSION", kUnknownVersion, NULL); FireInvalidate("EXTENSION", kUnknownVersion, NULL); } TEST_F(ChromeInvalidationClientTest, InvalidateVersionMultipleTypes) { using ::testing::Mock; syncable::ModelTypeSet types; types.insert(syncable::BOOKMARKS); types.insert(syncable::APPS); client_.RegisterTypes(types); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::APPS, ""))); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))); // Should trigger both. FireInvalidate("APP", 3, NULL); FireInvalidate("EXTENSION", 2, NULL); Mock::VerifyAndClearExpectations(&mock_listener_); // Should both be dropped. FireInvalidate("APP", 1, NULL); FireInvalidate("EXTENSION", 1, NULL); Mock::VerifyAndClearExpectations(&mock_listener_); // InvalidateAll shouldn't change any version state. EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); FireInvalidateAll(); Mock::VerifyAndClearExpectations(&mock_listener_); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::PREFERENCES, ""))); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMap(syncable::APPS, ""))); // Should trigger all three. FireInvalidate("PREFERENCE", 5, NULL); FireInvalidate("EXTENSION", 3, NULL); FireInvalidate("APP", 4, NULL); } TEST_F(ChromeInvalidationClientTest, InvalidateAll) { syncable::ModelTypeSet types; types.insert(syncable::PREFERENCES); types.insert(syncable::EXTENSIONS); client_.RegisterTypes(types); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); FireInvalidateAll(); } TEST_F(ChromeInvalidationClientTest, RegisterTypes) { syncable::ModelTypeSet types; types.insert(syncable::PREFERENCES); types.insert(syncable::EXTENSIONS); client_.RegisterTypes(types); // Registered types should be preserved across Stop/Start. TearDown(); SetUp(); EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); FireInvalidateAll(); } // Outbound packet sending should be resilient to // changing/disappearing base tasks. TEST_F(ChromeInvalidationClientTest, OutboundPackets) { SimulateInformOutboundListener(); notifier::FakeBaseTask fake_base_task; client_.ChangeBaseTask(fake_base_task.AsWeakPtr()); SimulateInformOutboundListener(); { notifier::FakeBaseTask fake_base_task2; client_.ChangeBaseTask(fake_base_task2.AsWeakPtr()); } SimulateInformOutboundListener(); } // TODO(akalin): Flesh out unit tests. } // namespace sync_notifier