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