Index: chrome/browser/password_manager/password_store_mac_unittest.cc |
diff --git a/chrome/browser/password_manager/password_store_mac_unittest.cc b/chrome/browser/password_manager/password_store_mac_unittest.cc |
index 7fefc31567eaf04d0d85b740e794bc6cbacecc91..3db112dcaebac21d8d2063780104e04efc94cf58 100644 |
--- a/chrome/browser/password_manager/password_store_mac_unittest.cc |
+++ b/chrome/browser/password_manager/password_store_mac_unittest.cc |
@@ -2,19 +2,49 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
+#include "testing/gmock/include/gmock/gmock.h" |
#include "testing/gtest/include/gtest/gtest.h" |
#include "base/basictypes.h" |
+#include "base/file_util.h" |
+#include "base/path_service.h" |
+#include "base/scoped_temp_dir.h" |
#include "base/stl_util-inl.h" |
#include "base/string_util.h" |
#include "base/utf_string_conversions.h" |
+#include "chrome/browser/chrome_thread.h" |
#include "chrome/browser/keychain_mock_mac.h" |
#include "chrome/browser/password_manager/password_store_mac.h" |
#include "chrome/browser/password_manager/password_store_mac_internal.h" |
+#include "chrome/common/chrome_paths.h" |
using webkit_glue::PasswordForm; |
+using testing::_; |
+using testing::DoAll; |
+using testing::WithArg; |
-class PasswordStoreMacTest : public testing::Test { |
+namespace { |
+ |
+class MockPasswordStoreConsumer : public PasswordStoreConsumer { |
+public: |
+ MOCK_METHOD2(OnPasswordStoreRequestDone, |
+ void(int, const std::vector<webkit_glue::PasswordForm*>&)); |
+}; |
+ |
+ACTION(STLDeleteElements0) { |
+ STLDeleteContainerPointers(arg0.begin(), arg0.end()); |
+} |
+ |
+ACTION(QuitUIMessageLoop) { |
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
+ MessageLoop::current()->Quit(); |
+} |
+ |
+} // namespace |
+ |
+#pragma mark - |
+ |
+class PasswordStoreMacInternalsTest : public testing::Test { |
public: |
virtual void SetUp() { |
MockKeychain::KeychainTestData test_data[] = { |
@@ -193,7 +223,7 @@ static void CheckFormsAgainstExpectations( |
#pragma mark - |
-TEST_F(PasswordStoreMacTest, TestKeychainToFormTranslation) { |
+TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) { |
typedef struct { |
const PasswordForm::Scheme scheme; |
const char* signon_realm; |
@@ -291,7 +321,7 @@ TEST_F(PasswordStoreMacTest, TestKeychainToFormTranslation) { |
} |
} |
-TEST_F(PasswordStoreMacTest, TestKeychainSearch) { |
+TEST_F(PasswordStoreMacInternalsTest, TestKeychainSearch) { |
struct TestDataAndExpectation { |
const PasswordFormData data; |
const size_t expected_fill_matches; |
@@ -352,7 +382,9 @@ TEST_F(PasswordStoreMacTest, TestKeychainSearch) { |
EXPECT_EQ(test_data[i].expected_fill_matches, matching_items.size()); |
STLDeleteElements(&matching_items); |
- // Check matches teating the form as a merging target. |
+ // Check matches treating the form as a merging target. |
+ EXPECT_EQ(test_data[i].expected_merge_matches > 0, |
+ keychain_adapter.HasPasswordsMergeableWithForm(*query_form)); |
matching_items = keychain_adapter.PasswordsMergeableWithForm(*query_form); |
EXPECT_EQ(test_data[i].expected_merge_matches, matching_items.size()); |
STLDeleteElements(&matching_items); |
@@ -391,7 +423,7 @@ static void SetPasswordFormRealm(PasswordForm* form, const char* realm) { |
form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec(); |
} |
-TEST_F(PasswordStoreMacTest, TestKeychainExactSearch) { |
+TEST_F(PasswordStoreMacInternalsTest, TestKeychainExactSearch) { |
MacKeychainPasswordFormAdapter keychain_adapter(keychain_); |
PasswordFormData base_form_data[] = { |
@@ -410,6 +442,7 @@ TEST_F(PasswordStoreMacTest, TestKeychainExactSearch) { |
// Create a base form and make sure we find a match. |
scoped_ptr<PasswordForm> base_form(CreatePasswordFormFromData( |
base_form_data[i])); |
+ EXPECT_TRUE(keychain_adapter.HasPasswordsMergeableWithForm(*base_form)); |
PasswordForm* match = |
keychain_adapter.PasswordExactlyMatchingForm(*base_form); |
EXPECT_TRUE(match != NULL); |
@@ -455,7 +488,7 @@ TEST_F(PasswordStoreMacTest, TestKeychainExactSearch) { |
} |
} |
-TEST_F(PasswordStoreMacTest, TestKeychainAdd) { |
+TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) { |
struct TestDataAndExpectation { |
PasswordFormData data; |
bool should_succeed; |
@@ -494,6 +527,8 @@ TEST_F(PasswordStoreMacTest, TestKeychainAdd) { |
bool add_succeeded = owned_keychain_adapter.AddPassword(*in_form); |
EXPECT_EQ(test_data[i].should_succeed, add_succeeded); |
if (add_succeeded) { |
+ EXPECT_TRUE(owned_keychain_adapter.HasPasswordsMergeableWithForm( |
+ *in_form)); |
scoped_ptr<PasswordForm> out_form( |
owned_keychain_adapter.PasswordExactlyMatchingForm(*in_form)); |
EXPECT_TRUE(out_form.get() != NULL); |
@@ -524,7 +559,7 @@ TEST_F(PasswordStoreMacTest, TestKeychainAdd) { |
} |
} |
-TEST_F(PasswordStoreMacTest, TestKeychainRemove) { |
+TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) { |
struct TestDataAndExpectation { |
PasswordFormData data; |
bool should_succeed; |
@@ -563,7 +598,7 @@ TEST_F(PasswordStoreMacTest, TestKeychainRemove) { |
} |
} |
-TEST_F(PasswordStoreMacTest, TestFormMatch) { |
+TEST_F(PasswordStoreMacInternalsTest, TestFormMatch) { |
PasswordForm base_form; |
base_form.signon_realm = std::string("http://some.domain.com/"); |
base_form.origin = GURL("http://some.domain.com/page.html"); |
@@ -625,7 +660,7 @@ TEST_F(PasswordStoreMacTest, TestFormMatch) { |
} |
} |
-TEST_F(PasswordStoreMacTest, TestFormMerge) { |
+TEST_F(PasswordStoreMacInternalsTest, TestFormMerge) { |
// Set up a bunch of test data to use in varying combinations. |
PasswordFormData keychain_user_1 = |
{ PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
@@ -780,7 +815,7 @@ TEST_F(PasswordStoreMacTest, TestFormMerge) { |
} |
} |
-TEST_F(PasswordStoreMacTest, TestPasswordBulkLookup) { |
+TEST_F(PasswordStoreMacInternalsTest, TestPasswordBulkLookup) { |
PasswordFormData db_data[] = { |
{ PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
"http://some.domain.com/", "http://some.domain.com/action.cgi", |
@@ -823,7 +858,7 @@ TEST_F(PasswordStoreMacTest, TestPasswordBulkLookup) { |
STLDeleteElements(&merged_forms); |
} |
-TEST_F(PasswordStoreMacTest, TestPasswordGetAll) { |
+TEST_F(PasswordStoreMacInternalsTest, TestPasswordGetAll) { |
MacKeychainPasswordFormAdapter keychain_adapter(keychain_); |
MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); |
owned_keychain_adapter.SetFindsOnlyOwnedItems(true); |
@@ -856,3 +891,134 @@ TEST_F(PasswordStoreMacTest, TestPasswordGetAll) { |
EXPECT_EQ(arraysize(owned_password_data), owned_passwords.size()); |
STLDeleteElements(&owned_passwords); |
} |
+ |
+#pragma mark - |
+ |
+class PasswordStoreMacTest : public testing::Test { |
+ public: |
+ PasswordStoreMacTest() : ui_thread_(ChromeThread::UI, &message_loop_) {} |
+ |
+ virtual void SetUp() { |
+ login_db_ = new LoginDatabase(); |
+ ASSERT_TRUE(db_dir_.CreateUniqueTempDir()); |
+ FilePath db_file = db_dir_.path().AppendASCII("login.db"); |
+ ASSERT_TRUE(login_db_->Init(db_file)); |
+ |
+ keychain_ = new MockKeychain(3); |
+ |
+ store_ = new PasswordStoreMac(keychain_, login_db_); |
+ ASSERT_TRUE(store_->Init()); |
+ } |
+ |
+ virtual void TearDown() { |
+ MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask); |
+ MessageLoop::current()->Run(); |
+ } |
+ |
+ protected: |
+ MessageLoopForUI message_loop_; |
+ ChromeThread ui_thread_; |
+ |
+ MockKeychain* keychain_; // Owned by store_. |
+ LoginDatabase* login_db_; // Owned by store_. |
+ scoped_refptr<PasswordStoreMac> store_; |
+ ScopedTempDir db_dir_; |
+}; |
+ |
+TEST_F(PasswordStoreMacTest, TestStoreUpdate) { |
+ // Insert a password into both the database and the keychain. |
+ // This is done manually, rather than through store_->AddLogin, because the |
+ // Mock Keychain isn't smart enough to be able to support update generically, |
+ // so some.domain.com triggers special handling to test it that make inserting |
+ // fail. |
+ PasswordFormData joint_data = { |
+ PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
+ "http://some.domain.com/insecure.html", "login.cgi", |
+ L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1 |
+ }; |
+ scoped_ptr<PasswordForm> joint_form(CreatePasswordFormFromData(joint_data)); |
+ login_db_->AddLogin(*joint_form); |
+ MockKeychain::KeychainTestData joint_keychain_data = { |
+ kSecAuthenticationTypeHTMLForm, "some.domain.com", |
+ kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "20020601171500Z", |
+ "joe_user", "sekrit", false }; |
+ keychain_->AddTestItem(joint_keychain_data); |
+ |
+ // Insert a password into the keychain only. |
+ MockKeychain::KeychainTestData keychain_only_data = { |
+ kSecAuthenticationTypeHTMLForm, "keychain.only.com", |
+ kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z", |
+ "keychain", "only", false |
+ }; |
+ keychain_->AddTestItem(keychain_only_data); |
+ |
+ struct UpdateData { |
+ PasswordFormData form_data; |
+ const char* password; // NULL indicates no entry should be present. |
+ }; |
+ |
+ // Make a series of update calls. |
+ UpdateData updates[] = { |
+ // Update the keychain+db passwords (the normal password update case). |
+ { { PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
+ "http://some.domain.com/insecure.html", "login.cgi", |
+ L"username", L"password", L"submit", L"joe_user", L"53krit", |
+ true, false, 2 }, |
+ "53krit", |
+ }, |
+ // Update the keychain-only password; this simulates the initial use of a |
+ // password stored by another browsers. |
+ { { PasswordForm::SCHEME_HTML, "http://keychain.only.com/", |
+ "http://keychain.only.com/login.html", "login.cgi", |
+ L"username", L"password", L"submit", L"keychain", L"only", |
+ true, false, 2 }, |
+ "only", |
+ }, |
+ // Update a password that doesn't exist in either location. This tests the |
+ // case where a form is filled, then the stored login is removed, then the |
+ // form is submitted. |
+ { { PasswordForm::SCHEME_HTML, "http://different.com/", |
+ "http://different.com/index.html", "login.cgi", |
+ L"username", L"password", L"submit", L"abc", L"123", |
+ true, false, 2 }, |
+ NULL, |
+ }, |
+ }; |
+ for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) { |
+ scoped_ptr<PasswordForm> form(CreatePasswordFormFromData( |
+ updates[i].form_data)); |
+ store_->UpdateLogin(*form); |
+ } |
+ |
+ // Do a store-level query to wait for all the operations above to be done. |
+ MockPasswordStoreConsumer consumer; |
+ ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)).WillByDefault( |
+ QuitUIMessageLoop()); |
+ EXPECT_CALL(consumer, OnPasswordStoreRequestDone(_, _)).WillOnce( |
+ DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); |
+ store_->GetLogins(*joint_form, &consumer); |
+ MessageLoop::current()->Run(); |
+ |
+ MacKeychainPasswordFormAdapter keychain_adapter(keychain_); |
+ for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) { |
+ scoped_ptr<PasswordForm> query_form( |
+ CreatePasswordFormFromData(updates[i].form_data)); |
+ |
+ std::vector<PasswordForm*> matching_items = |
+ keychain_adapter.PasswordsFillingForm(*query_form); |
+ if (updates[i].password) { |
+ EXPECT_GT(matching_items.size(), 0U) << "iteration " << i; |
+ if (matching_items.size() >= 1) |
+ EXPECT_EQ(ASCIIToUTF16(updates[i].password), |
+ matching_items[0]->password_value) << "iteration " << i; |
+ } else { |
+ EXPECT_EQ(0U, matching_items.size()) << "iteration " << i; |
+ } |
+ STLDeleteElements(&matching_items); |
+ |
+ login_db_->GetLogins(*query_form, &matching_items); |
+ EXPECT_EQ(updates[i].password ? 1U : 0U, matching_items.size()) |
+ << "iteration " << i; |
+ STLDeleteElements(&matching_items); |
+ } |
+} |