OLD | NEW |
| (Empty) |
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 #include "sync/engine/model_type_worker.h" | |
6 | |
7 #include <stddef.h> | |
8 #include <stdint.h> | |
9 | |
10 #include <utility> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/memory/ptr_util.h" | |
14 #include "base/strings/stringprintf.h" | |
15 #include "sync/engine/commit_contribution.h" | |
16 #include "sync/internal_api/public/base/model_type.h" | |
17 #include "sync/internal_api/public/model_type_processor.h" | |
18 #include "sync/internal_api/public/non_blocking_sync_common.h" | |
19 #include "sync/protocol/data_type_state.pb.h" | |
20 #include "sync/protocol/sync.pb.h" | |
21 #include "sync/sessions/status_controller.h" | |
22 #include "sync/syncable/syncable_util.h" | |
23 #include "sync/test/engine/mock_model_type_processor.h" | |
24 #include "sync/test/engine/mock_nudge_handler.h" | |
25 #include "sync/test/engine/single_type_mock_server.h" | |
26 #include "sync/test/fake_encryptor.h" | |
27 #include "testing/gtest/include/gtest/gtest.h" | |
28 | |
29 static const syncer::ModelType kModelType = syncer::PREFERENCES; | |
30 | |
31 // Special constant value taken from cryptographer.cc. | |
32 const char kNigoriKeyName[] = "nigori-key"; | |
33 | |
34 namespace syncer_v2 { | |
35 | |
36 using syncer::Cryptographer; | |
37 using syncer::CommitContribution; | |
38 using syncer::KeyParams; | |
39 using syncer::Nigori; | |
40 using syncer::sessions::StatusController; | |
41 | |
42 // Tests the ModelTypeWorker. | |
43 // | |
44 // This class passes messages between the model thread and sync server. | |
45 // As such, its code is subject to lots of different race conditions. This | |
46 // test harness lets us exhaustively test all possible races. We try to | |
47 // focus on just a few interesting cases. | |
48 // | |
49 // Inputs: | |
50 // - Initial data type state from the model thread. | |
51 // - Commit requests from the model thread. | |
52 // - Update responses from the server. | |
53 // - Commit responses from the server. | |
54 // - The cryptographer, if encryption is enabled. | |
55 // | |
56 // Outputs: | |
57 // - Commit requests to the server. | |
58 // - Commit responses to the model thread. | |
59 // - Update responses to the model thread. | |
60 // - Nudges to the sync scheduler. | |
61 // | |
62 // We use the MockModelTypeProcessor to stub out all communication | |
63 // with the model thread. That interface is synchronous, which makes it | |
64 // much easier to test races. | |
65 // | |
66 // The interface with the server is built around "pulling" data from this | |
67 // class, so we don't have to mock out any of it. We wrap it with some | |
68 // convenience functions so we can emulate server behavior. | |
69 class ModelTypeWorkerTest : public ::testing::Test { | |
70 public: | |
71 ModelTypeWorkerTest(); | |
72 ~ModelTypeWorkerTest() override; | |
73 | |
74 // One of these Initialize functions should be called at the beginning of | |
75 // each test. | |
76 | |
77 // Initializes with no data type state. We will be unable to perform any | |
78 // significant server action until we receive an update response that | |
79 // contains the type root node for this type. | |
80 void FirstInitialize(); | |
81 | |
82 // Initializes with some existing data type state. Allows us to start | |
83 // committing items right away. | |
84 void NormalInitialize(); | |
85 | |
86 // Initialize with some saved pending updates from the model thread. | |
87 void InitializeWithPendingUpdates( | |
88 const UpdateResponseDataList& initial_pending_updates); | |
89 | |
90 // Initialize with a custom initial DataTypeState and pending updates. | |
91 void InitializeWithState(const sync_pb::DataTypeState& state, | |
92 const UpdateResponseDataList& pending_updates); | |
93 | |
94 ModelTypeWorker* worker() const { return worker_.get(); } | |
95 | |
96 // Introduce a new key that the local cryptographer can't decrypt. | |
97 void NewForeignEncryptionKey(); | |
98 | |
99 // Update the local cryptographer with all relevant keys. | |
100 void UpdateLocalCryptographer(); | |
101 | |
102 // Use the Nth nigori instance to encrypt incoming updates. | |
103 // The default value, zero, indicates no encryption. | |
104 void SetUpdateEncryptionFilter(int n); | |
105 | |
106 // Modifications on the model thread that get sent to the worker under test. | |
107 void CommitRequest(const std::string& tag, const std::string& value); | |
108 void DeleteRequest(const std::string& tag); | |
109 | |
110 // Pretends to receive update messages from the server. | |
111 void TriggerTypeRootUpdateFromServer(); | |
112 void TriggerPartialUpdateFromServer(int64_t version_offset, | |
113 const std::string& tag, | |
114 const std::string& value); | |
115 void TriggerUpdateFromServer(int64_t version_offset, | |
116 const std::string& tag, | |
117 const std::string& value); | |
118 void TriggerTombstoneFromServer(int64_t version_offset, | |
119 const std::string& tag); | |
120 | |
121 // Delivers specified protos as updates. | |
122 // | |
123 // Does not update mock server state. Should be used as a last resort when | |
124 // writing test cases that require entities that don't fit the normal sync | |
125 // protocol. Try to use the other, higher level methods if possible. | |
126 void DeliverRawUpdates(const SyncEntityList& update_list); | |
127 | |
128 // By default, this harness behaves as if all tasks posted to the model | |
129 // thread are executed immediately. However, this is not necessarily true. | |
130 // The model's TaskRunner has a queue, and the tasks we post to it could | |
131 // linger there for a while. In the meantime, the model thread could | |
132 // continue posting tasks to the worker based on its stale state. | |
133 // | |
134 // If you want to test those race cases, then these functions are for you. | |
135 void SetModelThreadIsSynchronous(bool is_synchronous); | |
136 void PumpModelThread(); | |
137 | |
138 // Returns true if the |worker_| is ready to commit something. | |
139 bool WillCommit(); | |
140 | |
141 // Pretend to successfully commit all outstanding unsynced items. | |
142 // It is safe to call this only if WillCommit() returns true. | |
143 void DoSuccessfulCommit(); | |
144 | |
145 // Callback when processor got disconnected with sync. | |
146 void DisconnectProcessor(); | |
147 | |
148 bool IsProcessorDisconnected(); | |
149 void ResetWorker(); | |
150 | |
151 // Read commit messages the worker_ sent to the emulated server. | |
152 size_t GetNumCommitMessagesOnServer() const; | |
153 sync_pb::ClientToServerMessage GetNthCommitMessageOnServer(size_t n) const; | |
154 | |
155 // Read the latest version of sync entities committed to the emulated server. | |
156 bool HasCommitEntityOnServer(const std::string& tag) const; | |
157 sync_pb::SyncEntity GetLatestCommitEntityOnServer( | |
158 const std::string& tag) const; | |
159 | |
160 // Read the latest update messages received on the model thread. | |
161 // Note that if the model thread is in non-blocking mode, this data will not | |
162 // be updated until the response is actually processed by the model thread. | |
163 size_t GetNumModelThreadUpdateResponses() const; | |
164 UpdateResponseDataList GetNthModelThreadUpdateResponse(size_t n) const; | |
165 sync_pb::DataTypeState GetNthModelThreadUpdateState(size_t n) const; | |
166 | |
167 // Reads the latest update response datas on the model thread. | |
168 // Note that if the model thread is in non-blocking mode, this data will not | |
169 // be updated until the response is actually processed by the model thread. | |
170 bool HasUpdateResponseOnModelThread(const std::string& tag) const; | |
171 UpdateResponseData GetUpdateResponseOnModelThread( | |
172 const std::string& tag) const; | |
173 | |
174 // Read the latest commit messages received on the model thread. | |
175 // Note that if the model thread is in non-blocking mode, this data will not | |
176 // be updated until the response is actually processed by the model thread. | |
177 size_t GetNumModelThreadCommitResponses() const; | |
178 CommitResponseDataList GetNthModelThreadCommitResponse(size_t n) const; | |
179 sync_pb::DataTypeState GetNthModelThreadCommitState(size_t n) const; | |
180 | |
181 // Reads the latest commit response datas on the model thread. | |
182 // Note that if the model thread is in non-blocking mode, this data will not | |
183 // be updated until the response is actually processed by the model thread. | |
184 bool HasCommitResponseOnModelThread(const std::string& tag) const; | |
185 CommitResponseData GetCommitResponseOnModelThread( | |
186 const std::string& tag) const; | |
187 | |
188 // Returns the number of commit nudges sent to the mock nudge handler. | |
189 int GetNumCommitNudges() const; | |
190 | |
191 // Returns the number of initial sync nudges sent to the mock nudge handler. | |
192 int GetNumInitialDownloadNudges() const; | |
193 | |
194 // Returns the name of the encryption key in the cryptographer last passed to | |
195 // the CommitQueue. Returns an empty string if no crypgorapher is | |
196 // in use. See also: UpdateLocalCryptographer(). | |
197 std::string GetLocalCryptographerKeyName() const; | |
198 | |
199 // Helpers for building various messages and structures. | |
200 static std::string GenerateTagHash(const std::string& tag); | |
201 static sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag, | |
202 const std::string& value); | |
203 | |
204 // Returns a set of KeyParams for the cryptographer. Each input 'n' value | |
205 // results in a different set of parameters. | |
206 static KeyParams GetNthKeyParams(int n); | |
207 | |
208 // Returns the name for the given Nigori. | |
209 // | |
210 // Uses some 'white-box' knowledge to mimic the names that a real sync client | |
211 // would generate. It's probably not necessary to do so, but it can't hurt. | |
212 static std::string GetNigoriName(const Nigori& nigori); | |
213 | |
214 // Modifies the input/output parameter |specifics| by encrypting it with | |
215 // a Nigori intialized with the specified KeyParams. | |
216 static void EncryptUpdate(const KeyParams& params, | |
217 sync_pb::EntitySpecifics* specifics); | |
218 | |
219 private: | |
220 // An encryptor for our cryptographer. | |
221 syncer::FakeEncryptor fake_encryptor_; | |
222 | |
223 // The cryptographer itself. NULL if we're not encrypting the type. | |
224 std::unique_ptr<Cryptographer> cryptographer_; | |
225 | |
226 // The number of the most recent foreign encryption key known to our | |
227 // cryptographer. Note that not all of these will be decryptable. | |
228 int foreign_encryption_key_index_; | |
229 | |
230 // The number of the encryption key used to encrypt incoming updates. A zero | |
231 // value implies no encryption. | |
232 int update_encryption_filter_index_; | |
233 | |
234 // The ModelTypeWorker being tested. | |
235 std::unique_ptr<ModelTypeWorker> worker_; | |
236 | |
237 // Non-owned, possibly NULL pointer. This object belongs to the | |
238 // ModelTypeWorker under test. | |
239 MockModelTypeProcessor* mock_type_processor_; | |
240 | |
241 // A mock that emulates enough of the sync server that it can be used | |
242 // a single UpdateHandler and CommitContributor pair. In this test | |
243 // harness, the |worker_| is both of them. | |
244 syncer::SingleTypeMockServer mock_server_; | |
245 | |
246 // A mock to track the number of times the CommitQueue requests to | |
247 // sync. | |
248 syncer::MockNudgeHandler mock_nudge_handler_; | |
249 | |
250 bool is_processor_disconnected_; | |
251 }; | |
252 | |
253 ModelTypeWorkerTest::ModelTypeWorkerTest() | |
254 : foreign_encryption_key_index_(0), | |
255 update_encryption_filter_index_(0), | |
256 mock_type_processor_(NULL), | |
257 mock_server_(kModelType), | |
258 is_processor_disconnected_(false) {} | |
259 | |
260 ModelTypeWorkerTest::~ModelTypeWorkerTest() {} | |
261 | |
262 void ModelTypeWorkerTest::FirstInitialize() { | |
263 sync_pb::DataTypeState initial_state; | |
264 initial_state.mutable_progress_marker()->set_data_type_id( | |
265 GetSpecificsFieldNumberFromModelType(kModelType)); | |
266 | |
267 InitializeWithState(initial_state, UpdateResponseDataList()); | |
268 } | |
269 | |
270 void ModelTypeWorkerTest::NormalInitialize() { | |
271 InitializeWithPendingUpdates(UpdateResponseDataList()); | |
272 } | |
273 | |
274 void ModelTypeWorkerTest::InitializeWithPendingUpdates( | |
275 const UpdateResponseDataList& initial_pending_updates) { | |
276 sync_pb::DataTypeState initial_state; | |
277 initial_state.mutable_progress_marker()->set_data_type_id( | |
278 GetSpecificsFieldNumberFromModelType(kModelType)); | |
279 initial_state.mutable_progress_marker()->set_token( | |
280 "some_saved_progress_token"); | |
281 | |
282 initial_state.set_initial_sync_done(true); | |
283 | |
284 InitializeWithState(initial_state, initial_pending_updates); | |
285 | |
286 mock_nudge_handler_.ClearCounters(); | |
287 } | |
288 | |
289 void ModelTypeWorkerTest::InitializeWithState( | |
290 const sync_pb::DataTypeState& state, | |
291 const UpdateResponseDataList& initial_pending_updates) { | |
292 DCHECK(!worker_); | |
293 | |
294 // We don't get to own this object. The |worker_| keeps a unique_ptr to it. | |
295 mock_type_processor_ = new MockModelTypeProcessor(); | |
296 mock_type_processor_->SetDisconnectCallback(base::Bind( | |
297 &ModelTypeWorkerTest::DisconnectProcessor, base::Unretained(this))); | |
298 std::unique_ptr<ModelTypeProcessor> proxy(mock_type_processor_); | |
299 | |
300 std::unique_ptr<Cryptographer> cryptographer_copy; | |
301 if (cryptographer_) { | |
302 cryptographer_copy.reset(new Cryptographer(*cryptographer_)); | |
303 } | |
304 | |
305 // TODO(maxbogue): crbug.com/529498: Inject pending updates somehow. | |
306 worker_.reset(new ModelTypeWorker(kModelType, state, | |
307 std::move(cryptographer_copy), | |
308 &mock_nudge_handler_, std::move(proxy))); | |
309 } | |
310 | |
311 void ModelTypeWorkerTest::NewForeignEncryptionKey() { | |
312 if (!cryptographer_) { | |
313 cryptographer_.reset(new Cryptographer(&fake_encryptor_)); | |
314 } | |
315 | |
316 foreign_encryption_key_index_++; | |
317 | |
318 sync_pb::NigoriKeyBag bag; | |
319 | |
320 for (int i = 0; i <= foreign_encryption_key_index_; ++i) { | |
321 Nigori nigori; | |
322 KeyParams params = GetNthKeyParams(i); | |
323 nigori.InitByDerivation(params.hostname, params.username, params.password); | |
324 | |
325 sync_pb::NigoriKey* key = bag.add_key(); | |
326 | |
327 key->set_name(GetNigoriName(nigori)); | |
328 nigori.ExportKeys(key->mutable_user_key(), key->mutable_encryption_key(), | |
329 key->mutable_mac_key()); | |
330 } | |
331 | |
332 // Re-create the last nigori from that loop. | |
333 Nigori last_nigori; | |
334 KeyParams params = GetNthKeyParams(foreign_encryption_key_index_); | |
335 last_nigori.InitByDerivation(params.hostname, params.username, | |
336 params.password); | |
337 | |
338 // Serialize and encrypt the bag with the last nigori. | |
339 std::string serialized_bag; | |
340 bag.SerializeToString(&serialized_bag); | |
341 | |
342 sync_pb::EncryptedData encrypted; | |
343 encrypted.set_key_name(GetNigoriName(last_nigori)); | |
344 last_nigori.Encrypt(serialized_bag, encrypted.mutable_blob()); | |
345 | |
346 // Update the cryptographer with new pending keys. | |
347 cryptographer_->SetPendingKeys(encrypted); | |
348 | |
349 // Update the worker with the latest cryptographer. | |
350 if (worker_) { | |
351 worker_->UpdateCryptographer( | |
352 base::WrapUnique(new Cryptographer(*cryptographer_))); | |
353 } | |
354 } | |
355 | |
356 void ModelTypeWorkerTest::UpdateLocalCryptographer() { | |
357 if (!cryptographer_) { | |
358 cryptographer_.reset(new Cryptographer(&fake_encryptor_)); | |
359 } | |
360 | |
361 KeyParams params = GetNthKeyParams(foreign_encryption_key_index_); | |
362 bool success = cryptographer_->DecryptPendingKeys(params); | |
363 DCHECK(success); | |
364 | |
365 // Update the worker with the latest cryptographer. | |
366 if (worker_) { | |
367 worker_->UpdateCryptographer( | |
368 base::WrapUnique(new Cryptographer(*cryptographer_))); | |
369 } | |
370 } | |
371 | |
372 void ModelTypeWorkerTest::SetUpdateEncryptionFilter(int n) { | |
373 update_encryption_filter_index_ = n; | |
374 } | |
375 | |
376 void ModelTypeWorkerTest::CommitRequest(const std::string& name, | |
377 const std::string& value) { | |
378 const std::string tag_hash = GenerateTagHash(name); | |
379 CommitRequestData data = mock_type_processor_->CommitRequest( | |
380 tag_hash, GenerateSpecifics(name, value)); | |
381 CommitRequestDataList list; | |
382 list.push_back(data); | |
383 worker_->EnqueueForCommit(list); | |
384 } | |
385 | |
386 void ModelTypeWorkerTest::DeleteRequest(const std::string& tag) { | |
387 const std::string tag_hash = GenerateTagHash(tag); | |
388 CommitRequestData data = mock_type_processor_->DeleteRequest(tag_hash); | |
389 CommitRequestDataList list; | |
390 list.push_back(data); | |
391 worker_->EnqueueForCommit(list); | |
392 } | |
393 | |
394 void ModelTypeWorkerTest::TriggerTypeRootUpdateFromServer() { | |
395 sync_pb::SyncEntity entity = mock_server_.TypeRootUpdate(); | |
396 SyncEntityList entity_list; | |
397 entity_list.push_back(&entity); | |
398 | |
399 StatusController dummy_status; | |
400 | |
401 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(), | |
402 mock_server_.GetContext(), entity_list, | |
403 &dummy_status); | |
404 worker_->PassiveApplyUpdates(&dummy_status); | |
405 } | |
406 | |
407 void ModelTypeWorkerTest::TriggerPartialUpdateFromServer( | |
408 int64_t version_offset, | |
409 const std::string& tag, | |
410 const std::string& value) { | |
411 sync_pb::SyncEntity entity = mock_server_.UpdateFromServer( | |
412 version_offset, GenerateTagHash(tag), GenerateSpecifics(tag, value)); | |
413 | |
414 if (update_encryption_filter_index_ != 0) { | |
415 EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_), | |
416 entity.mutable_specifics()); | |
417 } | |
418 | |
419 SyncEntityList entity_list; | |
420 entity_list.push_back(&entity); | |
421 | |
422 StatusController dummy_status; | |
423 | |
424 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(), | |
425 mock_server_.GetContext(), entity_list, | |
426 &dummy_status); | |
427 } | |
428 | |
429 void ModelTypeWorkerTest::TriggerUpdateFromServer(int64_t version_offset, | |
430 const std::string& tag, | |
431 const std::string& value) { | |
432 TriggerPartialUpdateFromServer(version_offset, tag, value); | |
433 StatusController dummy_status; | |
434 worker_->ApplyUpdates(&dummy_status); | |
435 } | |
436 | |
437 void ModelTypeWorkerTest::DeliverRawUpdates(const SyncEntityList& list) { | |
438 StatusController dummy_status; | |
439 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(), | |
440 mock_server_.GetContext(), list, | |
441 &dummy_status); | |
442 worker_->ApplyUpdates(&dummy_status); | |
443 } | |
444 | |
445 void ModelTypeWorkerTest::TriggerTombstoneFromServer(int64_t version_offset, | |
446 const std::string& tag) { | |
447 sync_pb::SyncEntity entity = | |
448 mock_server_.TombstoneFromServer(version_offset, GenerateTagHash(tag)); | |
449 | |
450 if (update_encryption_filter_index_ != 0) { | |
451 EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_), | |
452 entity.mutable_specifics()); | |
453 } | |
454 | |
455 SyncEntityList entity_list; | |
456 entity_list.push_back(&entity); | |
457 | |
458 StatusController dummy_status; | |
459 | |
460 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(), | |
461 mock_server_.GetContext(), entity_list, | |
462 &dummy_status); | |
463 worker_->ApplyUpdates(&dummy_status); | |
464 } | |
465 | |
466 void ModelTypeWorkerTest::SetModelThreadIsSynchronous(bool is_synchronous) { | |
467 mock_type_processor_->SetSynchronousExecution(is_synchronous); | |
468 } | |
469 | |
470 void ModelTypeWorkerTest::PumpModelThread() { | |
471 mock_type_processor_->RunQueuedTasks(); | |
472 } | |
473 | |
474 bool ModelTypeWorkerTest::WillCommit() { | |
475 std::unique_ptr<CommitContribution> contribution( | |
476 worker_->GetContribution(INT_MAX)); | |
477 | |
478 if (contribution) { | |
479 contribution->CleanUp(); // Gracefully abort the commit. | |
480 return true; | |
481 } else { | |
482 return false; | |
483 } | |
484 } | |
485 | |
486 // Conveniently, this is all one big synchronous operation. The sync thread | |
487 // remains blocked while the commit is in progress, so we don't need to worry | |
488 // about other tasks being run between the time when the commit request is | |
489 // issued and the time when the commit response is received. | |
490 void ModelTypeWorkerTest::DoSuccessfulCommit() { | |
491 DCHECK(WillCommit()); | |
492 std::unique_ptr<CommitContribution> contribution( | |
493 worker_->GetContribution(INT_MAX)); | |
494 | |
495 sync_pb::ClientToServerMessage message; | |
496 contribution->AddToCommitMessage(&message); | |
497 | |
498 sync_pb::ClientToServerResponse response = | |
499 mock_server_.DoSuccessfulCommit(message); | |
500 | |
501 StatusController dummy_status; | |
502 contribution->ProcessCommitResponse(response, &dummy_status); | |
503 contribution->CleanUp(); | |
504 } | |
505 | |
506 void ModelTypeWorkerTest::DisconnectProcessor() { | |
507 DCHECK(!is_processor_disconnected_); | |
508 is_processor_disconnected_ = true; | |
509 } | |
510 | |
511 bool ModelTypeWorkerTest::IsProcessorDisconnected() { | |
512 return is_processor_disconnected_; | |
513 } | |
514 | |
515 void ModelTypeWorkerTest::ResetWorker() { | |
516 worker_.reset(); | |
517 } | |
518 | |
519 size_t ModelTypeWorkerTest::GetNumCommitMessagesOnServer() const { | |
520 return mock_server_.GetNumCommitMessages(); | |
521 } | |
522 | |
523 sync_pb::ClientToServerMessage ModelTypeWorkerTest::GetNthCommitMessageOnServer( | |
524 size_t n) const { | |
525 DCHECK_LT(n, GetNumCommitMessagesOnServer()); | |
526 return mock_server_.GetNthCommitMessage(n); | |
527 } | |
528 | |
529 bool ModelTypeWorkerTest::HasCommitEntityOnServer( | |
530 const std::string& tag) const { | |
531 const std::string tag_hash = GenerateTagHash(tag); | |
532 return mock_server_.HasCommitEntity(tag_hash); | |
533 } | |
534 | |
535 sync_pb::SyncEntity ModelTypeWorkerTest::GetLatestCommitEntityOnServer( | |
536 const std::string& tag) const { | |
537 DCHECK(HasCommitEntityOnServer(tag)); | |
538 const std::string tag_hash = GenerateTagHash(tag); | |
539 return mock_server_.GetLastCommittedEntity(tag_hash); | |
540 } | |
541 | |
542 size_t ModelTypeWorkerTest::GetNumModelThreadUpdateResponses() const { | |
543 return mock_type_processor_->GetNumUpdateResponses(); | |
544 } | |
545 | |
546 UpdateResponseDataList ModelTypeWorkerTest::GetNthModelThreadUpdateResponse( | |
547 size_t n) const { | |
548 DCHECK_LT(n, GetNumModelThreadUpdateResponses()); | |
549 return mock_type_processor_->GetNthUpdateResponse(n); | |
550 } | |
551 | |
552 sync_pb::DataTypeState ModelTypeWorkerTest::GetNthModelThreadUpdateState( | |
553 size_t n) const { | |
554 DCHECK_LT(n, GetNumModelThreadUpdateResponses()); | |
555 return mock_type_processor_->GetNthTypeStateReceivedInUpdateResponse(n); | |
556 } | |
557 | |
558 bool ModelTypeWorkerTest::HasUpdateResponseOnModelThread( | |
559 const std::string& tag) const { | |
560 const std::string tag_hash = GenerateTagHash(tag); | |
561 return mock_type_processor_->HasUpdateResponse(tag_hash); | |
562 } | |
563 | |
564 UpdateResponseData ModelTypeWorkerTest::GetUpdateResponseOnModelThread( | |
565 const std::string& tag) const { | |
566 const std::string tag_hash = GenerateTagHash(tag); | |
567 return mock_type_processor_->GetUpdateResponse(tag_hash); | |
568 } | |
569 | |
570 size_t ModelTypeWorkerTest::GetNumModelThreadCommitResponses() const { | |
571 return mock_type_processor_->GetNumCommitResponses(); | |
572 } | |
573 | |
574 CommitResponseDataList ModelTypeWorkerTest::GetNthModelThreadCommitResponse( | |
575 size_t n) const { | |
576 DCHECK_LT(n, GetNumModelThreadCommitResponses()); | |
577 return mock_type_processor_->GetNthCommitResponse(n); | |
578 } | |
579 | |
580 sync_pb::DataTypeState ModelTypeWorkerTest::GetNthModelThreadCommitState( | |
581 size_t n) const { | |
582 DCHECK_LT(n, GetNumModelThreadCommitResponses()); | |
583 return mock_type_processor_->GetNthTypeStateReceivedInCommitResponse(n); | |
584 } | |
585 | |
586 bool ModelTypeWorkerTest::HasCommitResponseOnModelThread( | |
587 const std::string& tag) const { | |
588 const std::string tag_hash = GenerateTagHash(tag); | |
589 return mock_type_processor_->HasCommitResponse(tag_hash); | |
590 } | |
591 | |
592 CommitResponseData ModelTypeWorkerTest::GetCommitResponseOnModelThread( | |
593 const std::string& tag) const { | |
594 DCHECK(HasCommitResponseOnModelThread(tag)); | |
595 const std::string tag_hash = GenerateTagHash(tag); | |
596 return mock_type_processor_->GetCommitResponse(tag_hash); | |
597 } | |
598 | |
599 int ModelTypeWorkerTest::GetNumCommitNudges() const { | |
600 return mock_nudge_handler_.GetNumCommitNudges(); | |
601 } | |
602 | |
603 int ModelTypeWorkerTest::GetNumInitialDownloadNudges() const { | |
604 return mock_nudge_handler_.GetNumInitialDownloadNudges(); | |
605 } | |
606 | |
607 std::string ModelTypeWorkerTest::GetLocalCryptographerKeyName() const { | |
608 if (!cryptographer_) { | |
609 return std::string(); | |
610 } | |
611 | |
612 return cryptographer_->GetDefaultNigoriKeyName(); | |
613 } | |
614 | |
615 // static. | |
616 std::string ModelTypeWorkerTest::GenerateTagHash(const std::string& tag) { | |
617 const std::string& client_tag_hash = | |
618 syncer::syncable::GenerateSyncableHash(kModelType, tag); | |
619 return client_tag_hash; | |
620 } | |
621 | |
622 // static. | |
623 sync_pb::EntitySpecifics ModelTypeWorkerTest::GenerateSpecifics( | |
624 const std::string& tag, | |
625 const std::string& value) { | |
626 sync_pb::EntitySpecifics specifics; | |
627 specifics.mutable_preference()->set_name(tag); | |
628 specifics.mutable_preference()->set_value(value); | |
629 return specifics; | |
630 } | |
631 | |
632 // static. | |
633 std::string ModelTypeWorkerTest::GetNigoriName(const Nigori& nigori) { | |
634 std::string name; | |
635 if (!nigori.Permute(Nigori::Password, kNigoriKeyName, &name)) { | |
636 NOTREACHED(); | |
637 return std::string(); | |
638 } | |
639 | |
640 return name; | |
641 } | |
642 | |
643 // static. | |
644 KeyParams ModelTypeWorkerTest::GetNthKeyParams(int n) { | |
645 KeyParams params; | |
646 params.hostname = std::string("localhost"); | |
647 params.username = std::string("userX"); | |
648 params.password = base::StringPrintf("pw%02d", n); | |
649 return params; | |
650 } | |
651 | |
652 // static. | |
653 void ModelTypeWorkerTest::EncryptUpdate(const KeyParams& params, | |
654 sync_pb::EntitySpecifics* specifics) { | |
655 Nigori nigori; | |
656 nigori.InitByDerivation(params.hostname, params.username, params.password); | |
657 | |
658 sync_pb::EntitySpecifics original_specifics = *specifics; | |
659 std::string plaintext; | |
660 original_specifics.SerializeToString(&plaintext); | |
661 | |
662 std::string encrypted; | |
663 nigori.Encrypt(plaintext, &encrypted); | |
664 | |
665 specifics->Clear(); | |
666 AddDefaultFieldValue(kModelType, specifics); | |
667 specifics->mutable_encrypted()->set_key_name(GetNigoriName(nigori)); | |
668 specifics->mutable_encrypted()->set_blob(encrypted); | |
669 } | |
670 | |
671 // Requests a commit and verifies the messages sent to the client and server as | |
672 // a result. | |
673 // | |
674 // This test performs sanity checks on most of the fields in these messages. | |
675 // For the most part this is checking that the test code behaves as expected | |
676 // and the |worker_| doesn't mess up its simple task of moving around these | |
677 // values. It makes sense to have one or two tests that are this thorough, but | |
678 // we shouldn't be this verbose in all tests. | |
679 TEST_F(ModelTypeWorkerTest, SimpleCommit) { | |
680 NormalInitialize(); | |
681 | |
682 EXPECT_FALSE(WillCommit()); | |
683 EXPECT_EQ(0U, GetNumCommitMessagesOnServer()); | |
684 EXPECT_EQ(0U, GetNumModelThreadCommitResponses()); | |
685 | |
686 CommitRequest("tag1", "value1"); | |
687 | |
688 EXPECT_EQ(1, GetNumCommitNudges()); | |
689 | |
690 ASSERT_TRUE(WillCommit()); | |
691 DoSuccessfulCommit(); | |
692 | |
693 const std::string& client_tag_hash = GenerateTagHash("tag1"); | |
694 | |
695 // Exhaustively verify the SyncEntity sent in the commit message. | |
696 ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); | |
697 EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); | |
698 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); | |
699 const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1"); | |
700 EXPECT_FALSE(entity.id_string().empty()); | |
701 EXPECT_EQ(0, entity.version()); | |
702 EXPECT_NE(0, entity.mtime()); | |
703 EXPECT_NE(0, entity.ctime()); | |
704 EXPECT_FALSE(entity.name().empty()); | |
705 EXPECT_EQ(client_tag_hash, entity.client_defined_unique_tag()); | |
706 EXPECT_EQ("tag1", entity.specifics().preference().name()); | |
707 EXPECT_FALSE(entity.deleted()); | |
708 EXPECT_EQ("value1", entity.specifics().preference().value()); | |
709 | |
710 // Exhaustively verify the commit response returned to the model thread. | |
711 ASSERT_EQ(1U, GetNumModelThreadCommitResponses()); | |
712 EXPECT_EQ(1U, GetNthModelThreadCommitResponse(0).size()); | |
713 ASSERT_TRUE(HasCommitResponseOnModelThread("tag1")); | |
714 const CommitResponseData& commit_response = | |
715 GetCommitResponseOnModelThread("tag1"); | |
716 | |
717 // The ID changes in a commit response to initial commit. | |
718 EXPECT_FALSE(commit_response.id.empty()); | |
719 EXPECT_NE(entity.id_string(), commit_response.id); | |
720 | |
721 EXPECT_EQ(client_tag_hash, commit_response.client_tag_hash); | |
722 EXPECT_LT(0, commit_response.response_version); | |
723 EXPECT_LT(0, commit_response.sequence_number); | |
724 EXPECT_FALSE(commit_response.specifics_hash.empty()); | |
725 } | |
726 | |
727 TEST_F(ModelTypeWorkerTest, SimpleDelete) { | |
728 NormalInitialize(); | |
729 | |
730 // We can't delete an entity that was never committed. | |
731 // Step 1 is to create and commit a new entity. | |
732 CommitRequest("tag1", "value1"); | |
733 EXPECT_EQ(1, GetNumCommitNudges()); | |
734 ASSERT_TRUE(WillCommit()); | |
735 DoSuccessfulCommit(); | |
736 | |
737 ASSERT_TRUE(HasCommitResponseOnModelThread("tag1")); | |
738 const CommitResponseData& initial_commit_response = | |
739 GetCommitResponseOnModelThread("tag1"); | |
740 int64_t base_version = initial_commit_response.response_version; | |
741 | |
742 // Now that we have an entity, we can delete it. | |
743 DeleteRequest("tag1"); | |
744 ASSERT_TRUE(WillCommit()); | |
745 DoSuccessfulCommit(); | |
746 | |
747 // Verify the SyncEntity sent in the commit message. | |
748 ASSERT_EQ(2U, GetNumCommitMessagesOnServer()); | |
749 EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size()); | |
750 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); | |
751 const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1"); | |
752 EXPECT_FALSE(entity.id_string().empty()); | |
753 EXPECT_EQ(GenerateTagHash("tag1"), entity.client_defined_unique_tag()); | |
754 EXPECT_EQ(base_version, entity.version()); | |
755 EXPECT_TRUE(entity.deleted()); | |
756 | |
757 // Deletions should contain enough specifics to identify the type. | |
758 EXPECT_TRUE(entity.has_specifics()); | |
759 EXPECT_EQ(kModelType, syncer::GetModelTypeFromSpecifics(entity.specifics())); | |
760 | |
761 // Verify the commit response returned to the model thread. | |
762 ASSERT_EQ(2U, GetNumModelThreadCommitResponses()); | |
763 EXPECT_EQ(1U, GetNthModelThreadCommitResponse(1).size()); | |
764 ASSERT_TRUE(HasCommitResponseOnModelThread("tag1")); | |
765 const CommitResponseData& commit_response = | |
766 GetCommitResponseOnModelThread("tag1"); | |
767 | |
768 EXPECT_EQ(entity.id_string(), commit_response.id); | |
769 EXPECT_EQ(entity.client_defined_unique_tag(), | |
770 commit_response.client_tag_hash); | |
771 EXPECT_EQ(entity.version(), commit_response.response_version); | |
772 } | |
773 | |
774 // The server doesn't like it when we try to delete an entity it's never heard | |
775 // of before. This test helps ensure we avoid that scenario. | |
776 TEST_F(ModelTypeWorkerTest, NoDeleteUncommitted) { | |
777 NormalInitialize(); | |
778 | |
779 // Request the commit of a new, never-before-seen item. | |
780 CommitRequest("tag1", "value1"); | |
781 EXPECT_TRUE(WillCommit()); | |
782 EXPECT_EQ(1, GetNumCommitNudges()); | |
783 | |
784 // Request a deletion of that item before we've had a chance to commit it. | |
785 DeleteRequest("tag1"); | |
786 EXPECT_FALSE(WillCommit()); | |
787 EXPECT_EQ(2, GetNumCommitNudges()); | |
788 } | |
789 | |
790 // Verifies the sending of an "initial sync done" signal. | |
791 TEST_F(ModelTypeWorkerTest, SendInitialSyncDone) { | |
792 FirstInitialize(); // Initialize with no saved sync state. | |
793 EXPECT_EQ(0U, GetNumModelThreadUpdateResponses()); | |
794 EXPECT_EQ(1, GetNumInitialDownloadNudges()); | |
795 | |
796 EXPECT_FALSE(worker()->IsInitialSyncEnded()); | |
797 | |
798 // Receive an update response that contains only the type root node. | |
799 TriggerTypeRootUpdateFromServer(); | |
800 | |
801 // One update triggered by ApplyUpdates, which the worker interprets to mean | |
802 // "initial sync done". This triggers a model thread update, too. | |
803 EXPECT_EQ(1U, GetNumModelThreadUpdateResponses()); | |
804 | |
805 // The update contains no entities. | |
806 EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(0).size()); | |
807 | |
808 const sync_pb::DataTypeState& state = GetNthModelThreadUpdateState(0); | |
809 EXPECT_FALSE(state.progress_marker().token().empty()); | |
810 EXPECT_TRUE(state.initial_sync_done()); | |
811 EXPECT_TRUE(worker()->IsInitialSyncEnded()); | |
812 } | |
813 | |
814 // Commit two new entities in two separate commit messages. | |
815 TEST_F(ModelTypeWorkerTest, TwoNewItemsCommittedSeparately) { | |
816 NormalInitialize(); | |
817 | |
818 // Commit the first of two entities. | |
819 CommitRequest("tag1", "value1"); | |
820 EXPECT_EQ(1, GetNumCommitNudges()); | |
821 ASSERT_TRUE(WillCommit()); | |
822 DoSuccessfulCommit(); | |
823 ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); | |
824 EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); | |
825 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); | |
826 const sync_pb::SyncEntity& tag1_entity = | |
827 GetLatestCommitEntityOnServer("tag1"); | |
828 | |
829 // Commit the second of two entities. | |
830 CommitRequest("tag2", "value2"); | |
831 EXPECT_EQ(2, GetNumCommitNudges()); | |
832 ASSERT_TRUE(WillCommit()); | |
833 DoSuccessfulCommit(); | |
834 ASSERT_EQ(2U, GetNumCommitMessagesOnServer()); | |
835 EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size()); | |
836 ASSERT_TRUE(HasCommitEntityOnServer("tag2")); | |
837 const sync_pb::SyncEntity& tag2_entity = | |
838 GetLatestCommitEntityOnServer("tag2"); | |
839 | |
840 EXPECT_FALSE(WillCommit()); | |
841 | |
842 // The IDs assigned by the |worker_| should be unique. | |
843 EXPECT_NE(tag1_entity.id_string(), tag2_entity.id_string()); | |
844 | |
845 // Check that the committed specifics values are sane. | |
846 EXPECT_EQ(tag1_entity.specifics().preference().value(), "value1"); | |
847 EXPECT_EQ(tag2_entity.specifics().preference().value(), "value2"); | |
848 | |
849 // There should have been two separate commit responses sent to the model | |
850 // thread. They should be uninteresting, so we don't bother inspecting them. | |
851 EXPECT_EQ(2U, GetNumModelThreadCommitResponses()); | |
852 } | |
853 | |
854 // Test normal update receipt code path. | |
855 TEST_F(ModelTypeWorkerTest, ReceiveUpdates) { | |
856 NormalInitialize(); | |
857 | |
858 const std::string& tag_hash = GenerateTagHash("tag1"); | |
859 | |
860 TriggerUpdateFromServer(10, "tag1", "value1"); | |
861 | |
862 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); | |
863 UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0); | |
864 ASSERT_EQ(1U, updates_list.size()); | |
865 | |
866 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); | |
867 UpdateResponseData update = GetUpdateResponseOnModelThread("tag1"); | |
868 const EntityData& entity = update.entity.value(); | |
869 | |
870 EXPECT_FALSE(entity.id.empty()); | |
871 EXPECT_EQ(tag_hash, entity.client_tag_hash); | |
872 EXPECT_LT(0, update.response_version); | |
873 EXPECT_FALSE(entity.creation_time.is_null()); | |
874 EXPECT_FALSE(entity.modification_time.is_null()); | |
875 EXPECT_FALSE(entity.non_unique_name.empty()); | |
876 EXPECT_FALSE(entity.is_deleted()); | |
877 EXPECT_EQ("tag1", entity.specifics.preference().name()); | |
878 EXPECT_EQ("value1", entity.specifics.preference().value()); | |
879 } | |
880 | |
881 // Test that an update download coming in multiple parts gets accumulated into | |
882 // one call to the processor. | |
883 TEST_F(ModelTypeWorkerTest, ReceiveMultiPartUpdates) { | |
884 NormalInitialize(); | |
885 | |
886 // A partial update response doesn't pass anything to the processor. | |
887 TriggerPartialUpdateFromServer(10, "tag1", "value1"); | |
888 ASSERT_EQ(0U, GetNumModelThreadUpdateResponses()); | |
889 | |
890 // Trigger the completion of the update. | |
891 TriggerUpdateFromServer(10, "tag2", "value2"); | |
892 | |
893 // Processor received exactly one update with entities in the right order. | |
894 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); | |
895 UpdateResponseDataList updates = GetNthModelThreadUpdateResponse(0); | |
896 ASSERT_EQ(2U, updates.size()); | |
897 EXPECT_EQ(GenerateTagHash("tag1"), updates[0].entity->client_tag_hash); | |
898 EXPECT_EQ(GenerateTagHash("tag2"), updates[1].entity->client_tag_hash); | |
899 | |
900 // A subsequent update doesn't pass the same entities again. | |
901 TriggerUpdateFromServer(10, "tag3", "value3"); | |
902 ASSERT_EQ(2U, GetNumModelThreadUpdateResponses()); | |
903 updates = GetNthModelThreadUpdateResponse(1); | |
904 ASSERT_EQ(1U, updates.size()); | |
905 EXPECT_EQ(GenerateTagHash("tag3"), updates[0].entity->client_tag_hash); | |
906 } | |
907 | |
908 // Test commit of encrypted updates. | |
909 TEST_F(ModelTypeWorkerTest, EncryptedCommit) { | |
910 NormalInitialize(); | |
911 | |
912 ASSERT_EQ(0U, GetNumModelThreadUpdateResponses()); | |
913 | |
914 NewForeignEncryptionKey(); | |
915 UpdateLocalCryptographer(); | |
916 | |
917 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); | |
918 EXPECT_EQ(GetLocalCryptographerKeyName(), | |
919 GetNthModelThreadUpdateState(0).encryption_key_name()); | |
920 | |
921 // Normal commit request stuff. | |
922 CommitRequest("tag1", "value1"); | |
923 DoSuccessfulCommit(); | |
924 ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); | |
925 EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); | |
926 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); | |
927 const sync_pb::SyncEntity& tag1_entity = | |
928 GetLatestCommitEntityOnServer("tag1"); | |
929 | |
930 EXPECT_TRUE(tag1_entity.specifics().has_encrypted()); | |
931 | |
932 // The title should be overwritten. | |
933 EXPECT_EQ(tag1_entity.name(), "encrypted"); | |
934 | |
935 // The type should be set, but there should be no non-encrypted contents. | |
936 EXPECT_TRUE(tag1_entity.specifics().has_preference()); | |
937 EXPECT_FALSE(tag1_entity.specifics().preference().has_name()); | |
938 EXPECT_FALSE(tag1_entity.specifics().preference().has_value()); | |
939 } | |
940 | |
941 // Test items are not committed when encryption is required but unavailable. | |
942 TEST_F(ModelTypeWorkerTest, EncryptionBlocksCommits) { | |
943 NormalInitialize(); | |
944 | |
945 CommitRequest("tag1", "value1"); | |
946 EXPECT_TRUE(WillCommit()); | |
947 | |
948 // We know encryption is in use on this account, but don't have the necessary | |
949 // encryption keys. The worker should refuse to commit. | |
950 NewForeignEncryptionKey(); | |
951 EXPECT_FALSE(WillCommit()); | |
952 | |
953 // Once the cryptographer is returned to a normal state, we should be able to | |
954 // commit again. | |
955 EXPECT_EQ(1, GetNumCommitNudges()); | |
956 UpdateLocalCryptographer(); | |
957 EXPECT_EQ(2, GetNumCommitNudges()); | |
958 EXPECT_TRUE(WillCommit()); | |
959 | |
960 // Verify the committed entity was properly encrypted. | |
961 DoSuccessfulCommit(); | |
962 ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); | |
963 EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); | |
964 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); | |
965 const sync_pb::SyncEntity& tag1_entity = | |
966 GetLatestCommitEntityOnServer("tag1"); | |
967 EXPECT_TRUE(tag1_entity.specifics().has_encrypted()); | |
968 EXPECT_EQ(tag1_entity.name(), "encrypted"); | |
969 EXPECT_TRUE(tag1_entity.specifics().has_preference()); | |
970 EXPECT_FALSE(tag1_entity.specifics().preference().has_name()); | |
971 EXPECT_FALSE(tag1_entity.specifics().preference().has_value()); | |
972 } | |
973 | |
974 // Test the receipt of decryptable entities. | |
975 TEST_F(ModelTypeWorkerTest, ReceiveDecryptableEntities) { | |
976 NormalInitialize(); | |
977 | |
978 // Create a new Nigori and allow the cryptographer to decrypt it. | |
979 NewForeignEncryptionKey(); | |
980 UpdateLocalCryptographer(); | |
981 | |
982 // First, receive an unencrypted entry. | |
983 TriggerUpdateFromServer(10, "tag1", "value1"); | |
984 | |
985 // Test some basic properties regarding the update. | |
986 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); | |
987 UpdateResponseData update1 = GetUpdateResponseOnModelThread("tag1"); | |
988 EXPECT_EQ("tag1", update1.entity->specifics.preference().name()); | |
989 EXPECT_EQ("value1", update1.entity->specifics.preference().value()); | |
990 EXPECT_TRUE(update1.encryption_key_name.empty()); | |
991 | |
992 // Set received updates to be encrypted using the new nigori. | |
993 SetUpdateEncryptionFilter(1); | |
994 | |
995 // This next update will be encrypted. | |
996 TriggerUpdateFromServer(10, "tag2", "value2"); | |
997 | |
998 // Test its basic features and the value of encryption_key_name. | |
999 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag2")); | |
1000 UpdateResponseData update2 = GetUpdateResponseOnModelThread("tag2"); | |
1001 EXPECT_EQ("tag2", update2.entity->specifics.preference().name()); | |
1002 EXPECT_EQ("value2", update2.entity->specifics.preference().value()); | |
1003 EXPECT_FALSE(update2.encryption_key_name.empty()); | |
1004 } | |
1005 | |
1006 // Test initializing a CommitQueue with a cryptographer at startup. | |
1007 TEST_F(ModelTypeWorkerTest, InitializeWithCryptographer) { | |
1008 // Set up some encryption state. | |
1009 NewForeignEncryptionKey(); | |
1010 UpdateLocalCryptographer(); | |
1011 | |
1012 // Then initialize. | |
1013 NormalInitialize(); | |
1014 | |
1015 // The worker should tell the model thread about encryption as soon as | |
1016 // possible, so that it will have the chance to re-encrypt local data if | |
1017 // necessary. | |
1018 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); | |
1019 EXPECT_EQ(GetLocalCryptographerKeyName(), | |
1020 GetNthModelThreadUpdateState(0).encryption_key_name()); | |
1021 } | |
1022 | |
1023 // Receive updates that are initially undecryptable, then ensure they get | |
1024 // delivered to the model thread when decryption becomes possible. | |
1025 TEST_F(ModelTypeWorkerTest, ReceiveUndecryptableEntries) { | |
1026 NormalInitialize(); | |
1027 | |
1028 // Receive a new foreign encryption key that we can't decrypt. | |
1029 NewForeignEncryptionKey(); | |
1030 | |
1031 // Receive an encrypted with that new key, which we can't access. | |
1032 SetUpdateEncryptionFilter(1); | |
1033 TriggerUpdateFromServer(10, "tag1", "value1"); | |
1034 | |
1035 // At this point, the cryptographer does not have access to the key, so the | |
1036 // updates will be undecryptable. They'll be transfered to the model thread | |
1037 // for safe-keeping as pending updates. | |
1038 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); | |
1039 UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0); | |
1040 EXPECT_EQ(0U, updates_list.size()); | |
1041 | |
1042 // The update will be delivered as soon as decryption becomes possible. | |
1043 UpdateLocalCryptographer(); | |
1044 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); | |
1045 UpdateResponseData update = GetUpdateResponseOnModelThread("tag1"); | |
1046 EXPECT_EQ("tag1", update.entity->specifics.preference().name()); | |
1047 EXPECT_EQ("value1", update.entity->specifics.preference().value()); | |
1048 EXPECT_FALSE(update.encryption_key_name.empty()); | |
1049 } | |
1050 | |
1051 // Ensure that even encrypted updates can cause conflicts. | |
1052 TEST_F(ModelTypeWorkerTest, EncryptedUpdateOverridesPendingCommit) { | |
1053 NormalInitialize(); | |
1054 | |
1055 // Prepeare to commit an item. | |
1056 CommitRequest("tag1", "value1"); | |
1057 EXPECT_TRUE(WillCommit()); | |
1058 | |
1059 // Receive an encrypted update for that item. | |
1060 SetUpdateEncryptionFilter(1); | |
1061 TriggerUpdateFromServer(10, "tag1", "value1"); | |
1062 | |
1063 // The pending commit state should be cleared. | |
1064 EXPECT_FALSE(WillCommit()); | |
1065 | |
1066 // The encrypted update will be delivered to the model thread. | |
1067 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); | |
1068 UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0); | |
1069 EXPECT_EQ(0U, updates_list.size()); | |
1070 } | |
1071 | |
1072 // Test decryption of pending updates saved across a restart. | |
1073 TEST_F(ModelTypeWorkerTest, RestorePendingEntries) { | |
1074 // Create a fake pending update. | |
1075 EntityData entity; | |
1076 entity.client_tag_hash = GenerateTagHash("tag1"); | |
1077 entity.id = "SomeID"; | |
1078 entity.creation_time = | |
1079 base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10); | |
1080 entity.modification_time = | |
1081 base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11); | |
1082 entity.non_unique_name = "encrypted"; | |
1083 entity.specifics = GenerateSpecifics("tag1", "value1"); | |
1084 EncryptUpdate(GetNthKeyParams(1), &(entity.specifics)); | |
1085 | |
1086 UpdateResponseData update; | |
1087 update.entity = entity.PassToPtr(); | |
1088 update.response_version = 100; | |
1089 | |
1090 // Inject the update during CommitQueue initialization. | |
1091 UpdateResponseDataList saved_pending_updates; | |
1092 saved_pending_updates.push_back(update); | |
1093 InitializeWithPendingUpdates(saved_pending_updates); | |
1094 | |
1095 // Update will be undecryptable at first. | |
1096 EXPECT_EQ(0U, GetNumModelThreadUpdateResponses()); | |
1097 ASSERT_FALSE(HasUpdateResponseOnModelThread("tag1")); | |
1098 | |
1099 // Update the cryptographer so it can decrypt that update. | |
1100 NewForeignEncryptionKey(); | |
1101 UpdateLocalCryptographer(); | |
1102 | |
1103 // Verify the item gets decrypted and sent back to the model thread. | |
1104 // TODO(maxbogue): crbug.com/529498: Uncomment when pending updates are | |
1105 // handled by the worker again. | |
1106 // ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); | |
1107 } | |
1108 | |
1109 // Test decryption of pending updates saved across a restart. This test | |
1110 // differs from the previous one in that the restored updates can be decrypted | |
1111 // immediately after the CommitQueue is constructed. | |
1112 TEST_F(ModelTypeWorkerTest, RestoreApplicableEntries) { | |
1113 // Update the cryptographer so it can decrypt that update. | |
1114 NewForeignEncryptionKey(); | |
1115 UpdateLocalCryptographer(); | |
1116 | |
1117 // Create a fake pending update. | |
1118 EntityData entity; | |
1119 entity.client_tag_hash = GenerateTagHash("tag1"); | |
1120 entity.id = "SomeID"; | |
1121 entity.creation_time = | |
1122 base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10); | |
1123 entity.modification_time = | |
1124 base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11); | |
1125 entity.non_unique_name = "encrypted"; | |
1126 | |
1127 entity.specifics = GenerateSpecifics("tag1", "value1"); | |
1128 EncryptUpdate(GetNthKeyParams(1), &(entity.specifics)); | |
1129 | |
1130 UpdateResponseData update; | |
1131 update.entity = entity.PassToPtr(); | |
1132 update.response_version = 100; | |
1133 | |
1134 // Inject the update during CommitQueue initialization. | |
1135 UpdateResponseDataList saved_pending_updates; | |
1136 saved_pending_updates.push_back(update); | |
1137 InitializeWithPendingUpdates(saved_pending_updates); | |
1138 | |
1139 // Verify the item gets decrypted and sent back to the model thread. | |
1140 // TODO(maxbogue): crbug.com/529498: Uncomment when pending updates are | |
1141 // handled by the worker again. | |
1142 // ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); | |
1143 } | |
1144 | |
1145 // Test that undecryptable updates provide sufficient reason to not commit. | |
1146 // | |
1147 // This should be rare in practice. Usually the cryptographer will be in an | |
1148 // unusable state when we receive undecryptable updates, and that alone will be | |
1149 // enough to prevent all commits. | |
1150 TEST_F(ModelTypeWorkerTest, CommitBlockedByPending) { | |
1151 NormalInitialize(); | |
1152 | |
1153 // Prepeare to commit an item. | |
1154 CommitRequest("tag1", "value1"); | |
1155 EXPECT_TRUE(WillCommit()); | |
1156 | |
1157 // Receive an encrypted update for that item. | |
1158 SetUpdateEncryptionFilter(1); | |
1159 TriggerUpdateFromServer(10, "tag1", "value1"); | |
1160 | |
1161 // The pending commit state should be cleared. | |
1162 EXPECT_FALSE(WillCommit()); | |
1163 | |
1164 // The pending update will be delivered to the model thread. | |
1165 HasUpdateResponseOnModelThread("tag1"); | |
1166 | |
1167 // Pretend the update arrived too late to prevent another commit request. | |
1168 CommitRequest("tag1", "value2"); | |
1169 | |
1170 EXPECT_FALSE(WillCommit()); | |
1171 } | |
1172 | |
1173 // Verify that corrupted encrypted updates don't cause crashes. | |
1174 TEST_F(ModelTypeWorkerTest, ReceiveCorruptEncryption) { | |
1175 // Initialize the worker with basic encryption state. | |
1176 NormalInitialize(); | |
1177 NewForeignEncryptionKey(); | |
1178 UpdateLocalCryptographer(); | |
1179 | |
1180 // Manually create an update. | |
1181 sync_pb::SyncEntity entity; | |
1182 entity.set_client_defined_unique_tag(GenerateTagHash("tag1")); | |
1183 entity.set_id_string("SomeID"); | |
1184 entity.set_version(1); | |
1185 entity.set_ctime(1000); | |
1186 entity.set_mtime(1001); | |
1187 entity.set_name("encrypted"); | |
1188 entity.set_deleted(false); | |
1189 | |
1190 // Encrypt it. | |
1191 entity.mutable_specifics()->CopyFrom(GenerateSpecifics("tag1", "value1")); | |
1192 EncryptUpdate(GetNthKeyParams(1), entity.mutable_specifics()); | |
1193 | |
1194 // Replace a few bytes to corrupt it. | |
1195 entity.mutable_specifics()->mutable_encrypted()->mutable_blob()->replace( | |
1196 0, 4, "xyz!"); | |
1197 | |
1198 SyncEntityList entity_list; | |
1199 entity_list.push_back(&entity); | |
1200 | |
1201 // If a corrupt update could trigger a crash, this is where it would happen. | |
1202 DeliverRawUpdates(entity_list); | |
1203 | |
1204 EXPECT_FALSE(HasUpdateResponseOnModelThread("tag1")); | |
1205 | |
1206 // Deliver a non-corrupt update to see if the everything still works. | |
1207 SetUpdateEncryptionFilter(1); | |
1208 TriggerUpdateFromServer(10, "tag1", "value1"); | |
1209 EXPECT_TRUE(HasUpdateResponseOnModelThread("tag1")); | |
1210 } | |
1211 | |
1212 // Test that processor has been disconnected from Sync when worker got | |
1213 // disconnected. | |
1214 TEST_F(ModelTypeWorkerTest, DisconnectProcessorFromSyncTest) { | |
1215 // Initialize the worker with basic state. | |
1216 NormalInitialize(); | |
1217 EXPECT_FALSE(IsProcessorDisconnected()); | |
1218 ResetWorker(); | |
1219 EXPECT_TRUE(IsProcessorDisconnected()); | |
1220 } | |
1221 | |
1222 } // namespace syncer_v2 | |
OLD | NEW |