1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/prefs/pref_service.h" 6#include "base/strings/utf_string_conversions.h" 7#include "base/time/time.h" 8#include "chrome/browser/ui/passwords/manage_passwords_bubble.h" 9#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h" 10#include "chrome/browser/ui/passwords/manage_passwords_icon.h" 11#include "chrome/browser/ui/passwords/manage_passwords_icon_mock.h" 12#include "chrome/browser/ui/passwords/manage_passwords_ui_controller_mock.h" 13#include "chrome/test/base/chrome_render_view_host_test_harness.h" 14#include "chrome/test/base/testing_profile.h" 15#include "components/autofill/core/common/password_form.h" 16#include "components/password_manager/core/browser/password_form_manager.h" 17#include "components/password_manager/core/browser/stub_password_manager_client.h" 18#include "components/password_manager/core/browser/stub_password_manager_driver.h" 19#include "components/password_manager/core/common/password_manager_ui.h" 20#include "content/public/test/test_browser_thread_bundle.h" 21#include "content/public/test/web_contents_tester.h" 22#include "testing/gmock/include/gmock/gmock.h" 23#include "testing/gtest/include/gtest/gtest.h" 24 25namespace { 26 27const int64 kSlowNavigationDelayInMS = 2000; 28const int64 kQuickNavigationDelayInMS = 500; 29 30class MockElapsedTimer : public base::ElapsedTimer { 31 public: 32 MockElapsedTimer() {} 33 virtual base::TimeDelta Elapsed() const OVERRIDE { return delta_; } 34 35 void Advance(int64 ms) { delta_ = base::TimeDelta::FromMilliseconds(ms); } 36 37 private: 38 base::TimeDelta delta_; 39 40 DISALLOW_COPY_AND_ASSIGN(MockElapsedTimer); 41}; 42 43} // namespace 44 45class ManagePasswordsUIControllerTest : public ChromeRenderViewHostTestHarness { 46 public: 47 ManagePasswordsUIControllerTest() {} 48 49 virtual void SetUp() OVERRIDE { 50 ChromeRenderViewHostTestHarness::SetUp(); 51 52 // Create the test UIController here so that it's bound to 53 // |test_web_contents_|, and will be retrieved correctly via 54 // ManagePasswordsUIController::FromWebContents in |controller()|. 55 new ManagePasswordsUIControllerMock(web_contents()); 56 57 test_form_.origin = GURL("http://example.com"); 58 59 // We need to be on a "webby" URL for most tests. 60 content::WebContentsTester::For(web_contents()) 61 ->NavigateAndCommit(GURL("http://example.com")); 62 } 63 64 autofill::PasswordForm& test_form() { return test_form_; } 65 66 ManagePasswordsUIControllerMock* controller() { 67 return static_cast<ManagePasswordsUIControllerMock*>( 68 ManagePasswordsUIController::FromWebContents(web_contents())); 69 } 70 71 private: 72 autofill::PasswordForm test_form_; 73}; 74 75TEST_F(ManagePasswordsUIControllerTest, DefaultState) { 76 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, controller()->state()); 77 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 78 EXPECT_EQ(GURL::EmptyGURL(), controller()->origin()); 79 80 ManagePasswordsIconMock mock; 81 controller()->UpdateIconAndBubbleState(&mock); 82 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state()); 83} 84 85TEST_F(ManagePasswordsUIControllerTest, PasswordAutofilled) { 86 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 87 autofill::PasswordFormMap map; 88 map[kTestUsername] = &test_form(); 89 controller()->OnPasswordAutofilled(map); 90 91 EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state()); 92 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 93 EXPECT_EQ(test_form().origin, controller()->origin()); 94 EXPECT_EQ(1u, controller()->best_matches().size()); 95 ASSERT_EQ(1u, controller()->best_matches().count(kTestUsername)); 96 97 // Controller should store a separate copy of the form as it doesn't own it. 98 EXPECT_NE(&test_form(), 99 controller()->best_matches().find(kTestUsername)->second); 100 101 ManagePasswordsIconMock mock; 102 controller()->UpdateIconAndBubbleState(&mock); 103 EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state()); 104} 105 106TEST_F(ManagePasswordsUIControllerTest, PasswordSubmitted) { 107 password_manager::StubPasswordManagerClient client; 108 password_manager::StubPasswordManagerDriver driver; 109 scoped_ptr<password_manager::PasswordFormManager> test_form_manager( 110 new password_manager::PasswordFormManager( 111 NULL, &client, &driver, test_form(), false)); 112 controller()->OnPasswordSubmitted(test_form_manager.Pass()); 113 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE, 114 controller()->state()); 115 EXPECT_TRUE(controller()->PasswordPendingUserDecision()); 116 117 // TODO(mkwst): This should be the value of test_form().origin, but 118 // it's being masked by the stub implementation of 119 // ManagePasswordsUIControllerMock::PendingCredentials. 120 EXPECT_EQ(GURL::EmptyGURL(), controller()->origin()); 121 122 ManagePasswordsIconMock mock; 123 controller()->UpdateIconAndBubbleState(&mock); 124 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, 125 mock.state()); 126} 127 128TEST_F(ManagePasswordsUIControllerTest, PasswordSaved) { 129 password_manager::StubPasswordManagerClient client; 130 password_manager::StubPasswordManagerDriver driver; 131 scoped_ptr<password_manager::PasswordFormManager> test_form_manager( 132 new password_manager::PasswordFormManager( 133 NULL, &client, &driver, test_form(), false)); 134 controller()->OnPasswordSubmitted(test_form_manager.Pass()); 135 136 ManagePasswordsIconMock mock; 137 controller()->UpdateIconAndBubbleState(&mock); 138 controller()->SavePassword(); 139 controller()->UpdateIconAndBubbleState(&mock); 140 EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state()); 141} 142 143TEST_F(ManagePasswordsUIControllerTest, PasswordBlacklisted) { 144 password_manager::StubPasswordManagerClient client; 145 password_manager::StubPasswordManagerDriver driver; 146 scoped_ptr<password_manager::PasswordFormManager> test_form_manager( 147 new password_manager::PasswordFormManager( 148 NULL, &client, &driver, test_form(), false)); 149 controller()->OnPasswordSubmitted(test_form_manager.Pass()); 150 151 ManagePasswordsIconMock mock; 152 controller()->UpdateIconAndBubbleState(&mock); 153 controller()->NeverSavePassword(); 154 controller()->UpdateIconAndBubbleState(&mock); 155 EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state()); 156} 157 158TEST_F(ManagePasswordsUIControllerTest, QuickNavigations) { 159 password_manager::StubPasswordManagerClient client; 160 password_manager::StubPasswordManagerDriver driver; 161 scoped_ptr<password_manager::PasswordFormManager> test_form_manager( 162 new password_manager::PasswordFormManager( 163 NULL, &client, &driver, test_form(), false)); 164 controller()->OnPasswordSubmitted(test_form_manager.Pass()); 165 ManagePasswordsIconMock mock; 166 controller()->UpdateIconAndBubbleState(&mock); 167 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, 168 mock.state()); 169 170 // Fake-navigate within a second. We expect the bubble's state to persist 171 // if a navigation occurs too quickly for a user to reasonably have been 172 // able to interact with the bubble. This happens on `accounts.google.com`, 173 // for instance. 174 scoped_ptr<MockElapsedTimer> timer(new MockElapsedTimer()); 175 timer->Advance(kQuickNavigationDelayInMS); 176 controller()->SetTimer(timer.release()); 177 controller()->DidNavigateMainFrame(content::LoadCommittedDetails(), 178 content::FrameNavigateParams()); 179 controller()->UpdateIconAndBubbleState(&mock); 180 181 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, 182 mock.state()); 183} 184 185TEST_F(ManagePasswordsUIControllerTest, SlowNavigations) { 186 password_manager::StubPasswordManagerClient client; 187 password_manager::StubPasswordManagerDriver driver; 188 scoped_ptr<password_manager::PasswordFormManager> test_form_manager( 189 new password_manager::PasswordFormManager( 190 NULL, &client, &driver, test_form(), false)); 191 controller()->OnPasswordSubmitted(test_form_manager.Pass()); 192 ManagePasswordsIconMock mock; 193 controller()->UpdateIconAndBubbleState(&mock); 194 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, 195 mock.state()); 196 197 // Fake-navigate after a second. We expect the bubble's state to be reset 198 // if a navigation occurs after this limit. 199 scoped_ptr<MockElapsedTimer> timer(new MockElapsedTimer()); 200 timer->Advance(kSlowNavigationDelayInMS); 201 controller()->SetTimer(timer.release()); 202 controller()->DidNavigateMainFrame(content::LoadCommittedDetails(), 203 content::FrameNavigateParams()); 204 controller()->UpdateIconAndBubbleState(&mock); 205 206 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state()); 207} 208 209TEST_F(ManagePasswordsUIControllerTest, PasswordSubmittedToNonWebbyURL) { 210 // Navigate to a non-webby URL, then see what happens! 211 content::WebContentsTester::For(web_contents()) 212 ->NavigateAndCommit(GURL("chrome://sign-in")); 213 214 password_manager::StubPasswordManagerClient client; 215 password_manager::StubPasswordManagerDriver driver; 216 scoped_ptr<password_manager::PasswordFormManager> test_form_manager( 217 new password_manager::PasswordFormManager( 218 NULL, &client, &driver, test_form(), false)); 219 controller()->OnPasswordSubmitted(test_form_manager.Pass()); 220 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, controller()->state()); 221 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 222 223 // TODO(mkwst): This should be the value of test_form().origin, but 224 // it's being masked by the stub implementation of 225 // ManagePasswordsUIControllerMock::PendingCredentials. 226 EXPECT_EQ(GURL::EmptyGURL(), controller()->origin()); 227 228 ManagePasswordsIconMock mock; 229 controller()->UpdateIconAndBubbleState(&mock); 230 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state()); 231} 232 233TEST_F(ManagePasswordsUIControllerTest, BlacklistBlockedAutofill) { 234 test_form().blacklisted_by_user = true; 235 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 236 autofill::PasswordFormMap map; 237 map[kTestUsername] = &test_form(); 238 controller()->OnBlacklistBlockedAutofill(map); 239 240 EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, controller()->state()); 241 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 242 EXPECT_EQ(test_form().origin, controller()->origin()); 243 EXPECT_EQ(1u, controller()->best_matches().size()); 244 ASSERT_EQ(1u, controller()->best_matches().count(kTestUsername)); 245 246 // Controller should store a separate copy of the form as it doesn't own it. 247 EXPECT_NE(&test_form(), 248 controller()->best_matches().find(kTestUsername)->second); 249 250 ManagePasswordsIconMock mock; 251 controller()->UpdateIconAndBubbleState(&mock); 252 EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state()); 253} 254 255TEST_F(ManagePasswordsUIControllerTest, ClickedUnblacklist) { 256 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 257 autofill::PasswordFormMap map; 258 map[kTestUsername] = &test_form(); 259 controller()->OnBlacklistBlockedAutofill(map); 260 controller()->UnblacklistSite(); 261 262 EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state()); 263 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 264 EXPECT_EQ(test_form().origin, controller()->origin()); 265 266 ManagePasswordsIconMock mock; 267 controller()->UpdateIconAndBubbleState(&mock); 268 EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state()); 269} 270 271TEST_F(ManagePasswordsUIControllerTest, UnblacklistedElsewhere) { 272 test_form().blacklisted_by_user = true; 273 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 274 autofill::PasswordFormMap map; 275 map[kTestUsername] = &test_form(); 276 controller()->OnBlacklistBlockedAutofill(map); 277 278 password_manager::PasswordStoreChange change( 279 password_manager::PasswordStoreChange::REMOVE, test_form()); 280 password_manager::PasswordStoreChangeList list(1, change); 281 controller()->OnLoginsChanged(list); 282 283 EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state()); 284 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 285 EXPECT_EQ(test_form().origin, controller()->origin()); 286 287 ManagePasswordsIconMock mock; 288 controller()->UpdateIconAndBubbleState(&mock); 289 EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state()); 290} 291 292TEST_F(ManagePasswordsUIControllerTest, BlacklistedElsewhere) { 293 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 294 autofill::PasswordFormMap map; 295 map[kTestUsername] = &test_form(); 296 controller()->OnPasswordAutofilled(map); 297 298 test_form().blacklisted_by_user = true; 299 password_manager::PasswordStoreChange change( 300 password_manager::PasswordStoreChange::ADD, test_form()); 301 password_manager::PasswordStoreChangeList list(1, change); 302 controller()->OnLoginsChanged(list); 303 304 EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, controller()->state()); 305 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 306 EXPECT_EQ(test_form().origin, controller()->origin()); 307 308 ManagePasswordsIconMock mock; 309 controller()->UpdateIconAndBubbleState(&mock); 310 EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state()); 311} 312 313TEST_F(ManagePasswordsUIControllerTest, AutomaticPasswordSave) { 314 password_manager::StubPasswordManagerClient client; 315 password_manager::StubPasswordManagerDriver driver; 316 scoped_ptr<password_manager::PasswordFormManager> test_form_manager( 317 new password_manager::PasswordFormManager( 318 NULL, &client, &driver, test_form(), false)); 319 320 controller()->OnAutomaticPasswordSave(test_form_manager.Pass()); 321 EXPECT_EQ(password_manager::ui::CONFIRMATION_STATE, controller()->state()); 322 323 ManagePasswordsIconMock mock; 324 controller()->UpdateIconAndBubbleState(&mock); 325 EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state()); 326} 327