Index: chrome/browser/sync/profile_sync_service_password_unittest.cc |
diff --git a/chrome/browser/sync/profile_sync_service_password_unittest.cc b/chrome/browser/sync/profile_sync_service_password_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4038f8e5ccc3ab14d583c0ab8e1ba808f7881e41 |
--- /dev/null |
+++ b/chrome/browser/sync/profile_sync_service_password_unittest.cc |
@@ -0,0 +1,472 @@ |
+// Copyright (c) 2010 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 <vector> |
+ |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+#include "base/task.h" |
+#include "base/time.h" |
+#include "base/utf_string_conversions.h" |
+#include "base/waitable_event.h" |
+#include "chrome/browser/password_manager/password_store.h" |
+#include "chrome/browser/sync/engine/syncapi.h" |
+#include "chrome/browser/sync/glue/password_change_processor.h" |
+#include "chrome/browser/sync/glue/password_data_type_controller.h" |
+#include "chrome/browser/sync/glue/password_model_associator.h" |
+#include "chrome/browser/sync/glue/sync_backend_host_mock.h" |
+#include "chrome/browser/sync/profile_sync_factory.h" |
+#include "chrome/browser/sync/profile_sync_factory_mock.h" |
+#include "chrome/browser/sync/profile_sync_service.h" |
+#include "chrome/browser/sync/profile_sync_test_util.h" |
+#include "chrome/browser/sync/protocol/password_specifics.pb.h" |
+#include "chrome/browser/sync/syncable/directory_manager.h" |
+#include "chrome/browser/sync/syncable/syncable.h" |
+#include "chrome/browser/sync/test_profile_sync_service.h" |
+#include "chrome/common/notification_source.h" |
+#include "chrome/common/notification_type.h" |
+#include "chrome/test/sync/engine/test_id_factory.h" |
+#include "chrome/test/profile_mock.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "webkit/glue/password_form.h" |
+ |
+using base::Time; |
+using base::WaitableEvent; |
+using browser_sync::PasswordChangeProcessor; |
+using browser_sync::PasswordDataTypeController; |
+using browser_sync::PasswordModelAssociator; |
+using browser_sync::SyncBackendHostMock; |
+using browser_sync::TestIdFactory; |
+using browser_sync::UnrecoverableErrorHandler; |
+using sync_api::SyncManager; |
+using sync_api::UserShare; |
+using syncable::BASE_VERSION; |
+using syncable::CREATE; |
+using syncable::DirectoryManager; |
+using syncable::ID; |
+using syncable::IS_DEL; |
+using syncable::IS_DIR; |
+using syncable::IS_UNAPPLIED_UPDATE; |
+using syncable::IS_UNSYNCED; |
+using syncable::MutableEntry; |
+using syncable::SERVER_IS_DIR; |
+using syncable::SERVER_VERSION; |
+using syncable::SPECIFICS; |
+using syncable::ScopedDirLookup; |
+using syncable::UNIQUE_SERVER_TAG; |
+using syncable::UNITTEST; |
+using syncable::WriteTransaction; |
+using testing::_; |
+using testing::DoAll; |
+using testing::DoDefault; |
+using testing::ElementsAre; |
+using testing::Eq; |
+using testing::Invoke; |
+using testing::Return; |
+using testing::SaveArg; |
+using testing::SetArgumentPointee; |
+using webkit_glue::PasswordForm; |
+ |
+ACTION_P3(MakePasswordSyncComponents, service, ps, dtc) { |
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); |
+ PasswordModelAssociator* model_associator = |
+ new PasswordModelAssociator(service, ps, dtc); |
+ PasswordChangeProcessor* change_processor = |
+ new PasswordChangeProcessor(model_associator, ps, dtc); |
+ return ProfileSyncFactory::SyncComponents(model_associator, |
+ change_processor); |
+} |
+ |
+class MockPasswordStore : public PasswordStore { |
+ public: |
+ MOCK_METHOD1(RemoveLogin, void(const PasswordForm&)); |
+ MOCK_METHOD2(GetLogins, int(const PasswordForm&, PasswordStoreConsumer*)); |
+ MOCK_METHOD1(AddLogin, void(const PasswordForm&)); |
+ MOCK_METHOD1(UpdateLogin, void(const PasswordForm&)); |
+ MOCK_METHOD1(AddLoginImpl, void(const PasswordForm&)); |
+ MOCK_METHOD1(UpdateLoginImpl, void(const PasswordForm&)); |
+ MOCK_METHOD1(RemoveLoginImpl, void(const PasswordForm&)); |
+ MOCK_METHOD2(RemoveLoginsCreatedBetweenImpl, void(const base::Time&, |
+ const base::Time&)); |
+ MOCK_METHOD2(GetLoginsImpl, void(GetLoginsRequest*, const PasswordForm&)); |
+ MOCK_METHOD1(GetAutofillableLoginsImpl, void(GetLoginsRequest*)); |
+ MOCK_METHOD1(GetBlacklistLoginsImpl, void(GetLoginsRequest*)); |
+ MOCK_METHOD1(FillAutofillableLogins, |
+ bool(std::vector<PasswordForm*>*)); |
+ MOCK_METHOD1(FillBlacklistLogins, |
+ bool(std::vector<PasswordForm*>*)); |
+}; |
+ |
+class ProfileSyncServicePasswordTest : public testing::Test { |
+ protected: |
+ ProfileSyncServicePasswordTest() |
+ : ui_thread_(ChromeThread::UI, &message_loop_), |
+ db_thread_(ChromeThread::DB) { |
+ } |
+ |
+ virtual void SetUp() { |
+ password_store_ = new MockPasswordStore(); |
+ db_thread_.Start(); |
+ |
+ notification_service_ = new ThreadNotificationService(&db_thread_); |
+ notification_service_->Init(); |
+ } |
+ |
+ virtual void TearDown() { |
+ service_.reset(); |
+ notification_service_->TearDown(); |
+ db_thread_.Stop(); |
+ MessageLoop::current()->RunAllPending(); |
+ } |
+ |
+ void StartSyncService(Task* task) { |
+ if (!service_.get()) { |
+ service_.reset(new TestProfileSyncService(&factory_, &profile_, |
+ false, false)); |
+ service_->AddObserver(&observer_); |
+ PasswordDataTypeController* data_type_controller = |
+ new PasswordDataTypeController(&factory_, |
+ &profile_, |
+ service_.get()); |
+ |
+ EXPECT_CALL(factory_, CreatePasswordSyncComponents(_, _, _)). |
+ WillOnce(MakePasswordSyncComponents(service_.get(), |
+ password_store_.get(), |
+ data_type_controller)); |
+ EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). |
+ WillOnce(MakeDataTypeManager(&backend_)); |
+ |
+ EXPECT_CALL(profile_, GetPasswordStore(_)). |
+ WillOnce(Return(password_store_.get())); |
+ |
+ // State changes once for the backend init and once for startup done. |
+ EXPECT_CALL(observer_, OnStateChanged()). |
+ WillOnce(InvokeTask(task)). |
+ WillOnce(Return()). |
+ WillOnce(QuitUIMessageLoop()); |
+ service_->RegisterDataTypeController(data_type_controller); |
+ service_->Initialize(); |
+ MessageLoop::current()->Run(); |
+ } |
+ } |
+ |
+ void CreatePasswordRoot() { |
+ UserShare* user_share = service_->backend()->GetUserShareHandle(); |
+ DirectoryManager* dir_manager = user_share->dir_manager.get(); |
+ |
+ ScopedDirLookup dir(dir_manager, user_share->authenticated_name); |
+ ASSERT_TRUE(dir.good()); |
+ |
+ WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
+ MutableEntry node(&wtrans, |
+ CREATE, |
+ wtrans.root_id(), |
+ browser_sync::kPasswordTag); |
+ node.Put(UNIQUE_SERVER_TAG, browser_sync::kPasswordTag); |
+ node.Put(IS_DIR, true); |
+ node.Put(SERVER_IS_DIR, false); |
+ node.Put(IS_UNSYNCED, false); |
+ node.Put(IS_UNAPPLIED_UPDATE, false); |
+ node.Put(SERVER_VERSION, 20); |
+ node.Put(BASE_VERSION, 20); |
+ node.Put(IS_DEL, false); |
+ node.Put(ID, ids_.MakeServer(browser_sync::kPasswordTag)); |
+ sync_pb::EntitySpecifics specifics; |
+ specifics.MutableExtension(sync_pb::password); |
+ node.Put(SPECIFICS, specifics); |
+ } |
+ |
+ void AddPasswordSyncNode(const PasswordForm& entry) { |
+ sync_api::WriteTransaction trans( |
+ service_->backend()->GetUserShareHandle()); |
+ sync_api::ReadNode password_root(&trans); |
+ ASSERT_TRUE(password_root.InitByTagLookup(browser_sync::kPasswordTag)); |
+ |
+ sync_api::WriteNode node(&trans); |
+ std::string tag = PasswordModelAssociator::MakeTag(entry); |
+ ASSERT_TRUE(node.InitUniqueByCreation(syncable::PASSWORD, |
+ password_root, |
+ tag)); |
+ PasswordModelAssociator::WriteToSyncNode(entry, &node); |
+ } |
+ |
+ void GetPasswordEntriesFromSyncDB(std::vector<PasswordForm>* entries) { |
+ sync_api::ReadTransaction trans(service_->backend()->GetUserShareHandle()); |
+ sync_api::ReadNode password_root(&trans); |
+ ASSERT_TRUE(password_root.InitByTagLookup(browser_sync::kPasswordTag)); |
+ |
+ int64 child_id = password_root.GetFirstChildId(); |
+ while (child_id != sync_api::kInvalidId) { |
+ sync_api::ReadNode child_node(&trans); |
+ ASSERT_TRUE(child_node.InitByIdLookup(child_id)); |
+ |
+ sync_pb::PasswordSpecificsData password; |
+ ASSERT_TRUE(child_node.GetPasswordSpecifics(&password)); |
+ |
+ PasswordForm form; |
+ PasswordModelAssociator::CopyPassword(password, &form); |
+ |
+ entries->push_back(form); |
+ |
+ child_id = child_node.GetSuccessorId(); |
+ } |
+ } |
+ |
+ bool ComparePasswords(const PasswordForm& lhs, const PasswordForm& rhs) { |
+ return lhs.scheme == rhs.scheme && |
+ lhs.signon_realm == rhs.signon_realm && |
+ lhs.origin == rhs.origin && |
+ lhs.action == rhs.action && |
+ lhs.username_element == rhs.username_element && |
+ lhs.username_value == rhs.username_value && |
+ lhs.password_element == rhs.password_element && |
+ lhs.password_value == rhs.password_value && |
+ lhs.ssl_valid == rhs.ssl_valid && |
+ lhs.preferred == rhs.preferred && |
+ lhs.date_created == rhs.date_created && |
+ lhs.blacklisted_by_user == rhs.blacklisted_by_user; |
+ } |
+ |
+ void SetIdleChangeProcessorExpectations() { |
+ EXPECT_CALL(*(password_store_.get()), AddLoginImpl(_)).Times(0); |
+ EXPECT_CALL(*(password_store_.get()), UpdateLoginImpl(_)).Times(0); |
+ EXPECT_CALL(*(password_store_.get()), RemoveLoginImpl(_)).Times(0); |
+ } |
+ |
+ friend class CreatePasswordRootTask; |
+ friend class AddPasswordEntriesTask; |
+ |
+ MessageLoopForUI message_loop_; |
+ ChromeThread ui_thread_; |
+ ChromeThread db_thread_; |
+ scoped_refptr<ThreadNotificationService> notification_service_; |
+ |
+ scoped_ptr<TestProfileSyncService> service_; |
+ ProfileMock profile_; |
+ ProfileSyncFactoryMock factory_; |
+ ProfileSyncServiceObserverMock observer_; |
+ SyncBackendHostMock backend_; |
+ scoped_refptr<MockPasswordStore> password_store_; |
+ |
+ TestIdFactory ids_; |
+}; |
+ |
+class CreatePasswordRootTask : public Task { |
+ public: |
+ explicit CreatePasswordRootTask(ProfileSyncServicePasswordTest* test) |
+ : test_(test) { |
+ } |
+ |
+ virtual void Run() { |
+ test_->CreatePasswordRoot(); |
+ } |
+ |
+ private: |
+ ProfileSyncServicePasswordTest* test_; |
+}; |
+ |
+class AddPasswordEntriesTask : public Task { |
+ public: |
+ AddPasswordEntriesTask(ProfileSyncServicePasswordTest* test, |
+ const std::vector<PasswordForm>& entries) |
+ : test_(test), entries_(entries) { |
+ } |
+ |
+ virtual void Run() { |
+ test_->CreatePasswordRoot(); |
+ for (size_t i = 0; i < entries_.size(); ++i) { |
+ test_->AddPasswordSyncNode(entries_[i]); |
+ } |
+ } |
+ |
+ private: |
+ ProfileSyncServicePasswordTest* test_; |
+ const std::vector<PasswordForm>& entries_; |
+}; |
+ |
+TEST_F(ProfileSyncServicePasswordTest, FailModelAssociation) { |
+ // Backend will be paused but not resumed. |
+ EXPECT_CALL(backend_, RequestPause()). |
+ WillOnce(testing::DoAll(Notify(NotificationType::SYNC_PAUSED), |
+ testing::Return(true))); |
+ // Don't create the root password node so startup fails. |
+ StartSyncService(NULL); |
+ EXPECT_TRUE(service_->unrecoverable_error_detected()); |
+} |
+ |
+TEST_F(ProfileSyncServicePasswordTest, EmptyNativeEmptySync) { |
+ EXPECT_CALL(*(password_store_.get()), FillAutofillableLogins(_)) |
+ .WillOnce(Return(true)); |
+ EXPECT_CALL(*(password_store_.get()), FillBlacklistLogins(_)) |
+ .WillOnce(Return(true)); |
+ SetIdleChangeProcessorExpectations(); |
+ CreatePasswordRootTask task(this); |
+ StartSyncService(&task); |
+ std::vector<PasswordForm> sync_entries; |
+ GetPasswordEntriesFromSyncDB(&sync_entries); |
+ EXPECT_EQ(0U, sync_entries.size()); |
+} |
+ |
+TEST_F(ProfileSyncServicePasswordTest, HasNativeEntriesEmptySync) { |
+ std::vector<PasswordForm*> forms; |
+ std::vector<PasswordForm> expected_forms; |
+ PasswordForm* new_form = new PasswordForm; |
+ new_form->scheme = PasswordForm::SCHEME_HTML; |
+ new_form->signon_realm = "pie"; |
+ new_form->origin = GURL("http://pie.com"); |
+ new_form->action = GURL("http://pie.com/submit"); |
+ new_form->username_element = UTF8ToUTF16("name"); |
+ new_form->username_value = UTF8ToUTF16("tom"); |
+ new_form->password_element = UTF8ToUTF16("cork"); |
+ new_form->password_value = UTF8ToUTF16("password1"); |
+ new_form->ssl_valid = true; |
+ new_form->preferred = false; |
+ new_form->date_created = base::Time::FromInternalValue(1234); |
+ new_form->blacklisted_by_user = false; |
+ forms.push_back(new_form); |
+ expected_forms.push_back(*new_form); |
+ EXPECT_CALL(*(password_store_.get()), FillAutofillableLogins(_)) |
+ .WillOnce(DoAll(SetArgumentPointee<0>(forms), Return(true))); |
+ EXPECT_CALL(*(password_store_.get()), FillBlacklistLogins(_)) |
+ .WillOnce(Return(true)); |
+ SetIdleChangeProcessorExpectations(); |
+ CreatePasswordRootTask task(this); |
+ StartSyncService(&task); |
+ std::vector<PasswordForm> sync_forms; |
+ GetPasswordEntriesFromSyncDB(&sync_forms); |
+ ASSERT_EQ(1U, sync_forms.size()); |
+ EXPECT_TRUE(ComparePasswords(expected_forms[0], sync_forms[0])); |
+} |
+ |
+TEST_F(ProfileSyncServicePasswordTest, HasNativeHasSyncNoMerge) { |
+ std::vector<PasswordForm*> native_forms; |
+ std::vector<PasswordForm> sync_forms; |
+ std::vector<PasswordForm> expected_forms; |
+ { |
+ PasswordForm* new_form = new PasswordForm; |
+ new_form->scheme = PasswordForm::SCHEME_HTML; |
+ new_form->signon_realm = "pie"; |
+ new_form->origin = GURL("http://pie.com"); |
+ new_form->action = GURL("http://pie.com/submit"); |
+ new_form->username_element = UTF8ToUTF16("name"); |
+ new_form->username_value = UTF8ToUTF16("tom"); |
+ new_form->password_element = UTF8ToUTF16("cork"); |
+ new_form->password_value = UTF8ToUTF16("password1"); |
+ new_form->ssl_valid = true; |
+ new_form->preferred = false; |
+ new_form->date_created = base::Time::FromInternalValue(1234); |
+ new_form->blacklisted_by_user = false; |
+ |
+ native_forms.push_back(new_form); |
+ expected_forms.push_back(*new_form); |
+ } |
+ |
+ { |
+ PasswordForm new_form; |
+ new_form.scheme = PasswordForm::SCHEME_HTML; |
+ new_form.signon_realm = "pie2"; |
+ new_form.origin = GURL("http://pie2.com"); |
+ new_form.action = GURL("http://pie2.com/submit"); |
+ new_form.username_element = UTF8ToUTF16("name2"); |
+ new_form.username_value = UTF8ToUTF16("tom2"); |
+ new_form.password_element = UTF8ToUTF16("cork2"); |
+ new_form.password_value = UTF8ToUTF16("password12"); |
+ new_form.ssl_valid = false; |
+ new_form.preferred = true; |
+ new_form.date_created = base::Time::FromInternalValue(12345); |
+ new_form.blacklisted_by_user = false; |
+ sync_forms.push_back(new_form); |
+ expected_forms.push_back(new_form); |
+ } |
+ |
+ EXPECT_CALL(*(password_store_.get()), FillAutofillableLogins(_)) |
+ .WillOnce(DoAll(SetArgumentPointee<0>(native_forms), Return(true))); |
+ EXPECT_CALL(*(password_store_.get()), FillBlacklistLogins(_)) |
+ .WillOnce(Return(true)); |
+ |
+ AddPasswordEntriesTask task(this, sync_forms); |
+ |
+ EXPECT_CALL(*(password_store_.get()), AddLoginImpl(_)).Times(1); |
+ StartSyncService(&task); |
+ |
+ std::vector<PasswordForm> new_sync_forms; |
+ GetPasswordEntriesFromSyncDB(&new_sync_forms); |
+ |
+ EXPECT_EQ(2U, new_sync_forms.size()); |
+ EXPECT_TRUE(ComparePasswords(expected_forms[0], new_sync_forms[0])); |
+ EXPECT_TRUE(ComparePasswords(expected_forms[1], new_sync_forms[1])); |
+} |
+ |
+TEST_F(ProfileSyncServicePasswordTest, HasNativeHasSyncMergeEntry) { |
+ std::vector<PasswordForm*> native_forms; |
+ std::vector<PasswordForm> sync_forms; |
+ std::vector<PasswordForm> expected_forms; |
+ { |
+ PasswordForm* new_form = new PasswordForm; |
+ new_form->scheme = PasswordForm::SCHEME_HTML; |
+ new_form->signon_realm = "pie"; |
+ new_form->origin = GURL("http://pie.com"); |
+ new_form->action = GURL("http://pie.com/submit"); |
+ new_form->username_element = UTF8ToUTF16("name"); |
+ new_form->username_value = UTF8ToUTF16("tom"); |
+ new_form->password_element = UTF8ToUTF16("cork"); |
+ new_form->password_value = UTF8ToUTF16("password1"); |
+ new_form->ssl_valid = true; |
+ new_form->preferred = false; |
+ new_form->date_created = base::Time::FromInternalValue(1234); |
+ new_form->blacklisted_by_user = false; |
+ |
+ native_forms.push_back(new_form); |
+ } |
+ |
+ { |
+ PasswordForm new_form; |
+ new_form.scheme = PasswordForm::SCHEME_HTML; |
+ new_form.signon_realm = "pie"; |
+ new_form.origin = GURL("http://pie.com"); |
+ new_form.action = GURL("http://pie.com/submit"); |
+ new_form.username_element = UTF8ToUTF16("name2"); |
+ new_form.username_value = UTF8ToUTF16("tom2"); |
+ new_form.password_element = UTF8ToUTF16("cork2"); |
+ new_form.password_value = UTF8ToUTF16("password12"); |
+ new_form.ssl_valid = false; |
+ new_form.preferred = true; |
+ new_form.date_created = base::Time::FromInternalValue(12345); |
+ new_form.blacklisted_by_user = false; |
+ sync_forms.push_back(new_form); |
+ } |
+ |
+ { |
+ PasswordForm new_form; |
+ new_form.scheme = PasswordForm::SCHEME_HTML; |
+ new_form.signon_realm = "pie"; |
+ new_form.origin = GURL("http://pie.com"); |
+ new_form.action = GURL("http://pie.com/submit"); |
+ new_form.username_element = UTF8ToUTF16("name2"); |
+ new_form.username_value = UTF8ToUTF16("tom2"); |
+ new_form.password_element = UTF8ToUTF16("cork2"); |
+ new_form.password_value = UTF8ToUTF16("password12"); |
+ new_form.ssl_valid = false; |
+ new_form.preferred = true; |
+ new_form.date_created = base::Time::FromInternalValue(12345); |
+ new_form.blacklisted_by_user = false; |
+ expected_forms.push_back(new_form); |
+ } |
+ |
+ EXPECT_CALL(*(password_store_.get()), FillAutofillableLogins(_)) |
+ .WillOnce(DoAll(SetArgumentPointee<0>(native_forms), Return(true))); |
+ EXPECT_CALL(*(password_store_.get()), FillBlacklistLogins(_)) |
+ .WillOnce(Return(true)); |
+ |
+ AddPasswordEntriesTask task(this, sync_forms); |
+ |
+ EXPECT_CALL(*(password_store_.get()), UpdateLoginImpl(_)).Times(1); |
+ StartSyncService(&task); |
+ |
+ std::vector<PasswordForm> new_sync_forms; |
+ GetPasswordEntriesFromSyncDB(&new_sync_forms); |
+ |
+ EXPECT_EQ(1U, new_sync_forms.size()); |
+ EXPECT_TRUE(ComparePasswords(expected_forms[0], new_sync_forms[0])); |
+} |