Home | History | Annotate | Download | only in engine
      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.
      4 
      5 #include "sync/engine/directory_commit_contribution.h"
      6 
      7 #include "base/message_loop/message_loop.h"
      8 #include "sync/internal_api/public/base/attachment_id_proto.h"
      9 #include "sync/sessions/status_controller.h"
     10 #include "sync/syncable/entry.h"
     11 #include "sync/syncable/mutable_entry.h"
     12 #include "sync/syncable/syncable_read_transaction.h"
     13 #include "sync/syncable/syncable_write_transaction.h"
     14 #include "sync/test/engine/test_directory_setter_upper.h"
     15 #include "sync/test/engine/test_id_factory.h"
     16 #include "sync/test/engine/test_syncable_utils.h"
     17 #include "testing/gtest/include/gtest/gtest.h"
     18 
     19 namespace syncer {
     20 
     21 class DirectoryCommitContributionTest : public ::testing::Test {
     22  public:
     23   virtual void SetUp() OVERRIDE {
     24     dir_maker_.SetUp();
     25 
     26     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
     27     CreateTypeRoot(&trans, dir(), PREFERENCES);
     28     CreateTypeRoot(&trans, dir(), EXTENSIONS);
     29     CreateTypeRoot(&trans, dir(), ARTICLES);
     30     CreateTypeRoot(&trans, dir(), BOOKMARKS);
     31   }
     32 
     33   virtual void TearDown() OVERRIDE {
     34     dir_maker_.TearDown();
     35   }
     36 
     37  protected:
     38   int64 CreateUnsyncedItemWithAttachments(
     39       syncable::WriteTransaction* trans,
     40       ModelType type,
     41       const std::string& tag,
     42       const sync_pb::AttachmentMetadata& attachment_metadata) {
     43     syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type);
     44     syncable::MutableEntry entry(
     45         trans,
     46         syncable::CREATE,
     47         type,
     48         parent_entry.GetId(),
     49         tag);
     50     if (attachment_metadata.record_size() > 0) {
     51       entry.PutAttachmentMetadata(attachment_metadata);
     52     }
     53     entry.PutIsUnsynced(true);
     54     return entry.GetMetahandle();
     55   }
     56 
     57   int64 CreateUnsyncedItem(syncable::WriteTransaction* trans,
     58                            ModelType type,
     59                            const std::string& tag) {
     60     return CreateUnsyncedItemWithAttachments(
     61         trans, type, tag, sync_pb::AttachmentMetadata());
     62   }
     63 
     64   int64 CreateSyncedItem(syncable::WriteTransaction* trans,
     65                          ModelType type,
     66                          const std::string& tag) {
     67     syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type);
     68     syncable::MutableEntry entry(
     69         trans,
     70         syncable::CREATE,
     71         type,
     72         parent_entry.GetId(),
     73         tag);
     74 
     75     entry.PutId(syncable::Id::CreateFromServerId(
     76         id_factory_.NewServerId().GetServerId()));
     77     entry.PutBaseVersion(10);
     78     entry.PutServerVersion(10);
     79     entry.PutIsUnappliedUpdate(false);
     80     entry.PutIsUnsynced(false);
     81     entry.PutIsDel(false);
     82     entry.PutServerIsDel(false);
     83 
     84     return entry.GetMetahandle();
     85   }
     86 
     87   void CreateSuccessfulCommitResponse(
     88       const sync_pb::SyncEntity& entity,
     89       sync_pb::CommitResponse::EntryResponse* response) {
     90     response->set_response_type(sync_pb::CommitResponse::SUCCESS);
     91     response->set_non_unique_name(entity.name());
     92     response->set_version(entity.version() + 1);
     93     response->set_parent_id_string(entity.parent_id_string());
     94 
     95     if (entity.id_string()[0] == '-')  // Look for the - in 'c-1234' style IDs.
     96       response->set_id_string(id_factory_.NewServerId().GetServerId());
     97     else
     98       response->set_id_string(entity.id_string());
     99   }
    100 
    101   syncable::Directory* dir() {
    102     return dir_maker_.directory();
    103   }
    104 
    105   TestIdFactory id_factory_;
    106 
    107   // Used in construction of DirectoryTypeDebugInfoEmitters.
    108   ObserverList<TypeDebugInfoObserver> type_observers_;
    109 
    110  private:
    111   base::MessageLoop loop_;  // Neeed to initialize the directory.
    112   TestDirectorySetterUpper dir_maker_;
    113 };
    114 
    115 // Verify that the DirectoryCommitContribution contains only entries of its
    116 // specified type.
    117 TEST_F(DirectoryCommitContributionTest, GatherByTypes) {
    118   int64 pref1;
    119   {
    120     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
    121     pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1");
    122     CreateUnsyncedItem(&trans, PREFERENCES, "pref2");
    123     CreateUnsyncedItem(&trans, EXTENSIONS, "extension1");
    124   }
    125 
    126   DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_);
    127   scoped_ptr<DirectoryCommitContribution> cc(
    128       DirectoryCommitContribution::Build(dir(), PREFERENCES, 5, &emitter));
    129   ASSERT_EQ(2U, cc->GetNumEntries());
    130 
    131   const std::vector<int64>& metahandles = cc->metahandles_;
    132   EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) !=
    133               metahandles.end());
    134   EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) !=
    135               metahandles.end());
    136 
    137   cc->CleanUp();
    138 }
    139 
    140 // Verify that the DirectoryCommitContributionTest builder function
    141 // truncates if necessary.
    142 TEST_F(DirectoryCommitContributionTest, GatherAndTruncate) {
    143   int64 pref1;
    144   int64 pref2;
    145   {
    146     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
    147     pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1");
    148     pref2 = CreateUnsyncedItem(&trans, PREFERENCES, "pref2");
    149     CreateUnsyncedItem(&trans, EXTENSIONS, "extension1");
    150   }
    151 
    152   DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_);
    153   scoped_ptr<DirectoryCommitContribution> cc(
    154       DirectoryCommitContribution::Build(dir(), PREFERENCES, 1, &emitter));
    155   ASSERT_EQ(1U, cc->GetNumEntries());
    156 
    157   int64 only_metahandle = cc->metahandles_[0];
    158   EXPECT_TRUE(only_metahandle == pref1 || only_metahandle == pref2);
    159 
    160   cc->CleanUp();
    161 }
    162 
    163 // Sanity check for building commits from DirectoryCommitContributions.
    164 // This test makes two CommitContribution objects of different types and uses
    165 // them to initialize a commit message.  Then it checks that the contents of the
    166 // commit message match those of the directory they came from.
    167 TEST_F(DirectoryCommitContributionTest, PrepareCommit) {
    168   {
    169     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
    170     CreateUnsyncedItem(&trans, PREFERENCES, "pref1");
    171     CreateUnsyncedItem(&trans, PREFERENCES, "pref2");
    172     CreateUnsyncedItem(&trans, EXTENSIONS, "extension1");
    173   }
    174 
    175   DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_);
    176   DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_);
    177   scoped_ptr<DirectoryCommitContribution> pref_cc(
    178       DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1));
    179   scoped_ptr<DirectoryCommitContribution> ext_cc(
    180       DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2));
    181 
    182   sync_pb::ClientToServerMessage message;
    183   pref_cc->AddToCommitMessage(&message);
    184   ext_cc->AddToCommitMessage(&message);
    185 
    186   const sync_pb::CommitMessage& commit_message = message.commit();
    187 
    188   std::set<syncable::Id> ids_for_commit;
    189   ASSERT_EQ(3, commit_message.entries_size());
    190   for (int i = 0; i < commit_message.entries_size(); ++i) {
    191     const sync_pb::SyncEntity& entity = commit_message.entries(i);
    192     // The entities in this test have client-style IDs since they've never been
    193     // committed before, so we must use CreateFromClientString to re-create them
    194     // from the commit message.
    195     ids_for_commit.insert(syncable::Id::CreateFromClientString(
    196             entity.id_string()));
    197   }
    198 
    199   ASSERT_EQ(3U, ids_for_commit.size());
    200   {
    201     syncable::ReadTransaction trans(FROM_HERE, dir());
    202     for (std::set<syncable::Id>::iterator it = ids_for_commit.begin();
    203          it != ids_for_commit.end(); ++it) {
    204       SCOPED_TRACE(it->value());
    205       syncable::Entry entry(&trans, syncable::GET_BY_ID, *it);
    206       ASSERT_TRUE(entry.good());
    207       EXPECT_TRUE(entry.GetSyncing());
    208     }
    209   }
    210 
    211   pref_cc->CleanUp();
    212   ext_cc->CleanUp();
    213 }
    214 
    215 // Check that deletion requests include a model type.
    216 // This was not always the case, but was implemented to allow us to loosen some
    217 // other restrictions in the protocol.
    218 TEST_F(DirectoryCommitContributionTest, DeletedItemsWithSpecifics) {
    219   int64 pref1;
    220   {
    221     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
    222     pref1 = CreateSyncedItem(&trans, PREFERENCES, "pref1");
    223     syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, pref1);
    224     e1.PutIsDel(true);
    225     e1.PutIsUnsynced(true);
    226   }
    227 
    228   DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_);
    229   scoped_ptr<DirectoryCommitContribution> pref_cc(
    230       DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter));
    231   ASSERT_TRUE(pref_cc);
    232 
    233   sync_pb::ClientToServerMessage message;
    234   pref_cc->AddToCommitMessage(&message);
    235 
    236   const sync_pb::CommitMessage& commit_message = message.commit();
    237   ASSERT_EQ(1, commit_message.entries_size());
    238   EXPECT_TRUE(
    239       commit_message.entries(0).specifics().has_preference());
    240 
    241   pref_cc->CleanUp();
    242 }
    243 
    244 // As ususal, bookmarks are special.  Bookmark deletion is special.
    245 // Deleted bookmarks include a valid "is folder" bit and their full specifics
    246 // (especially the meta info, which is what server really wants).
    247 TEST_F(DirectoryCommitContributionTest, DeletedBookmarksWithSpecifics) {
    248   int64 bm1;
    249   {
    250     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
    251     bm1 = CreateSyncedItem(&trans, BOOKMARKS, "bm1");
    252     syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, bm1);
    253 
    254     e1.PutIsDir(true);
    255     e1.PutServerIsDir(true);
    256 
    257     sync_pb::EntitySpecifics specifics;
    258     sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
    259     bm_specifics->set_url("http://www.chrome.com");
    260     bm_specifics->set_title("Chrome");
    261     sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info();
    262     meta_info->set_key("K");
    263     meta_info->set_value("V");
    264     e1.PutSpecifics(specifics);
    265 
    266     e1.PutIsDel(true);
    267     e1.PutIsUnsynced(true);
    268   }
    269 
    270   DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_);
    271   scoped_ptr<DirectoryCommitContribution> bm_cc(
    272       DirectoryCommitContribution::Build(dir(), BOOKMARKS, 25, &emitter));
    273   ASSERT_TRUE(bm_cc);
    274 
    275   sync_pb::ClientToServerMessage message;
    276   bm_cc->AddToCommitMessage(&message);
    277 
    278   const sync_pb::CommitMessage& commit_message = message.commit();
    279   ASSERT_EQ(1, commit_message.entries_size());
    280 
    281   const sync_pb::SyncEntity& entity = commit_message.entries(0);
    282   EXPECT_TRUE(entity.has_folder());
    283   ASSERT_TRUE(entity.specifics().has_bookmark());
    284   ASSERT_EQ(1, entity.specifics().bookmark().meta_info_size());
    285   EXPECT_EQ("K", entity.specifics().bookmark().meta_info(0).key());
    286   EXPECT_EQ("V", entity.specifics().bookmark().meta_info(0).value());
    287 
    288   bm_cc->CleanUp();
    289 }
    290 
    291 // Test that bookmarks support hierarchy.
    292 TEST_F(DirectoryCommitContributionTest, HierarchySupport_Bookmark) {
    293 
    294   // Create a normal-looking bookmark item.
    295   int64 bm1;
    296   {
    297     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
    298     bm1 = CreateSyncedItem(&trans, BOOKMARKS, "bm1");
    299     syncable::MutableEntry e(&trans, syncable::GET_BY_HANDLE, bm1);
    300 
    301     sync_pb::EntitySpecifics specifics;
    302     sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
    303     bm_specifics->set_url("http://www.chrome.com");
    304     bm_specifics->set_title("Chrome");
    305     e.PutSpecifics(specifics);
    306 
    307     e.PutIsDel(false);
    308     e.PutIsUnsynced(true);
    309 
    310     EXPECT_TRUE(e.ShouldMaintainHierarchy());
    311   }
    312 
    313   DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_);
    314   scoped_ptr<DirectoryCommitContribution> bm_cc(
    315       DirectoryCommitContribution::Build(dir(), BOOKMARKS, 25, &emitter));
    316 
    317   sync_pb::ClientToServerMessage message;
    318   bm_cc->AddToCommitMessage(&message);
    319   const sync_pb::CommitMessage& commit_message = message.commit();
    320   bm_cc->CleanUp();
    321 
    322   ASSERT_EQ(1, commit_message.entries_size());
    323   EXPECT_TRUE(commit_message.entries(0).has_parent_id_string());
    324   EXPECT_FALSE(commit_message.entries(0).parent_id_string().empty());
    325 }
    326 
    327 // Test that preferences do not support hierarchy.
    328 TEST_F(DirectoryCommitContributionTest, HierarchySupport_Preferences) {
    329   // Create a normal-looking prefs item.
    330   int64 pref1;
    331   {
    332     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
    333     pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1");
    334     syncable::MutableEntry e(&trans, syncable::GET_BY_HANDLE, pref1);
    335 
    336     EXPECT_FALSE(e.ShouldMaintainHierarchy());
    337   }
    338 
    339   DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_);
    340   scoped_ptr<DirectoryCommitContribution> pref_cc(
    341       DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter));
    342 
    343   sync_pb::ClientToServerMessage message;
    344   pref_cc->AddToCommitMessage(&message);
    345   const sync_pb::CommitMessage& commit_message = message.commit();
    346   pref_cc->CleanUp();
    347 
    348   ASSERT_EQ(1, commit_message.entries_size());
    349   EXPECT_FALSE(commit_message.entries(0).has_parent_id_string());
    350   EXPECT_TRUE(commit_message.entries(0).parent_id_string().empty());
    351 }
    352 
    353 void AddAttachment(sync_pb::AttachmentMetadata* metadata, bool is_on_server) {
    354   sync_pb::AttachmentMetadataRecord record;
    355   *record.mutable_id() = CreateAttachmentIdProto();
    356   record.set_is_on_server(is_on_server);
    357   *metadata->add_record() = record;
    358 }
    359 
    360 // Creates some unsynced items, pretends to commit them, and hands back a
    361 // specially crafted response to the syncer in order to test commit response
    362 // processing.  The response simulates a succesful commit scenario.
    363 TEST_F(DirectoryCommitContributionTest, ProcessCommitResponse) {
    364   int64 pref1_handle;
    365   int64 pref2_handle;
    366   int64 ext1_handle;
    367   {
    368     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
    369     pref1_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref1");
    370     pref2_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref2");
    371     ext1_handle = CreateUnsyncedItem(&trans, EXTENSIONS, "extension1");
    372   }
    373 
    374   DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_);
    375   DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_);
    376   scoped_ptr<DirectoryCommitContribution> pref_cc(
    377       DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1));
    378   scoped_ptr<DirectoryCommitContribution> ext_cc(
    379       DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2));
    380 
    381   sync_pb::ClientToServerMessage message;
    382   pref_cc->AddToCommitMessage(&message);
    383   ext_cc->AddToCommitMessage(&message);
    384 
    385   const sync_pb::CommitMessage& commit_message = message.commit();
    386   ASSERT_EQ(3, commit_message.entries_size());
    387 
    388   sync_pb::ClientToServerResponse response;
    389   for (int i = 0; i < commit_message.entries_size(); ++i) {
    390     sync_pb::SyncEntity entity = commit_message.entries(i);
    391     sync_pb::CommitResponse_EntryResponse* entry_response =
    392         response.mutable_commit()->add_entryresponse();
    393     CreateSuccessfulCommitResponse(entity, entry_response);
    394   }
    395 
    396   sessions::StatusController status;
    397 
    398   // Process these in reverse order.  Just because we can.
    399   ext_cc->ProcessCommitResponse(response, &status);
    400   pref_cc->ProcessCommitResponse(response, &status);
    401 
    402   {
    403     syncable::ReadTransaction trans(FROM_HERE, dir());
    404     syncable::Entry p1(&trans, syncable::GET_BY_HANDLE, pref1_handle);
    405     EXPECT_TRUE(p1.GetId().ServerKnows());
    406     EXPECT_FALSE(p1.GetSyncing());
    407     EXPECT_LT(0, p1.GetServerVersion());
    408 
    409     syncable::Entry p2(&trans, syncable::GET_BY_HANDLE, pref2_handle);
    410     EXPECT_TRUE(p2.GetId().ServerKnows());
    411     EXPECT_FALSE(p2.GetSyncing());
    412     EXPECT_LT(0, p2.GetServerVersion());
    413 
    414     syncable::Entry e1(&trans, syncable::GET_BY_HANDLE, ext1_handle);
    415     EXPECT_TRUE(e1.GetId().ServerKnows());
    416     EXPECT_FALSE(e1.GetSyncing());
    417     EXPECT_LT(0, e1.GetServerVersion());
    418   }
    419 
    420   pref_cc->CleanUp();
    421   ext_cc->CleanUp();
    422 }
    423 
    424 // Creates some unsynced items with attachments and verifies that only items
    425 // where all attachments have been uploaded to the server are eligible to be
    426 // committed.
    427 TEST_F(DirectoryCommitContributionTest, ProcessCommitResponseWithAttachments) {
    428   int64 art1_handle;
    429   int64 art2_handle;
    430   int64 art3_handle;
    431   {
    432     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
    433 
    434     // art1 has two attachments, both have been uploaded to the server.  art1 is
    435     // eligible to be committed.
    436     sync_pb::AttachmentMetadata art1_attachments;
    437     AddAttachment(&art1_attachments, true /* is_on_server */);
    438     AddAttachment(&art1_attachments, true /* is_on_server */);
    439     art1_handle = CreateUnsyncedItemWithAttachments(
    440         &trans, ARTICLES, "art1", art1_attachments);
    441 
    442     // art2 has two attachments, one of which has been uploaded to the
    443     // server. art2 is not eligible to be committed.
    444     sync_pb::AttachmentMetadata art2_attachments;
    445     AddAttachment(&art2_attachments, false /* is_on_server */);
    446     AddAttachment(&art2_attachments, true /* is_on_server */);
    447     art2_handle = CreateUnsyncedItemWithAttachments(
    448         &trans, ARTICLES, "art2", art2_attachments);
    449 
    450     // art3 has two attachments, neither of which have been uploaded to the
    451     // server. art2 is not eligible to be committed.
    452     sync_pb::AttachmentMetadata art3_attachments;
    453     AddAttachment(&art3_attachments, false /* is_on_server */);
    454     AddAttachment(&art3_attachments, false /* is_on_server */);
    455     art3_handle = CreateUnsyncedItemWithAttachments(
    456         &trans, ARTICLES, "art3", art3_attachments);
    457   }
    458 
    459   DirectoryTypeDebugInfoEmitter emitter(ARTICLES, &type_observers_);
    460   scoped_ptr<DirectoryCommitContribution> art_cc(
    461       DirectoryCommitContribution::Build(dir(), ARTICLES, 25, &emitter));
    462 
    463   // Only art1 is ready.
    464   EXPECT_EQ(1U, art_cc->GetNumEntries());
    465 
    466   sync_pb::ClientToServerMessage message;
    467   art_cc->AddToCommitMessage(&message);
    468 
    469   const sync_pb::CommitMessage& commit_message = message.commit();
    470   ASSERT_EQ(1, commit_message.entries_size());
    471 
    472   sync_pb::ClientToServerResponse response;
    473   for (int i = 0; i < commit_message.entries_size(); ++i) {
    474     sync_pb::SyncEntity entity = commit_message.entries(i);
    475     sync_pb::CommitResponse_EntryResponse* entry_response =
    476         response.mutable_commit()->add_entryresponse();
    477     CreateSuccessfulCommitResponse(entity, entry_response);
    478   }
    479 
    480   sessions::StatusController status;
    481   art_cc->ProcessCommitResponse(response, &status);
    482   {
    483     syncable::ReadTransaction trans(FROM_HERE, dir());
    484 
    485     syncable::Entry a1(&trans, syncable::GET_BY_HANDLE, art1_handle);
    486     EXPECT_TRUE(a1.GetId().ServerKnows());
    487     EXPECT_FALSE(a1.GetSyncing());
    488     EXPECT_LT(0, a1.GetServerVersion());
    489 
    490     syncable::Entry a2(&trans, syncable::GET_BY_HANDLE, art2_handle);
    491     EXPECT_FALSE(a2.GetId().ServerKnows());
    492     EXPECT_FALSE(a2.GetSyncing());
    493     EXPECT_EQ(0, a2.GetServerVersion());
    494 
    495     syncable::Entry a3(&trans, syncable::GET_BY_HANDLE, art3_handle);
    496     EXPECT_FALSE(a3.GetId().ServerKnows());
    497     EXPECT_FALSE(a3.GetSyncing());
    498     EXPECT_EQ(0, a3.GetServerVersion());
    499   }
    500 
    501   art_cc->CleanUp();
    502 }
    503 
    504 }  // namespace syncer
    505