bookmark_model_type_processor_unittest.cc 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. // Copyright 2017 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. #include "components/sync_bookmarks/bookmark_model_type_processor.h"
  5. #include <map>
  6. #include <string>
  7. #include <utility>
  8. #include <vector>
  9. #include "base/callback_helpers.h"
  10. #include "base/guid.h"
  11. #include "base/memory/raw_ptr.h"
  12. #include "base/strings/utf_string_conversions.h"
  13. #include "base/test/bind.h"
  14. #include "base/test/gmock_move_support.h"
  15. #include "base/test/metrics/histogram_tester.h"
  16. #include "base/test/mock_callback.h"
  17. #include "base/test/scoped_feature_list.h"
  18. #include "base/test/task_environment.h"
  19. #include "components/bookmarks/browser/bookmark_model.h"
  20. #include "components/bookmarks/test/test_bookmark_client.h"
  21. #include "components/favicon/core/test/mock_favicon_service.h"
  22. #include "components/sync/base/client_tag_hash.h"
  23. #include "components/sync/base/unique_position.h"
  24. #include "components/sync/engine/commit_queue.h"
  25. #include "components/sync/engine/data_type_activation_response.h"
  26. #include "components/sync/model/data_type_activation_request.h"
  27. #include "components/sync/model/type_entities_count.h"
  28. #include "components/sync/protocol/bookmark_model_metadata.pb.h"
  29. #include "components/sync/protocol/bookmark_specifics.pb.h"
  30. #include "components/sync/protocol/model_type_state.pb.h"
  31. #include "components/sync_bookmarks/switches.h"
  32. #include "components/sync_bookmarks/synced_bookmark_tracker_entity.h"
  33. #include "components/undo/bookmark_undo_service.h"
  34. #include "testing/gmock/include/gmock/gmock.h"
  35. #include "testing/gtest/include/gtest/gtest.h"
  36. #include "ui/base/models/tree_node_iterator.h"
  37. namespace sync_bookmarks {
  38. namespace {
  39. using base::ASCIIToUTF16;
  40. using testing::ElementsAre;
  41. using testing::Eq;
  42. using testing::IsEmpty;
  43. using testing::IsNull;
  44. using testing::NiceMock;
  45. using testing::NotNull;
  46. using testing::UnorderedElementsAre;
  47. const char kBookmarkBarTag[] = "bookmark_bar";
  48. const char kOtherBookmarksTag[] = "other_bookmarks";
  49. const char kMobileBookmarksTag[] = "synced_bookmarks";
  50. const char kBookmarkBarId[] = "bookmark_bar_id";
  51. const char kOtherBookmarksId[] = "other_bookmarks_id";
  52. const char kMobileBookmarksId[] = "mobile_bookmarks_id";
  53. const char kBookmarksRootId[] = "root_id";
  54. const char kCacheGuid[] = "generated_id";
  55. struct BookmarkInfo {
  56. std::string server_id;
  57. std::string title;
  58. std::string url; // empty for folders.
  59. std::string parent_id;
  60. std::string server_tag;
  61. };
  62. MATCHER_P(CommitRequestDataMatchesGuid, guid, "") {
  63. const syncer::CommitRequestData* data = arg.get();
  64. return data != nullptr && data->entity != nullptr &&
  65. data->entity->specifics.bookmark().guid() == guid.AsLowercaseString();
  66. }
  67. MATCHER_P(TrackedEntityCorrespondsToBookmarkNode, bookmark_node, "") {
  68. const SyncedBookmarkTrackerEntity* entity = arg;
  69. return entity->bookmark_node() == bookmark_node;
  70. }
  71. syncer::UpdateResponseData CreateUpdateResponseData(
  72. const BookmarkInfo& bookmark_info,
  73. const syncer::UniquePosition& unique_position,
  74. int response_version,
  75. const base::GUID& guid) {
  76. syncer::EntityData data;
  77. data.id = bookmark_info.server_id;
  78. data.legacy_parent_id = bookmark_info.parent_id;
  79. data.server_defined_unique_tag = bookmark_info.server_tag;
  80. data.originator_client_item_id = guid.AsLowercaseString();
  81. sync_pb::BookmarkSpecifics* bookmark_specifics =
  82. data.specifics.mutable_bookmark();
  83. bookmark_specifics->set_guid(guid.AsLowercaseString());
  84. bookmark_specifics->set_legacy_canonicalized_title(bookmark_info.title);
  85. bookmark_specifics->set_full_title(bookmark_info.title);
  86. *bookmark_specifics->mutable_unique_position() = unique_position.ToProto();
  87. if (bookmark_info.url.empty()) {
  88. bookmark_specifics->set_type(sync_pb::BookmarkSpecifics::FOLDER);
  89. } else {
  90. bookmark_specifics->set_type(sync_pb::BookmarkSpecifics::URL);
  91. bookmark_specifics->set_url(bookmark_info.url);
  92. }
  93. syncer::UpdateResponseData response_data;
  94. response_data.entity = std::move(data);
  95. response_data.response_version = response_version;
  96. return response_data;
  97. }
  98. syncer::UpdateResponseData CreateUpdateResponseData(
  99. const BookmarkInfo& bookmark_info,
  100. const syncer::UniquePosition& unique_position,
  101. int response_version) {
  102. return CreateUpdateResponseData(bookmark_info, unique_position,
  103. response_version,
  104. base::GUID::GenerateRandomV4());
  105. }
  106. sync_pb::ModelTypeState CreateDummyModelTypeState() {
  107. sync_pb::ModelTypeState model_type_state;
  108. model_type_state.set_cache_guid(kCacheGuid);
  109. model_type_state.set_initial_sync_done(true);
  110. return model_type_state;
  111. }
  112. // |node| must not be nullptr.
  113. sync_pb::BookmarkMetadata CreateNodeMetadata(
  114. const bookmarks::BookmarkNode* node,
  115. const std::string& server_id,
  116. const syncer::UniquePosition& unique_position =
  117. syncer::UniquePosition::InitialPosition(
  118. syncer::UniquePosition::RandomSuffix())) {
  119. sync_pb::BookmarkMetadata bookmark_metadata;
  120. bookmark_metadata.set_id(node->id());
  121. bookmark_metadata.mutable_metadata()->set_server_id(server_id);
  122. bookmark_metadata.mutable_metadata()->set_client_tag_hash(
  123. syncer::ClientTagHash::FromUnhashed(syncer::BOOKMARKS,
  124. node->guid().AsLowercaseString())
  125. .value());
  126. *bookmark_metadata.mutable_metadata()->mutable_unique_position() =
  127. unique_position.ToProto();
  128. // Required by SyncedBookmarkTracker during validation of local metadata.
  129. if (!node->is_folder()) {
  130. bookmark_metadata.mutable_metadata()->set_bookmark_favicon_hash(123);
  131. }
  132. return bookmark_metadata;
  133. }
  134. // Same as above but marks the node as unsynced (pending commit). |node| must
  135. // not be nullptr.
  136. sync_pb::BookmarkMetadata CreateUnsyncedNodeMetadata(
  137. const bookmarks::BookmarkNode* node,
  138. const std::string& server_id) {
  139. sync_pb::BookmarkMetadata bookmark_metadata =
  140. CreateNodeMetadata(node, server_id);
  141. // Mark the entity as unsynced.
  142. bookmark_metadata.mutable_metadata()->set_sequence_number(2);
  143. bookmark_metadata.mutable_metadata()->set_acked_sequence_number(1);
  144. return bookmark_metadata;
  145. }
  146. sync_pb::BookmarkModelMetadata CreateMetadataForPermanentNodes(
  147. const bookmarks::BookmarkModel* bookmark_model) {
  148. sync_pb::BookmarkModelMetadata model_metadata;
  149. *model_metadata.mutable_model_type_state() = CreateDummyModelTypeState();
  150. *model_metadata.add_bookmarks_metadata() =
  151. CreateNodeMetadata(bookmark_model->bookmark_bar_node(),
  152. /*server_id=*/kBookmarkBarId);
  153. *model_metadata.add_bookmarks_metadata() =
  154. CreateNodeMetadata(bookmark_model->mobile_node(),
  155. /*server_id=*/kMobileBookmarksId);
  156. *model_metadata.add_bookmarks_metadata() =
  157. CreateNodeMetadata(bookmark_model->other_node(),
  158. /*server_id=*/kOtherBookmarksId);
  159. return model_metadata;
  160. }
  161. void AssertState(const BookmarkModelTypeProcessor* processor,
  162. const std::vector<BookmarkInfo>& bookmarks) {
  163. const SyncedBookmarkTracker* tracker = processor->GetTrackerForTest();
  164. ASSERT_THAT(tracker, NotNull());
  165. // Make sure the tracker contains all bookmarks in |bookmarks| + the
  166. // 3 permanent nodes.
  167. ASSERT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(bookmarks.size() + 3));
  168. for (BookmarkInfo bookmark : bookmarks) {
  169. const SyncedBookmarkTrackerEntity* entity =
  170. tracker->GetEntityForSyncId(bookmark.server_id);
  171. ASSERT_THAT(entity, NotNull());
  172. const bookmarks::BookmarkNode* node = entity->bookmark_node();
  173. ASSERT_THAT(node->GetTitle(), Eq(ASCIIToUTF16(bookmark.title)));
  174. ASSERT_THAT(node->url(), Eq(GURL(bookmark.url)));
  175. const SyncedBookmarkTrackerEntity* parent_entity =
  176. tracker->GetEntityForSyncId(bookmark.parent_id);
  177. ASSERT_THAT(node->parent(), Eq(parent_entity->bookmark_node()));
  178. }
  179. }
  180. class MockCommitQueue : public syncer::CommitQueue {
  181. public:
  182. MOCK_METHOD(void, NudgeForCommit, (), (override));
  183. };
  184. class ProxyCommitQueue : public syncer::CommitQueue {
  185. public:
  186. explicit ProxyCommitQueue(CommitQueue* commit_queue)
  187. : commit_queue_(commit_queue) {
  188. DCHECK(commit_queue_);
  189. }
  190. void NudgeForCommit() override { commit_queue_->NudgeForCommit(); }
  191. private:
  192. raw_ptr<CommitQueue> commit_queue_ = nullptr;
  193. };
  194. class BookmarkModelTypeProcessorTest : public testing::Test {
  195. public:
  196. BookmarkModelTypeProcessorTest()
  197. : processor_(std::make_unique<BookmarkModelTypeProcessor>(
  198. &bookmark_undo_service_)),
  199. bookmark_model_(bookmarks::TestBookmarkClient::CreateModel()) {
  200. processor_->SetFaviconService(&favicon_service_);
  201. }
  202. // Initialized the processor with bookmarks from the existing model and always
  203. // initializes permanent folders.
  204. void SimulateModelReadyToSyncWithInitialSyncDone() {
  205. sync_pb::BookmarkModelMetadata model_metadata =
  206. CreateMetadataForPermanentNodes(bookmark_model_.get());
  207. DCHECK(model_metadata.model_type_state().initial_sync_done());
  208. // By default, set that bookmarks are reuploaded to avoid reupload logic.
  209. model_metadata.set_bookmarks_hierarchy_fields_reuploaded(true);
  210. // Rely on the order of iterating over the tree: left child is always
  211. // handled before the current one. In this case increasing unique position
  212. // will always represent the right order.
  213. syncer::UniquePosition next_unique_position =
  214. syncer::UniquePosition::InitialPosition(
  215. syncer::UniquePosition::RandomSuffix());
  216. ui::TreeNodeIterator<const bookmarks::BookmarkNode> iterator(
  217. bookmark_model_->root_node());
  218. while (iterator.has_next()) {
  219. const bookmarks::BookmarkNode* node = iterator.Next();
  220. if (node->is_permanent_node()) {
  221. // Permanent nodes have been added explicitly.
  222. continue;
  223. }
  224. *model_metadata.add_bookmarks_metadata() = CreateNodeMetadata(
  225. node, /*server_id=*/"id_" + node->guid().AsLowercaseString(),
  226. next_unique_position);
  227. next_unique_position = syncer::UniquePosition::After(
  228. next_unique_position, syncer::UniquePosition::RandomSuffix());
  229. }
  230. processor_->ModelReadyToSync(model_metadata.SerializeAsString(),
  231. schedule_save_closure_.Get(),
  232. bookmark_model_.get());
  233. ASSERT_THAT(processor_->GetTrackerForTest(), NotNull());
  234. }
  235. void SimulateModelReadyToSyncWithoutLocalMetadata() {
  236. processor_->ModelReadyToSync(
  237. /*metadata_str=*/std::string(), schedule_save_closure_.Get(),
  238. bookmark_model_.get());
  239. }
  240. void SimulateOnSyncStarting() {
  241. syncer::DataTypeActivationRequest request;
  242. request.cache_guid = kCacheGuid;
  243. processor_->OnSyncStarting(request, base::DoNothing());
  244. }
  245. void SimulateConnectSync() {
  246. processor_->ConnectSync(
  247. std::make_unique<ProxyCommitQueue>(&mock_commit_queue_));
  248. }
  249. // Simulate browser restart.
  250. void ResetModelTypeProcessor() {
  251. processor_ =
  252. std::make_unique<BookmarkModelTypeProcessor>(&bookmark_undo_service_);
  253. processor_->SetFaviconService(&favicon_service_);
  254. }
  255. void DestroyBookmarkModel() { bookmark_model_.reset(); }
  256. bookmarks::BookmarkModel* bookmark_model() { return bookmark_model_.get(); }
  257. bookmarks::TestBookmarkClient* bookmark_client() {
  258. return static_cast<bookmarks::TestBookmarkClient*>(
  259. bookmark_model_->client());
  260. }
  261. BookmarkUndoService* bookmark_undo_service() {
  262. return &bookmark_undo_service_;
  263. }
  264. favicon::FaviconService* favicon_service() { return &favicon_service_; }
  265. MockCommitQueue* mock_commit_queue() { return &mock_commit_queue_; }
  266. BookmarkModelTypeProcessor* processor() { return processor_.get(); }
  267. base::MockCallback<base::RepeatingClosure>* schedule_save_closure() {
  268. return &schedule_save_closure_;
  269. }
  270. syncer::CommitRequestDataList GetLocalChangesFromProcessor(
  271. size_t max_entries) {
  272. base::MockOnceCallback<void(syncer::CommitRequestDataList &&)> callback;
  273. syncer::CommitRequestDataList local_changes;
  274. // Destruction of the mock upon return will verify that Run() was indeed
  275. // invoked.
  276. EXPECT_CALL(callback, Run).WillOnce(MoveArg(&local_changes));
  277. processor_->GetLocalChanges(max_entries, callback.Get());
  278. return local_changes;
  279. }
  280. private:
  281. base::test::TaskEnvironment task_environment_;
  282. NiceMock<base::MockCallback<base::RepeatingClosure>> schedule_save_closure_;
  283. BookmarkUndoService bookmark_undo_service_;
  284. NiceMock<favicon::MockFaviconService> favicon_service_;
  285. NiceMock<MockCommitQueue> mock_commit_queue_;
  286. std::unique_ptr<BookmarkModelTypeProcessor> processor_;
  287. std::unique_ptr<bookmarks::BookmarkModel> bookmark_model_;
  288. };
  289. TEST_F(BookmarkModelTypeProcessorTest, ShouldDoInitialMerge) {
  290. const syncer::UniquePosition kRandomPosition =
  291. syncer::UniquePosition::InitialPosition(
  292. syncer::UniquePosition::RandomSuffix());
  293. SimulateModelReadyToSyncWithoutLocalMetadata();
  294. SimulateOnSyncStarting();
  295. syncer::UpdateResponseDataList updates;
  296. // Add update for the permanent folders.
  297. updates.push_back(
  298. CreateUpdateResponseData({kBookmarkBarId, std::string(), std::string(),
  299. kBookmarksRootId, kBookmarkBarTag},
  300. kRandomPosition, /*response_version=*/0));
  301. updates.push_back(
  302. CreateUpdateResponseData({kOtherBookmarksId, std::string(), std::string(),
  303. kBookmarksRootId, kOtherBookmarksTag},
  304. kRandomPosition, /*response_version=*/0));
  305. updates.push_back(CreateUpdateResponseData(
  306. {kMobileBookmarksId, std::string(), std::string(), kBookmarksRootId,
  307. kMobileBookmarksTag},
  308. kRandomPosition, /*response_version=*/0));
  309. ASSERT_THAT(processor()->GetTrackerForTest(), IsNull());
  310. base::HistogramTester histogram_tester;
  311. processor()->OnUpdateReceived(CreateDummyModelTypeState(),
  312. std::move(updates));
  313. EXPECT_THAT(processor()->GetTrackerForTest(), NotNull());
  314. histogram_tester.ExpectUniqueSample(
  315. "Sync.ModelTypeInitialUpdateReceived",
  316. /*sample=*/syncer::ModelTypeHistogramValue(syncer::BOOKMARKS),
  317. /*expected_bucket_count=*/3);
  318. }
  319. TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) {
  320. SimulateModelReadyToSyncWithInitialSyncDone();
  321. SimulateOnSyncStarting();
  322. // Add update for another node under the bookmarks bar.
  323. const std::string kNodeId = "node_id";
  324. const std::string kTitle = "title";
  325. const std::string kUrl = "http://www.url.com";
  326. const syncer::UniquePosition kRandomPosition =
  327. syncer::UniquePosition::InitialPosition(
  328. syncer::UniquePosition::RandomSuffix());
  329. syncer::UpdateResponseDataList updates;
  330. updates.push_back(
  331. CreateUpdateResponseData({kNodeId, kTitle, kUrl, kBookmarkBarId,
  332. /*server_tag=*/std::string()},
  333. kRandomPosition, /*response_version=*/0));
  334. const bookmarks::BookmarkNode* bookmark_bar =
  335. bookmark_model()->bookmark_bar_node();
  336. ASSERT_TRUE(bookmark_bar->children().empty());
  337. processor()->OnUpdateReceived(CreateDummyModelTypeState(),
  338. std::move(updates));
  339. ASSERT_THAT(bookmark_bar->children().front().get(), NotNull());
  340. EXPECT_THAT(bookmark_bar->children().front()->GetTitle(),
  341. Eq(ASCIIToUTF16(kTitle)));
  342. EXPECT_THAT(bookmark_bar->children().front()->url(), Eq(GURL(kUrl)));
  343. }
  344. TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) {
  345. const std::string kTitle = "title";
  346. const GURL kUrl("http://www.url.com");
  347. const syncer::UniquePosition kRandomPosition =
  348. syncer::UniquePosition::InitialPosition(
  349. syncer::UniquePosition::RandomSuffix());
  350. const bookmarks::BookmarkNode* bookmark_bar =
  351. bookmark_model()->bookmark_bar_node();
  352. const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
  353. bookmark_bar, /*index=*/0, base::UTF8ToUTF16(kTitle), kUrl);
  354. SimulateOnSyncStarting();
  355. SimulateModelReadyToSyncWithInitialSyncDone();
  356. const SyncedBookmarkTrackerEntity* entity =
  357. processor()->GetTrackerForTest()->GetEntityForBookmarkNode(bookmark_node);
  358. ASSERT_THAT(entity, NotNull());
  359. // Process an update for the same bookmark.
  360. const std::string kNewTitle = "new-title";
  361. const std::string kNewUrl = "http://www.new-url.com";
  362. syncer::UpdateResponseDataList updates;
  363. updates.push_back(CreateUpdateResponseData(
  364. {entity->metadata().server_id(), kNewTitle, kNewUrl, kBookmarkBarId,
  365. /*server_tag=*/std::string()},
  366. kRandomPosition, /*response_version=*/1, bookmark_node->guid()));
  367. base::HistogramTester histogram_tester;
  368. processor()->OnUpdateReceived(CreateDummyModelTypeState(),
  369. std::move(updates));
  370. // Check if the bookmark has been updated properly.
  371. EXPECT_THAT(bookmark_bar->children().front().get(), Eq(bookmark_node));
  372. EXPECT_THAT(bookmark_node->GetTitle(), Eq(ASCIIToUTF16(kNewTitle)));
  373. EXPECT_THAT(bookmark_node->url(), Eq(GURL(kNewUrl)));
  374. histogram_tester.ExpectUniqueSample(
  375. "Sync.ModelTypeIncrementalUpdateReceived",
  376. /*sample=*/syncer::ModelTypeHistogramValue(syncer::BOOKMARKS),
  377. /*expected_bucket_count=*/1);
  378. }
  379. TEST_F(
  380. BookmarkModelTypeProcessorTest,
  381. ShouldScheduleSaveAfterRemoteUpdateWithOnlyMetadataChangeAndReflections) {
  382. const std::string kTitle = "title";
  383. const GURL kUrl("http://www.url.com");
  384. const syncer::UniquePosition kRandomPosition =
  385. syncer::UniquePosition::InitialPosition(
  386. syncer::UniquePosition::RandomSuffix());
  387. const bookmarks::BookmarkNode* bookmark_bar =
  388. bookmark_model()->bookmark_bar_node();
  389. const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
  390. bookmark_bar, /*index=*/0, base::UTF8ToUTF16(kTitle), kUrl);
  391. SimulateModelReadyToSyncWithInitialSyncDone();
  392. SimulateOnSyncStarting();
  393. const SyncedBookmarkTrackerEntity* entity =
  394. processor()->GetTrackerForTest()->GetEntityForBookmarkNode(bookmark_node);
  395. ASSERT_THAT(entity, NotNull());
  396. // Process an update for the same bookmark with the same data.
  397. syncer::UpdateResponseDataList updates;
  398. updates.push_back(CreateUpdateResponseData(
  399. {entity->metadata().server_id(), kTitle, kUrl.spec(), kBookmarkBarId,
  400. /*server_tag=*/std::string()},
  401. kRandomPosition, /*response_version=*/1, bookmark_node->guid()));
  402. updates[0].response_version++;
  403. EXPECT_CALL(*schedule_save_closure(), Run());
  404. processor()->OnUpdateReceived(CreateDummyModelTypeState(),
  405. std::move(updates));
  406. }
  407. TEST_F(BookmarkModelTypeProcessorTest, ShouldDecodeSyncMetadata) {
  408. const std::string kNodeId = "node_id1";
  409. const std::string kTitle = "title1";
  410. const std::string kUrl = "http://www.url1.com";
  411. std::vector<BookmarkInfo> bookmarks = {
  412. {kNodeId, kTitle, kUrl, kBookmarkBarId, /*server_tag=*/std::string()}};
  413. const bookmarks::BookmarkNode* bookmark_bar_node =
  414. bookmark_model()->bookmark_bar_node();
  415. const bookmarks::BookmarkNode* bookmarknode = bookmark_model()->AddURL(
  416. /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
  417. GURL(kUrl));
  418. SimulateModelReadyToSyncWithoutLocalMetadata();
  419. SimulateOnSyncStarting();
  420. sync_pb::BookmarkModelMetadata model_metadata =
  421. CreateMetadataForPermanentNodes(bookmark_model());
  422. // Add an entry for the bookmark node.
  423. *model_metadata.add_bookmarks_metadata() =
  424. CreateNodeMetadata(bookmarknode, kNodeId);
  425. // Create a new processor and init it with the metadata str.
  426. BookmarkModelTypeProcessor new_processor(bookmark_undo_service());
  427. std::string metadata_str;
  428. model_metadata.SerializeToString(&metadata_str);
  429. new_processor.ModelReadyToSync(metadata_str, base::DoNothing(),
  430. bookmark_model());
  431. AssertState(&new_processor, bookmarks);
  432. }
  433. TEST_F(BookmarkModelTypeProcessorTest, ShouldDecodeEncodedSyncMetadata) {
  434. const std::string kNodeId1 = "node_id1";
  435. const std::string kTitle1 = "title1";
  436. const GURL kUrl1("http://www.url1.com");
  437. const std::string kNodeId2 = "node_id2";
  438. const std::string kTitle2 = "title2";
  439. const GURL kUrl2("http://www.url2.com");
  440. const bookmarks::BookmarkNode* bookmark_bar =
  441. bookmark_model()->bookmark_bar_node();
  442. bookmark_model()->AddURL(bookmark_bar, /*index=*/0,
  443. base::UTF8ToUTF16(kTitle1), kUrl1);
  444. bookmark_model()->AddURL(bookmark_bar, /*index=*/1,
  445. base::UTF8ToUTF16(kTitle2), kUrl2);
  446. SimulateOnSyncStarting();
  447. SimulateModelReadyToSyncWithInitialSyncDone();
  448. // Create a new processor and init it with the same metadata str.
  449. BookmarkModelTypeProcessor new_processor(bookmark_undo_service());
  450. new_processor.ModelReadyToSync(processor()->EncodeSyncMetadata(),
  451. base::DoNothing(), bookmark_model());
  452. new_processor.GetTrackerForTest()->CheckAllNodesTracked(bookmark_model());
  453. // Make sure shutdown doesn't crash.
  454. DestroyBookmarkModel();
  455. EXPECT_FALSE(processor()->IsConnectedForTest());
  456. EXPECT_FALSE(new_processor.IsConnectedForTest());
  457. EXPECT_THAT(processor()->GetTrackerForTest(), NotNull());
  458. EXPECT_THAT(new_processor.GetTrackerForTest(), NotNull());
  459. }
  460. TEST_F(BookmarkModelTypeProcessorTest, ShouldDecodeEmptyMetadata) {
  461. // No save should be scheduled.
  462. EXPECT_CALL(*schedule_save_closure(), Run()).Times(0);
  463. SimulateModelReadyToSyncWithoutLocalMetadata();
  464. EXPECT_THAT(processor()->GetTrackerForTest(), IsNull());
  465. }
  466. TEST_F(BookmarkModelTypeProcessorTest,
  467. ShouldIgnoreNonEmptyMetadataWhileSyncNotDone) {
  468. sync_pb::BookmarkModelMetadata model_metadata;
  469. model_metadata.mutable_model_type_state()->set_initial_sync_done(false);
  470. // Add entries to the metadata.
  471. sync_pb::BookmarkMetadata* bookmark_metadata =
  472. model_metadata.add_bookmarks_metadata();
  473. bookmark_metadata->set_id(bookmark_model()->bookmark_bar_node()->id());
  474. bookmark_metadata->mutable_metadata()->set_server_id(kBookmarkBarId);
  475. // Create a new processor and init it with the metadata str.
  476. BookmarkModelTypeProcessor new_processor(bookmark_undo_service());
  477. // A save should be scheduled.
  478. NiceMock<base::MockCallback<base::RepeatingClosure>>
  479. new_schedule_save_closure;
  480. EXPECT_CALL(new_schedule_save_closure, Run());
  481. std::string metadata_str;
  482. model_metadata.SerializeToString(&metadata_str);
  483. new_processor.ModelReadyToSync(metadata_str, new_schedule_save_closure.Get(),
  484. bookmark_model());
  485. // Metadata are corrupted, so no tracker should have been created.
  486. EXPECT_THAT(new_processor.GetTrackerForTest(), IsNull());
  487. }
  488. TEST_F(BookmarkModelTypeProcessorTest,
  489. ShouldIgnoreMetadataNotMatchingTheModel) {
  490. sync_pb::BookmarkModelMetadata model_metadata;
  491. model_metadata.mutable_model_type_state()->set_initial_sync_done(true);
  492. // Add entries for only the bookmark bar. However, the TestBookmarkClient will
  493. // create all the 3 permanent nodes.
  494. *model_metadata.add_bookmarks_metadata() =
  495. CreateNodeMetadata(bookmark_model()->bookmark_bar_node(),
  496. /*server_id=*/kBookmarkBarId);
  497. // Create a new processor and init it with the metadata str.
  498. BookmarkModelTypeProcessor new_processor(bookmark_undo_service());
  499. // A save should be scheduled.
  500. NiceMock<base::MockCallback<base::RepeatingClosure>>
  501. new_schedule_save_closure;
  502. EXPECT_CALL(new_schedule_save_closure, Run());
  503. std::string metadata_str;
  504. model_metadata.SerializeToString(&metadata_str);
  505. new_processor.ModelReadyToSync(metadata_str, new_schedule_save_closure.Get(),
  506. bookmark_model());
  507. // Metadata are corrupted, so no tracker should have been created.
  508. EXPECT_THAT(new_processor.GetTrackerForTest(), IsNull());
  509. }
  510. // Verifies that the model type state stored in the tracker gets
  511. // updated upon handling remote updates by assigning a new encryption
  512. // key name.
  513. TEST_F(BookmarkModelTypeProcessorTest,
  514. ShouldUpdateModelTypeStateUponHandlingRemoteUpdates) {
  515. // Initialize the process to make sure the tracker has been created.
  516. SimulateModelReadyToSyncWithInitialSyncDone();
  517. SimulateOnSyncStarting();
  518. const SyncedBookmarkTracker* tracker = processor()->GetTrackerForTest();
  519. // The encryption key name should be empty.
  520. ASSERT_TRUE(tracker->model_type_state().encryption_key_name().empty());
  521. // Build a model type state with an encryption key name.
  522. const std::string kEncryptionKeyName = "new_encryption_key_name";
  523. sync_pb::ModelTypeState model_type_state(CreateDummyModelTypeState());
  524. model_type_state.set_encryption_key_name(kEncryptionKeyName);
  525. // Push empty updates list to the processor together with the updated model
  526. // type state.
  527. syncer::UpdateResponseDataList empty_updates_list;
  528. processor()->OnUpdateReceived(model_type_state,
  529. std::move(empty_updates_list));
  530. // The model type state inside the tracker should have been updated, and
  531. // carries the new encryption key name.
  532. EXPECT_THAT(tracker->model_type_state().encryption_key_name(),
  533. Eq(kEncryptionKeyName));
  534. }
  535. // This tests that when the encryption key changes, but the received entities
  536. // are already encrypted with the up-to-date encryption key, no recommit is
  537. // needed.
  538. TEST_F(BookmarkModelTypeProcessorTest,
  539. ShouldNotRecommitEntitiesWhenEncryptionIsUpToDate) {
  540. // Initialize the process to make sure the tracker has been created.
  541. SimulateOnSyncStarting();
  542. SimulateModelReadyToSyncWithInitialSyncDone();
  543. SimulateConnectSync();
  544. const SyncedBookmarkTracker* tracker = processor()->GetTrackerForTest();
  545. // The encryption key name should be empty.
  546. ASSERT_TRUE(tracker->model_type_state().encryption_key_name().empty());
  547. // Build a model type state with an encryption key name.
  548. const std::string kEncryptionKeyName = "new_encryption_key_name";
  549. sync_pb::ModelTypeState model_type_state(CreateDummyModelTypeState());
  550. model_type_state.set_encryption_key_name(kEncryptionKeyName);
  551. // Push an update that is encrypted with the new encryption key.
  552. const std::string kNodeId = "node_id";
  553. syncer::UpdateResponseData response_data = CreateUpdateResponseData(
  554. {kNodeId, "title", "http://www.url.com", /*parent_id=*/kBookmarkBarId,
  555. /*server_tag=*/std::string()},
  556. syncer::UniquePosition::InitialPosition(
  557. syncer::UniquePosition::RandomSuffix()),
  558. /*response_version=*/0);
  559. response_data.encryption_key_name = kEncryptionKeyName;
  560. EXPECT_CALL(*mock_commit_queue(), NudgeForCommit()).Times(0);
  561. syncer::UpdateResponseDataList updates;
  562. updates.push_back(std::move(response_data));
  563. processor()->OnUpdateReceived(model_type_state, std::move(updates));
  564. // The bookmarks shouldn't be marked for committing.
  565. ASSERT_THAT(tracker->GetEntityForSyncId(kNodeId), NotNull());
  566. EXPECT_THAT(tracker->GetEntityForSyncId(kNodeId)->IsUnsynced(), Eq(false));
  567. }
  568. // Verifies that the processor doesn't crash if sync is stopped before receiving
  569. // remote updates or tracking metadata.
  570. TEST_F(BookmarkModelTypeProcessorTest, ShouldStopBeforeReceivingRemoteUpdates) {
  571. SimulateModelReadyToSyncWithoutLocalMetadata();
  572. SimulateOnSyncStarting();
  573. ASSERT_THAT(processor()->GetTrackerForTest(), IsNull());
  574. processor()->OnSyncStopping(syncer::CLEAR_METADATA);
  575. EXPECT_THAT(processor()->GetTrackerForTest(), IsNull());
  576. }
  577. TEST_F(BookmarkModelTypeProcessorTest, ShouldStopAfterReceivingRemoteUpdates) {
  578. // Initialize the process to make sure the tracker has been created.
  579. SimulateModelReadyToSyncWithInitialSyncDone();
  580. SimulateOnSyncStarting();
  581. ASSERT_THAT(processor()->GetTrackerForTest(), NotNull());
  582. processor()->OnSyncStopping(syncer::CLEAR_METADATA);
  583. EXPECT_THAT(processor()->GetTrackerForTest(), IsNull());
  584. }
  585. TEST_F(BookmarkModelTypeProcessorTest,
  586. ShouldReportNoCountersWhenModelIsNotLoaded) {
  587. SimulateOnSyncStarting();
  588. ASSERT_THAT(processor()->GetTrackerForTest(), IsNull());
  589. syncer::TypeEntitiesCount count(syncer::BOOKMARKS);
  590. // Assign an arbitrary non-zero number of entities to be able to check that
  591. // actually a 0 has been written to it later.
  592. count.non_tombstone_entities = 1000;
  593. processor()->GetTypeEntitiesCountForDebugging(base::BindLambdaForTesting(
  594. [&](const syncer::TypeEntitiesCount& returned_count) {
  595. count = returned_count;
  596. }));
  597. EXPECT_EQ(0, count.non_tombstone_entities);
  598. }
  599. TEST_F(BookmarkModelTypeProcessorTest,
  600. ShouldNotCommitEntitiesWithoutLoadedFavicons) {
  601. const std::string kNodeId = "node_id1";
  602. const std::string kTitle = "title1";
  603. const std::string kUrl = "http://www.url1.com";
  604. const std::string kIconUrl = "http://www.url1.com/favicon";
  605. const bookmarks::BookmarkNode* bookmark_bar_node =
  606. bookmark_model()->bookmark_bar_node();
  607. const bookmarks::BookmarkNode* node = bookmark_model()->AddURL(
  608. /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
  609. GURL(kUrl));
  610. // Add an entry for the bookmark node that is unsynced.
  611. sync_pb::BookmarkModelMetadata model_metadata =
  612. CreateMetadataForPermanentNodes(bookmark_model());
  613. *model_metadata.add_bookmarks_metadata() =
  614. CreateUnsyncedNodeMetadata(node, kNodeId);
  615. SimulateOnSyncStarting();
  616. processor()->ModelReadyToSync(model_metadata.SerializeAsString(),
  617. schedule_save_closure()->Get(),
  618. bookmark_model());
  619. ASSERT_FALSE(bookmark_client()->HasFaviconLoadTasks());
  620. EXPECT_THAT(GetLocalChangesFromProcessor(/*max_entries=*/10), IsEmpty());
  621. EXPECT_TRUE(node->is_favicon_loading());
  622. bookmark_client()->SimulateFaviconLoaded(GURL(kUrl), GURL(kIconUrl),
  623. gfx::Image());
  624. ASSERT_TRUE(node->is_favicon_loaded());
  625. EXPECT_THAT(GetLocalChangesFromProcessor(/*max_entries=*/10),
  626. ElementsAre(CommitRequestDataMatchesGuid(node->guid())));
  627. }
  628. TEST_F(BookmarkModelTypeProcessorTest,
  629. ShouldCommitEntitiesWhileOtherFaviconsLoading) {
  630. const std::string kNodeId1 = "node_id1";
  631. const std::string kNodeId2 = "node_id2";
  632. const std::string kTitle = "title";
  633. const std::string kUrl1 = "http://www.url1.com";
  634. const std::string kUrl2 = "http://www.url2.com";
  635. const std::string kIconUrl = "http://www.url.com/favicon";
  636. const bookmarks::BookmarkNode* bookmark_bar_node =
  637. bookmark_model()->bookmark_bar_node();
  638. const bookmarks::BookmarkNode* node1 = bookmark_model()->AddURL(
  639. /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
  640. GURL(kUrl1));
  641. const bookmarks::BookmarkNode* node2 = bookmark_model()->AddURL(
  642. /*parent=*/bookmark_bar_node, /*index=*/1, base::UTF8ToUTF16(kTitle),
  643. GURL(kUrl2));
  644. // Add entries for the two bookmark nodes and mark them as unsynced.
  645. sync_pb::BookmarkModelMetadata model_metadata =
  646. CreateMetadataForPermanentNodes(bookmark_model());
  647. *model_metadata.add_bookmarks_metadata() =
  648. CreateUnsyncedNodeMetadata(node1, kNodeId1);
  649. *model_metadata.add_bookmarks_metadata() =
  650. CreateUnsyncedNodeMetadata(node2, kNodeId2);
  651. SimulateOnSyncStarting();
  652. processor()->ModelReadyToSync(model_metadata.SerializeAsString(),
  653. schedule_save_closure()->Get(),
  654. bookmark_model());
  655. // The goal of this test is to mimic the case where one bookmark (the first
  656. // one listed by SyncedBookmarkTracker::GetEntitiesWithLocalChanges()) has no
  657. // loaded favicon, while the second one does. The precise order is not known
  658. // in advance (in the current implementation, it depends on the iteration
  659. // order for raw pointers in an unordered_set) which means the test needs to
  660. // pass for both cases.
  661. const std::vector<const SyncedBookmarkTrackerEntity*> unsynced_entities =
  662. processor()->GetTrackerForTest()->GetEntitiesWithLocalChanges();
  663. ASSERT_THAT(
  664. unsynced_entities,
  665. UnorderedElementsAre(TrackedEntityCorrespondsToBookmarkNode(node1),
  666. TrackedEntityCorrespondsToBookmarkNode(node2)));
  667. // Force a favicon load for the second listed entity, but leave the first
  668. // without loaded favicon.
  669. bookmark_model()->GetFavicon(unsynced_entities[1]->bookmark_node());
  670. bookmark_client()->SimulateFaviconLoaded(
  671. unsynced_entities[1]->bookmark_node()->url(), GURL(kIconUrl),
  672. gfx::Image());
  673. ASSERT_TRUE(unsynced_entities[1]->bookmark_node()->is_favicon_loaded());
  674. ASSERT_FALSE(unsynced_entities[0]->bookmark_node()->is_favicon_loaded());
  675. ASSERT_FALSE(unsynced_entities[0]->bookmark_node()->is_favicon_loading());
  676. EXPECT_THAT(GetLocalChangesFromProcessor(/*max_entries=*/1),
  677. ElementsAre(CommitRequestDataMatchesGuid(
  678. unsynced_entities[1]->bookmark_node()->guid())));
  679. // |unsynced_entities[0]| has been excluded from the result above because the
  680. // favicon isn't loaded, but the loading process should have started now (see
  681. // BookmarkLocalChangesBuilder::BuildCommitRequests()).
  682. EXPECT_TRUE(unsynced_entities[0]->bookmark_node()->is_favicon_loading());
  683. }
  684. TEST_F(BookmarkModelTypeProcessorTest, ShouldReuploadLegacyBookmarksOnStart) {
  685. const std::string kTitle = "title";
  686. const GURL kUrl("http://www.url.com");
  687. const bookmarks::BookmarkNode* node =
  688. bookmark_model()->AddURL(bookmark_model()->bookmark_bar_node(),
  689. /*index=*/0, base::UTF8ToUTF16(kTitle), kUrl);
  690. SimulateOnSyncStarting();
  691. SimulateModelReadyToSyncWithInitialSyncDone();
  692. SimulateConnectSync();
  693. ASSERT_THAT(processor()->GetTrackerForTest()->GetEntityForBookmarkNode(node),
  694. NotNull());
  695. const std::string server_id = processor()
  696. ->GetTrackerForTest()
  697. ->GetEntityForBookmarkNode(node)
  698. ->metadata()
  699. .server_id();
  700. sync_pb::BookmarkModelMetadata model_metadata =
  701. processor()->GetTrackerForTest()->BuildBookmarkModelMetadata();
  702. model_metadata.clear_bookmarks_hierarchy_fields_reuploaded();
  703. ASSERT_FALSE(processor()->GetTrackerForTest()->HasLocalChanges());
  704. // Simulate browser restart, enable sync reupload and initialize the processor
  705. // again.
  706. ResetModelTypeProcessor();
  707. base::test::ScopedFeatureList features;
  708. features.InitAndEnableFeature(switches::kSyncReuploadBookmarks);
  709. std::string metadata_str;
  710. model_metadata.SerializeToString(&metadata_str);
  711. processor()->ModelReadyToSync(metadata_str, base::DoNothing(),
  712. bookmark_model());
  713. SimulateOnSyncStarting();
  714. SimulateConnectSync();
  715. ASSERT_THAT(processor()->GetTrackerForTest(), NotNull());
  716. const SyncedBookmarkTrackerEntity* entity =
  717. processor()->GetTrackerForTest()->GetEntityForSyncId(server_id);
  718. ASSERT_THAT(entity, NotNull());
  719. // Entity should be synced before until first update is received.
  720. ASSERT_FALSE(entity->IsUnsynced());
  721. ASSERT_FALSE(processor()
  722. ->GetTrackerForTest()
  723. ->BuildBookmarkModelMetadata()
  724. .bookmarks_hierarchy_fields_reuploaded());
  725. // Synchronize with the server and get any updates.
  726. EXPECT_CALL(*mock_commit_queue(), NudgeForCommit());
  727. processor()->OnUpdateReceived(CreateDummyModelTypeState(),
  728. syncer::UpdateResponseDataList());
  729. // Check that all entities are unsynced now and metadata is marked as
  730. // reuploaded.
  731. EXPECT_TRUE(entity->IsUnsynced());
  732. EXPECT_TRUE(processor()
  733. ->GetTrackerForTest()
  734. ->BuildBookmarkModelMetadata()
  735. .bookmarks_hierarchy_fields_reuploaded());
  736. }
  737. } // namespace
  738. } // namespace sync_bookmarks