Index: chrome/browser/password_manager/password_store_x_unittest.cc |
=================================================================== |
--- chrome/browser/password_manager/password_store_x_unittest.cc (revision 49657) |
+++ chrome/browser/password_manager/password_store_x_unittest.cc (working copy) |
@@ -3,14 +3,14 @@ |
// found in the LICENSE file. |
#include "base/basictypes.h" |
+#include "base/scoped_temp_dir.h" |
#include "base/stl_util-inl.h" |
#include "base/string_util.h" |
-#include "base/scoped_temp_dir.h" |
#include "base/time.h" |
#include "base/waitable_event.h" |
+#include "chrome/browser/password_manager/password_form_data.h" |
#include "chrome/browser/password_manager/password_store_change.h" |
-#include "chrome/browser/password_manager/password_store_default.h" |
-#include "chrome/browser/password_manager/password_form_data.h" |
+#include "chrome/browser/password_manager/password_store_x.h" |
#include "chrome/browser/webdata/web_data_service.h" |
#include "chrome/common/notification_service.h" |
#include "chrome/common/pref_names.h" |
@@ -32,7 +32,7 @@ |
class MockPasswordStoreConsumer : public PasswordStoreConsumer { |
public: |
MOCK_METHOD2(OnPasswordStoreRequestDone, |
- void(int, const std::vector<webkit_glue::PasswordForm*>&)); |
+ void(int, const std::vector<PasswordForm*>&)); |
}; |
class MockWebDataServiceConsumer : public WebDataServiceConsumer { |
@@ -61,9 +61,9 @@ |
// This class will add and remove a mock notification observer from |
// the DB thread. |
-class DBThreadObserverHelper : |
- public base::RefCountedThreadSafe<DBThreadObserverHelper, |
- ChromeThread::DeleteOnDBThread> { |
+class DBThreadObserverHelper |
+ : public base::RefCountedThreadSafe<DBThreadObserverHelper, |
+ ChromeThread::DeleteOnDBThread> { |
public: |
DBThreadObserverHelper() : done_event_(true, false) {} |
@@ -101,13 +101,224 @@ |
MockNotificationObserver observer_; |
}; |
+class FailingBackend : public PasswordStoreX::NativeBackend { |
+ public: |
+ virtual bool Init() { return true; } |
+ |
+ virtual bool AddLogin(const PasswordForm& form) { return false; } |
+ virtual bool UpdateLogin(const PasswordForm& form) { return false; } |
+ virtual bool RemoveLogin(const PasswordForm& form) { return false; } |
+ |
+ virtual bool RemoveLoginsCreatedBetween(const base::Time& delete_begin, |
+ const base::Time& delete_end) { |
+ return false; |
+ } |
+ |
+ virtual bool GetLogins(const PasswordForm& form, PasswordFormList* forms) { |
+ return false; |
+ } |
+ |
+ virtual bool GetLoginsCreatedBetween(const base::Time& get_begin, |
+ const base::Time& get_end, |
+ PasswordFormList* forms) { |
+ return false; |
+ } |
+ |
+ virtual bool GetAutofillableLogins(PasswordFormList* forms) { return false; } |
+ virtual bool GetBlacklistLogins(PasswordFormList* forms) { return false; } |
+}; |
+ |
+class MockBackend : public PasswordStoreX::NativeBackend { |
+ public: |
+ virtual bool Init() { return true; } |
+ |
+ virtual bool AddLogin(const PasswordForm& form) { |
+ all_forms_.push_back(form); |
+ return true; |
+ } |
+ |
+ virtual bool UpdateLogin(const PasswordForm& form) { |
+ for (size_t i = 0; i < all_forms_.size(); ++i) |
+ if (CompareForms(all_forms_[i], form, true)) |
+ all_forms_[i] = form; |
+ return true; |
+ } |
+ |
+ virtual bool RemoveLogin(const PasswordForm& form) { |
+ for (size_t i = 0; i < all_forms_.size(); ++i) |
+ if (CompareForms(all_forms_[i], form, false)) |
+ erase(i--); |
+ return true; |
+ } |
+ |
+ virtual bool RemoveLoginsCreatedBetween(const base::Time& delete_begin, |
+ const base::Time& delete_end) { |
+ for (size_t i = 0; i < all_forms_.size(); ++i) { |
+ if (delete_begin <= all_forms_[i].date_created && |
+ (delete_end.is_null() || all_forms_[i].date_created < delete_end)) |
+ erase(i--); |
+ } |
+ return true; |
+ } |
+ |
+ virtual bool GetLogins(const PasswordForm& form, PasswordFormList* forms) { |
+ for (size_t i = 0; i < all_forms_.size(); ++i) |
+ if (all_forms_[i].signon_realm == form.signon_realm) |
+ forms->push_back(new PasswordForm(all_forms_[i])); |
+ return true; |
+ } |
+ |
+ virtual bool GetLoginsCreatedBetween(const base::Time& get_begin, |
+ const base::Time& get_end, |
+ PasswordFormList* forms) { |
+ for (size_t i = 0; i < all_forms_.size(); ++i) |
+ if (get_begin <= all_forms_[i].date_created && |
+ (get_end.is_null() || all_forms_[i].date_created < get_end)) |
+ forms->push_back(new PasswordForm(all_forms_[i])); |
+ return true; |
+ } |
+ |
+ virtual bool GetAutofillableLogins(PasswordFormList* forms) { |
+ for (size_t i = 0; i < all_forms_.size(); ++i) |
+ if (!all_forms_[i].blacklisted_by_user) |
+ forms->push_back(new PasswordForm(all_forms_[i])); |
+ return true; |
+ } |
+ |
+ virtual bool GetBlacklistLogins(PasswordFormList* forms) { |
+ for (size_t i = 0; i < all_forms_.size(); ++i) |
+ if (all_forms_[i].blacklisted_by_user) |
+ forms->push_back(new PasswordForm(all_forms_[i])); |
+ return true; |
+ } |
+ |
+ private: |
+ void erase(size_t index) { |
+ if (index < all_forms_.size() - 1) |
+ all_forms_[index] = all_forms_[all_forms_.size() - 1]; |
+ all_forms_.pop_back(); |
+ } |
+ |
+ bool CompareForms(const PasswordForm& a, const PasswordForm& b, bool update) { |
+ // An update check doesn't care about the submit element. |
+ if (!update && a.submit_element != b.submit_element) |
+ return false; |
+ return a.origin == b.origin && |
+ a.password_element == b.password_element && |
+ a.signon_realm == b.signon_realm && |
+ a.username_element == b.username_element && |
+ a.username_value == b.username_value; |
+ } |
+ |
+ std::vector<PasswordForm> all_forms_; |
+}; |
+ |
+class MockLoginDatabaseReturn { |
+ public: |
+ MOCK_METHOD1(OnLoginDatabaseQueryDone, |
+ void(const std::vector<PasswordForm*>&)); |
+}; |
+ |
+class LoginDatabaseQueryTask : public Task { |
+ public: |
+ LoginDatabaseQueryTask(LoginDatabase* login_db, |
+ bool autofillable, |
+ MockLoginDatabaseReturn* mock_return) |
+ : login_db_(login_db), autofillable_(autofillable), |
+ mock_return_(mock_return) { |
+ } |
+ |
+ virtual void Run() { |
+ std::vector<PasswordForm*> forms; |
+ if (autofillable_) |
+ login_db_->GetAutofillableLogins(&forms); |
+ else |
+ login_db_->GetBlacklistLogins(&forms); |
+ mock_return_->OnLoginDatabaseQueryDone(forms); |
+ } |
+ |
+ private: |
+ LoginDatabase* login_db_; |
+ bool autofillable_; |
+ MockLoginDatabaseReturn* mock_return_; |
+}; |
+ |
+const PasswordFormData g_autofillable_data[] = { |
+ { PasswordForm::SCHEME_HTML, |
+ "http://foo.example.com", |
+ "http://foo.example.com/origin", |
+ "http://foo.example.com/action", |
+ L"submit_element", |
+ L"username_element", |
+ L"password_element", |
+ L"username_value", |
+ L"password_value", |
+ true, false, 1 }, |
+ { PasswordForm::SCHEME_HTML, |
+ "http://bar.example.com", |
+ "http://bar.example.com/origin", |
+ "http://bar.example.com/action", |
+ L"submit_element", |
+ L"username_element", |
+ L"password_element", |
+ L"username_value", |
+ L"password_value", |
+ true, false, 2 }, |
+ { PasswordForm::SCHEME_HTML, |
+ "http://baz.example.com", |
+ "http://baz.example.com/origin", |
+ "http://baz.example.com/action", |
+ L"submit_element", |
+ L"username_element", |
+ L"password_element", |
+ L"username_value", |
+ L"password_value", |
+ true, false, 3 }, |
+}; |
+const PasswordFormData g_blacklisted_data[] = { |
+ { PasswordForm::SCHEME_HTML, |
+ "http://blacklisted.example.com", |
+ "http://blacklisted.example.com/origin", |
+ "http://blacklisted.example.com/action", |
+ L"submit_element", |
+ L"username_element", |
+ L"password_element", |
+ NULL, |
+ NULL, |
+ false, false, 1 }, |
+ { PasswordForm::SCHEME_HTML, |
+ "http://blacklisted2.example.com", |
+ "http://blacklisted2.example.com/origin", |
+ "http://blacklisted2.example.com/action", |
+ L"submit_element", |
+ L"username_element", |
+ L"password_element", |
+ NULL, |
+ NULL, |
+ false, false, 2 }, |
+}; |
+ |
} // anonymous namespace |
typedef std::vector<PasswordForm*> VectorOfForms; |
-class PasswordStoreDefaultTest : public testing::Test { |
+// LoginDatabase isn't reference counted, but in these unit tests that won't be |
+// a problem as it always outlives the threads we post tasks to. |
+template<> |
+struct RunnableMethodTraits<LoginDatabase> { |
+ void RetainCallee(LoginDatabase*) {} |
+ void ReleaseCallee(LoginDatabase*) {} |
+}; |
+ |
+enum BackendType { |
+ NO_BACKEND, |
+ FAILING_BACKEND, |
+ WORKING_BACKEND |
+}; |
+ |
+class PasswordStoreXTest : public testing::TestWithParam<BackendType> { |
protected: |
- PasswordStoreDefaultTest() |
+ PasswordStoreXTest() |
: ui_thread_(ChromeThread::UI, &message_loop_), |
db_thread_(ChromeThread::DB) { |
} |
@@ -133,6 +344,17 @@ |
db_thread_.Stop(); |
} |
+ PasswordStoreX::NativeBackend* GetBackend() { |
+ switch (GetParam()) { |
+ case FAILING_BACKEND: |
+ return new FailingBackend(); |
+ case WORKING_BACKEND: |
+ return new MockBackend(); |
+ default: |
+ return NULL; |
+ } |
+ } |
+ |
MessageLoopForUI message_loop_; |
ChromeThread ui_thread_; |
ChromeThread db_thread_; // PasswordStore, WDS schedule work on this thread. |
@@ -157,72 +379,17 @@ |
arg)->GetValue().empty(); |
} |
-TEST_F(PasswordStoreDefaultTest, Migration) { |
- PasswordFormData autofillable_data[] = { |
- { PasswordForm::SCHEME_HTML, |
- "http://foo.example.com", |
- "http://foo.example.com/origin", |
- "http://foo.example.com/action", |
- L"submit_element", |
- L"username_element", |
- L"password_element", |
- L"username_value", |
- L"password_value", |
- true, false, 1 }, |
- { PasswordForm::SCHEME_HTML, |
- "http://bar.example.com", |
- "http://bar.example.com/origin", |
- "http://bar.example.com/action", |
- L"submit_element", |
- L"username_element", |
- L"password_element", |
- L"username_value", |
- L"password_value", |
- true, false, 2 }, |
- { PasswordForm::SCHEME_HTML, |
- "http://baz.example.com", |
- "http://baz.example.com/origin", |
- "http://baz.example.com/action", |
- L"submit_element", |
- L"username_element", |
- L"password_element", |
- L"username_value", |
- L"password_value", |
- true, false, 3 }, |
- }; |
- PasswordFormData blacklisted_data[] = { |
- { PasswordForm::SCHEME_HTML, |
- "http://blacklisted.example.com", |
- "http://blacklisted.example.com/origin", |
- "http://blacklisted.example.com/action", |
- L"submit_element", |
- L"username_element", |
- L"password_element", |
- NULL, |
- NULL, |
- false, false, 1 }, |
- { PasswordForm::SCHEME_HTML, |
- "http://blacklisted2.example.com", |
- "http://blacklisted2.example.com/origin", |
- "http://blacklisted2.example.com/action", |
- L"submit_element", |
- L"username_element", |
- L"password_element", |
- NULL, |
- NULL, |
- false, false, 2 }, |
- }; |
- |
+TEST_P(PasswordStoreXTest, WDSMigration) { |
VectorOfForms expected_autofillable; |
- for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(autofillable_data); ++i) { |
+ for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(g_autofillable_data); ++i) { |
expected_autofillable.push_back( |
- CreatePasswordFormFromData(autofillable_data[i])); |
+ CreatePasswordFormFromData(g_autofillable_data[i])); |
} |
VectorOfForms expected_blacklisted; |
- for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(blacklisted_data); ++i) { |
+ for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(g_blacklisted_data); ++i) { |
expected_blacklisted.push_back( |
- CreatePasswordFormFromData(blacklisted_data[i])); |
+ CreatePasswordFormFromData(g_blacklisted_data[i])); |
} |
// Populate the WDS with logins that should be migrated. |
@@ -242,11 +409,14 @@ |
done.Wait(); |
// Initializing the PasswordStore should trigger a migration. |
- scoped_refptr<PasswordStoreDefault> store( |
- new PasswordStoreDefault(login_db_.release(), profile_.get(), wds_.get())); |
+ scoped_refptr<PasswordStoreX> store( |
+ new PasswordStoreX(login_db_.release(), |
+ profile_.get(), |
+ wds_.get(), |
+ GetBackend())); |
store->Init(); |
- // Check that the migration preference has not been initialized; |
+ // Check that the migration preference has not been initialized. |
ASSERT_TRUE(NULL == profile_->GetPrefs()->FindPreference( |
prefs::kLoginDatabaseMigrated)); |
@@ -319,7 +489,7 @@ |
STLDeleteElements(&expected_blacklisted); |
} |
-TEST_F(PasswordStoreDefaultTest, MigrationAlreadyDone) { |
+TEST_P(PasswordStoreXTest, WDSMigrationAlreadyDone) { |
PasswordFormData wds_data[] = { |
{ PasswordForm::SCHEME_HTML, |
"http://bar.example.com", |
@@ -356,9 +526,11 @@ |
true); |
// Initializing the PasswordStore shouldn't trigger a migration. |
- scoped_refptr<PasswordStoreDefault> store( |
- new PasswordStoreDefault(login_db_.release(), profile_.get(), |
- wds_.get())); |
+ scoped_refptr<PasswordStoreX> store( |
+ new PasswordStoreX(login_db_.release(), |
+ profile_.get(), |
+ wds_.get(), |
+ GetBackend())); |
store->Init(); |
MockPasswordStoreConsumer consumer; |
@@ -379,15 +551,17 @@ |
STLDeleteElements(&unexpected_autofillable); |
} |
-TEST_F(PasswordStoreDefaultTest, Notifications) { |
- // Prentend that the migration has already taken place. |
+TEST_P(PasswordStoreXTest, Notifications) { |
+ // Pretend that the migration has already taken place. |
profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated, |
true); |
// Initializing the PasswordStore shouldn't trigger a migration. |
- scoped_refptr<PasswordStoreDefault> store( |
- new PasswordStoreDefault(login_db_.release(), profile_.get(), |
- wds_.get())); |
+ scoped_refptr<PasswordStoreX> store( |
+ new PasswordStoreX(login_db_.release(), |
+ profile_.get(), |
+ wds_.get(), |
+ GetBackend())); |
store->Init(); |
PasswordFormData form_data = |
@@ -465,3 +639,133 @@ |
ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); |
done.Wait(); |
} |
+ |
+TEST_P(PasswordStoreXTest, NativeMigration) { |
+ VectorOfForms expected_autofillable; |
+ for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(g_autofillable_data); ++i) { |
+ expected_autofillable.push_back( |
+ CreatePasswordFormFromData(g_autofillable_data[i])); |
+ } |
+ |
+ VectorOfForms expected_blacklisted; |
+ for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(g_blacklisted_data); ++i) { |
+ expected_blacklisted.push_back( |
+ CreatePasswordFormFromData(g_blacklisted_data[i])); |
+ } |
+ |
+ LoginDatabase* login_db = login_db_.get(); |
+ |
+ // Populate the login DB with logins that should be migrated. |
+ for (VectorOfForms::iterator it = expected_autofillable.begin(); |
+ it != expected_autofillable.end(); ++it) { |
+ ChromeThread::PostTask(ChromeThread::DB, |
+ FROM_HERE, |
+ NewRunnableMethod(login_db, |
+ &LoginDatabase::AddLogin, |
+ **it)); |
+ } |
+ for (VectorOfForms::iterator it = expected_blacklisted.begin(); |
+ it != expected_blacklisted.end(); ++it) { |
+ ChromeThread::PostTask(ChromeThread::DB, |
+ FROM_HERE, |
+ NewRunnableMethod(login_db, |
+ &LoginDatabase::AddLogin, |
+ **it)); |
+ } |
+ |
+ // Schedule another task on the DB thread to notify us that it's safe to |
+ // carry on with the test. |
+ WaitableEvent done(false, false); |
+ ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); |
+ done.Wait(); |
+ |
+ // Pretend that the WDS migration has already taken place. |
+ profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated, |
+ true); |
+ |
+ // Initializing the PasswordStore shouldn't trigger a native migration (yet). |
+ scoped_refptr<PasswordStoreX> store( |
+ new PasswordStoreX(login_db_.release(), |
+ profile_.get(), |
+ wds_.get(), |
+ GetBackend())); |
+ store->Init(); |
+ |
+ MockPasswordStoreConsumer consumer; |
+ |
+ // Make sure we quit the MessageLoop even if the test fails. |
+ ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) |
+ .WillByDefault(QuitUIMessageLoop()); |
+ |
+ // The autofillable forms should have been migrated to the native backend. |
+ EXPECT_CALL(consumer, |
+ OnPasswordStoreRequestDone(_, |
+ ContainsAllPasswordForms(expected_autofillable))) |
+ .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); |
+ |
+ store->GetAutofillableLogins(&consumer); |
+ MessageLoop::current()->Run(); |
+ |
+ // The blacklisted forms should have been migrated to the native backend. |
+ EXPECT_CALL(consumer, |
+ OnPasswordStoreRequestDone(_, |
+ ContainsAllPasswordForms(expected_blacklisted))) |
+ .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); |
+ |
+ store->GetBlacklistLogins(&consumer); |
+ MessageLoop::current()->Run(); |
+ |
+ VectorOfForms empty; |
+ MockLoginDatabaseReturn ld_return; |
+ |
+ if (GetParam() == WORKING_BACKEND) { |
+ // No autofillable logins should be left in the login DB. |
+ EXPECT_CALL(ld_return, |
+ OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty))); |
+ } else { |
+ // The autofillable logins should still be in the login DB. |
+ EXPECT_CALL(ld_return, |
+ OnLoginDatabaseQueryDone( |
+ ContainsAllPasswordForms(expected_autofillable))) |
+ .WillOnce(WithArg<0>(STLDeleteElements0())); |
+ } |
+ |
+ ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, |
+ new LoginDatabaseQueryTask(login_db, true, &ld_return)); |
+ |
+ // Wait for the login DB methods to execute on the DB thread. |
+ ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); |
+ done.Wait(); |
+ |
+ if (GetParam() == WORKING_BACKEND) { |
+ // Likewise, no blacklisted logins should be left in the login DB. |
+ EXPECT_CALL(ld_return, |
+ OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty))); |
+ } else { |
+ // The blacklisted logins should still be in the login DB. |
+ EXPECT_CALL(ld_return, |
+ OnLoginDatabaseQueryDone( |
+ ContainsAllPasswordForms(expected_blacklisted))) |
+ .WillOnce(WithArg<0>(STLDeleteElements0())); |
+ } |
+ |
+ ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, |
+ new LoginDatabaseQueryTask(login_db, false, &ld_return)); |
+ |
+ // Wait for the login DB methods to execute on the DB thread. |
+ ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); |
+ done.Wait(); |
+ |
+ STLDeleteElements(&expected_autofillable); |
+ STLDeleteElements(&expected_blacklisted); |
+} |
+ |
+INSTANTIATE_TEST_CASE_P(NoBackend, |
+ PasswordStoreXTest, |
+ testing::Values(NO_BACKEND)); |
+INSTANTIATE_TEST_CASE_P(FailingBackend, |
+ PasswordStoreXTest, |
+ testing::Values(FAILING_BACKEND)); |
+INSTANTIATE_TEST_CASE_P(WorkingBackend, |
+ PasswordStoreXTest, |
+ testing::Values(WORKING_BACKEND)); |