1 // Copyright (c) 2011 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 #include <string> 6 7 #include "base/message_loop.h" 8 #include "chrome/browser/sync/notifier/chrome_invalidation_client.h" 9 #include "chrome/browser/sync/notifier/state_writer.h" 10 #include "chrome/browser/sync/syncable/model_type.h" 11 #include "chrome/browser/sync/syncable/model_type_payload_map.h" 12 #include "jingle/notifier/base/fake_base_task.h" 13 #include "testing/gmock/include/gmock/gmock.h" 14 #include "testing/gtest/include/gtest/gtest.h" 15 16 namespace sync_notifier { 17 18 using ::testing::_; 19 using ::testing::Return; 20 using ::testing::StrictMock; 21 22 namespace { 23 24 const char kClientId[] = "client_id"; 25 const char kClientInfo[] = "client_info"; 26 const char kState[] = "state"; 27 28 static const int64 kUnknownVersion = 29 invalidation::InvalidationListener::UNKNOWN_OBJECT_VERSION; 30 31 class MockListener : public ChromeInvalidationClient::Listener { 32 public: 33 MOCK_METHOD1(OnInvalidate, void(const syncable::ModelTypePayloadMap&)); 34 MOCK_METHOD1(OnSessionStatusChanged, void(bool)); 35 }; 36 37 class MockStateWriter : public StateWriter { 38 public: 39 MOCK_METHOD1(WriteState, void(const std::string&)); 40 }; 41 42 class MockCallback { 43 public: 44 MOCK_METHOD0(Run, void()); 45 46 invalidation::Closure* MakeClosure() { 47 return invalidation::NewPermanentCallback(this, &MockCallback::Run); 48 } 49 }; 50 51 } // namespace 52 53 class ChromeInvalidationClientTest : public testing::Test { 54 protected: 55 virtual void SetUp() { 56 client_.Start(kClientId, kClientInfo, kState, 57 &mock_listener_, &mock_state_writer_, 58 fake_base_task_.AsWeakPtr()); 59 } 60 61 virtual void TearDown() { 62 client_.Stop(); 63 message_loop_.RunAllPending(); 64 } 65 66 // Simulates DoInformOutboundListener() from network-manager.cc. 67 void SimulateInformOutboundListener() { 68 // Explicitness hack here to work around broken callback 69 // implementations. 70 void (invalidation::NetworkCallback::*run_function)( 71 invalidation::NetworkEndpoint* const&) = 72 &invalidation::NetworkCallback::Run; 73 74 client_.chrome_system_resources_.ScheduleOnListenerThread( 75 invalidation::NewPermanentCallback( 76 client_.handle_outbound_packet_callback_.get(), run_function, 77 client_.invalidation_client_->network_endpoint())); 78 } 79 80 // |payload| can be NULL, but not |type_name|. 81 void FireInvalidate(const char* type_name, 82 int64 version, const char* payload) { 83 const invalidation::ObjectId object_id( 84 invalidation::ObjectSource::CHROME_SYNC, type_name); 85 std::string payload_tmp = payload ? payload : ""; 86 const invalidation::Invalidation invalidation( 87 object_id, version, payload ? &payload_tmp : NULL, NULL); 88 MockCallback mock_callback; 89 EXPECT_CALL(mock_callback, Run()); 90 client_.Invalidate(invalidation, mock_callback.MakeClosure()); 91 } 92 93 void FireInvalidateAll() { 94 MockCallback mock_callback; 95 EXPECT_CALL(mock_callback, Run()); 96 client_.InvalidateAll(mock_callback.MakeClosure()); 97 } 98 99 MessageLoop message_loop_; 100 StrictMock<MockListener> mock_listener_; 101 StrictMock<MockStateWriter> mock_state_writer_; 102 notifier::FakeBaseTask fake_base_task_; 103 ChromeInvalidationClient client_; 104 }; 105 106 namespace { 107 108 syncable::ModelTypePayloadMap MakeMap(syncable::ModelType model_type, 109 const std::string& payload) { 110 syncable::ModelTypePayloadMap type_payloads; 111 type_payloads[model_type] = payload; 112 return type_payloads; 113 } 114 115 syncable::ModelTypePayloadMap MakeMapFromSet(syncable::ModelTypeSet types, 116 const std::string& payload) { 117 syncable::ModelTypePayloadMap type_payloads; 118 for (syncable::ModelTypeSet::const_iterator it = types.begin(); 119 it != types.end(); ++it) { 120 type_payloads[*it] = payload; 121 } 122 return type_payloads; 123 } 124 125 } // namespace 126 127 TEST_F(ChromeInvalidationClientTest, InvalidateBadObjectId) { 128 syncable::ModelTypeSet types; 129 types.insert(syncable::BOOKMARKS); 130 types.insert(syncable::APPS); 131 client_.RegisterTypes(types); 132 EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); 133 FireInvalidate("bad", 1, NULL); 134 } 135 136 TEST_F(ChromeInvalidationClientTest, InvalidateNoPayload) { 137 EXPECT_CALL(mock_listener_, 138 OnInvalidate(MakeMap(syncable::BOOKMARKS, ""))); 139 FireInvalidate("BOOKMARK", 1, NULL); 140 } 141 142 TEST_F(ChromeInvalidationClientTest, InvalidateWithPayload) { 143 EXPECT_CALL(mock_listener_, 144 OnInvalidate(MakeMap(syncable::PREFERENCES, "payload"))); 145 FireInvalidate("PREFERENCE", 1, "payload"); 146 } 147 148 TEST_F(ChromeInvalidationClientTest, InvalidateVersion) { 149 using ::testing::Mock; 150 151 EXPECT_CALL(mock_listener_, 152 OnInvalidate(MakeMap(syncable::APPS, ""))); 153 154 // Should trigger. 155 FireInvalidate("APP", 1, NULL); 156 157 Mock::VerifyAndClearExpectations(&mock_listener_); 158 159 // Should be dropped. 160 FireInvalidate("APP", 1, NULL); 161 } 162 163 TEST_F(ChromeInvalidationClientTest, InvalidateUnknownVersion) { 164 EXPECT_CALL(mock_listener_, 165 OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))) 166 .Times(2); 167 168 // Should trigger twice. 169 FireInvalidate("EXTENSION", kUnknownVersion, NULL); 170 FireInvalidate("EXTENSION", kUnknownVersion, NULL); 171 } 172 173 TEST_F(ChromeInvalidationClientTest, InvalidateVersionMultipleTypes) { 174 using ::testing::Mock; 175 176 syncable::ModelTypeSet types; 177 types.insert(syncable::BOOKMARKS); 178 types.insert(syncable::APPS); 179 client_.RegisterTypes(types); 180 181 EXPECT_CALL(mock_listener_, 182 OnInvalidate(MakeMap(syncable::APPS, ""))); 183 EXPECT_CALL(mock_listener_, 184 OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))); 185 186 // Should trigger both. 187 FireInvalidate("APP", 3, NULL); 188 FireInvalidate("EXTENSION", 2, NULL); 189 190 Mock::VerifyAndClearExpectations(&mock_listener_); 191 192 // Should both be dropped. 193 FireInvalidate("APP", 1, NULL); 194 FireInvalidate("EXTENSION", 1, NULL); 195 196 Mock::VerifyAndClearExpectations(&mock_listener_); 197 198 // InvalidateAll shouldn't change any version state. 199 EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); 200 FireInvalidateAll(); 201 202 Mock::VerifyAndClearExpectations(&mock_listener_); 203 204 EXPECT_CALL(mock_listener_, 205 OnInvalidate(MakeMap(syncable::PREFERENCES, ""))); 206 EXPECT_CALL(mock_listener_, 207 OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))); 208 EXPECT_CALL(mock_listener_, 209 OnInvalidate(MakeMap(syncable::APPS, ""))); 210 211 // Should trigger all three. 212 FireInvalidate("PREFERENCE", 5, NULL); 213 FireInvalidate("EXTENSION", 3, NULL); 214 FireInvalidate("APP", 4, NULL); 215 } 216 217 TEST_F(ChromeInvalidationClientTest, InvalidateAll) { 218 syncable::ModelTypeSet types; 219 types.insert(syncable::PREFERENCES); 220 types.insert(syncable::EXTENSIONS); 221 client_.RegisterTypes(types); 222 EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); 223 FireInvalidateAll(); 224 } 225 226 TEST_F(ChromeInvalidationClientTest, RegisterTypes) { 227 syncable::ModelTypeSet types; 228 types.insert(syncable::PREFERENCES); 229 types.insert(syncable::EXTENSIONS); 230 client_.RegisterTypes(types); 231 // Registered types should be preserved across Stop/Start. 232 TearDown(); 233 SetUp(); 234 EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); 235 FireInvalidateAll(); 236 } 237 238 // Outbound packet sending should be resilient to 239 // changing/disappearing base tasks. 240 TEST_F(ChromeInvalidationClientTest, OutboundPackets) { 241 SimulateInformOutboundListener(); 242 243 notifier::FakeBaseTask fake_base_task; 244 client_.ChangeBaseTask(fake_base_task.AsWeakPtr()); 245 246 SimulateInformOutboundListener(); 247 248 { 249 notifier::FakeBaseTask fake_base_task2; 250 client_.ChangeBaseTask(fake_base_task2.AsWeakPtr()); 251 } 252 253 SimulateInformOutboundListener(); 254 } 255 256 // TODO(akalin): Flesh out unit tests. 257 258 } // namespace sync_notifier 259