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. 4 5 #include "base/basictypes.h" 6 #include "base/bind.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_form_data.h" 16 #include "chrome/browser/password_manager/password_store_change.h" 17 #include "chrome/browser/password_manager/password_store_consumer.h" 18 #include "chrome/browser/password_manager/password_store_default.h" 19 #include "chrome/common/pref_names.h" 20 #include "chrome/test/base/testing_profile.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" 28 29 using base::WaitableEvent; 30 using content::BrowserThread; 31 using content::PasswordForm; 32 using testing::_; 33 using testing::DoAll; 34 using testing::ElementsAreArray; 35 using testing::Pointee; 36 using testing::Property; 37 using testing::WithArg; 38 39 namespace { 40 41 class MockPasswordStoreConsumer : public PasswordStoreConsumer { 42 public: 43 MOCK_METHOD2(OnPasswordStoreRequestDone, 44 void(CancelableRequestProvider::Handle, 45 const std::vector<PasswordForm*>&)); 46 MOCK_METHOD1(OnGetPasswordStoreResults, 47 void(const std::vector<PasswordForm*>&)); 48 }; 49 50 // This class will add and remove a mock notification observer from 51 // the DB thread. 52 class DBThreadObserverHelper 53 : public base::RefCountedThreadSafe<DBThreadObserverHelper, 54 BrowserThread::DeleteOnDBThread> { 55 public: 56 DBThreadObserverHelper() : done_event_(true, false) {} 57 58 void Init(PasswordStore* password_store) { 59 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 60 BrowserThread::PostTask( 61 BrowserThread::DB, 62 FROM_HERE, 63 base::Bind(&DBThreadObserverHelper::AddObserverTask, 64 this, 65 make_scoped_refptr(password_store))); 66 done_event_.Wait(); 67 } 68 69 content::MockNotificationObserver& observer() { 70 return observer_; 71 } 72 73 protected: 74 friend struct BrowserThread::DeleteOnThread<BrowserThread::DB>; 75 friend class base::DeleteHelper<DBThreadObserverHelper>; 76 77 virtual ~DBThreadObserverHelper() { 78 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 79 registrar_.RemoveAll(); 80 } 81 82 void AddObserverTask(PasswordStore* password_store) { 83 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 84 registrar_.Add(&observer_, 85 chrome::NOTIFICATION_LOGINS_CHANGED, 86 content::Source<PasswordStore>(password_store)); 87 done_event_.Signal(); 88 } 89 90 WaitableEvent done_event_; 91 content::NotificationRegistrar registrar_; 92 content::MockNotificationObserver observer_; 93 }; 94 95 } // anonymous namespace 96 97 class PasswordStoreDefaultTest : public testing::Test { 98 protected: 99 PasswordStoreDefaultTest() 100 : ui_thread_(BrowserThread::UI, &message_loop_), 101 db_thread_(BrowserThread::DB) { 102 } 103 104 virtual void SetUp() { 105 ASSERT_TRUE(db_thread_.Start()); 106 107 profile_.reset(new TestingProfile()); 108 109 login_db_.reset(new LoginDatabase()); 110 ASSERT_TRUE(login_db_->Init(profile_->GetPath().Append( 111 FILE_PATH_LITERAL("login_test")))); 112 } 113 114 virtual void TearDown() { 115 base::MessageLoop::current()->PostTask(FROM_HERE, 116 base::MessageLoop::QuitClosure()); 117 base::MessageLoop::current()->Run(); 118 db_thread_.Stop(); 119 } 120 121 base::MessageLoopForUI message_loop_; 122 content::TestBrowserThread ui_thread_; 123 // PasswordStore, WDS schedule work on this thread. 124 content::TestBrowserThread db_thread_; 125 126 scoped_ptr<LoginDatabase> login_db_; 127 scoped_ptr<TestingProfile> profile_; 128 }; 129 130 ACTION(STLDeleteElements0) { 131 STLDeleteContainerPointers(arg0.begin(), arg0.end()); 132 } 133 134 ACTION(QuitUIMessageLoop) { 135 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 136 base::MessageLoop::current()->Quit(); 137 } 138 139 TEST_F(PasswordStoreDefaultTest, NonASCIIData) { 140 scoped_refptr<PasswordStoreDefault> store( 141 new PasswordStoreDefault(login_db_.release(), profile_.get())); 142 store->Init(); 143 144 // Some non-ASCII password form data. 145 static const PasswordFormData form_data[] = { 146 { PasswordForm::SCHEME_HTML, 147 "http://foo.example.com", 148 "http://foo.example.com/origin", 149 "http://foo.example.com/action", 150 L"", 151 L"?", 152 L"", 153 L" ", 154 L"", 155 true, false, 1 }, 156 }; 157 158 // Build the expected forms vector and add the forms to the store. 159 std::vector<PasswordForm*> expected_forms; 160 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(form_data); ++i) { 161 PasswordForm* form = CreatePasswordFormFromData(form_data[i]); 162 expected_forms.push_back(form); 163 store->AddLogin(*form); 164 } 165 166 // The PasswordStore schedules tasks to run on the DB thread so we schedule 167 // yet another task to notify us that it's safe to carry on with the test. 168 // The PasswordStore doesn't really understand that it's "done" once the tasks 169 // we posted above have completed, so there's no formal notification for that. 170 WaitableEvent done(false, false); 171 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 172 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 173 done.Wait(); 174 175 MockPasswordStoreConsumer consumer; 176 177 // Make sure we quit the MessageLoop even if the test fails. 178 ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) 179 .WillByDefault(QuitUIMessageLoop()); 180 181 // We expect to get the same data back, even though it's not all ASCII. 182 EXPECT_CALL(consumer, 183 OnPasswordStoreRequestDone(_, 184 ContainsAllPasswordForms(expected_forms))) 185 .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); 186 187 store->GetAutofillableLogins(&consumer); 188 base::MessageLoop::current()->Run(); 189 190 STLDeleteElements(&expected_forms); 191 } 192 193 TEST_F(PasswordStoreDefaultTest, Notifications) { 194 scoped_refptr<PasswordStore> store( 195 new PasswordStoreDefault(login_db_.release(), profile_.get())); 196 store->Init(); 197 198 PasswordFormData form_data = 199 { PasswordForm::SCHEME_HTML, 200 "http://bar.example.com", 201 "http://bar.example.com/origin", 202 "http://bar.example.com/action", 203 L"submit_element", 204 L"username_element", 205 L"password_element", 206 L"username_value", 207 L"password_value", 208 true, false, 1 }; 209 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data)); 210 211 scoped_refptr<DBThreadObserverHelper> helper = new DBThreadObserverHelper; 212 helper->Init(store.get()); 213 214 const PasswordStoreChange expected_add_changes[] = { 215 PasswordStoreChange(PasswordStoreChange::ADD, *form), 216 }; 217 218 EXPECT_CALL( 219 helper->observer(), 220 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED), 221 content::Source<PasswordStore>(store.get()), 222 Property(&content::Details<const PasswordStoreChangeList>::ptr, 223 Pointee(ElementsAreArray(expected_add_changes))))); 224 225 // Adding a login should trigger a notification. 226 store->AddLogin(*form); 227 228 // The PasswordStore schedules tasks to run on the DB thread so we schedule 229 // yet another task to notify us that it's safe to carry on with the test. 230 WaitableEvent done(false, false); 231 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 232 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 233 done.Wait(); 234 235 // Change the password. 236 form->password_value = WideToUTF16(L"a different password"); 237 238 const PasswordStoreChange expected_update_changes[] = { 239 PasswordStoreChange(PasswordStoreChange::UPDATE, *form), 240 }; 241 242 EXPECT_CALL( 243 helper->observer(), 244 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED), 245 content::Source<PasswordStore>(store.get()), 246 Property(&content::Details<const PasswordStoreChangeList>::ptr, 247 Pointee(ElementsAreArray(expected_update_changes))))); 248 249 // Updating the login with the new password should trigger a notification. 250 store->UpdateLogin(*form); 251 252 // Wait for PasswordStore to send the notification. 253 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 254 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 255 done.Wait(); 256 257 const PasswordStoreChange expected_delete_changes[] = { 258 PasswordStoreChange(PasswordStoreChange::REMOVE, *form), 259 }; 260 261 EXPECT_CALL( 262 helper->observer(), 263 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED), 264 content::Source<PasswordStore>(store.get()), 265 Property(&content::Details<const PasswordStoreChangeList>::ptr, 266 Pointee(ElementsAreArray(expected_delete_changes))))); 267 268 // Deleting the login should trigger a notification. 269 store->RemoveLogin(*form); 270 271 // Wait for PasswordStore to send the notification. 272 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 273 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 274 done.Wait(); 275 276 store->ShutdownOnUIThread(); 277 } 278