blob: 08f78fdd2d0ad7bb726826307e97f91816f79355 [file] [log] [blame]
// Copyright 2014 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/metrics/histogram_samples.h"
#include "base/prefs/pref_service.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/statistics_delta_reader.h"
#include "base/time/time.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
#include "chrome/browser/ui/passwords/manage_passwords_icon.h"
#include "chrome/browser/ui/passwords/manage_passwords_icon_mock.h"
#include "chrome/browser/ui/passwords/manage_passwords_ui_controller_mock.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/password_form_manager.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/common/password_manager_ui.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const int64 kSlowNavigationDelayInMS = 2000;
const int64 kQuickNavigationDelayInMS = 500;
class MockElapsedTimer : public base::ElapsedTimer {
public:
MockElapsedTimer() {}
virtual base::TimeDelta Elapsed() const OVERRIDE { return delta_; }
void Advance(int64 ms) { delta_ = base::TimeDelta::FromMilliseconds(ms); }
private:
base::TimeDelta delta_;
DISALLOW_COPY_AND_ASSIGN(MockElapsedTimer);
};
} // namespace
class ManagePasswordsUIControllerTest : public ChromeRenderViewHostTestHarness {
public:
ManagePasswordsUIControllerTest() {}
virtual void SetUp() OVERRIDE {
ChromeRenderViewHostTestHarness::SetUp();
// Create the test UIController here so that it's bound to
// |test_web_contents_|, and will be retrieved correctly via
// ManagePasswordsUIController::FromWebContents in |controller()|.
new ManagePasswordsUIControllerMock(web_contents());
test_form_.origin = GURL("http://example.com");
// We need to be on a "webby" URL for most tests.
content::WebContentsTester::For(web_contents())
->NavigateAndCommit(GURL("http://example.com"));
}
autofill::PasswordForm& test_form() { return test_form_; }
ManagePasswordsUIControllerMock* controller() {
return static_cast<ManagePasswordsUIControllerMock*>(
ManagePasswordsUIController::FromWebContents(web_contents()));
}
private:
autofill::PasswordForm test_form_;
};
TEST_F(ManagePasswordsUIControllerTest, DefaultState) {
EXPECT_EQ(password_manager::ui::INACTIVE_STATE, controller()->state());
EXPECT_FALSE(controller()->PasswordPendingUserDecision());
EXPECT_EQ(GURL::EmptyGURL(), controller()->origin());
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state());
}
TEST_F(ManagePasswordsUIControllerTest, PasswordAutofilled) {
base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
autofill::PasswordFormMap map;
map[kTestUsername] = &test_form();
controller()->OnPasswordAutofilled(map);
EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state());
EXPECT_FALSE(controller()->PasswordPendingUserDecision());
EXPECT_EQ(test_form().origin, controller()->origin());
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state());
}
TEST_F(ManagePasswordsUIControllerTest, PasswordSubmitted) {
password_manager::StubPasswordManagerClient client;
password_manager::StubPasswordManagerDriver driver;
password_manager::PasswordFormManager* test_form_manager =
new password_manager::PasswordFormManager(
NULL, &client, &driver, test_form(), false);
controller()->OnPasswordSubmitted(test_form_manager);
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE,
controller()->state());
EXPECT_TRUE(controller()->PasswordPendingUserDecision());
// TODO(mkwst): This should be the value of test_form().origin, but
// it's being masked by the stub implementation of
// ManagePasswordsUIControllerMock::PendingCredentials.
EXPECT_EQ(GURL::EmptyGURL(), controller()->origin());
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, mock.state());
}
TEST_F(ManagePasswordsUIControllerTest, QuickNavigations) {
password_manager::StubPasswordManagerClient client;
password_manager::StubPasswordManagerDriver driver;
password_manager::PasswordFormManager* test_form_manager =
new password_manager::PasswordFormManager(
NULL, &client, &driver, test_form(), false);
controller()->OnPasswordSubmitted(test_form_manager);
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, mock.state());
// Fake-navigate within a second. We expect the bubble's state to persist
// if a navigation occurs too quickly for a user to reasonably have been
// able to interact with the bubble. This happens on `accounts.google.com`,
// for instance.
scoped_ptr<MockElapsedTimer> timer(new MockElapsedTimer());
timer->Advance(kQuickNavigationDelayInMS);
controller()->SetTimer(timer.release());
controller()->DidNavigateMainFrame(content::LoadCommittedDetails(),
content::FrameNavigateParams());
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, mock.state());
}
TEST_F(ManagePasswordsUIControllerTest, SlowNavigations) {
password_manager::StubPasswordManagerClient client;
password_manager::StubPasswordManagerDriver driver;
password_manager::PasswordFormManager* test_form_manager =
new password_manager::PasswordFormManager(
NULL, &client, &driver, test_form(), false);
controller()->OnPasswordSubmitted(test_form_manager);
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, mock.state());
// Fake-navigate after a second. We expect the bubble's state to be reset
// if a navigation occurs after this limit.
scoped_ptr<MockElapsedTimer> timer(new MockElapsedTimer());
timer->Advance(kSlowNavigationDelayInMS);
controller()->SetTimer(timer.release());
controller()->DidNavigateMainFrame(content::LoadCommittedDetails(),
content::FrameNavigateParams());
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state());
}
TEST_F(ManagePasswordsUIControllerTest, PasswordSubmittedToNonWebbyURL) {
// Navigate to a non-webby URL, then see what happens!
content::WebContentsTester::For(web_contents())
->NavigateAndCommit(GURL("chrome://sign-in"));
password_manager::StubPasswordManagerClient client;
password_manager::StubPasswordManagerDriver driver;
password_manager::PasswordFormManager* test_form_manager =
new password_manager::PasswordFormManager(
NULL, &client, &driver, test_form(), false);
controller()->OnPasswordSubmitted(test_form_manager);
EXPECT_EQ(password_manager::ui::INACTIVE_STATE, controller()->state());
EXPECT_FALSE(controller()->PasswordPendingUserDecision());
// TODO(mkwst): This should be the value of test_form().origin, but
// it's being masked by the stub implementation of
// ManagePasswordsUIControllerMock::PendingCredentials.
EXPECT_EQ(GURL::EmptyGURL(), controller()->origin());
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state());
}
TEST_F(ManagePasswordsUIControllerTest, BlacklistBlockedAutofill) {
test_form().blacklisted_by_user = true;
base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
autofill::PasswordFormMap map;
map[kTestUsername] = &test_form();
controller()->OnBlacklistBlockedAutofill(map);
EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, controller()->state());
EXPECT_FALSE(controller()->PasswordPendingUserDecision());
EXPECT_EQ(test_form().origin, controller()->origin());
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state());
}
TEST_F(ManagePasswordsUIControllerTest, ClickedUnblacklist) {
base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
autofill::PasswordFormMap map;
map[kTestUsername] = &test_form();
controller()->OnBlacklistBlockedAutofill(map);
controller()->UnblacklistSite();
EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state());
EXPECT_FALSE(controller()->PasswordPendingUserDecision());
EXPECT_EQ(test_form().origin, controller()->origin());
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state());
}
TEST_F(ManagePasswordsUIControllerTest, UnblacklistedElsewhere) {
test_form().blacklisted_by_user = true;
base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
autofill::PasswordFormMap map;
map[kTestUsername] = &test_form();
controller()->OnBlacklistBlockedAutofill(map);
password_manager::PasswordStoreChange change(
password_manager::PasswordStoreChange::REMOVE, test_form());
password_manager::PasswordStoreChangeList list(1, change);
controller()->OnLoginsChanged(list);
EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state());
EXPECT_FALSE(controller()->PasswordPendingUserDecision());
EXPECT_EQ(test_form().origin, controller()->origin());
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state());
}
TEST_F(ManagePasswordsUIControllerTest, BlacklistedElsewhere) {
base::string16 kTestUsername = base::ASCIIToUTF16("test_username");
autofill::PasswordFormMap map;
map[kTestUsername] = &test_form();
controller()->OnPasswordAutofilled(map);
test_form().blacklisted_by_user = true;
password_manager::PasswordStoreChange change(
password_manager::PasswordStoreChange::ADD, test_form());
password_manager::PasswordStoreChangeList list(1, change);
controller()->OnLoginsChanged(list);
EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, controller()->state());
EXPECT_FALSE(controller()->PasswordPendingUserDecision());
EXPECT_EQ(test_form().origin, controller()->origin());
ManagePasswordsIconMock mock;
controller()->UpdateIconAndBubbleState(&mock);
EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state());
}