Sync: store FakeServer as WeakPtr
[chromium-blink-merge.git] / sync / test / fake_server / fake_server.cc
blobe8f8dc02947b551707134ea822b19f8c29ba87be
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.
5 #include "sync/test/fake_server/fake_server.h"
7 #include <algorithm>
8 #include <limits>
9 #include <string>
10 #include <vector>
12 #include "base/basictypes.h"
13 #include "base/guid.h"
14 #include "base/logging.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/stl_util.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/synchronization/lock.h"
21 #include "net/base/net_errors.h"
22 #include "net/http/http_status_code.h"
23 #include "sync/internal_api/public/base/model_type.h"
24 #include "sync/protocol/sync.pb.h"
25 #include "sync/test/fake_server/bookmark_entity.h"
26 #include "sync/test/fake_server/permanent_entity.h"
27 #include "sync/test/fake_server/tombstone_entity.h"
28 #include "sync/test/fake_server/unique_client_entity.h"
30 using std::string;
31 using std::vector;
33 using syncer::GetModelType;
34 using syncer::ModelType;
35 using syncer::ModelTypeSet;
37 namespace fake_server {
39 class FakeServerEntity;
41 namespace {
43 // The default store birthday value.
44 static const char kDefaultStoreBirthday[] = "1234567890";
46 // The default keystore key.
47 static const char kDefaultKeystoreKey[] = "1111111111111111";
49 // Properties of the bookmark bar permanent folder.
50 static const char kBookmarkBarFolderServerTag[] = "bookmark_bar";
51 static const char kBookmarkBarFolderName[] = "Bookmark Bar";
53 // Properties of the other bookmarks permanent folder.
54 static const char kOtherBookmarksFolderServerTag[] = "other_bookmarks";
55 static const char kOtherBookmarksFolderName[] = "Other Bookmarks";
57 // Properties of the synced bookmarks permanent folder.
58 static const char kSyncedBookmarksFolderServerTag[] = "synced_bookmarks";
59 static const char kSyncedBookmarksFolderName[] = "Synced Bookmarks";
61 // A filter used during GetUpdates calls to determine what information to
62 // send back to the client. There is a 1:1 correspondence between any given
63 // GetUpdates call and an UpdateSieve instance.
64 class UpdateSieve {
65 public:
66 ~UpdateSieve() { }
68 // Factory method for creating an UpdateSieve.
69 static scoped_ptr<UpdateSieve> Create(
70 const sync_pb::GetUpdatesMessage& get_updates_message);
72 // Sets the progress markers in |get_updates_response| given the progress
73 // markers from the original GetUpdatesMessage and |new_version| (the latest
74 // version in the entries sent back).
75 void UpdateProgressMarkers(
76 int64 new_version,
77 sync_pb::GetUpdatesResponse* get_updates_response) const {
78 ModelTypeToVersionMap::const_iterator it;
79 for (it = request_from_version_.begin(); it != request_from_version_.end();
80 ++it) {
81 sync_pb::DataTypeProgressMarker* new_marker =
82 get_updates_response->add_new_progress_marker();
83 new_marker->set_data_type_id(
84 GetSpecificsFieldNumberFromModelType(it->first));
86 int64 version = std::max(new_version, it->second);
87 new_marker->set_token(base::Int64ToString(version));
91 // Determines whether the server should send an |entity| to the client as
92 // part of a GetUpdatesResponse.
93 bool ClientWantsItem(FakeServerEntity* entity) const {
94 int64 version = entity->GetVersion();
95 if (version <= min_version_) {
96 return false;
97 } else if (entity->IsDeleted()) {
98 return true;
101 ModelTypeToVersionMap::const_iterator it =
102 request_from_version_.find(entity->GetModelType());
104 return it == request_from_version_.end() ? false : it->second < version;
107 // Returns the minimum version seen across all types.
108 int64 GetMinVersion() const {
109 return min_version_;
112 private:
113 typedef std::map<ModelType, int64> ModelTypeToVersionMap;
115 // Creates an UpdateSieve.
116 UpdateSieve(const ModelTypeToVersionMap request_from_version,
117 const int64 min_version)
118 : request_from_version_(request_from_version),
119 min_version_(min_version) { }
121 // Maps data type IDs to the latest version seen for that type.
122 const ModelTypeToVersionMap request_from_version_;
124 // The minimum version seen among all data types.
125 const int min_version_;
128 scoped_ptr<UpdateSieve> UpdateSieve::Create(
129 const sync_pb::GetUpdatesMessage& get_updates_message) {
130 CHECK_GT(get_updates_message.from_progress_marker_size(), 0)
131 << "A GetUpdates request must have at least one progress marker.";
133 UpdateSieve::ModelTypeToVersionMap request_from_version;
134 int64 min_version = std::numeric_limits<int64>::max();
135 for (int i = 0; i < get_updates_message.from_progress_marker_size(); i++) {
136 sync_pb::DataTypeProgressMarker marker =
137 get_updates_message.from_progress_marker(i);
139 int64 version = 0;
140 // Let the version remain zero if there is no token or an empty token (the
141 // first request for this type).
142 if (marker.has_token() && !marker.token().empty()) {
143 bool parsed = base::StringToInt64(marker.token(), &version);
144 CHECK(parsed) << "Unable to parse progress marker token.";
147 ModelType model_type = syncer::GetModelTypeFromSpecificsFieldNumber(
148 marker.data_type_id());
149 request_from_version[model_type] = version;
151 if (version < min_version)
152 min_version = version;
155 return scoped_ptr<UpdateSieve>(
156 new UpdateSieve(request_from_version, min_version));
159 // Returns whether |entity| is deleted or a folder.
160 bool IsDeletedOrFolder(const FakeServerEntity* entity) {
161 DCHECK(entity);
162 return entity->IsDeleted() || entity->IsFolder();
165 } // namespace
167 FakeServer::FakeServer() : version_(0),
168 store_birthday_(kDefaultStoreBirthday),
169 authenticated_(true),
170 error_type_(sync_pb::SyncEnums::SUCCESS),
171 alternate_triggered_errors_(false),
172 request_counter_(0),
173 network_enabled_(true),
174 weak_ptr_factory_(this) {
175 keystore_keys_.push_back(kDefaultKeystoreKey);
177 const bool create_result = CreateDefaultPermanentItems();
178 DCHECK(create_result) << "Permanent items were not created successfully.";
181 FakeServer::~FakeServer() {
182 DCHECK(thread_checker_.CalledOnValidThread());
183 STLDeleteContainerPairSecondPointers(entities_.begin(), entities_.end());
186 bool FakeServer::CreatePermanentBookmarkFolder(const std::string& server_tag,
187 const std::string& name) {
188 DCHECK(thread_checker_.CalledOnValidThread());
189 FakeServerEntity* entity =
190 PermanentEntity::Create(syncer::BOOKMARKS, server_tag, name,
191 ModelTypeToRootTag(syncer::BOOKMARKS));
192 if (entity == NULL)
193 return false;
195 SaveEntity(entity);
196 return true;
199 bool FakeServer::CreateDefaultPermanentItems() {
200 ModelTypeSet all_types = syncer::ProtocolTypes();
201 for (ModelTypeSet::Iterator it = all_types.First(); it.Good(); it.Inc()) {
202 ModelType model_type = it.Get();
203 FakeServerEntity* top_level_entity =
204 PermanentEntity::CreateTopLevel(model_type);
205 if (top_level_entity == NULL) {
206 return false;
208 SaveEntity(top_level_entity);
210 if (model_type == syncer::BOOKMARKS) {
211 if (!CreatePermanentBookmarkFolder(kBookmarkBarFolderServerTag,
212 kBookmarkBarFolderName))
213 return false;
214 if (!CreatePermanentBookmarkFolder(kOtherBookmarksFolderServerTag,
215 kOtherBookmarksFolderName))
216 return false;
220 return true;
223 void FakeServer::SaveEntity(FakeServerEntity* entity) {
224 delete entities_[entity->GetId()];
225 entity->SetVersion(++version_);
226 entities_[entity->GetId()] = entity;
229 void FakeServer::HandleCommand(const string& request,
230 const base::Closure& completion_closure,
231 int* error_code,
232 int* response_code,
233 std::string* response) {
234 DCHECK(thread_checker_.CalledOnValidThread());
235 if (!network_enabled_) {
236 *error_code = net::ERR_FAILED;
237 *response_code = net::ERR_FAILED;
238 *response = string();
239 completion_closure.Run();
240 return;
242 request_counter_++;
244 if (!authenticated_) {
245 *error_code = 0;
246 *response_code = net::HTTP_UNAUTHORIZED;
247 *response = string();
248 completion_closure.Run();
249 return;
252 sync_pb::ClientToServerMessage message;
253 bool parsed = message.ParseFromString(request);
254 CHECK(parsed) << "Unable to parse the ClientToServerMessage.";
256 sync_pb::ClientToServerResponse response_proto;
258 if (message.has_store_birthday() &&
259 message.store_birthday() != store_birthday_) {
260 response_proto.set_error_code(sync_pb::SyncEnums::NOT_MY_BIRTHDAY);
261 } else if (error_type_ != sync_pb::SyncEnums::SUCCESS &&
262 ShouldSendTriggeredError()) {
263 response_proto.set_error_code(error_type_);
264 } else if (triggered_actionable_error_.get() && ShouldSendTriggeredError()) {
265 sync_pb::ClientToServerResponse_Error* error =
266 response_proto.mutable_error();
267 error->CopyFrom(*(triggered_actionable_error_.get()));
268 } else {
269 bool success = false;
270 switch (message.message_contents()) {
271 case sync_pb::ClientToServerMessage::GET_UPDATES:
272 success = HandleGetUpdatesRequest(message.get_updates(),
273 response_proto.mutable_get_updates());
274 break;
275 case sync_pb::ClientToServerMessage::COMMIT:
276 success = HandleCommitRequest(message.commit(),
277 message.invalidator_client_id(),
278 response_proto.mutable_commit());
279 break;
280 default:
281 *error_code = net::ERR_NOT_IMPLEMENTED;
282 *response_code = 0;
283 *response = string();
284 completion_closure.Run();
285 return;
288 if (!success) {
289 // TODO(pvalenzuela): Add logging here so that tests have more info about
290 // the failure.
291 *error_code = net::ERR_FAILED;
292 *response_code = 0;
293 *response = string();
294 completion_closure.Run();
295 return;
298 response_proto.set_error_code(sync_pb::SyncEnums::SUCCESS);
301 response_proto.set_store_birthday(store_birthday_);
303 *error_code = 0;
304 *response_code = net::HTTP_OK;
305 *response = response_proto.SerializeAsString();
306 completion_closure.Run();
309 bool FakeServer::HandleGetUpdatesRequest(
310 const sync_pb::GetUpdatesMessage& get_updates,
311 sync_pb::GetUpdatesResponse* response) {
312 // TODO(pvalenzuela): Implement batching instead of sending all information
313 // at once.
314 response->set_changes_remaining(0);
316 scoped_ptr<UpdateSieve> sieve = UpdateSieve::Create(get_updates);
318 // This folder is called "Synced Bookmarks" by sync and is renamed
319 // "Mobile Bookmarks" by the mobile client UIs.
320 if (get_updates.create_mobile_bookmarks_folder() &&
321 !CreatePermanentBookmarkFolder(kSyncedBookmarksFolderServerTag,
322 kSyncedBookmarksFolderName)) {
323 return false;
326 bool send_encryption_keys_based_on_nigori = false;
327 int64 max_response_version = 0;
328 for (EntityMap::iterator it = entities_.begin(); it != entities_.end();
329 ++it) {
330 FakeServerEntity* entity = it->second;
331 if (sieve->ClientWantsItem(entity)) {
332 sync_pb::SyncEntity* response_entity = response->add_entries();
333 entity->SerializeAsProto(response_entity);
334 max_response_version = std::max(max_response_version,
335 response_entity->version());
337 if (entity->GetModelType() == syncer::NIGORI) {
338 send_encryption_keys_based_on_nigori =
339 response_entity->specifics().nigori().passphrase_type() ==
340 sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE;
345 if (send_encryption_keys_based_on_nigori ||
346 get_updates.need_encryption_key()) {
347 for (vector<string>::iterator it = keystore_keys_.begin();
348 it != keystore_keys_.end(); ++it) {
349 response->add_encryption_keys(*it);
353 sieve->UpdateProgressMarkers(max_response_version, response);
354 return true;
357 string FakeServer::CommitEntity(
358 const sync_pb::SyncEntity& client_entity,
359 sync_pb::CommitResponse_EntryResponse* entry_response,
360 string client_guid,
361 string parent_id) {
362 if (client_entity.version() == 0 && client_entity.deleted()) {
363 return string();
366 FakeServerEntity* entity;
367 if (client_entity.deleted()) {
368 entity = TombstoneEntity::Create(client_entity.id_string());
369 // TODO(pvalenzuela): Change the behavior of DeleteChilden so that it does
370 // not modify server data if it fails.
371 if (!DeleteChildren(client_entity.id_string())) {
372 return string();
374 } else if (GetModelType(client_entity) == syncer::NIGORI) {
375 // NIGORI is the only permanent item type that should be updated by the
376 // client.
377 entity = PermanentEntity::CreateUpdatedNigoriEntity(
378 client_entity,
379 entities_[client_entity.id_string()]);
380 } else if (client_entity.has_client_defined_unique_tag()) {
381 entity = UniqueClientEntity::Create(client_entity);
382 } else {
383 // TODO(pvalenzuela): Validate entity's parent ID.
384 if (entities_.find(client_entity.id_string()) != entities_.end()) {
385 entity = BookmarkEntity::CreateUpdatedVersion(
386 client_entity,
387 entities_[client_entity.id_string()],
388 parent_id);
389 } else {
390 entity = BookmarkEntity::CreateNew(client_entity, parent_id, client_guid);
394 if (entity == NULL) {
395 // TODO(pvalenzuela): Add logging so that it is easier to determine why
396 // creation failed.
397 return string();
400 SaveEntity(entity);
401 BuildEntryResponseForSuccessfulCommit(entry_response, entity);
402 return entity->GetId();
405 void FakeServer::BuildEntryResponseForSuccessfulCommit(
406 sync_pb::CommitResponse_EntryResponse* entry_response,
407 FakeServerEntity* entity) {
408 entry_response->set_response_type(sync_pb::CommitResponse::SUCCESS);
409 entry_response->set_id_string(entity->GetId());
411 if (entity->IsDeleted()) {
412 entry_response->set_version(entity->GetVersion() + 1);
413 } else {
414 entry_response->set_version(entity->GetVersion());
415 entry_response->set_name(entity->GetName());
419 bool FakeServer::IsChild(const string& id, const string& potential_parent_id) {
420 if (entities_.find(id) == entities_.end()) {
421 // We've hit an ID (probably the imaginary root entity) that isn't stored
422 // by the server, so it can't be a child.
423 return false;
424 } else if (entities_[id]->GetParentId() == potential_parent_id) {
425 return true;
426 } else {
427 // Recursively look up the tree.
428 return IsChild(entities_[id]->GetParentId(), potential_parent_id);
432 bool FakeServer::DeleteChildren(const string& id) {
433 vector<string> child_ids;
434 for (EntityMap::iterator it = entities_.begin(); it != entities_.end();
435 ++it) {
436 if (IsChild(it->first, id)) {
437 child_ids.push_back(it->first);
441 for (vector<string>::iterator it = child_ids.begin(); it != child_ids.end();
442 ++it) {
443 FakeServerEntity* tombstone = TombstoneEntity::Create(*it);
444 if (tombstone == NULL) {
445 LOG(WARNING) << "Tombstone creation failed for entity with ID " << *it;
446 return false;
448 SaveEntity(tombstone);
451 return true;
454 bool FakeServer::HandleCommitRequest(
455 const sync_pb::CommitMessage& commit,
456 const std::string& invalidator_client_id,
457 sync_pb::CommitResponse* response) {
458 std::map<string, string> client_to_server_ids;
459 string guid = commit.cache_guid();
460 ModelTypeSet committed_model_types;
462 // TODO(pvalenzuela): Add validation of CommitMessage.entries.
463 ::google::protobuf::RepeatedPtrField<sync_pb::SyncEntity>::const_iterator it;
464 for (it = commit.entries().begin(); it != commit.entries().end(); ++it) {
465 sync_pb::CommitResponse_EntryResponse* entry_response =
466 response->add_entryresponse();
468 sync_pb::SyncEntity client_entity = *it;
469 string parent_id = client_entity.parent_id_string();
470 if (client_to_server_ids.find(parent_id) !=
471 client_to_server_ids.end()) {
472 parent_id = client_to_server_ids[parent_id];
475 string entity_id = CommitEntity(client_entity,
476 entry_response,
477 guid,
478 parent_id);
479 if (entity_id.empty()) {
480 return false;
483 // Record the ID if it was renamed.
484 if (entity_id != client_entity.id_string()) {
485 client_to_server_ids[client_entity.id_string()] = entity_id;
487 FakeServerEntity* entity = entities_[entity_id];
488 committed_model_types.Put(entity->GetModelType());
491 FOR_EACH_OBSERVER(Observer, observers_,
492 OnCommit(invalidator_client_id, committed_model_types));
493 return true;
496 scoped_ptr<base::DictionaryValue> FakeServer::GetEntitiesAsDictionaryValue() {
497 DCHECK(thread_checker_.CalledOnValidThread());
498 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
500 // Initialize an empty ListValue for all ModelTypes.
501 ModelTypeSet all_types = ModelTypeSet::All();
502 for (ModelTypeSet::Iterator it = all_types.First(); it.Good(); it.Inc()) {
503 dictionary->Set(ModelTypeToString(it.Get()), new base::ListValue());
506 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
507 ++it) {
508 FakeServerEntity* entity = it->second;
509 if (IsDeletedOrFolder(entity)) {
510 // Tombstones are ignored as they don't represent current data. Folders
511 // are also ignored as current verification infrastructure does not
512 // consider them.
513 continue;
515 base::ListValue* list_value;
516 if (!dictionary->GetList(ModelTypeToString(entity->GetModelType()),
517 &list_value)) {
518 return scoped_ptr<base::DictionaryValue>();
520 // TODO(pvalenzuela): Store more data for each entity so additional
521 // verification can be performed. One example of additional verification
522 // is checking the correctness of the bookmark hierarchy.
523 list_value->Append(new base::StringValue(entity->GetName()));
526 return dictionary.Pass();
529 std::vector<sync_pb::SyncEntity> FakeServer::GetSyncEntitiesByModelType(
530 ModelType model_type) {
531 std::vector<sync_pb::SyncEntity> sync_entities;
532 DCHECK(thread_checker_.CalledOnValidThread());
533 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
534 ++it) {
535 FakeServerEntity* entity = it->second;
536 if (!IsDeletedOrFolder(entity) && entity->GetModelType() == model_type) {
537 sync_pb::SyncEntity sync_entity;
538 entity->SerializeAsProto(&sync_entity);
539 sync_entities.push_back(sync_entity);
542 return sync_entities;
545 void FakeServer::InjectEntity(scoped_ptr<FakeServerEntity> entity) {
546 DCHECK(thread_checker_.CalledOnValidThread());
547 SaveEntity(entity.release());
550 bool FakeServer::SetNewStoreBirthday(const string& store_birthday) {
551 DCHECK(thread_checker_.CalledOnValidThread());
552 if (store_birthday_ == store_birthday)
553 return false;
555 store_birthday_ = store_birthday;
556 return true;
559 void FakeServer::SetAuthenticated() {
560 DCHECK(thread_checker_.CalledOnValidThread());
561 authenticated_ = true;
564 void FakeServer::SetUnauthenticated() {
565 DCHECK(thread_checker_.CalledOnValidThread());
566 authenticated_ = false;
569 bool FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType& error_type) {
570 DCHECK(thread_checker_.CalledOnValidThread());
571 if (triggered_actionable_error_.get()) {
572 DVLOG(1) << "Only one type of error can be triggered at any given time.";
573 return false;
576 error_type_ = error_type;
577 return true;
580 bool FakeServer::TriggerActionableError(
581 const sync_pb::SyncEnums::ErrorType& error_type,
582 const string& description,
583 const string& url,
584 const sync_pb::SyncEnums::Action& action) {
585 DCHECK(thread_checker_.CalledOnValidThread());
586 if (error_type_ != sync_pb::SyncEnums::SUCCESS) {
587 DVLOG(1) << "Only one type of error can be triggered at any given time.";
588 return false;
591 sync_pb::ClientToServerResponse_Error* error =
592 new sync_pb::ClientToServerResponse_Error();
593 error->set_error_type(error_type);
594 error->set_error_description(description);
595 error->set_url(url);
596 error->set_action(action);
597 triggered_actionable_error_.reset(error);
598 return true;
601 bool FakeServer::EnableAlternatingTriggeredErrors() {
602 DCHECK(thread_checker_.CalledOnValidThread());
603 if (error_type_ == sync_pb::SyncEnums::SUCCESS &&
604 !triggered_actionable_error_.get()) {
605 DVLOG(1) << "No triggered error set. Alternating can't be enabled.";
606 return false;
609 alternate_triggered_errors_ = true;
610 // Reset the counter so that the the first request yields a triggered error.
611 request_counter_ = 0;
612 return true;
615 bool FakeServer::ShouldSendTriggeredError() const {
616 if (!alternate_triggered_errors_)
617 return true;
619 // Check that the counter is odd so that we trigger an error on the first
620 // request after alternating is enabled.
621 return request_counter_ % 2 != 0;
624 void FakeServer::AddObserver(Observer* observer) {
625 DCHECK(thread_checker_.CalledOnValidThread());
626 observers_.AddObserver(observer);
629 void FakeServer::RemoveObserver(Observer* observer) {
630 DCHECK(thread_checker_.CalledOnValidThread());
631 observers_.RemoveObserver(observer);
634 void FakeServer::EnableNetwork() {
635 DCHECK(thread_checker_.CalledOnValidThread());
636 network_enabled_ = true;
639 void FakeServer::DisableNetwork() {
640 DCHECK(thread_checker_.CalledOnValidThread());
641 network_enabled_ = false;
644 std::string FakeServer::GetBookmarkBarFolderId() const {
645 DCHECK(thread_checker_.CalledOnValidThread());
646 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
647 ++it) {
648 FakeServerEntity* entity = it->second;
649 if (entity->GetName() == kBookmarkBarFolderName &&
650 entity->IsFolder() &&
651 entity->GetModelType() == syncer::BOOKMARKS) {
652 return entity->GetId();
655 NOTREACHED() << "Bookmark Bar entity not found.";
656 return "";
659 base::WeakPtr<FakeServer> FakeServer::AsWeakPtr() {
660 DCHECK(thread_checker_.CalledOnValidThread());
661 return weak_ptr_factory_.GetWeakPtr();
664 } // namespace fake_server