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"
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"
33 using syncer::GetModelType
;
34 using syncer::GetModelTypeFromSpecifics
;
35 using syncer::ModelType
;
36 using syncer::ModelTypeSet
;
38 namespace fake_server
{
40 class FakeServerEntity
;
44 // The default keystore key.
45 static const char kDefaultKeystoreKey
[] = "1111111111111111";
47 // Properties of the bookmark bar permanent folder.
48 static const char kBookmarkBarFolderServerTag
[] = "bookmark_bar";
49 static const char kBookmarkBarFolderName
[] = "Bookmark Bar";
51 // Properties of the other bookmarks permanent folder.
52 static const char kOtherBookmarksFolderServerTag
[] = "other_bookmarks";
53 static const char kOtherBookmarksFolderName
[] = "Other Bookmarks";
55 // Properties of the synced bookmarks permanent folder.
56 static const char kSyncedBookmarksFolderServerTag
[] = "synced_bookmarks";
57 static const char kSyncedBookmarksFolderName
[] = "Synced Bookmarks";
59 // A filter used during GetUpdates calls to determine what information to
60 // send back to the client. There is a 1:1 correspondence between any given
61 // GetUpdates call and an UpdateSieve instance.
66 // Factory method for creating an UpdateSieve.
67 static scoped_ptr
<UpdateSieve
> Create(
68 const sync_pb::GetUpdatesMessage
& get_updates_message
);
70 // Sets the progress markers in |get_updates_response| given the progress
71 // markers from the original GetUpdatesMessage and |new_version| (the latest
72 // version in the entries sent back).
73 void UpdateProgressMarkers(
75 sync_pb::GetUpdatesResponse
* get_updates_response
) const {
76 ModelTypeToVersionMap::const_iterator it
;
77 for (it
= request_from_version_
.begin(); it
!= request_from_version_
.end();
79 sync_pb::DataTypeProgressMarker
* new_marker
=
80 get_updates_response
->add_new_progress_marker();
81 new_marker
->set_data_type_id(
82 GetSpecificsFieldNumberFromModelType(it
->first
));
84 int64 version
= std::max(new_version
, it
->second
);
85 new_marker
->set_token(base::Int64ToString(version
));
89 // Determines whether the server should send an |entity| to the client as
90 // part of a GetUpdatesResponse.
91 bool ClientWantsItem(const FakeServerEntity
& entity
) const {
92 int64 version
= entity
.GetVersion();
93 if (version
<= min_version_
) {
95 } else if (entity
.IsDeleted()) {
99 ModelTypeToVersionMap::const_iterator it
=
100 request_from_version_
.find(entity
.GetModelType());
102 return it
== request_from_version_
.end() ? false : it
->second
< version
;
105 // Returns the minimum version seen across all types.
106 int64
GetMinVersion() const {
111 typedef std::map
<ModelType
, int64
> ModelTypeToVersionMap
;
113 // Creates an UpdateSieve.
114 UpdateSieve(const ModelTypeToVersionMap request_from_version
,
115 const int64 min_version
)
116 : request_from_version_(request_from_version
),
117 min_version_(min_version
) { }
119 // Maps data type IDs to the latest version seen for that type.
120 const ModelTypeToVersionMap request_from_version_
;
122 // The minimum version seen among all data types.
123 const int min_version_
;
126 scoped_ptr
<UpdateSieve
> UpdateSieve::Create(
127 const sync_pb::GetUpdatesMessage
& get_updates_message
) {
128 CHECK_GT(get_updates_message
.from_progress_marker_size(), 0)
129 << "A GetUpdates request must have at least one progress marker.";
131 UpdateSieve::ModelTypeToVersionMap request_from_version
;
132 int64 min_version
= std::numeric_limits
<int64
>::max();
133 for (int i
= 0; i
< get_updates_message
.from_progress_marker_size(); i
++) {
134 sync_pb::DataTypeProgressMarker marker
=
135 get_updates_message
.from_progress_marker(i
);
138 // Let the version remain zero if there is no token or an empty token (the
139 // first request for this type).
140 if (marker
.has_token() && !marker
.token().empty()) {
141 bool parsed
= base::StringToInt64(marker
.token(), &version
);
142 CHECK(parsed
) << "Unable to parse progress marker token.";
145 ModelType model_type
= syncer::GetModelTypeFromSpecificsFieldNumber(
146 marker
.data_type_id());
147 request_from_version
[model_type
] = version
;
149 if (version
< min_version
)
150 min_version
= version
;
153 return scoped_ptr
<UpdateSieve
>(
154 new UpdateSieve(request_from_version
, min_version
));
157 // Returns whether |entity| is deleted or permanent.
158 bool IsDeletedOrPermanent(const FakeServerEntity
& entity
) {
159 return entity
.IsDeleted() || entity
.IsPermanent();
164 FakeServer::FakeServer() : version_(0),
166 authenticated_(true),
167 error_type_(sync_pb::SyncEnums::SUCCESS
),
168 alternate_triggered_errors_(false),
170 network_enabled_(true),
171 weak_ptr_factory_(this) {
172 keystore_keys_
.push_back(kDefaultKeystoreKey
);
174 const bool create_result
= CreateDefaultPermanentItems();
175 DCHECK(create_result
) << "Permanent items were not created successfully.";
178 FakeServer::~FakeServer() {}
180 bool FakeServer::CreatePermanentBookmarkFolder(const std::string
& server_tag
,
181 const std::string
& name
) {
182 DCHECK(thread_checker_
.CalledOnValidThread());
183 scoped_ptr
<FakeServerEntity
> entity
=
184 PermanentEntity::Create(syncer::BOOKMARKS
, server_tag
, name
,
185 ModelTypeToRootTag(syncer::BOOKMARKS
));
189 SaveEntity(entity
.Pass());
193 bool FakeServer::CreateDefaultPermanentItems() {
194 ModelTypeSet all_types
= syncer::ProtocolTypes();
195 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
196 ModelType model_type
= it
.Get();
197 scoped_ptr
<FakeServerEntity
> top_level_entity
=
198 PermanentEntity::CreateTopLevel(model_type
);
199 if (!top_level_entity
) {
202 SaveEntity(top_level_entity
.Pass());
204 if (model_type
== syncer::BOOKMARKS
) {
205 if (!CreatePermanentBookmarkFolder(kBookmarkBarFolderServerTag
,
206 kBookmarkBarFolderName
))
208 if (!CreatePermanentBookmarkFolder(kOtherBookmarksFolderServerTag
,
209 kOtherBookmarksFolderName
))
217 void FakeServer::UpdateEntityVersion(FakeServerEntity
* entity
) {
218 entity
->SetVersion(++version_
);
221 void FakeServer::SaveEntity(scoped_ptr
<FakeServerEntity
> entity
) {
222 UpdateEntityVersion(entity
.get());
223 const string id
= entity
->GetId();
224 entities_
.set(id
, entity
.Pass());
227 void FakeServer::HandleCommand(const string
& request
,
228 const base::Closure
& completion_closure
,
231 std::string
* response
) {
232 DCHECK(thread_checker_
.CalledOnValidThread());
233 if (!network_enabled_
) {
234 *error_code
= net::ERR_FAILED
;
235 *response_code
= net::ERR_FAILED
;
236 *response
= string();
237 completion_closure
.Run();
242 if (!authenticated_
) {
244 *response_code
= net::HTTP_UNAUTHORIZED
;
245 *response
= string();
246 completion_closure
.Run();
250 sync_pb::ClientToServerMessage message
;
251 bool parsed
= message
.ParseFromString(request
);
252 CHECK(parsed
) << "Unable to parse the ClientToServerMessage.";
254 sync_pb::ClientToServerResponse response_proto
;
256 if (message
.has_store_birthday() &&
257 message
.store_birthday() != GetStoreBirthday()) {
258 response_proto
.set_error_code(sync_pb::SyncEnums::NOT_MY_BIRTHDAY
);
259 } else if (error_type_
!= sync_pb::SyncEnums::SUCCESS
&&
260 ShouldSendTriggeredError()) {
261 response_proto
.set_error_code(error_type_
);
262 } else if (triggered_actionable_error_
.get() && ShouldSendTriggeredError()) {
263 sync_pb::ClientToServerResponse_Error
* error
=
264 response_proto
.mutable_error();
265 error
->CopyFrom(*(triggered_actionable_error_
.get()));
267 bool success
= false;
268 switch (message
.message_contents()) {
269 case sync_pb::ClientToServerMessage::GET_UPDATES
:
270 success
= HandleGetUpdatesRequest(message
.get_updates(),
271 response_proto
.mutable_get_updates());
273 case sync_pb::ClientToServerMessage::COMMIT
:
274 success
= HandleCommitRequest(message
.commit(),
275 message
.invalidator_client_id(),
276 response_proto
.mutable_commit());
278 case sync_pb::ClientToServerMessage::CLEAR_SERVER_DATA
:
280 response_proto
.mutable_clear_server_data();
284 *error_code
= net::ERR_NOT_IMPLEMENTED
;
286 *response
= string();
287 completion_closure
.Run();
292 // TODO(pvalenzuela): Add logging here so that tests have more info about
294 *error_code
= net::ERR_FAILED
;
296 *response
= string();
297 completion_closure
.Run();
301 response_proto
.set_error_code(sync_pb::SyncEnums::SUCCESS
);
304 response_proto
.set_store_birthday(GetStoreBirthday());
307 *response_code
= net::HTTP_OK
;
308 *response
= response_proto
.SerializeAsString();
309 completion_closure
.Run();
312 bool FakeServer::HandleGetUpdatesRequest(
313 const sync_pb::GetUpdatesMessage
& get_updates
,
314 sync_pb::GetUpdatesResponse
* response
) {
315 // TODO(pvalenzuela): Implement batching instead of sending all information
317 response
->set_changes_remaining(0);
319 scoped_ptr
<UpdateSieve
> sieve
= UpdateSieve::Create(get_updates
);
321 // This folder is called "Synced Bookmarks" by sync and is renamed
322 // "Mobile Bookmarks" by the mobile client UIs.
323 if (get_updates
.create_mobile_bookmarks_folder() &&
324 !CreatePermanentBookmarkFolder(kSyncedBookmarksFolderServerTag
,
325 kSyncedBookmarksFolderName
)) {
329 bool send_encryption_keys_based_on_nigori
= false;
330 int64 max_response_version
= 0;
331 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
333 const FakeServerEntity
& entity
= *it
->second
;
334 if (sieve
->ClientWantsItem(entity
)) {
335 sync_pb::SyncEntity
* response_entity
= response
->add_entries();
336 entity
.SerializeAsProto(response_entity
);
337 max_response_version
= std::max(max_response_version
,
338 response_entity
->version());
340 if (entity
.GetModelType() == syncer::NIGORI
) {
341 send_encryption_keys_based_on_nigori
=
342 response_entity
->specifics().nigori().passphrase_type() ==
343 sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE
;
348 if (send_encryption_keys_based_on_nigori
||
349 get_updates
.need_encryption_key()) {
350 for (vector
<string
>::iterator it
= keystore_keys_
.begin();
351 it
!= keystore_keys_
.end(); ++it
) {
352 response
->add_encryption_keys(*it
);
356 sieve
->UpdateProgressMarkers(max_response_version
, response
);
360 string
FakeServer::CommitEntity(
361 const sync_pb::SyncEntity
& client_entity
,
362 sync_pb::CommitResponse_EntryResponse
* entry_response
,
363 const string
& client_guid
,
364 const string
& parent_id
) {
365 if (client_entity
.version() == 0 && client_entity
.deleted()) {
369 scoped_ptr
<FakeServerEntity
> entity
;
370 if (client_entity
.deleted()) {
371 entity
= TombstoneEntity::Create(client_entity
.id_string());
372 DeleteChildren(client_entity
.id_string());
373 } else if (GetModelType(client_entity
) == syncer::NIGORI
) {
374 // NIGORI is the only permanent item type that should be updated by the
376 EntityMap::const_iterator iter
= entities_
.find(client_entity
.id_string());
377 CHECK(iter
!= entities_
.end());
378 entity
= PermanentEntity::CreateUpdatedNigoriEntity(client_entity
,
380 } else if (client_entity
.has_client_defined_unique_tag()) {
381 entity
= UniqueClientEntity::Create(client_entity
);
383 // TODO(pvalenzuela): Validate entity's parent ID.
384 EntityMap::const_iterator iter
= entities_
.find(client_entity
.id_string());
385 if (iter
!= entities_
.end()) {
386 entity
= BookmarkEntity::CreateUpdatedVersion(client_entity
,
387 *iter
->second
, parent_id
);
389 entity
= BookmarkEntity::CreateNew(client_entity
, parent_id
, client_guid
);
394 // TODO(pvalenzuela): Add logging so that it is easier to determine why
399 const std::string id
= entity
->GetId();
400 SaveEntity(entity
.Pass());
401 BuildEntryResponseForSuccessfulCommit(id
, entry_response
);
405 void FakeServer::BuildEntryResponseForSuccessfulCommit(
406 const std::string
& entity_id
,
407 sync_pb::CommitResponse_EntryResponse
* entry_response
) {
408 EntityMap::const_iterator iter
= entities_
.find(entity_id
);
409 CHECK(iter
!= entities_
.end());
410 const FakeServerEntity
& entity
= *iter
->second
;
411 entry_response
->set_response_type(sync_pb::CommitResponse::SUCCESS
);
412 entry_response
->set_id_string(entity
.GetId());
414 if (entity
.IsDeleted()) {
415 entry_response
->set_version(entity
.GetVersion() + 1);
417 entry_response
->set_version(entity
.GetVersion());
418 entry_response
->set_name(entity
.GetName());
422 bool FakeServer::IsChild(const string
& id
, const string
& potential_parent_id
) {
423 EntityMap::const_iterator iter
= entities_
.find(id
);
424 if (iter
== entities_
.end()) {
425 // We've hit an ID (probably the imaginary root entity) that isn't stored
426 // by the server, so it can't be a child.
430 const FakeServerEntity
& entity
= *iter
->second
;
431 if (entity
.GetParentId() == potential_parent_id
)
434 // Recursively look up the tree.
435 return IsChild(entity
.GetParentId(), potential_parent_id
);
438 void FakeServer::DeleteChildren(const string
& id
) {
439 std::set
<string
> tombstones_ids
;
440 // Find all the children of id.
441 for (auto& entity
: entities_
) {
442 if (IsChild(entity
.first
, id
)) {
443 tombstones_ids
.insert(entity
.first
);
447 for (auto& tombstone_id
: tombstones_ids
) {
448 SaveEntity(TombstoneEntity::Create(tombstone_id
));
452 bool FakeServer::HandleCommitRequest(const sync_pb::CommitMessage
& commit
,
453 const std::string
& invalidator_client_id
,
454 sync_pb::CommitResponse
* response
) {
455 std::map
<string
, string
> client_to_server_ids
;
456 string guid
= commit
.cache_guid();
457 ModelTypeSet committed_model_types
;
459 // TODO(pvalenzuela): Add validation of CommitMessage.entries.
460 ::google::protobuf::RepeatedPtrField
<sync_pb::SyncEntity
>::const_iterator it
;
461 for (it
= commit
.entries().begin(); it
!= commit
.entries().end(); ++it
) {
462 sync_pb::CommitResponse_EntryResponse
* entry_response
=
463 response
->add_entryresponse();
465 sync_pb::SyncEntity client_entity
= *it
;
466 string parent_id
= client_entity
.parent_id_string();
467 if (client_to_server_ids
.find(parent_id
) !=
468 client_to_server_ids
.end()) {
469 parent_id
= client_to_server_ids
[parent_id
];
472 const string entity_id
=
473 CommitEntity(client_entity
, entry_response
, guid
, parent_id
);
474 if (entity_id
.empty()) {
478 // Record the ID if it was renamed.
479 if (entity_id
!= client_entity
.id_string()) {
480 client_to_server_ids
[client_entity
.id_string()] = entity_id
;
483 EntityMap::const_iterator iter
= entities_
.find(entity_id
);
484 CHECK(iter
!= entities_
.end());
485 committed_model_types
.Put(iter
->second
->GetModelType());
488 FOR_EACH_OBSERVER(Observer
, observers_
,
489 OnCommit(invalidator_client_id
, committed_model_types
));
493 scoped_ptr
<base::DictionaryValue
> FakeServer::GetEntitiesAsDictionaryValue() {
494 DCHECK(thread_checker_
.CalledOnValidThread());
495 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue());
497 // Initialize an empty ListValue for all ModelTypes.
498 ModelTypeSet all_types
= ModelTypeSet::All();
499 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
500 dictionary
->Set(ModelTypeToString(it
.Get()), new base::ListValue());
503 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
505 const FakeServerEntity
& entity
= *it
->second
;
506 if (IsDeletedOrPermanent(entity
)) {
507 // Tombstones are ignored as they don't represent current data. Folders
508 // are also ignored as current verification infrastructure does not
512 base::ListValue
* list_value
;
513 if (!dictionary
->GetList(ModelTypeToString(entity
.GetModelType()),
515 return scoped_ptr
<base::DictionaryValue
>();
517 // TODO(pvalenzuela): Store more data for each entity so additional
518 // verification can be performed. One example of additional verification
519 // is checking the correctness of the bookmark hierarchy.
520 list_value
->Append(new base::StringValue(entity
.GetName()));
523 return dictionary
.Pass();
526 std::vector
<sync_pb::SyncEntity
> FakeServer::GetSyncEntitiesByModelType(
527 ModelType model_type
) {
528 std::vector
<sync_pb::SyncEntity
> sync_entities
;
529 DCHECK(thread_checker_
.CalledOnValidThread());
530 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
532 const FakeServerEntity
& entity
= *it
->second
;
533 if (!IsDeletedOrPermanent(entity
) && entity
.GetModelType() == model_type
) {
534 sync_pb::SyncEntity sync_entity
;
535 entity
.SerializeAsProto(&sync_entity
);
536 sync_entities
.push_back(sync_entity
);
539 return sync_entities
;
542 void FakeServer::InjectEntity(scoped_ptr
<FakeServerEntity
> entity
) {
543 DCHECK(thread_checker_
.CalledOnValidThread());
544 SaveEntity(entity
.Pass());
547 bool FakeServer::ModifyEntitySpecifics(
548 const std::string
& id
,
549 const sync_pb::EntitySpecifics
& updated_specifics
) {
550 EntityMap::const_iterator iter
= entities_
.find(id
);
551 if (iter
== entities_
.end() ||
552 iter
->second
->GetModelType() !=
553 GetModelTypeFromSpecifics(updated_specifics
)) {
557 scoped_ptr
<FakeServerEntity
> entity
= entities_
.take_and_erase(iter
);
558 entity
->SetSpecifics(updated_specifics
);
559 UpdateEntityVersion(entity
.get());
560 entities_
.insert(id
, entity
.Pass());
564 bool FakeServer::ModifyBookmarkEntity(
565 const std::string
& id
,
566 const std::string
& parent_id
,
567 const sync_pb::EntitySpecifics
& updated_specifics
) {
568 EntityMap::const_iterator iter
= entities_
.find(id
);
569 if (iter
== entities_
.end() ||
570 iter
->second
->GetModelType() != syncer::BOOKMARKS
||
571 GetModelTypeFromSpecifics(updated_specifics
) != syncer::BOOKMARKS
) {
575 scoped_ptr
<BookmarkEntity
> entity(
576 static_cast<BookmarkEntity
*>(entities_
.take_and_erase(iter
).release()));
578 entity
->SetParentId(parent_id
);
579 entity
->SetSpecifics(updated_specifics
);
580 if (updated_specifics
.has_bookmark()) {
581 entity
->SetName(updated_specifics
.bookmark().title());
583 UpdateEntityVersion(entity
.get());
584 entities_
.insert(id
, entity
.Pass());
588 void FakeServer::ClearServerData() {
589 DCHECK(thread_checker_
.CalledOnValidThread());
591 keystore_keys_
.clear();
595 void FakeServer::SetAuthenticated() {
596 DCHECK(thread_checker_
.CalledOnValidThread());
597 authenticated_
= true;
600 void FakeServer::SetUnauthenticated() {
601 DCHECK(thread_checker_
.CalledOnValidThread());
602 authenticated_
= false;
605 bool FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType
& error_type
) {
606 DCHECK(thread_checker_
.CalledOnValidThread());
607 if (triggered_actionable_error_
.get()) {
608 DVLOG(1) << "Only one type of error can be triggered at any given time.";
612 error_type_
= error_type
;
616 bool FakeServer::TriggerActionableError(
617 const sync_pb::SyncEnums::ErrorType
& error_type
,
618 const string
& description
,
620 const sync_pb::SyncEnums::Action
& action
) {
621 DCHECK(thread_checker_
.CalledOnValidThread());
622 if (error_type_
!= sync_pb::SyncEnums::SUCCESS
) {
623 DVLOG(1) << "Only one type of error can be triggered at any given time.";
627 sync_pb::ClientToServerResponse_Error
* error
=
628 new sync_pb::ClientToServerResponse_Error();
629 error
->set_error_type(error_type
);
630 error
->set_error_description(description
);
632 error
->set_action(action
);
633 triggered_actionable_error_
.reset(error
);
637 bool FakeServer::EnableAlternatingTriggeredErrors() {
638 DCHECK(thread_checker_
.CalledOnValidThread());
639 if (error_type_
== sync_pb::SyncEnums::SUCCESS
&&
640 !triggered_actionable_error_
.get()) {
641 DVLOG(1) << "No triggered error set. Alternating can't be enabled.";
645 alternate_triggered_errors_
= true;
646 // Reset the counter so that the the first request yields a triggered error.
647 request_counter_
= 0;
651 bool FakeServer::ShouldSendTriggeredError() const {
652 if (!alternate_triggered_errors_
)
655 // Check that the counter is odd so that we trigger an error on the first
656 // request after alternating is enabled.
657 return request_counter_
% 2 != 0;
660 void FakeServer::AddObserver(Observer
* observer
) {
661 DCHECK(thread_checker_
.CalledOnValidThread());
662 observers_
.AddObserver(observer
);
665 void FakeServer::RemoveObserver(Observer
* observer
) {
666 DCHECK(thread_checker_
.CalledOnValidThread());
667 observers_
.RemoveObserver(observer
);
670 void FakeServer::EnableNetwork() {
671 DCHECK(thread_checker_
.CalledOnValidThread());
672 network_enabled_
= true;
675 void FakeServer::DisableNetwork() {
676 DCHECK(thread_checker_
.CalledOnValidThread());
677 network_enabled_
= false;
680 std::string
FakeServer::GetBookmarkBarFolderId() const {
681 DCHECK(thread_checker_
.CalledOnValidThread());
682 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
684 FakeServerEntity
* entity
= it
->second
;
685 if (entity
->GetName() == kBookmarkBarFolderName
&&
686 entity
->IsFolder() &&
687 entity
->GetModelType() == syncer::BOOKMARKS
) {
688 return entity
->GetId();
691 NOTREACHED() << "Bookmark Bar entity not found.";
695 base::WeakPtr
<FakeServer
> FakeServer::AsWeakPtr() {
696 DCHECK(thread_checker_
.CalledOnValidThread());
697 return weak_ptr_factory_
.GetWeakPtr();
700 std::string
FakeServer::GetStoreBirthday() const {
701 DCHECK(thread_checker_
.CalledOnValidThread());
702 return base::Int64ToString(store_birthday_
);
705 } // namespace fake_server