Commit 882ec4ac authored by Jon Mann's avatar Jon Mann Committed by Commit Bot

Creates a new sync_wifi component and defines WifiConfigurationBridge.

This component is for the implementation of adding Wi-Fi networks to
Chrome sync in ChromeOS.  WifiConfigurationBridge is responsible for
tracking changes to the network list locally and on the server and
reconciling differences between them.  This CL is only concerned with
tracking the remote network list.

Bug: 966270
Change-Id: I57e330d4a53e5db2472c6fc5c3c5f2adbd3d673b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1703802
Commit-Queue: Jon Mann <jonmann@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Reviewed-by: default avatarMarc Treib <treib@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#681005}
parent 734810a2
......@@ -23,6 +23,7 @@ test("chromeos_components_unittests") {
"//chromeos/components/nearby:unit_tests",
"//chromeos/components/power:unit_tests",
"//chromeos/components/proximity_auth:unit_tests",
"//chromeos/components/sync_wifi:unit_tests",
"//chromeos/components/tether:unit_tests",
"//mojo/core/embedder",
]
......
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
static_library("sync_wifi") {
sources = [
"synced_network_updater.h",
"wifi_configuration_bridge.cc",
"wifi_configuration_bridge.h",
]
deps = [
"//base",
"//components/sync",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"wifi_configuration_bridge_unittest.cc",
]
deps = [
":sync_wifi",
"//base/test:test_support",
"//components/sync:test_support",
"//testing/gtest",
]
}
include_rules = [
"+components/sync",
]
jonmann@chromium.org
khorimoto@chromium.org
# COMPONENT: OS>Systems>Network>WiFi
Wifi Configuration Sync
sync_wifi is a component which provides the necessary APIs to sync Wi-Fi
credentials across devices. This component will receive changes from the
Chrome sync server as well as monitor local changes to the network list
and keep the two network lists in sync with each other. Local changes will
be monitored using chromeos::NetworkStateHandler and updated using
chromeos::NetworkConfigurationHandler. Changes from the server will be
received through the syncer::ModelTypeSyncBridge interface.
Only password protected networks which were added by the specific user will be
synced to their account. Public networks, enterprise networks, and networks
which have static ip configurations will not be synced.
The network configurations with credentials will be stored in the users
cryptohome using a syncer::ModelTypeStore and held in memory during the
user session. All network details will be encrypted before getting sent
to the Chrome sync server.
This feature is tracked at http://crbug.com/954648
\ No newline at end of file
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_SYNC_WIFI_SYNCED_NETWORK_UPDATER_H_
#define CHROMEOS_COMPONENTS_SYNC_WIFI_SYNCED_NETWORK_UPDATER_H_
#include <string>
#include "base/macros.h"
namespace sync_pb {
class WifiConfigurationSpecificsData;
}
namespace sync_wifi {
// Applies updates to synced networks to the local networking stack.
class SyncedNetworkUpdater {
public:
virtual ~SyncedNetworkUpdater() = default;
virtual void AddOrUpdateNetwork(
const sync_pb::WifiConfigurationSpecificsData& specifics) = 0;
virtual void RemoveNetwork(const std::string& ssid) = 0;
protected:
SyncedNetworkUpdater() = default;
private:
DISALLOW_COPY_AND_ASSIGN(SyncedNetworkUpdater);
};
} // namespace sync_wifi
#endif // CHROMEOS_COMPONENTS_SYNC_WIFI_SYNCED_NETWORK_UPDATER_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/sync_wifi/wifi_configuration_bridge.h"
#include <algorithm>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "chromeos/components/sync_wifi/synced_network_updater.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/metadata_change_list.h"
#include "components/sync/model/model_type_change_processor.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/protocol/model_type_state.pb.h"
namespace sync_wifi {
namespace {
std::unique_ptr<syncer::EntityData> GenerateWifiEntityData(
const sync_pb::WifiConfigurationSpecificsData& data) {
auto entity_data = std::make_unique<syncer::EntityData>();
entity_data->specifics.mutable_wifi_configuration()
->mutable_client_only_encrypted_data()
->CopyFrom(data);
entity_data->name = data.ssid();
return entity_data;
}
} // namespace
WifiConfigurationBridge::WifiConfigurationBridge(
SyncedNetworkUpdater* synced_network_updater,
std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor,
syncer::OnceModelTypeStoreFactory create_store_callback)
: ModelTypeSyncBridge(std::move(change_processor)),
synced_network_updater_(synced_network_updater) {
std::move(create_store_callback)
.Run(syncer::WIFI_CONFIGURATIONS,
base::BindOnce(&WifiConfigurationBridge::OnStoreCreated,
weak_ptr_factory_.GetWeakPtr()));
}
WifiConfigurationBridge::~WifiConfigurationBridge() {}
std::unique_ptr<syncer::MetadataChangeList>
WifiConfigurationBridge::CreateMetadataChangeList() {
return syncer::ModelTypeStore::WriteBatch::CreateMetadataChangeList();
}
base::Optional<syncer::ModelError> WifiConfigurationBridge::MergeSyncData(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_data) {
DCHECK(entries_.empty());
return ApplySyncChanges(std::move(metadata_change_list),
std::move(entity_data));
}
base::Optional<syncer::ModelError> WifiConfigurationBridge::ApplySyncChanges(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) {
std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch =
store_->CreateWriteBatch();
for (std::unique_ptr<syncer::EntityChange>& change : entity_changes) {
if (change->type() == syncer::EntityChange::ACTION_DELETE) {
auto it = entries_.find(change->storage_key());
if (it != entries_.end()) {
entries_.erase(it);
batch->DeleteData(change->storage_key());
synced_network_updater_->RemoveNetwork(change->storage_key());
}
continue;
}
auto& specifics = change->data()
.specifics.wifi_configuration()
.client_only_encrypted_data();
synced_network_updater_->AddOrUpdateNetwork(specifics);
batch->WriteData(change->storage_key(), specifics.SerializeAsString());
entries_[change->storage_key()] = std::move(specifics);
}
batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
Commit(std::move(batch));
return base::nullopt;
}
void WifiConfigurationBridge::GetData(StorageKeyList storage_keys,
DataCallback callback) {
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const std::string& id : storage_keys) {
auto it = entries_.find(id);
if (it == entries_.end()) {
continue;
}
batch->Put(id, GenerateWifiEntityData(it->second));
}
std::move(callback).Run(std::move(batch));
}
void WifiConfigurationBridge::GetAllDataForDebugging(DataCallback callback) {
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const auto& entry : entries_) {
batch->Put(entry.first, GenerateWifiEntityData(entry.second));
}
std::move(callback).Run(std::move(batch));
}
std::string WifiConfigurationBridge::GetClientTag(
const syncer::EntityData& entity_data) {
return GetStorageKey(entity_data);
}
std::string WifiConfigurationBridge::GetStorageKey(
const syncer::EntityData& entity_data) {
return entity_data.specifics.wifi_configuration()
.client_only_encrypted_data()
.ssid();
}
void WifiConfigurationBridge::OnStoreCreated(
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<syncer::ModelTypeStore> store) {
if (error) {
change_processor()->ReportError(*error);
return;
}
store_ = std::move(store);
store_->ReadAllData(base::BindOnce(&WifiConfigurationBridge::OnReadAllData,
weak_ptr_factory_.GetWeakPtr()));
}
void WifiConfigurationBridge::OnReadAllData(
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<syncer::ModelTypeStore::RecordList> records) {
if (error) {
change_processor()->ReportError(*error);
return;
}
for (syncer::ModelTypeStore::Record& record : *records) {
sync_pb::WifiConfigurationSpecificsData data;
if (record.id.empty() || !data.ParseFromString(record.value)) {
DVLOG(1) << "Unable to parse proto for entry with key: " << record.id;
continue;
}
entries_[record.id] = std::move(data);
}
store_->ReadAllMetadata(
base::BindOnce(&WifiConfigurationBridge::OnReadAllMetadata,
weak_ptr_factory_.GetWeakPtr()));
}
void WifiConfigurationBridge::OnReadAllMetadata(
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
if (error) {
change_processor()->ReportError(*error);
return;
}
change_processor()->ModelReadyToSync(std::move(metadata_batch));
}
void WifiConfigurationBridge::OnCommit(
const base::Optional<syncer::ModelError>& error) {
if (error)
change_processor()->ReportError(*error);
}
void WifiConfigurationBridge::Commit(
std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch) {
store_->CommitWriteBatch(std::move(batch),
base::BindOnce(&WifiConfigurationBridge::OnCommit,
weak_ptr_factory_.GetWeakPtr()));
}
std::vector<std::string> WifiConfigurationBridge::GetAllSsidsForTesting() {
std::vector<std::string> ssids;
for (const auto& entry : entries_)
ssids.push_back(entry.second.ssid());
return ssids;
}
} // namespace sync_wifi
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_SYNC_WIFI_WIFI_CONFIGURATION_BRIDGE_H_
#define CHROMEOS_COMPONENTS_SYNC_WIFI_WIFI_CONFIGURATION_BRIDGE_H_
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chromeos/components/sync_wifi/synced_network_updater.h"
#include "components/sync/base/model_type.h"
#include "components/sync/model/model_type_store.h"
#include "components/sync/model/model_type_sync_bridge.h"
namespace syncer {
class ModelTypeChangeProcessor;
} // namespace syncer
namespace sync_wifi {
// Receives updates to network configurations from the Chrome sync back end and
// from the system network stack and keeps both lists in sync.
class WifiConfigurationBridge : public syncer::ModelTypeSyncBridge {
public:
WifiConfigurationBridge(
SyncedNetworkUpdater* synced_network_updater,
std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor,
syncer::OnceModelTypeStoreFactory create_store_callback);
~WifiConfigurationBridge() override;
// syncer::ModelTypeSyncBridge:
std::unique_ptr<syncer::MetadataChangeList> CreateMetadataChangeList()
override;
base::Optional<syncer::ModelError> MergeSyncData(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_data) override;
base::Optional<syncer::ModelError> ApplySyncChanges(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) override;
void GetData(StorageKeyList storage_keys, DataCallback callback) override;
void GetAllDataForDebugging(DataCallback callback) override;
std::string GetClientTag(const syncer::EntityData& entity_data) override;
std::string GetStorageKey(const syncer::EntityData& entity_data) override;
// Comes from |entries_| the in-memory map.
std::vector<std::string> GetAllSsidsForTesting();
private:
void Commit(std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch);
// Callbacks for ModelTypeStore.
void OnStoreCreated(const base::Optional<syncer::ModelError>& error,
std::unique_ptr<syncer::ModelTypeStore> store);
void OnReadAllData(
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<syncer::ModelTypeStore::RecordList> records);
void OnReadAllMetadata(const base::Optional<syncer::ModelError>& error,
std::unique_ptr<syncer::MetadataBatch> metadata_batch);
void OnCommit(const base::Optional<syncer::ModelError>& error);
// An in-memory list of the proto's that mirrors what is on the sync server.
// This gets updated when changes are received from the server and after local
// changes have been committed. On initialization of this class, it is
// populated with the contents of |store_|.
base::flat_map<std::string, sync_pb::WifiConfigurationSpecificsData> entries_;
// The on disk store of WifiConfigurationSpecifics protos that mirrors what
// is on the sync server. This gets updated when changes are received from
// the server and after local changes have been committed to the server.
std::unique_ptr<syncer::ModelTypeStore> store_;
SyncedNetworkUpdater* synced_network_updater_;
base::WeakPtrFactory<WifiConfigurationBridge> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(WifiConfigurationBridge);
};
} // namespace sync_wifi
#endif // CHROMEOS_COMPONENTS_SYNC_WIFI_WIFI_CONFIGURATION_BRIDGE_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/sync_wifi/wifi_configuration_bridge.h"
#include <map>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "chromeos/components/sync_wifi/synced_network_updater.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/mock_model_type_change_processor.h"
#include "components/sync/model/model_type_store_test_util.h"
#include "components/sync/model_impl/in_memory_metadata_change_list.h"
#include "components/sync/protocol/model_type_state.pb.h"
#include "components/sync/test/test_matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sync_wifi {
namespace {
using sync_pb::WifiConfigurationSpecificsData;
using testing::_;
using testing::AllOf;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::Pair;
using testing::Return;
using testing::SizeIs;
using testing::UnorderedElementsAre;
const char kSsidMeow[] = "meow";
const char kSsidWoof[] = "woof";
WifiConfigurationSpecificsData CreateSpecifics(const std::string& ssid) {
WifiConfigurationSpecificsData specifics;
specifics.set_ssid(ssid);
specifics.set_security_type(
WifiConfigurationSpecificsData::SECURITY_TYPE_PSK);
specifics.set_passphrase("password");
specifics.set_automatically_connect(
WifiConfigurationSpecificsData::AUTOMATICALLY_CONNECT_ENABLED);
specifics.set_is_preferred(
WifiConfigurationSpecificsData::IS_PREFERRED_ENABLED);
specifics.set_metered(WifiConfigurationSpecificsData::METERED_OPTION_AUTO);
sync_pb::WifiConfigurationSpecificsData_ProxyConfiguration proxy_config;
proxy_config.set_proxy_option(WifiConfigurationSpecificsData::
ProxyConfiguration::PROXY_OPTION_DISABLED);
specifics.mutable_proxy_configuration()->CopyFrom(proxy_config);
return specifics;
}
std::unique_ptr<syncer::EntityData> GenerateWifiEntityData(
const sync_pb::WifiConfigurationSpecificsData& data) {
auto entity_data = std::make_unique<syncer::EntityData>();
entity_data->specifics.mutable_wifi_configuration()
->mutable_client_only_encrypted_data()
->CopyFrom(data);
entity_data->name = data.ssid();
return entity_data;
}
bool VectorContainsString(std::vector<std::string> v, std::string s) {
return std::find(v.begin(), v.end(), s) != v.end();
}
bool VectorContainsSsid(
const std::vector<sync_pb::WifiConfigurationSpecificsData>& v,
std::string s) {
for (sync_pb::WifiConfigurationSpecificsData specifics : v) {
if (specifics.ssid() == s)
return true;
}
return false;
}
class TestSyncedNetworkUpdater : public SyncedNetworkUpdater {
public:
TestSyncedNetworkUpdater() = default;
~TestSyncedNetworkUpdater() override = default;
const std::vector<sync_pb::WifiConfigurationSpecificsData>&
add_or_update_calls() {
return add_update_calls_;
}
const std::vector<std::string>& remove_calls() { return remove_calls_; }
private:
void AddOrUpdateNetwork(
const sync_pb::WifiConfigurationSpecificsData& specifics) override {
add_update_calls_.push_back(specifics);
}
void RemoveNetwork(const std::string& ssid) override {
remove_calls_.push_back(ssid);
}
std::vector<sync_pb::WifiConfigurationSpecificsData> add_update_calls_;
std::vector<std::string> remove_calls_;
};
class WifiConfigurationBridgeTest : public testing::Test {
protected:
WifiConfigurationBridgeTest()
: store_(syncer::ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()) {}
void SetUp() override {
ON_CALL(mock_processor_, IsTrackingMetadata()).WillByDefault(Return(true));
synced_network_updater_ = std::make_unique<TestSyncedNetworkUpdater>();
bridge_ = std::make_unique<WifiConfigurationBridge>(
synced_network_updater(), mock_processor_.CreateForwardingProcessor(),
syncer::ModelTypeStoreTestUtil::MoveStoreToFactory(std::move(store_)));
}
void DisableBridge() {
ON_CALL(mock_processor_, IsTrackingMetadata()).WillByDefault(Return(false));
}
syncer::EntityChangeList CreateEntityAddList(
const std::vector<WifiConfigurationSpecificsData>& specifics_list) {
syncer::EntityChangeList changes;
for (const auto& data : specifics_list) {
auto entity_data = std::make_unique<syncer::EntityData>();
sync_pb::WifiConfigurationSpecifics specifics;
specifics.mutable_client_only_encrypted_data()->CopyFrom(data);
entity_data->specifics.mutable_wifi_configuration()->CopyFrom(specifics);
entity_data->name = data.ssid();
changes.push_back(
syncer::EntityChange::CreateAdd(data.ssid(), std::move(entity_data)));
}
return changes;
}
syncer::MockModelTypeChangeProcessor* processor() { return &mock_processor_; }
WifiConfigurationBridge* bridge() { return bridge_.get(); }
TestSyncedNetworkUpdater* synced_network_updater() {
return synced_network_updater_.get();
}
private:
base::test::ScopedTaskEnvironment task_environment_;
std::unique_ptr<syncer::ModelTypeStore> store_;
testing::NiceMock<syncer::MockModelTypeChangeProcessor> mock_processor_;
std::unique_ptr<WifiConfigurationBridge> bridge_;
std::unique_ptr<TestSyncedNetworkUpdater> synced_network_updater_;
DISALLOW_COPY_AND_ASSIGN(WifiConfigurationBridgeTest);
};
TEST_F(WifiConfigurationBridgeTest, InitWithTwoNetworksFromServer) {
syncer::EntityChangeList remote_input;
WifiConfigurationSpecificsData entry1 = CreateSpecifics(kSsidMeow);
WifiConfigurationSpecificsData entry2 = CreateSpecifics(kSsidWoof);
remote_input.push_back(syncer::EntityChange::CreateAdd(
kSsidMeow, GenerateWifiEntityData(entry1)));
remote_input.push_back(syncer::EntityChange::CreateAdd(
kSsidWoof, GenerateWifiEntityData(entry2)));
bridge()->MergeSyncData(
std::make_unique<syncer::InMemoryMetadataChangeList>(),
std::move(remote_input));
std::vector<std::string> ssids = bridge()->GetAllSsidsForTesting();
EXPECT_EQ(2u, ssids.size());
EXPECT_TRUE(VectorContainsString(ssids, kSsidMeow));
EXPECT_TRUE(VectorContainsString(ssids, kSsidWoof));
const std::vector<sync_pb::WifiConfigurationSpecificsData>& networks =
synced_network_updater()->add_or_update_calls();
EXPECT_EQ(2u, networks.size());
EXPECT_TRUE(VectorContainsSsid(networks, kSsidMeow));
EXPECT_TRUE(VectorContainsSsid(networks, kSsidWoof));
}
TEST_F(WifiConfigurationBridgeTest, ApplySyncChangesAddTwoSpecifics) {
const WifiConfigurationSpecificsData specifics1 = CreateSpecifics(kSsidMeow);
const WifiConfigurationSpecificsData specifics2 = CreateSpecifics(kSsidWoof);
base::Optional<syncer::ModelError> error =
bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
CreateEntityAddList({specifics1, specifics2}));
EXPECT_FALSE(error);
std::vector<std::string> ssids = bridge()->GetAllSsidsForTesting();
EXPECT_EQ(2u, ssids.size());
EXPECT_TRUE(VectorContainsString(ssids, kSsidMeow));
EXPECT_TRUE(VectorContainsString(ssids, kSsidWoof));
const std::vector<sync_pb::WifiConfigurationSpecificsData>& networks =
synced_network_updater()->add_or_update_calls();
EXPECT_EQ(2u, networks.size());
EXPECT_TRUE(VectorContainsSsid(networks, kSsidMeow));
EXPECT_TRUE(VectorContainsSsid(networks, kSsidWoof));
}
TEST_F(WifiConfigurationBridgeTest, ApplySyncChangesOneAdd) {
WifiConfigurationSpecificsData entry = CreateSpecifics(kSsidMeow);
syncer::EntityChangeList add_changes;
add_changes.push_back(syncer::EntityChange::CreateAdd(
kSsidMeow, GenerateWifiEntityData(entry)));
bridge()->ApplySyncChanges(
std::make_unique<syncer::InMemoryMetadataChangeList>(),
std::move(add_changes));
std::vector<std::string> ssids = bridge()->GetAllSsidsForTesting();
EXPECT_EQ(1u, ssids.size());
EXPECT_TRUE(VectorContainsString(ssids, kSsidMeow));
const std::vector<sync_pb::WifiConfigurationSpecificsData>& networks =
synced_network_updater()->add_or_update_calls();
EXPECT_EQ(1u, networks.size());
EXPECT_TRUE(VectorContainsSsid(networks, kSsidMeow));
}
TEST_F(WifiConfigurationBridgeTest, ApplySyncChangesOneDeletion) {
WifiConfigurationSpecificsData entry = CreateSpecifics(kSsidMeow);
syncer::EntityChangeList add_changes;
add_changes.push_back(syncer::EntityChange::CreateAdd(
kSsidMeow, GenerateWifiEntityData(entry)));
bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
std::move(add_changes));
std::vector<std::string> ssids = bridge()->GetAllSsidsForTesting();
EXPECT_EQ(1u, ssids.size());
EXPECT_TRUE(VectorContainsString(ssids, kSsidMeow));
const std::vector<sync_pb::WifiConfigurationSpecificsData>& networks =
synced_network_updater()->add_or_update_calls();
EXPECT_EQ(1u, networks.size());
EXPECT_TRUE(VectorContainsSsid(networks, kSsidMeow));
syncer::EntityChangeList delete_changes;
delete_changes.push_back(syncer::EntityChange::CreateDelete(kSsidMeow));
bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
std::move(delete_changes));
EXPECT_TRUE(bridge()->GetAllSsidsForTesting().empty());
const std::vector<std::string>& removed_networks =
synced_network_updater()->remove_calls();
EXPECT_EQ(1u, removed_networks.size());
EXPECT_TRUE(VectorContainsString(removed_networks, kSsidMeow));
}
} // namespace
} // namespace sync_wifi
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment