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