blob: b027b9230de238ecff6ecd89d3ad43d204d21ece [file]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/test/fake_server.h"
#include <algorithm>
#include <limits>
#include <set>
#include <string_view>
#include <utility>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/hash/hash.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/test_file_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/sync/base/client_tag_hash.h"
#include "components/sync/base/data_type.h"
#include "components/sync/engine/loopback_server/persistent_tombstone_entity.h"
#include "components/sync/protocol/data_type_progress_marker.pb.h"
#include "components/sync/protocol/proto_value_conversions.h"
#include "components/sync/protocol/sync_entity.pb.h"
#include "components/sync/protocol/sync_enums.pb.h"
#include "components/sync_device_info/device_info_util.h"
#include "net/http/http_status_code.h"
#include "testing/gtest/include/gtest/gtest.h"
using syncer::DataType;
using syncer::DataTypeSet;
using syncer::GetDataTypeFromSpecifics;
using syncer::LoopbackServer;
using syncer::LoopbackServerEntity;
namespace fake_server {
FakeServer::FakeServer(const base::FilePath& loopback_server_dir)
: fake_state_file_path_(
loopback_server_dir.AppendASCII("fake_state.json")) {
CHECK(!loopback_server_dir.empty());
// Needed by syncer::LoopbackServer.
base::ScopedAllowBlockingForTesting allow_blocking;
loopback_server_ = std::make_unique<syncer::LoopbackServer>(
loopback_server_dir.AppendASCII("profile.pb"));
loopback_server_->set_observer_for_tests(this);
SetUpdateMode(syncer::AUTOFILL_VALUABLE, UpdateMode::kFull);
SetUpdateMode(syncer::AUTOFILL_WALLET_DATA, UpdateMode::kFull);
SetUpdateMode(syncer::AUTOFILL_WALLET_OFFER, UpdateMode::kFull);
LoadFakeStateFromDisk();
}
FakeServer::FakeServer()
: FakeServer(base::CreateUniqueTempDirectoryScopedToTest()) {}
FakeServer::~FakeServer() = default;
void FakeServer::LoadFakeStateFromDisk() {
base::ScopedAllowBlockingForTesting allow_blocking;
std::string json_string;
if (!base::ReadFileToString(fake_state_file_path_, &json_string)) {
return;
}
std::optional<base::Value> json =
base::JSONReader::Read(json_string, base::JSON_PARSE_RFC);
if (!json || !json->is_dict()) {
ADD_FAILURE() << "Failed decode FakeServer state";
return;
}
const base::DictValue& dict = json->GetDict();
std::optional<int> http_error_status_code =
dict.FindInt("http_error_status_code");
if (http_error_status_code) {
http_error_status_code_ =
static_cast<net::HttpStatusCode>(*http_error_status_code);
}
}
void FakeServer::WriteFakeStateToDisk() const {
base::ScopedAllowBlockingForTesting allow_blocking;
base::DictValue dict;
if (http_error_status_code_) {
dict.Set("http_error_status_code",
static_cast<int>(*http_error_status_code_));
}
std::string json_string;
if (base::JSONWriter::Write(dict, &json_string)) {
base::WriteFile(fake_state_file_path_, json_string);
}
}
namespace {
bool ClearProgressTokenIfExists(DataType data_type,
sync_pb::ClientToServerMessage* message) {
google::protobuf::RepeatedPtrField<sync_pb::DataTypeProgressMarker>*
progress_markers =
message->mutable_get_updates()->mutable_from_progress_marker();
for (int index = 0; index < progress_markers->size(); ++index) {
if (syncer::GetDataTypeFromSpecificsFieldNumber(
progress_markers->Get(index).data_type_id()) == data_type) {
progress_markers->at(index).clear_token();
return true;
}
}
return false;
}
DataTypeSet ClearProgressTokensForTypes(
DataTypeSet data_types,
sync_pb::ClientToServerMessage* message) {
syncer::DataTypeSet removed_token_types;
if (message->message_contents() ==
sync_pb::ClientToServerMessage::GET_UPDATES) {
for (const syncer::DataType type : data_types) {
if (ClearProgressTokenIfExists(type, message)) {
removed_token_types.Put(type);
}
}
}
return removed_token_types;
}
void AddClearAllGCDirectives(syncer::DataTypeSet data_types,
sync_pb::GetUpdatesResponse* gu_response) {
google::protobuf::RepeatedPtrField<sync_pb::DataTypeProgressMarker>*
progress_markers = gu_response->mutable_new_progress_marker();
for (sync_pb::DataTypeProgressMarker& progress_marker : *progress_markers) {
if (data_types.Has(syncer::GetDataTypeFromSpecificsFieldNumber(
progress_marker.data_type_id()))) {
progress_marker.mutable_gc_directive()->set_version_watermark(0);
}
}
}
std::string PrettyPrintValue(base::Value value) {
std::string message;
base::JSONWriter::WriteWithOptions(
value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &message);
return message;
}
} // namespace
void FakeServer::HandleEvent(const sync_pb::EventRequest& request) {
DCHECK(thread_checker_.CalledOnValidThread());
if (request.has_sync_disabled()) {
sync_pb::DeviceInfoSpecifics specifics;
specifics.set_cache_guid(request.sync_disabled().cache_guid());
InjectEntity(
syncer::PersistentTombstoneEntity::CreateNewForTest( // IN-TEST
syncer::DEVICE_INFO,
syncer::DeviceInfoUtil::SpecificsToTag(specifics)));
}
}
net::HttpStatusCode FakeServer::HandleCommand(const std::string& request,
std::string* response) {
DCHECK(thread_checker_.CalledOnValidThread());
response->clear();
request_counter_++;
sync_pb::ClientToServerMessage message;
bool parsed = message.ParseFromString(request);
DCHECK(parsed) << "Unable to parse the ClientToServerMessage.";
LogForTestFailure(FROM_HERE, "REQUEST",
PrettyPrintValue(syncer::ClientToServerMessageToValue(
message, {.include_specifics = true,
.include_full_get_update_triggers = false})));
sync_pb::ClientToServerResponse response_proto;
net::HttpStatusCode http_status_code =
HandleParsedCommand(message, &response_proto);
LogForTestFailure(
FROM_HERE, "RESPONSE",
PrettyPrintValue(syncer::ClientToServerResponseToValue(
response_proto, {.include_specifics = true,
.include_full_get_update_triggers = false})));
*response = response_proto.SerializeAsString();
return http_status_code;
}
net::HttpStatusCode FakeServer::HandleParsedCommand(
const sync_pb::ClientToServerMessage& message,
sync_pb::ClientToServerResponse* response) {
DCHECK(response);
response->Clear();
// Store last message from the client in any case.
switch (message.message_contents()) {
case sync_pb::ClientToServerMessage::GET_UPDATES:
last_getupdates_message_ = message;
for (Observer& observer : observers_) {
observer.OnWillGetUpdates(message);
}
break;
case sync_pb::ClientToServerMessage::COMMIT:
last_commit_message_ = message;
OnWillCommit();
break;
case sync_pb::ClientToServerMessage::CLEAR_SERVER_DATA:
// Don't care.
break;
case sync_pb::ClientToServerMessage::DEPRECATED_3:
case sync_pb::ClientToServerMessage::DEPRECATED_4:
NOTREACHED();
}
if (http_error_status_code_) {
return *http_error_status_code_;
}
if (message.message_contents() == sync_pb::ClientToServerMessage::COMMIT &&
commit_error_type_ != sync_pb::SyncEnums::SUCCESS &&
ShouldSendTriggeredError()) {
response->set_error_code(commit_error_type_);
return net::HTTP_OK;
}
if (error_type_ != sync_pb::SyncEnums::SUCCESS &&
ShouldSendTriggeredError()) {
response->set_error_code(error_type_);
return net::HTTP_OK;
}
if (triggered_actionable_error_.get() && ShouldSendTriggeredError()) {
*response->mutable_error() = *triggered_actionable_error_;
return net::HTTP_OK;
}
// The loopback server does not know how to handle Wallet or Offer requests
// -- and should not. The FakeServer is handling those instead. The
// loopback server has a strong expectations about how progress tokens are
// structured. To not interfere with this, we remove progress markers for
// full-update types before passing the request to the loopback server.
sync_pb::ClientToServerMessage message_for_loopback_server = message;
// If any of the data type progress markers are (simulated to be) too old,
// drop them from the message to the loopback server, so it'll respond with a
// full update.
syncer::DataTypeSet send_clear_all_directive_types =
ClearProgressTokensForTypes(old_progress_marker_types_,
&message_for_loopback_server);
old_progress_marker_types_.RemoveAll(send_clear_all_directive_types);
net::HttpStatusCode http_status_code =
SendToLoopbackServer(message_for_loopback_server, response);
if (response->has_get_updates() && disallow_sending_encryption_keys_) {
response->mutable_get_updates()->clear_encryption_keys();
}
if (http_status_code == net::HTTP_OK &&
message.message_contents() ==
sync_pb::ClientToServerMessage::GET_UPDATES) {
if (!send_clear_all_directive_types.empty()) {
AddClearAllGCDirectives(send_clear_all_directive_types,
response->mutable_get_updates());
}
// Populate `active_collaboration_ids`.
for (sync_pb::DataTypeProgressMarker& progress_marker :
*response->mutable_get_updates()->mutable_new_progress_marker()) {
DataType type = syncer::GetDataTypeFromSpecificsFieldNumber(
progress_marker.data_type_id());
if (!syncer::SharedTypes().Has(type)) {
continue;
}
sync_pb::GarbageCollectionDirective::CollaborationGarbageCollection*
collaboration_gc = progress_marker.mutable_gc_directive()
->mutable_collaboration_gc();
for (const syncer::CollaborationId& collaboration_id : collaborations_) {
collaboration_gc->add_active_collaboration_ids(
collaboration_id.value());
}
}
}
if (http_status_code == net::HTTP_OK &&
response->error_code() == sync_pb::SyncEnums::SUCCESS) {
DCHECK(!response->has_client_command());
*response->mutable_client_command() = client_command_;
if (message.has_get_updates()) {
for (Observer& observer : observers_) {
observer.OnSuccessfulGetUpdates();
}
}
}
return http_status_code;
}
net::HttpStatusCode FakeServer::SendToLoopbackServer(
const sync_pb::ClientToServerMessage& message,
sync_pb::ClientToServerResponse* response) {
base::ScopedAllowBlockingForTesting allow_blocking;
return loopback_server_->HandleCommand(message, response);
}
bool FakeServer::GetLastCommitMessage(sync_pb::ClientToServerMessage* message) {
if (!last_commit_message_.has_commit()) {
return false;
}
message->CopyFrom(last_commit_message_);
return true;
}
bool FakeServer::GetLastGetUpdatesMessage(
sync_pb::ClientToServerMessage* message) {
if (!last_getupdates_message_.has_get_updates()) {
return false;
}
message->CopyFrom(last_getupdates_message_);
return true;
}
void FakeServer::OverrideResponseType(
LoopbackServer::ResponseTypeProvider response_type_override) {
loopback_server_->OverrideResponseType(std::move(response_type_override));
}
void FakeServer::FlushToDisk() {
loopback_server_->FlushToDisk();
}
base::DictValue FakeServer::GetEntitiesAsDictForTesting() {
DCHECK(thread_checker_.CalledOnValidThread());
return loopback_server_->GetEntitiesAsDictForTesting();
}
std::vector<sync_pb::SyncEntity> FakeServer::GetSyncEntitiesByDataType(
DataType data_type) {
DCHECK(thread_checker_.CalledOnValidThread());
return loopback_server_->GetSyncEntitiesByDataType(data_type);
}
std::vector<sync_pb::SyncEntity> FakeServer::GetPermanentSyncEntitiesByDataType(
DataType data_type) {
DCHECK(thread_checker_.CalledOnValidThread());
return loopback_server_->GetPermanentSyncEntitiesByDataType(data_type);
}
const std::vector<std::vector<uint8_t>>& FakeServer::GetKeystoreKeys() const {
DCHECK(thread_checker_.CalledOnValidThread());
return loopback_server_->GetKeystoreKeysForTesting();
}
void FakeServer::TriggerKeystoreKeyRotation() {
DCHECK(thread_checker_.CalledOnValidThread());
loopback_server_->AddNewKeystoreKeyForTesting();
std::vector<sync_pb::SyncEntity> nigori_entities =
loopback_server_->GetPermanentSyncEntitiesByDataType(syncer::NIGORI);
DCHECK_EQ(nigori_entities.size(), 1U);
const int version = loopback_server_->GetMigrationVersion(syncer::NIGORI);
bool success = ModifyEntitySpecifics(
LoopbackServerEntity::GetTopLevelId(syncer::NIGORI, version),
nigori_entities[0].specifics());
DCHECK(success);
}
void FakeServer::InjectEntity(std::unique_ptr<LoopbackServerEntity> entity) {
DCHECK(thread_checker_.CalledOnValidThread());
const DataType data_type = entity->GetDataType();
OnWillCommit();
{
base::ScopedAllowBlockingForTesting allow_blocking;
loopback_server_->SaveEntity(std::move(entity));
loopback_server_->ScheduleSaveStateToFile();
}
// Notify observers so invalidations are mimic-ed.
OnCommit(/*committed_data_types=*/{data_type});
}
bool FakeServer::ModifyEntitySpecifics(
const std::string& id,
const sync_pb::EntitySpecifics& updated_specifics) {
OnWillCommit();
{
base::ScopedAllowBlockingForTesting allow_blocking;
if (!loopback_server_->ModifyEntitySpecifics(id, updated_specifics)) {
return false;
}
}
// Notify observers so invalidations are mimic-ed.
OnCommit(
/*committed_data_types=*/{GetDataTypeFromSpecifics(updated_specifics)});
return true;
}
bool FakeServer::ModifyBookmarkEntity(
const std::string& id,
const std::string& parent_id,
const sync_pb::EntitySpecifics& updated_specifics) {
OnWillCommit();
{
base::ScopedAllowBlockingForTesting allow_blocking;
if (!loopback_server_->ModifyBookmarkEntity(id, parent_id,
updated_specifics)) {
return false;
}
}
// Notify observers so invalidations are mimic-ed.
OnCommit(/*committed_data_types=*/{syncer::BOOKMARKS});
return true;
}
void FakeServer::ClearServerData() {
DCHECK(thread_checker_.CalledOnValidThread());
OnWillCommit();
{
base::ScopedAllowBlockingForTesting allow_blocking;
loopback_server_->ClearServerData();
}
// Notify observers so invalidations are mimic-ed.
OnCommit(/*committed_data_types=*/{syncer::NIGORI});
}
void FakeServer::DeleteAllEntitiesForDataType(DataType data_type) {
DCHECK(thread_checker_.CalledOnValidThread());
base::ScopedAllowBlockingForTesting allow_blocking;
loopback_server_->DeleteAllEntitiesForDataType(data_type);
}
void FakeServer::SetHttpError(net::HttpStatusCode http_status_code) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_GT(http_status_code, 0);
http_error_status_code_ = http_status_code;
WriteFakeStateToDisk();
}
void FakeServer::ClearHttpError() {
DCHECK(thread_checker_.CalledOnValidThread());
http_error_status_code_ = std::nullopt;
WriteFakeStateToDisk();
}
std::optional<net::HttpStatusCode> FakeServer::GetHttpError() const {
DCHECK(thread_checker_.CalledOnValidThread());
return http_error_status_code_;
}
void FakeServer::SetClientCommand(
const sync_pb::ClientCommand& client_command) {
DCHECK(thread_checker_.CalledOnValidThread());
client_command_ = client_command;
}
void FakeServer::TriggerCommitError(
const sync_pb::SyncEnums_ErrorType& error_type) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(error_type == sync_pb::SyncEnums::SUCCESS || !HasTriggeredError());
commit_error_type_ = error_type;
}
void FakeServer::TriggerError(const sync_pb::SyncEnums_ErrorType& error_type) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(error_type == sync_pb::SyncEnums::SUCCESS || !HasTriggeredError());
error_type_ = error_type;
}
void FakeServer::TriggerActionableProtocolError(
const sync_pb::SyncEnums_ErrorType& error_type,
const std::string& description,
const std::string& url,
const sync_pb::SyncEnums::Action& action) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!HasTriggeredError());
auto error = std::make_unique<sync_pb::ClientToServerResponse_Error>();
error->set_error_type(error_type);
error->set_error_description(description);
error->set_action(action);
triggered_actionable_error_ = std::move(error);
}
void FakeServer::ClearActionableProtocolError() {
triggered_actionable_error_.reset();
}
bool FakeServer::EnableAlternatingTriggeredErrors() {
DCHECK(thread_checker_.CalledOnValidThread());
if (error_type_ == sync_pb::SyncEnums::SUCCESS &&
!triggered_actionable_error_) {
DVLOG(1) << "No triggered error set. Alternating can't be enabled.";
return false;
}
alternate_triggered_errors_ = true;
// Reset the counter so that the the first request yields a triggered error.
request_counter_ = 0;
return true;
}
void FakeServer::SetRejectOldProgressMarkerForType(syncer::DataType data_type) {
old_progress_marker_types_.Put(data_type);
}
void FakeServer::SetUpdateMode(DataType data_type, UpdateMode update_mode) {
loopback_server_->SetUpdateMode(data_type, update_mode);
}
void FakeServer::DisallowSendingEncryptionKeys() {
disallow_sending_encryption_keys_ = true;
}
void FakeServer::SetThrottledTypes(syncer::DataTypeSet types) {
loopback_server_->SetThrottledTypesForTesting(types);
}
bool FakeServer::ShouldSendTriggeredError() const {
if (!alternate_triggered_errors_) {
return true;
}
// Check that the counter is odd so that we trigger an error on the first
// request after alternating is enabled.
return request_counter_ % 2 != 0;
}
bool FakeServer::HasTriggeredError() const {
return commit_error_type_ != sync_pb::SyncEnums::SUCCESS ||
error_type_ != sync_pb::SyncEnums::SUCCESS ||
triggered_actionable_error_;
}
void FakeServer::AddObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
observers_.AddObserver(observer);
}
void FakeServer::RemoveObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
observers_.RemoveObserver(observer);
}
void FakeServer::OnCommit(syncer::DataTypeSet committed_data_types) {
for (Observer& observer : observers_) {
observer.OnCommit(committed_data_types);
}
}
void FakeServer::OnCommittedDeletionOrigin(
syncer::DataType type,
const sync_pb::DeletionOrigin& deletion_origin) {
committed_deletion_origins_[type].push_back(deletion_origin);
}
void FakeServer::EnableStrongConsistencyWithConflictDetectionModel() {
DCHECK(thread_checker_.CalledOnValidThread());
loopback_server_->EnableStrongConsistencyWithConflictDetectionModel();
}
void FakeServer::SetMaxGetUpdatesBatchSize(int batch_size) {
DCHECK(thread_checker_.CalledOnValidThread());
loopback_server_->SetMaxGetUpdatesBatchSize(batch_size);
}
void FakeServer::SetBagOfChips(const sync_pb::ChipBag& bag_of_chips) {
DCHECK(thread_checker_.CalledOnValidThread());
loopback_server_->SetBagOfChipsForTesting(bag_of_chips);
}
void FakeServer::TriggerMigrationDoneError(syncer::DataTypeSet types) {
DCHECK(thread_checker_.CalledOnValidThread());
loopback_server_->TriggerMigrationForTesting(types);
}
void FakeServer::EnableGcDirectiveForMigration() {
DCHECK(thread_checker_.CalledOnValidThread());
loopback_server_->EnableGcDirectiveForMigration();
}
int FakeServer::GetMigrationVersion(syncer::DataType type) const {
DCHECK(thread_checker_.CalledOnValidThread());
return loopback_server_->GetMigrationVersion(type);
}
// static
int FakeServer::GetProgressMarkerMigrationVersion(
const sync_pb::DataTypeProgressMarker& progress_marker) {
return syncer::LoopbackServer::GetMigrationVersionFromProgressTokenForTesting(
progress_marker.token());
}
void FakeServer::AddCollaboration(syncer::CollaborationId collaboration_id) {
collaborations_.insert(std::move(collaboration_id));
// TODO(b/325917757): update collaboration data type.
}
void FakeServer::RemoveCollaboration(
const syncer::CollaborationId& collaboration_id) {
collaborations_.erase(collaboration_id);
// TODO(b/325917757): update collaboration data type.
}
std::string FakeServer::GetStoreBirthday() const {
return loopback_server_->GetStoreBirthday();
}
const std::vector<sync_pb::DeletionOrigin>&
FakeServer::GetCommittedDeletionOrigins(syncer::DataType type) const {
auto it = committed_deletion_origins_.find(type);
if (it == committed_deletion_origins_.end()) {
static const std::vector<sync_pb::DeletionOrigin> empty_result;
return empty_result;
}
return it->second;
}
base::WeakPtr<FakeServer> FakeServer::AsWeakPtr() {
DCHECK(thread_checker_.CalledOnValidThread());
return weak_ptr_factory_.GetWeakPtr();
}
void FakeServer::LogForTestFailure(const base::Location& location,
const std::string& title,
const std::string& body) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableFakeServerFailureOutput)) {
return;
}
if (gtest_scoped_traces_.empty()) {
gtest_scoped_traces_.push_back(std::make_unique<testing::ScopedTrace>(
location.file_name(), location.line_number(),
base::StringPrintf(
"Add --%s to hide verbose logs from the fake server.",
switches::kDisableFakeServerFailureOutput)));
}
gtest_scoped_traces_.push_back(std::make_unique<testing::ScopedTrace>(
location.file_name(), location.line_number(),
base::StringPrintf("--- %s %d (reverse chronological order) ---\n%s",
title, request_counter_, body)));
}
void FakeServer::OnWillCommit() {
for (Observer& observer : observers_) {
observer.OnWillCommit();
}
}
} // namespace fake_server