| 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));
|
|
|