1 // Copyright (c) 2012 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.
5 #include "base/basictypes.h"
7 #include "base/bind_helpers.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/stl_util.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/synchronization/waitable_event.h"
13 #include "base/time/time.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/password_manager/password_store_change.h"
16 #include "chrome/browser/password_manager/password_store_consumer.h"
17 #include "chrome/browser/password_manager/password_store_default.h"
18 #include "chrome/common/pref_names.h"
19 #include "chrome/test/base/testing_profile.h"
20 #include "components/password_manager/core/browser/password_form_data.h"
21 #include "content/public/browser/notification_details.h"
22 #include "content/public/browser/notification_registrar.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/test/mock_notification_observer.h"
25 #include "content/public/test/test_browser_thread.h"
26 #include "testing/gmock/include/gmock/gmock.h"
27 #include "testing/gtest/include/gtest/gtest.h"
29 using autofill::PasswordForm
;
30 using base::WaitableEvent
;
31 using content::BrowserThread
;
34 using testing::ElementsAreArray
;
35 using testing::Pointee
;
36 using testing::Property
;
37 using testing::WithArg
;
41 class MockPasswordStoreConsumer
: public PasswordStoreConsumer
{
43 MOCK_METHOD1(OnGetPasswordStoreResults
,
44 void(const std::vector
<PasswordForm
*>&));
47 // This class will add and remove a mock notification observer from
49 class DBThreadObserverHelper
50 : public base::RefCountedThreadSafe
<DBThreadObserverHelper
,
51 BrowserThread::DeleteOnDBThread
> {
53 DBThreadObserverHelper() : done_event_(true, false) {}
55 void Init(PasswordStore
* password_store
) {
56 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
57 BrowserThread::PostTask(
60 base::Bind(&DBThreadObserverHelper::AddObserverTask
,
62 make_scoped_refptr(password_store
)));
66 content::MockNotificationObserver
& observer() {
71 friend struct BrowserThread::DeleteOnThread
<BrowserThread::DB
>;
72 friend class base::DeleteHelper
<DBThreadObserverHelper
>;
74 virtual ~DBThreadObserverHelper() {
75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
76 registrar_
.RemoveAll();
79 void AddObserverTask(PasswordStore
* password_store
) {
80 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
81 registrar_
.Add(&observer_
,
82 chrome::NOTIFICATION_LOGINS_CHANGED
,
83 content::Source
<PasswordStore
>(password_store
));
87 WaitableEvent done_event_
;
88 content::NotificationRegistrar registrar_
;
89 content::MockNotificationObserver observer_
;
92 } // anonymous namespace
94 class PasswordStoreDefaultTest
: public testing::Test
{
96 PasswordStoreDefaultTest()
97 : ui_thread_(BrowserThread::UI
, &message_loop_
),
98 db_thread_(BrowserThread::DB
) {
101 virtual void SetUp() {
102 ASSERT_TRUE(db_thread_
.Start());
104 profile_
.reset(new TestingProfile());
106 login_db_
.reset(new LoginDatabase());
107 ASSERT_TRUE(login_db_
->Init(profile_
->GetPath().Append(
108 FILE_PATH_LITERAL("login_test"))));
111 virtual void TearDown() {
112 base::MessageLoop::current()->PostTask(FROM_HERE
,
113 base::MessageLoop::QuitClosure());
114 base::MessageLoop::current()->Run();
118 base::MessageLoopForUI message_loop_
;
119 content::TestBrowserThread ui_thread_
;
120 // PasswordStore, WDS schedule work on this thread.
121 content::TestBrowserThread db_thread_
;
123 scoped_ptr
<LoginDatabase
> login_db_
;
124 scoped_ptr
<TestingProfile
> profile_
;
127 ACTION(STLDeleteElements0
) {
128 STLDeleteContainerPointers(arg0
.begin(), arg0
.end());
131 ACTION(QuitUIMessageLoop
) {
132 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
133 base::MessageLoop::current()->Quit();
136 TEST_F(PasswordStoreDefaultTest
, NonASCIIData
) {
137 scoped_refptr
<PasswordStoreDefault
> store(new PasswordStoreDefault(
138 base::MessageLoopProxy::current(),
139 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB
),
140 login_db_
.release()));
143 // Some non-ASCII password form data.
144 static const PasswordFormData form_data
[] = {
145 { PasswordForm::SCHEME_HTML
,
146 "http://foo.example.com",
147 "http://foo.example.com/origin",
148 "http://foo.example.com/action",
157 // Build the expected forms vector and add the forms to the store.
158 std::vector
<PasswordForm
*> expected_forms
;
159 for (unsigned int i
= 0; i
< ARRAYSIZE_UNSAFE(form_data
); ++i
) {
160 PasswordForm
* form
= CreatePasswordFormFromData(form_data
[i
]);
161 expected_forms
.push_back(form
);
162 store
->AddLogin(*form
);
165 // The PasswordStore schedules tasks to run on the DB thread so we schedule
166 // yet another task to notify us that it's safe to carry on with the test.
167 // The PasswordStore doesn't really understand that it's "done" once the tasks
168 // we posted above have completed, so there's no formal notification for that.
169 WaitableEvent
done(false, false);
170 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
171 base::Bind(&WaitableEvent::Signal
, base::Unretained(&done
)));
174 MockPasswordStoreConsumer consumer
;
176 // Make sure we quit the MessageLoop even if the test fails.
177 ON_CALL(consumer
, OnGetPasswordStoreResults(_
))
178 .WillByDefault(QuitUIMessageLoop());
180 // We expect to get the same data back, even though it's not all ASCII.
181 EXPECT_CALL(consumer
,
182 OnGetPasswordStoreResults(ContainsAllPasswordForms(expected_forms
)))
183 .WillOnce(DoAll(WithArg
<0>(STLDeleteElements0()), QuitUIMessageLoop()));
185 store
->GetAutofillableLogins(&consumer
);
186 base::MessageLoop::current()->Run();
188 STLDeleteElements(&expected_forms
);
193 TEST_F(PasswordStoreDefaultTest
, Notifications
) {
194 scoped_refptr
<PasswordStoreDefault
> store(new PasswordStoreDefault(
195 base::MessageLoopProxy::current(),
196 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB
),
197 login_db_
.release()));
200 PasswordFormData form_data
=
201 { PasswordForm::SCHEME_HTML
,
202 "http://bar.example.com",
203 "http://bar.example.com/origin",
204 "http://bar.example.com/action",
211 scoped_ptr
<PasswordForm
> form(CreatePasswordFormFromData(form_data
));
213 scoped_refptr
<DBThreadObserverHelper
> helper
= new DBThreadObserverHelper
;
214 helper
->Init(store
.get());
216 const PasswordStoreChange expected_add_changes
[] = {
217 PasswordStoreChange(PasswordStoreChange::ADD
, *form
),
222 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED
),
223 content::Source
<PasswordStore
>(store
.get()),
224 Property(&content::Details
<const PasswordStoreChangeList
>::ptr
,
225 Pointee(ElementsAreArray(expected_add_changes
)))));
227 // Adding a login should trigger a notification.
228 store
->AddLogin(*form
);
230 // The PasswordStore schedules tasks to run on the DB thread so we schedule
231 // yet another task to notify us that it's safe to carry on with the test.
232 WaitableEvent
done(false, false);
233 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
234 base::Bind(&WaitableEvent::Signal
, base::Unretained(&done
)));
237 // Change the password.
238 form
->password_value
= base::ASCIIToUTF16("a different password");
240 const PasswordStoreChange expected_update_changes
[] = {
241 PasswordStoreChange(PasswordStoreChange::UPDATE
, *form
),
246 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED
),
247 content::Source
<PasswordStore
>(store
.get()),
248 Property(&content::Details
<const PasswordStoreChangeList
>::ptr
,
249 Pointee(ElementsAreArray(expected_update_changes
)))));
251 // Updating the login with the new password should trigger a notification.
252 store
->UpdateLogin(*form
);
254 // Wait for PasswordStore to send the notification.
255 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
256 base::Bind(&WaitableEvent::Signal
, base::Unretained(&done
)));
259 const PasswordStoreChange expected_delete_changes
[] = {
260 PasswordStoreChange(PasswordStoreChange::REMOVE
, *form
),
265 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED
),
266 content::Source
<PasswordStore
>(store
.get()),
267 Property(&content::Details
<const PasswordStoreChangeList
>::ptr
,
268 Pointee(ElementsAreArray(expected_delete_changes
)))));
270 // Deleting the login should trigger a notification.
271 store
->RemoveLogin(*form
);
273 // Wait for PasswordStore to send the notification.
274 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
275 base::Bind(&WaitableEvent::Signal
, base::Unretained(&done
)));