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
MakeClosure()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:
SetUp()55 virtual void SetUp() {
56 client_.Start(kClientId, kClientInfo, kState,
57 &mock_listener_, &mock_state_writer_,
58 fake_base_task_.AsWeakPtr());
59 }
60
TearDown()61 virtual void TearDown() {
62 client_.Stop();
63 message_loop_.RunAllPending();
64 }
65
66 // Simulates DoInformOutboundListener() from network-manager.cc.
SimulateInformOutboundListener()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|.
FireInvalidate(const char * type_name,int64 version,const char * payload)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
FireInvalidateAll()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
MakeMap(syncable::ModelType model_type,const std::string & payload)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
MakeMapFromSet(syncable::ModelTypeSet types,const std::string & payload)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
TEST_F(ChromeInvalidationClientTest,InvalidateBadObjectId)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
TEST_F(ChromeInvalidationClientTest,InvalidateNoPayload)136 TEST_F(ChromeInvalidationClientTest, InvalidateNoPayload) {
137 EXPECT_CALL(mock_listener_,
138 OnInvalidate(MakeMap(syncable::BOOKMARKS, "")));
139 FireInvalidate("BOOKMARK", 1, NULL);
140 }
141
TEST_F(ChromeInvalidationClientTest,InvalidateWithPayload)142 TEST_F(ChromeInvalidationClientTest, InvalidateWithPayload) {
143 EXPECT_CALL(mock_listener_,
144 OnInvalidate(MakeMap(syncable::PREFERENCES, "payload")));
145 FireInvalidate("PREFERENCE", 1, "payload");
146 }
147
TEST_F(ChromeInvalidationClientTest,InvalidateVersion)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
TEST_F(ChromeInvalidationClientTest,InvalidateUnknownVersion)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
TEST_F(ChromeInvalidationClientTest,InvalidateVersionMultipleTypes)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
TEST_F(ChromeInvalidationClientTest,InvalidateAll)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
TEST_F(ChromeInvalidationClientTest,RegisterTypes)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.
TEST_F(ChromeInvalidationClientTest,OutboundPackets)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