Index: ios/chrome/browser/passwords/credential_manager_unittest.mm |
diff --git a/ios/chrome/browser/passwords/credential_manager_unittest.mm b/ios/chrome/browser/passwords/credential_manager_unittest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4b7e97b79bd2f1c0f4da9a15884dd8c9bbd8f428 |
--- /dev/null |
+++ b/ios/chrome/browser/passwords/credential_manager_unittest.mm |
@@ -0,0 +1,610 @@ |
+// Copyright 2015 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 "ios/chrome/browser/passwords/credential_manager.h" |
+ |
+#include <memory> |
+#include <utility> |
+ |
+#include "base/mac/bind_objc_block.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "base/values.h" |
+#include "components/password_manager/core/browser/password_bubble_experiment.h" |
+#include "components/password_manager/core/browser/stub_password_manager_client.h" |
+#include "components/password_manager/core/browser/stub_password_manager_driver.h" |
+#include "components/password_manager/core/browser/test_password_store.h" |
+#include "components/password_manager/core/common/credential_manager_types.h" |
+#include "components/password_manager/core/common/password_manager_pref_names.h" |
+#include "components/prefs/pref_registry_simple.h" |
+#include "components/prefs/testing_pref_service.h" |
+#import "ios/web/public/web_state/web_state.h" |
+#import "ios/chrome/browser/passwords/js_credential_manager.h" |
+#import "ios/testing/ocmock_complex_type_helper.h" |
+#import "ios/web/public/test/web_test_with_web_state.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "testing/gtest_mac.h" |
+#include "third_party/ocmock/OCMock/OCMock.h" |
+#include "third_party/ocmock/gtest_support.h" |
+#include "url/gurl.h" |
+ |
+using testing::Return; |
+ |
+namespace { |
+// Type of a function invoked when a promise is resolved. |
+typedef void (^ResolvePromiseBlock)(NSInteger request_id, |
+ const web::Credential& credential); |
+} // namespace |
+ |
+// A helper to mock methods that have C++ object parameters. |
+@interface MockJSCredentialManager : OCMockComplexTypeHelper |
+@end |
+ |
+@implementation MockJSCredentialManager |
+- (void)resolvePromiseWithRequestID:(NSInteger)requestID |
+ credential:(const web::Credential&)credential |
+ completionHandler:(void (^)(BOOL))completionHandler { |
+ static_cast<ResolvePromiseBlock>([self blockForSelector:_cmd])(requestID, |
+ credential); |
+ completionHandler(YES); |
+} |
+@end |
+ |
+namespace { |
+ |
+// Returns a test credential. |
+autofill::PasswordForm GetTestPasswordForm1(bool zero_click_allowed) { |
+ autofill::PasswordForm form; |
+ form.username_value = base::ASCIIToUTF16("foo"); |
+ form.password_value = base::ASCIIToUTF16("bar"); |
+ form.skip_zero_click = !zero_click_allowed; |
+ form.ssl_valid = true; |
+ form.type = autofill::PasswordForm::Type::TYPE_API; |
+ return form; |
+} |
+ |
+// Returns a test credential matching |GetTestPasswordForm1()|. |
+web::Credential GetTestWebCredential1(bool zero_click_allowed) { |
+ web::Credential credential; |
+ autofill::PasswordForm form(GetTestPasswordForm1(zero_click_allowed)); |
+ credential.type = web::CredentialType::CREDENTIAL_TYPE_PASSWORD; |
+ credential.id = form.username_value; |
+ credential.password = form.password_value; |
+ return credential; |
+} |
+ |
+// Returns a different test credential. |
+autofill::PasswordForm GetTestPasswordForm2(bool zero_click_allowed) { |
+ autofill::PasswordForm form; |
+ form.username_value = base::ASCIIToUTF16("baz"); |
+ form.password_value = base::ASCIIToUTF16("bah"); |
+ form.skip_zero_click = !zero_click_allowed; |
+ form.ssl_valid = true; |
+ return form; |
+} |
+ |
+// Returns a test credential matching |GetTestPasswordForm2()|. |
+web::Credential GetTestWebCredential2(bool zero_click_allowed) { |
+ web::Credential credential; |
+ autofill::PasswordForm form(GetTestPasswordForm2(zero_click_allowed)); |
+ credential.type = web::CredentialType::CREDENTIAL_TYPE_PASSWORD; |
+ credential.id = form.username_value; |
+ credential.password = form.password_value; |
+ return credential; |
+} |
+ |
+typedef BOOL (^StringPredicate)(NSString*); |
+ |
+// Returns a block that takes a string argument and returns whether it is equal |
+// to |string|. |
+StringPredicate EqualsString(const char* string) { |
+ return [[^BOOL(NSString* other) { |
+ return [base::SysUTF8ToNSString(string) isEqualToString:other]; |
+ } copy] autorelease]; |
+} |
+ |
+// A stub PasswordManagerClient for testing. |
+class StubPasswordManagerClient |
+ : public password_manager::StubPasswordManagerClient { |
+ public: |
+ StubPasswordManagerClient() |
+ : password_manager::StubPasswordManagerClient(), |
+ password_store_(nullptr) { |
+ prefs_.registry()->RegisterBooleanPref( |
+ password_manager::prefs::kCredentialsEnableAutosignin, false); |
+ password_bubble_experiment::RegisterPrefs(prefs_.registry()); |
+ } |
+ |
+ ~StubPasswordManagerClient() override { |
+ if (password_store_) |
+ password_store_->ShutdownOnUIThread(); |
+ } |
+ |
+ MOCK_CONST_METHOD0(IsSavingAndFillingEnabledForCurrentPage, bool()); |
+ |
+ void SetPasswordStore( |
+ scoped_refptr<password_manager::PasswordStore> password_store) { |
+ password_store_ = password_store; |
+ } |
+ |
+ password_manager::PasswordStore* GetPasswordStore() const override { |
+ return password_store_.get(); |
+ } |
+ |
+ void SetUserChosenCredential( |
+ const password_manager::CredentialInfo& credential) { |
+ user_chosen_credential_ = credential; |
+ } |
+ |
+ bool PromptUserToChooseCredentials( |
+ ScopedVector<autofill::PasswordForm> local_forms, |
+ ScopedVector<autofill::PasswordForm> federated_forms, |
+ const GURL& origin, |
+ const CredentialsCallback& callback) override { |
+ return false; |
+ } |
+ |
+ bool PromptUserToSaveOrUpdatePassword( |
+ std::unique_ptr<password_manager::PasswordFormManager> form_to_save, |
+ password_manager::CredentialSourceType type, |
+ bool update_password) override { |
+ if (!update_password) |
+ saved_form_ = std::move(form_to_save); |
+ return true; |
+ } |
+ |
+ PrefService* GetPrefs() override { return &prefs_; } |
+ |
+ password_manager::PasswordFormManager* saved_form() { |
+ return saved_form_.get(); |
+ } |
+ |
+ private: |
+ // PrefService for testing. |
+ TestingPrefServiceSimple prefs_; |
+ |
+ // PasswordStore for testing. |
+ scoped_refptr<password_manager::PasswordStore> password_store_; |
+ |
+ // The password form shown to the user for saving. |
+ std::unique_ptr<password_manager::PasswordFormManager> saved_form_; |
+ |
+ // The credential to be returned to callers of PromptUserToChooseCredentials. |
+ password_manager::CredentialInfo user_chosen_credential_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(StubPasswordManagerClient); |
+}; |
+ |
+// Tests for CredentialManager. |
+class CredentialManagerTest : public web::WebTestWithWebState { |
+ public: |
+ CredentialManagerTest() {} |
+ ~CredentialManagerTest() override {} |
+ |
+ void SetUp() override { |
+ web::WebTestWithWebState::SetUp(); |
+ id originalMock = |
+ [OCMockObject niceMockForClass:[JSCredentialManager class]]; |
+ mock_js_credential_manager_.reset([[MockJSCredentialManager alloc] |
+ initWithRepresentedObject:originalMock]); |
+ credential_manager_.reset(new CredentialManager( |
+ web_state(), &stub_client_, &stub_driver_, |
+ static_cast<id>(mock_js_credential_manager_.get()))); |
+ } |
+ |
+ // Sets up an expectation that the promise identified by |request_id| will be |
+ // resolved with |credential|. |verified| must point to a variable that will |
+ // be checked by the caller to ensure that the expectations were run. (This |
+ // is necessary because OCMock doesn't handle methods with C++ object |
+ // parameters.) |
+ void ExpectPromiseResolved(bool* verified, |
+ int request_id, |
+ const web::Credential& credential) { |
+ SEL selector = |
+ @selector(resolvePromiseWithRequestID:credential:completionHandler:); |
+ web::Credential strong_credential(credential); |
+ [mock_js_credential_manager_ |
+ onSelector:selector |
+ callBlockExpectation:^(NSInteger block_request_id, |
+ const web::Credential& block_credential) { |
+ EXPECT_EQ(request_id, block_request_id); |
+ EXPECT_TRUE(CredentialsEqual(strong_credential, block_credential)); |
+ *verified = true; |
+ }]; |
+ } |
+ |
+ // Same as |ExpectPromiseResolved(bool*, int, const web::Credential&)| but |
+ // does not expect a credential to be passed. |
+ void ExpectPromiseResolved(int request_id) { |
+ [[[mock_js_credential_manager_ representedObject] expect] |
+ resolvePromiseWithRequestID:request_id |
+ completionHandler:nil]; |
+ } |
+ |
+ // Clears the expectations set up by |ExpectPromiseResolved()|. |
+ void ClearPromiseResolutionExpectations() { |
+ SEL selector = |
+ @selector(resolvePromiseWithRequestID:credential:completionHandler:); |
+ [mock_js_credential_manager_ removeBlockExpectationOnSelector:selector]; |
+ } |
+ |
+ // Sets up an expectation that the promise identified by |request_id| will be |
+ // rejected with the given |error_type| and |message|. The caller should use |
+ // EXPECT_OCMOCK_VERIFY to verify that the expectations were run. |
+ void ExpectPromiseRejected(int request_id, |
+ const char* error_type, |
+ const char* message) { |
+ [[[mock_js_credential_manager_ representedObject] expect] |
+ rejectPromiseWithRequestID:request_id |
+ errorType:[OCMArg |
+ checkWithBlock:EqualsString(error_type)] |
+ message:[OCMArg checkWithBlock:EqualsString(message)] |
+ completionHandler:[OCMArg any]]; |
+ } |
+ |
+ protected: |
+ // Mock for PasswordManagerClient. |
+ StubPasswordManagerClient stub_client_; |
+ |
+ // Stub for PasswordManagerDriver. |
+ password_manager::StubPasswordManagerDriver stub_driver_; |
+ |
+ // Mock for JSCredentialManager. |
+ base::scoped_nsobject<MockJSCredentialManager> mock_js_credential_manager_; |
+ |
+ // CredentialManager for testing. |
+ std::unique_ptr<CredentialManager> credential_manager_; |
+ |
+ private: |
+ explicit CredentialManagerTest(const CredentialManagerTest&) = delete; |
+ CredentialManagerTest& operator=(const CredentialManagerTest&) = delete; |
+}; |
+ |
+// Tests that a credential request is rejected properly when the PasswordStore |
+// is unavailable. |
+TEST_F(CredentialManagerTest, RequestRejectedWhenPasswordStoreUnavailable) { |
+ // Clear the password store. |
+ stub_client_.SetPasswordStore(nullptr); |
+ |
+ // Requesting a credential should reject the request with an error. |
+ const int request_id = 0; |
+ ExpectPromiseRejected(request_id, |
+ kCredentialsPasswordStoreUnavailableErrorType, |
+ kCredentialsPasswordStoreUnavailableErrorMessage); |
+ credential_manager_->CredentialsRequested(request_id, |
+ GURL("http://foo.com/login"), false, |
+ std::vector<std::string>(), true); |
+ |
+ // Pump the message loop and verify. |
+ WaitForBackgroundTasks(); |
+ EXPECT_OCMOCK_VERIFY([mock_js_credential_manager_ representedObject]); |
+} |
+ |
+// Tests that a credential request is rejected when another request is pending. |
+TEST_F(CredentialManagerTest, RequestRejectedWhenExistingRequestIsPending) { |
+ // Set a password store, but prevent requests from completing. |
+ stub_client_.SetPasswordStore(new password_manager::TestPasswordStore); |
+ |
+ // Make an initial request. Don't pump the message loop, so that the task |
+ // doesn't complete. Expect the request to resolve with an empty credential |
+ // after the message loop is pumped. |
+ const int first_request_id = 0; |
+ bool first_verified = false; |
+ ExpectPromiseResolved(&first_verified, first_request_id, web::Credential()); |
+ credential_manager_->CredentialsRequested(first_request_id, |
+ GURL("http://foo.com/login"), false, |
+ std::vector<std::string>(), true); |
+ |
+ // Making a second request and then pumping the message loop should reject the |
+ // request with an error. |
+ const int second_request_id = 0; |
+ ExpectPromiseRejected(second_request_id, kCredentialsPendingRequestErrorType, |
+ kCredentialsPendingRequestErrorMessage); |
+ credential_manager_->CredentialsRequested(second_request_id, |
+ GURL("http://foo.com/login"), false, |
+ std::vector<std::string>(), true); |
+ |
+ // Pump the message loop and verify. |
+ WaitForBackgroundTasks(); |
+ EXPECT_TRUE(first_verified); |
+ EXPECT_OCMOCK_VERIFY([mock_js_credential_manager_ representedObject]); |
+} |
+ |
+// Tests that a zero-click credential request is resolved properly with an empty |
+// credential when zero-click sign-in is disabled. |
+TEST_F(CredentialManagerTest, |
+ ZeroClickRequestResolvedWithEmptyCredentialWhenZeroClickSignInDisabled) { |
+ // Set a password store, but request a zero-click credential with zero-click |
+ // disabled. |
+ stub_client_.SetPasswordStore(new password_manager::TestPasswordStore); |
+ const bool zero_click = true; |
+ static_cast<TestingPrefServiceSimple*>(stub_client_.GetPrefs()) |
+ ->SetUserPref(password_manager::prefs::kCredentialsEnableAutosignin, |
+ new base::FundamentalValue(!zero_click)); |
+ |
+ // Requesting a zero-click credential should immediately resolve the request |
+ // with an empty credential. |
+ const int request_id = 0; |
+ bool verified = false; |
+ ExpectPromiseResolved(&verified, request_id, web::Credential()); |
+ credential_manager_->CredentialsRequested( |
+ request_id, GURL("http://foo.com/login"), zero_click, |
+ std::vector<std::string>(), true); |
+ |
+ // Pump the message loop and verify. |
+ WaitForBackgroundTasks(); |
+ EXPECT_TRUE(verified); |
+} |
+ |
+// Tests that a credential request is properly resolved with an empty credential |
+// when no credentials are available. |
+TEST_F(CredentialManagerTest, |
+ RequestResolvedWithEmptyCredentialWhenNoneAvailable) { |
+ // Set a password store with no credentials, enable zero-click, and request a |
+ // zero-click credential. |
+ stub_client_.SetPasswordStore(new password_manager::TestPasswordStore); |
+ const bool zero_click = true; |
+ static_cast<TestingPrefServiceSimple*>(stub_client_.GetPrefs()) |
+ ->SetUserPref(password_manager::prefs::kCredentialsEnableAutosignin, |
+ new base::FundamentalValue(zero_click)); |
+ |
+ // Requesting a zero-click credential should try to retrieve PasswordForms |
+ // from the PasswordStore and resolve the request with an empty Credential |
+ // when none are found. |
+ const int request_id = 0; |
+ bool verified = false; |
+ ExpectPromiseResolved(&verified, request_id, web::Credential()); |
+ credential_manager_->CredentialsRequested( |
+ request_id, GURL("http://foo.com/login"), zero_click, |
+ std::vector<std::string>(), true); |
+ |
+ // Pump the message loop and verify. |
+ WaitForBackgroundTasks(); |
+ EXPECT_TRUE(verified); |
+} |
+ |
+// Tests that a zero-click credential request properly resolves. |
+TEST_F(CredentialManagerTest, ZeroClickRequestResolved) { |
+ // Set a password store with a credential, enable zero-click, and request a |
+ // zero-click credential. |
+ scoped_refptr<password_manager::TestPasswordStore> store( |
+ new password_manager::TestPasswordStore); |
+ const bool zero_click = true; |
+ store->AddLogin(GetTestPasswordForm1(zero_click)); |
+ stub_client_.SetPasswordStore(store); |
+ static_cast<TestingPrefServiceSimple*>(stub_client_.GetPrefs()) |
+ ->SetUserPref(password_manager::prefs::kCredentialsEnableAutosignin, |
+ new base::FundamentalValue(zero_click)); |
+ // Without the first run experience, the zero-click credentials won't be |
+ // passed to the site. |
+ static_cast<TestingPrefServiceSimple*>(stub_client_.GetPrefs()) |
+ ->SetUserPref( |
+ password_manager::prefs::kWasAutoSignInFirstRunExperienceShown, |
+ new base::FundamentalValue(true)); |
+ WaitForBackgroundTasks(); |
+ |
+ // Requesting a zero-click credential should retrieve a PasswordForm from the |
+ // PasswordStore and resolve the request with a corresponding Credential. |
+ const int request_id = 0; |
+ bool verified = false; |
+ ExpectPromiseResolved(&verified, request_id, |
+ GetTestWebCredential1(zero_click)); |
+ credential_manager_->CredentialsRequested( |
+ request_id, GURL("http://foo.com/login"), zero_click, |
+ std::vector<std::string>(), true); |
+ |
+ // Pump the message loop and verify. |
+ WaitForBackgroundTasks(); |
+ EXPECT_TRUE(verified); |
+} |
+ |
+// Tests that a credential request properly resolves. |
+// TODO(crbug.com/598851): Reenabled this test. |
+TEST_F(CredentialManagerTest, DISABLED_RequestResolved) { |
+ // Set a password store with two credentials and set a credential to be |
+ // returned by the PasswordManagerClient as if chosen by the user. |
+ scoped_refptr<password_manager::TestPasswordStore> store( |
+ new password_manager::TestPasswordStore); |
+ const bool zero_click = false; |
+ store->AddLogin(GetTestPasswordForm1(zero_click)); |
+ store->AddLogin(GetTestPasswordForm2(zero_click)); |
+ stub_client_.SetPasswordStore(store); |
+ stub_client_.SetUserChosenCredential(password_manager::CredentialInfo( |
+ GetTestPasswordForm2(zero_click), |
+ password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD)); |
+ WaitForBackgroundTasks(); |
+ |
+ // Request a credential. The request should be resolved with the credential |
+ // set on the stub client. |
+ const int request_id = 0; |
+ bool verified = false; |
+ ExpectPromiseResolved(&verified, request_id, |
+ GetTestWebCredential2(zero_click)); |
+ credential_manager_->CredentialsRequested( |
+ request_id, GURL("http://foo.com/login"), zero_click, |
+ std::vector<std::string>(), true); |
+ |
+ // Pump the message loop and verify. |
+ WaitForBackgroundTasks(); |
+ EXPECT_TRUE(verified); |
+} |
+ |
+// Tests that two requests back-to-back succeed when they wait to be resolved. |
+// TODO(crbug.com/598851): Reenable this test. |
+TEST_F(CredentialManagerTest, DISABLED_ConsecutiveRequestsResolve) { |
+ // Set a password store with two credentials and set a credential to be |
+ // returned by the PasswordManagerClient as if chosen by the user. |
+ scoped_refptr<password_manager::TestPasswordStore> store( |
+ new password_manager::TestPasswordStore); |
+ const bool zero_click = false; |
+ store->AddLogin(GetTestPasswordForm1(zero_click)); |
+ stub_client_.SetPasswordStore(store); |
+ stub_client_.SetUserChosenCredential(password_manager::CredentialInfo( |
+ GetTestPasswordForm1(zero_click), |
+ password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD)); |
+ WaitForBackgroundTasks(); |
+ |
+ // Request a credential. The request should be resolved with the credential |
+ // set on the stub client. |
+ const int first_request_id = 0; |
+ bool first_verified = false; |
+ ExpectPromiseResolved(&first_verified, first_request_id, |
+ GetTestWebCredential1(zero_click)); |
+ credential_manager_->CredentialsRequested( |
+ first_request_id, GURL("http://foo.com/login"), zero_click, |
+ std::vector<std::string>(), true); |
+ |
+ // Pump the message loop and verify. |
+ WaitForBackgroundTasks(); |
+ EXPECT_TRUE(first_verified); |
+ |
+ ClearPromiseResolutionExpectations(); |
+ |
+ // Make a second request. It should be resolved again. |
+ const int second_request_id = 1; |
+ bool second_verified = false; |
+ ExpectPromiseResolved(&second_verified, second_request_id, |
+ GetTestWebCredential1(zero_click)); |
+ credential_manager_->CredentialsRequested( |
+ second_request_id, GURL("http://foo.com/login"), zero_click, |
+ std::vector<std::string>(), true); |
+ |
+ // Pump the message loop and verify. |
+ WaitForBackgroundTasks(); |
+ EXPECT_TRUE(second_verified); |
+} |
+ |
+// Tests that notifySignedIn prompts the user to save a password. |
+TEST_F(CredentialManagerTest, |
+ SignInResolvesAndPromptsUserWhenSavingEnabledAndNotBlacklisted) { |
+ // Set a password store so the PasswordFormManager can retrieve credentials. |
+ scoped_refptr<password_manager::TestPasswordStore> store( |
+ new password_manager::TestPasswordStore); |
+ stub_client_.SetPasswordStore(store); |
+ const bool saving_enabled = true; |
+ EXPECT_CALL(stub_client_, IsSavingAndFillingEnabledForCurrentPage()) |
+ .WillOnce(Return(saving_enabled)) |
+ .WillOnce(Return(saving_enabled)); |
+ |
+ // Notify the browser that the user signed in. |
+ const int request_id = 0; |
+ ExpectPromiseResolved(request_id); |
+ const bool zero_click = false; |
+ credential_manager_->SignedIn(request_id, GURL("http://foo.com/login"), |
+ GetTestWebCredential1(zero_click)); |
+ |
+ // Pump the message loop and verify. |
+ WaitForBackgroundTasks(); |
+ autofill::PasswordForm expected_observed_form( |
+ GetTestPasswordForm1(zero_click)); |
+ expected_observed_form.username_value.clear(); |
+ expected_observed_form.password_value.clear(); |
+ EXPECT_EQ(expected_observed_form, stub_client_.saved_form()->observed_form()); |
+} |
+ |
+// Tests that notifySignedIn doesn't prompt the user to save a password when the |
+// password manager is disabled for the current page. |
+TEST_F(CredentialManagerTest, |
+ SignInResolvesAndDoesNotPromptsUserWhenSavingDisabledAndNotBlacklisted) { |
+ // Disable saving. |
+ scoped_refptr<password_manager::TestPasswordStore> store( |
+ new password_manager::TestPasswordStore); |
+ stub_client_.SetPasswordStore(store); |
+ const bool saving_enabled = false; |
+ EXPECT_CALL(stub_client_, IsSavingAndFillingEnabledForCurrentPage()) |
+ .WillOnce(Return(saving_enabled)); |
+ |
+ // Notify the browser that the user signed in. |
+ const int request_id = 0; |
+ ExpectPromiseResolved(request_id); |
+ const bool zero_click = false; |
+ credential_manager_->SignedIn(request_id, GURL("http://foo.com/login"), |
+ GetTestWebCredential1(zero_click)); |
+ |
+ // Pump the message loop and verify that no form was saved. |
+ WaitForBackgroundTasks(); |
+ EXPECT_FALSE(stub_client_.saved_form()); |
+} |
+ |
+// Tests that notifySignedIn doesn't prompt the user to save a password when the |
+// submitted form is blacklisted by the password manager. |
+TEST_F(CredentialManagerTest, |
+ SignInResolvesAndDoesNotPromptUserWhenSavingEnabledAndBlacklisted) { |
+ // Disable saving. |
+ scoped_refptr<password_manager::TestPasswordStore> store( |
+ new password_manager::TestPasswordStore); |
+ stub_client_.SetPasswordStore(store); |
+ const bool saving_enabled = true; |
+ EXPECT_CALL(stub_client_, IsSavingAndFillingEnabledForCurrentPage()) |
+ .WillOnce(Return(saving_enabled)) |
+ .WillOnce(Return(saving_enabled)); |
+ |
+ // Save the credential that will be signed in and mark it blacklisted. |
+ const bool zero_click = false; |
+ autofill::PasswordForm blacklisted_form(GetTestPasswordForm1(zero_click)); |
+ blacklisted_form.blacklisted_by_user = true; |
+ store->AddLogin(blacklisted_form); |
+ WaitForBackgroundTasks(); |
+ |
+ // Notify the browser that the user signed in. |
+ const int request_id = 0; |
+ ExpectPromiseResolved(request_id); |
+ credential_manager_->SignedIn(request_id, GURL("http://foo.com/login"), |
+ GetTestWebCredential1(zero_click)); |
+ |
+ // Pump the message loop and verify that no form was saved. |
+ WaitForBackgroundTasks(); |
+ EXPECT_FALSE(stub_client_.saved_form()); |
+} |
+ |
+// Tests that notifySignedOut marks credentials as non-zero-click. |
+TEST_F(CredentialManagerTest, SignOutResolvesAndMarksFormsNonZeroClick) { |
+ // Create two zero-click credentials for the current page. |
+ std::string current_origin(credential_manager_->GetOrigin().spec()); |
+ autofill::PasswordForm form1(GetTestPasswordForm1(true)); |
+ form1.signon_realm = current_origin; |
+ autofill::PasswordForm form2(GetTestPasswordForm2(true)); |
+ form2.signon_realm = current_origin; |
+ |
+ // Add both credentials to the store. |
+ scoped_refptr<password_manager::TestPasswordStore> store( |
+ new password_manager::TestPasswordStore); |
+ stub_client_.SetPasswordStore(store); |
+ store->AddLogin(form1); |
+ store->AddLogin(form2); |
+ WaitForBackgroundTasks(); |
+ |
+ // Check that both credentials in the store are zero-click. |
+ EXPECT_EQ(1U, store->stored_passwords().size()); |
+ const std::vector<autofill::PasswordForm>& passwords_before_signout = |
+ store->stored_passwords().find(current_origin)->second; |
+ EXPECT_EQ(2U, passwords_before_signout.size()); |
+ for (const autofill::PasswordForm& form : passwords_before_signout) |
+ EXPECT_FALSE(form.skip_zero_click); |
+ |
+ // Sign out the current origin, which has credentials, and a second origin, |
+ // which doesnt. |
+ const int first_request_id = 0; |
+ ExpectPromiseResolved(first_request_id); |
+ credential_manager_->SignedOut(first_request_id, |
+ credential_manager_->GetOrigin()); |
+ const int second_request_id = 1; |
+ ExpectPromiseResolved(second_request_id); |
+ credential_manager_->SignedOut(second_request_id, GURL("https://foo.com")); |
+ |
+ // Pump the message loop to let the promises resolve. Check that the |
+ // credentials in the store are now non-zero-click and that signing out an |
+ // origin with no credentials had no effect. |
+ WaitForBackgroundTasks(); |
+ EXPECT_EQ(1U, store->stored_passwords().size()); |
+ const std::vector<autofill::PasswordForm>& passwords_after_signout = |
+ store->stored_passwords().find(current_origin)->second; |
+ EXPECT_EQ(2U, passwords_after_signout.size()); |
+ for (const autofill::PasswordForm& form : passwords_after_signout) |
+ EXPECT_TRUE(form.skip_zero_click); |
+} |
+ |
+} // namespace |