root/chrome/browser/password_manager/password_store_x_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Init
  2. AddLogin
  3. UpdateLogin
  4. RemoveLogin
  5. RemoveLoginsCreatedBetween
  6. GetLogins
  7. GetLoginsCreatedBetween
  8. GetAutofillableLogins
  9. GetBlacklistLogins
  10. Init
  11. AddLogin
  12. UpdateLogin
  13. RemoveLogin
  14. RemoveLoginsCreatedBetween
  15. GetLogins
  16. GetLoginsCreatedBetween
  17. GetAutofillableLogins
  18. GetBlacklistLogins
  19. erase
  20. CompareForms
  21. LoginDatabaseQueryCallback
  22. InitExpectedForms
  23. SetUp
  24. TearDown
  25. GetBackend
  26. ACTION
  27. TEST_P
  28. TEST_P

// Copyright (c) 2012 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 "base/basictypes.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/platform_file.h"
#include "base/prefs/pref_service.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/password_manager/password_store_x.h"
#include "chrome/test/base/testing_browser_process.h"
#include "components/password_manager/core/browser/password_form_data.h"
#include "components/password_manager/core/browser/password_store_change.h"
#include "components/password_manager/core/browser/password_store_consumer.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using autofill::PasswordForm;
using content::BrowserThread;
using testing::_;
using testing::DoAll;
using testing::ElementsAreArray;
using testing::Pointee;
using testing::Property;
using testing::WithArg;

typedef std::vector<PasswordForm*> VectorOfForms;

namespace {

class MockPasswordStoreConsumer : public PasswordStoreConsumer {
 public:
  MOCK_METHOD1(OnGetPasswordStoreResults,
               void(const std::vector<PasswordForm*>&));
};

class MockPasswordStoreObserver : public PasswordStore::Observer {
 public:
  MOCK_METHOD1(OnLoginsChanged,
               void(const PasswordStoreChangeList& changes));
};

class FailingBackend : public PasswordStoreX::NativeBackend {
 public:
  virtual bool Init() OVERRIDE { return true; }

  virtual bool AddLogin(const PasswordForm& form) OVERRIDE { return false; }
  virtual bool UpdateLogin(const PasswordForm& form) OVERRIDE { return false; }
  virtual bool RemoveLogin(const PasswordForm& form) OVERRIDE { return false; }

  virtual bool RemoveLoginsCreatedBetween(
      const base::Time& delete_begin,
      const base::Time& delete_end) OVERRIDE {
    return false;
  }

  virtual bool GetLogins(const PasswordForm& form,
                         PasswordFormList* forms) OVERRIDE {
    return false;
  }

  virtual bool GetLoginsCreatedBetween(const base::Time& get_begin,
                                       const base::Time& get_end,
                                       PasswordFormList* forms) OVERRIDE {
    return false;
  }

  virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE {
    return false;
  }
  virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE {
    return false;
  }
};

class MockBackend : public PasswordStoreX::NativeBackend {
 public:
  virtual bool Init() OVERRIDE { return true; }

  virtual bool AddLogin(const PasswordForm& form) OVERRIDE {
    all_forms_.push_back(form);
    return true;
  }

  virtual bool UpdateLogin(const PasswordForm& form) OVERRIDE {
    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) OVERRIDE {
    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) OVERRIDE {
    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) OVERRIDE {
    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) OVERRIDE {
    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) OVERRIDE {
    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) OVERRIDE {
    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*>&));
};

void LoginDatabaseQueryCallback(LoginDatabase* login_db,
                                bool autofillable,
                                MockLoginDatabaseReturn* mock_return) {
  std::vector<PasswordForm*> forms;
  if (autofillable)
    login_db->GetAutofillableLogins(&forms);
  else
    login_db->GetBlacklistLogins(&forms);
  mock_return->OnLoginDatabaseQueryDone(forms);
}

// Generate |count| expected logins, either auto-fillable or blacklisted.
void InitExpectedForms(bool autofillable, size_t count, VectorOfForms* forms) {
  const char* domain = autofillable ? "example" : "blacklisted";
  for (size_t i = 0; i < count; ++i) {
    std::string realm = base::StringPrintf("http://%zu.%s.com", i, domain);
    std::string origin = base::StringPrintf("http://%zu.%s.com/origin",
                                            i, domain);
    std::string action = base::StringPrintf("http://%zu.%s.com/action",
                                            i, domain);
    PasswordFormData data = {
      PasswordForm::SCHEME_HTML,
      realm.c_str(),
      origin.c_str(),
      action.c_str(),
      L"submit_element",
      L"username_element",
      L"password_element",
      autofillable ? L"username_value" : NULL,
      autofillable ? L"password_value" : NULL,
      autofillable, false, static_cast<double>(i + 1) };
    forms->push_back(CreatePasswordFormFromData(data));
  }
}

}  // anonymous namespace

enum BackendType {
  NO_BACKEND,
  FAILING_BACKEND,
  WORKING_BACKEND
};

class PasswordStoreXTest : public testing::TestWithParam<BackendType> {
 protected:
  virtual void SetUp() {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());

    login_db_.reset(new LoginDatabase());
    ASSERT_TRUE(login_db_->Init(temp_dir_.path().Append("login_test")));
  }

  virtual void TearDown() {
    base::RunLoop().RunUntilIdle();
  }

  PasswordStoreX::NativeBackend* GetBackend() {
    switch (GetParam()) {
      case FAILING_BACKEND:
        return new FailingBackend();
      case WORKING_BACKEND:
        return new MockBackend();
      default:
        return NULL;
    }
  }

  content::TestBrowserThreadBundle thread_bundle_;

  scoped_ptr<LoginDatabase> login_db_;
  base::ScopedTempDir temp_dir_;
};

ACTION(STLDeleteElements0) {
  STLDeleteContainerPointers(arg0.begin(), arg0.end());
}

TEST_P(PasswordStoreXTest, Notifications) {
  scoped_refptr<PasswordStoreX> store(
      new PasswordStoreX(base::MessageLoopProxy::current(),
                         base::MessageLoopProxy::current(),
                         login_db_.release(),
                         GetBackend()));
  store->Init(syncer::SyncableService::StartSyncFlare());

  PasswordFormData form_data =
  { 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, 1 };
  scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data));

  MockPasswordStoreObserver observer;
  store->AddObserver(&observer);

  const PasswordStoreChange expected_add_changes[] = {
    PasswordStoreChange(PasswordStoreChange::ADD, *form),
  };

  EXPECT_CALL(
      observer,
      OnLoginsChanged(ElementsAreArray(expected_add_changes)));

  // Adding a login should trigger a notification.
  store->AddLogin(*form);

  // The PasswordStore schedules tasks to run on the DB thread. Wait for them
  // to complete.
  base::RunLoop().RunUntilIdle();

  // Change the password.
  form->password_value = base::ASCIIToUTF16("a different password");

  const PasswordStoreChange expected_update_changes[] = {
    PasswordStoreChange(PasswordStoreChange::UPDATE, *form),
  };

  EXPECT_CALL(
      observer,
      OnLoginsChanged(ElementsAreArray(expected_update_changes)));

  // Updating the login with the new password should trigger a notification.
  store->UpdateLogin(*form);

  // Wait for PasswordStore to send execute.
  base::RunLoop().RunUntilIdle();

  const PasswordStoreChange expected_delete_changes[] = {
    PasswordStoreChange(PasswordStoreChange::REMOVE, *form),
  };

  EXPECT_CALL(
      observer,
      OnLoginsChanged(ElementsAreArray(expected_delete_changes)));

  // Deleting the login should trigger a notification.
  store->RemoveLogin(*form);

  // Wait for PasswordStore to execute.
  base::RunLoop().RunUntilIdle();

  store->RemoveObserver(&observer);

  store->Shutdown();
}

TEST_P(PasswordStoreXTest, NativeMigration) {
  VectorOfForms expected_autofillable;
  InitExpectedForms(true, 50, &expected_autofillable);

  VectorOfForms expected_blacklisted;
  InitExpectedForms(false, 50, &expected_blacklisted);

  // Get the initial size of the login DB file, before we populate it.
  // This will be used later to make sure it gets back to this size.
  const base::FilePath login_db_file = temp_dir_.path().Append("login_test");
  base::File::Info db_file_start_info;
  ASSERT_TRUE(base::GetFileInfo(login_db_file, &db_file_start_info));

  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) {
    login_db->AddLogin(**it);
  }
  for (VectorOfForms::iterator it = expected_blacklisted.begin();
       it != expected_blacklisted.end(); ++it) {
    login_db->AddLogin(**it);
  }

  // Get the new size of the login DB file. We expect it to be larger.
  base::File::Info db_file_full_info;
  ASSERT_TRUE(base::GetFileInfo(login_db_file, &db_file_full_info));
  EXPECT_GT(db_file_full_info.size, db_file_start_info.size);

  // Initializing the PasswordStore shouldn't trigger a native migration (yet).
  scoped_refptr<PasswordStoreX> store(
      new PasswordStoreX(base::MessageLoopProxy::current(),
                         base::MessageLoopProxy::current(),
                         login_db_.release(),
                         GetBackend()));
  store->Init(syncer::SyncableService::StartSyncFlare());

  MockPasswordStoreConsumer consumer;

  // The autofillable forms should have been migrated to the native backend.
  EXPECT_CALL(consumer,
      OnGetPasswordStoreResults(
          ContainsAllPasswordForms(expected_autofillable)))
      .WillOnce(WithArg<0>(STLDeleteElements0()));

  store->GetAutofillableLogins(&consumer);
  base::RunLoop().RunUntilIdle();

  // The blacklisted forms should have been migrated to the native backend.
  EXPECT_CALL(consumer,
      OnGetPasswordStoreResults(ContainsAllPasswordForms(expected_blacklisted)))
      .WillOnce(WithArg<0>(STLDeleteElements0()));

  store->GetBlacklistLogins(&consumer);
  base::RunLoop().RunUntilIdle();

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

  LoginDatabaseQueryCallback(login_db, true, &ld_return);

  // Wait for the login DB methods to execute.
  base::RunLoop().RunUntilIdle();

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

  LoginDatabaseQueryCallback(login_db, false, &ld_return);

  // Wait for the login DB methods to execute.
  base::RunLoop().RunUntilIdle();

  if (GetParam() == WORKING_BACKEND) {
    // If the migration succeeded, then not only should there be no logins left
    // in the login DB, but also the file should have been deleted and then
    // recreated. We approximate checking for this by checking that the file
    // size is equal to the size before we populated it, even though it was
    // larger after populating it.
    base::File::Info db_file_end_info;
    ASSERT_TRUE(base::GetFileInfo(login_db_file, &db_file_end_info));
    EXPECT_EQ(db_file_start_info.size, db_file_end_info.size);
  }

  STLDeleteElements(&expected_autofillable);
  STLDeleteElements(&expected_blacklisted);

  store->Shutdown();
}

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

/* [<][>][^][v][top][bottom][index][help] */