Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(389)

Side by Side Diff: ios/chrome/browser/passwords/password_controller_unittest.mm

Issue 2152593002: Upstream password manager unit tests (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix GN Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "ios/chrome/browser/passwords/password_controller.h" 5 #import "ios/chrome/browser/passwords/password_controller.h"
6 6
7 #import <Foundation/Foundation.h>
8
7 #include <memory> 9 #include <memory>
8 #include <vector> 10 #include <utility>
9 11
12 #include "base/json/json_reader.h"
13 #include "base/mac/bind_objc_block.h"
10 #include "base/mac/scoped_nsobject.h" 14 #include "base/mac/scoped_nsobject.h"
11 #include "base/memory/ptr_util.h" 15 #include "base/memory/ptr_util.h"
16 #include "base/memory/ref_counted.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "base/strings/utf_string_conversions.h"
19 #import "base/test/ios/wait_util.h"
20 #include "base/values.h"
21 #include "components/autofill/core/common/password_form_fill_data.h"
12 #include "components/password_manager/core/browser/log_manager.h" 22 #include "components/password_manager/core/browser/log_manager.h"
13 #include "components/password_manager/core/browser/password_form_manager.h" 23 #include "components/password_manager/core/browser/mock_password_store.h"
14 #include "components/password_manager/core/browser/stub_password_manager_client. h" 24 #include "components/password_manager/core/browser/stub_password_manager_client. h"
15 #include "components/password_manager/core/common/password_manager_pref_names.h" 25 #include "components/password_manager/core/common/password_manager_pref_names.h"
16 #include "components/syncable_prefs/testing_pref_service_syncable.h" 26 #include "components/prefs/pref_registry_simple.h"
27 #include "components/prefs/testing_pref_service.h"
28 #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
29 #import "ios/chrome/browser/autofill/form_suggestion_controller.h"
17 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h" 30 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
31 #import "ios/chrome/browser/passwords/js_password_manager.h"
32 #import "ios/web/public/web_state/web_state.h"
33 #import "ios/web/public/test/web_test_with_web_state.h"
18 #import "ios/web/public/test/test_web_state.h" 34 #import "ios/web/public/test/test_web_state.h"
19 #include "testing/gmock/include/gmock/gmock.h" 35 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h" 36 #include "testing/gtest/include/gtest/gtest.h"
37 #include "testing/gtest_mac.h"
38 #import "third_party/ocmock/OCMock/OCMock.h"
39 #import "third_party/ocmock/OCMock/OCPartialMockObject.h"
21 #include "url/gurl.h" 40 #include "url/gurl.h"
22 41
23 using testing::_; 42 using autofill::PasswordForm;
43 using autofill::PasswordFormFillData;
24 using testing::Return; 44 using testing::Return;
25 45
46 namespace {
47
26 class MockWebState : public web::TestWebState { 48 class MockWebState : public web::TestWebState {
27 public: 49 public:
28 MOCK_CONST_METHOD0(GetBrowserState, web::BrowserState*(void)); 50 MOCK_CONST_METHOD0(GetBrowserState, web::BrowserState*(void));
29 }; 51 };
30 52
31 class MockPasswordManagerClient 53 class MockPasswordManagerClient
32 : public password_manager::StubPasswordManagerClient { 54 : public password_manager::StubPasswordManagerClient {
33 public: 55 public:
34 // |form_manager| stays owned by the mock. 56 explicit MockPasswordManagerClient(password_manager::PasswordStore* store)
35 MOCK_METHOD3(PromptUserToSaveOrUpdatePasswordPtr, 57 : store_(store) {}
36 void(password_manager::PasswordFormManager* form_manager, 58
37 password_manager::CredentialSourceType type, 59 ~MockPasswordManagerClient() override = default;
38 bool update_password)); 60
39 MOCK_CONST_METHOD0(GetLogManager, password_manager::LogManager*(void)); 61 MOCK_CONST_METHOD0(GetLogManager, password_manager::LogManager*(void));
40 62
41 // Workaround for std::unique_ptr<> lacking a copy constructor. 63 PrefService* GetPrefs() override { return &prefs_; }
42 bool PromptUserToSaveOrUpdatePassword( 64
43 std::unique_ptr<password_manager::PasswordFormManager> manager, 65 password_manager::PasswordStore* GetPasswordStore() const override {
44 password_manager::CredentialSourceType type, 66 return store_;
45 bool update_password) override {
46 PromptUserToSaveOrUpdatePasswordPtr(manager.get(), type, update_password);
47 return false;
48 } 67 }
68
69 private:
70 TestingPrefServiceSimple prefs_;
71 password_manager::PasswordStore* const store_;
49 }; 72 };
50 73
51 class MockLogManager : public password_manager::LogManager { 74 class MockLogManager : public password_manager::LogManager {
52 public: 75 public:
53 MOCK_CONST_METHOD1(LogSavePasswordProgress, void(const std::string& text)); 76 MOCK_CONST_METHOD1(LogSavePasswordProgress, void(const std::string& text));
54 MOCK_CONST_METHOD0(IsLoggingActive, bool(void)); 77 MOCK_CONST_METHOD0(IsLoggingActive, bool(void));
55 78
56 // Methods not important for testing. 79 // Methods not important for testing.
57 void OnLogRouterAvailabilityChanged(bool router_can_be_used) override {} 80 void OnLogRouterAvailabilityChanged(bool router_can_be_used) override {}
58 void SetSuspended(bool suspended) override {} 81 void SetSuspended(bool suspended) override {}
59 }; 82 };
60 83
61 TEST(PasswordControllerTest, SaveOnNonHTMLLandingPage) { 84 // Creates PasswordController with the given |web_state| and a mock client
62 // Create the PasswordController with a MockPasswordManagerClient. 85 // using the given |store|. If not null, |weak_client| is filled with a
86 // non-owning pointer to the created client. The created controller is
87 // returned.
88 base::scoped_nsobject<PasswordController> CreatePasswordController(
89 web::WebState* web_state,
90 password_manager::PasswordStore* store,
91 MockPasswordManagerClient** weak_client) {
92 auto client = base::WrapUnique(new MockPasswordManagerClient(store));
93 if (weak_client)
94 *weak_client = client.get();
95 return base::scoped_nsobject<PasswordController>([[PasswordController alloc]
96 initWithWebState:web_state
97 passwordsUiDelegate:nil
98 client:std::move(client)]);
99 }
100
101 } // namespace
102
103 @interface PasswordController (
104 Testing)<CRWWebStateObserver, FormSuggestionProvider>
105
106 - (void)findPasswordFormsWithCompletionHandler:
107 (void (^)(const std::vector<PasswordForm>&))completionHandler;
108
109 - (void)extractSubmittedPasswordForm:(const std::string&)formName
110 completionHandler:
111 (void (^)(BOOL found,
112 const PasswordForm& form))completionHandler;
113
114 - (void)fillPasswordForm:(const PasswordFormFillData&)formData
115 completionHandler:(void (^)(BOOL))completionHandler;
116
117 - (BOOL)getPasswordForm:(PasswordForm*)form
118 fromDictionary:(const base::DictionaryValue*)dictionary
119 pageURL:(const GURL&)pageLocation;
120
121 // Provides access to JavaScript Manager for testing with mocks.
122 @property(readonly) JsPasswordManager* passwordJsManager;
123
124 @end
125
126 // Real FormSuggestionController is wrapped to register the addition of
127 // suggestions.
128 @interface PasswordsTestSuggestionController : FormSuggestionController
129
130 @property(nonatomic, copy) NSArray* suggestions;
131
132 - (void)dealloc;
133
134 @end
135
136 @implementation PasswordsTestSuggestionController
137
138 @synthesize suggestions = _suggestions;
139
140 - (void)updateKeyboardWithSuggestions:(NSArray*)suggestions {
141 self.suggestions = suggestions;
142 }
143
144 - (void)dealloc {
145 [_suggestions release];
146 [super dealloc];
147 }
148
149 @end
150
151 class PasswordControllerTest : public web::WebTestWithWebState {
152 public:
153 PasswordControllerTest()
154 : store_(new testing::NiceMock<password_manager::MockPasswordStore>()) {}
155
156 ~PasswordControllerTest() override { store_->ShutdownOnUIThread(); }
157
158 void SetUp() override {
159 web::WebTestWithWebState::SetUp();
160 passwordController_ =
161 CreatePasswordController(web_state(), store_.get(), nullptr);
162 @autoreleasepool {
163 // Make sure the temporary array is released after SetUp finishes,
164 // otherwise [passwordController_ suggestionProvider] will be retained
165 // until PlatformTest teardown, at which point all Chrome objects are
166 // already gone and teardown may access invalid memory.
167 suggestionController_.reset([[PasswordsTestSuggestionController alloc]
168 initWithWebState:web_state()
169 providers:@[ [passwordController_ suggestionProvider] ]]);
170 accessoryViewController_.reset([[FormInputAccessoryViewController alloc]
171 initWithWebState:web_state()
172 providers:@[ [suggestionController_ accessoryViewProvider] ]]);
173 }
174 }
175
176 protected:
177 // Helper method for PasswordControllerTest.DontFillReadonly. Tries to load
178 // |html| and find and fill there a form with hard-coded form data. Returns
179 // YES on success, NO otherwise.
180 BOOL BasicFormFill(NSString* html);
181
182 // Retrieve the current suggestions from suggestionController_ sorted in
183 // alphabetical order according to their value properties.
184 NSArray* GetSortedSuggestionValues() {
185 NSMutableArray* suggestion_values = [NSMutableArray array];
186 for (FormSuggestion* suggestion in [suggestionController_ suggestions])
187 [suggestion_values addObject:suggestion.value];
188 return [suggestion_values
189 sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
190 }
191
192 // Returns an identifier for the |form_number|th form in the page.
193 std::string FormName(int form_number) {
194 NSString* kFormNamingScript =
195 @"__gCrWeb.common.getFormIdentifier("
196 " document.querySelectorAll('form')[%d]);";
197 return base::SysNSStringToUTF8(EvaluateJavaScriptAsString(
198 [NSString stringWithFormat:kFormNamingScript, form_number]));
199 }
200
201 // Sets up a partial mock that intercepts calls to the selector
202 // -fillPasswordForm:withUsername:password:completionHandler: to the
203 // PasswordController's JavaScript manager. For the first
204 // |target_failure_count| calls, skips the invocation of the real JavaScript
205 // manager, giving the effect that password form fill failed. As soon as
206 // |failure_count| reaches |target_failure_count|, stop the partial mock
207 // and let the original JavaScript manager execute.
208 void SetFillPasswordFormFailureCount(int target_failure_count) {
209 id original_manager = [passwordController_ passwordJsManager];
210 OCPartialMockObject* failing_manager =
211 [OCMockObject partialMockForObject:original_manager];
212 __block int failure_count = 0;
213 void (^fail_invocation)(NSInvocation*) = ^(NSInvocation* invocation) {
214 if (failure_count >= target_failure_count) {
215 [failing_manager stop];
216 [invocation invokeWithTarget:original_manager];
217 } else {
218 ++failure_count;
219 // Fetches the completion handler from |invocation| and calls it with
220 // failure status.
221 void (^completionHandler)(BOOL);
222 const NSInteger kArgOffset = 1;
223 const NSInteger kCompletionHandlerArgIndex = 4;
224 [invocation getArgument:&completionHandler
225 atIndex:(kCompletionHandlerArgIndex + kArgOffset)];
226 ASSERT_TRUE(completionHandler);
227 completionHandler(NO);
228 }
229 };
230 [[[failing_manager stub] andDo:fail_invocation]
231 fillPasswordForm:[OCMArg any]
232 withUsername:[OCMArg any]
233 password:[OCMArg any]
234 completionHandler:[OCMArg any]];
235 }
236
237 // SuggestionController for testing.
238 base::scoped_nsobject<PasswordsTestSuggestionController>
239 suggestionController_;
240
241 // FormInputAccessoryViewController for testing.
242 base::scoped_nsobject<FormInputAccessoryViewController>
243 accessoryViewController_;
244
245 // PasswordController for testing.
246 base::scoped_nsobject<PasswordController> passwordController_;
247
248 scoped_refptr<password_manager::PasswordStore> store_;
249 };
250
251 struct PasswordFormTestData {
252 const char* const page_location;
253 const char* const json_string;
254 const char* const expected_origin;
255 const char* const expected_action;
256 const char* const expected_username_element;
257 const char* const expected_username_value;
258 const char* const expected_new_password_element;
259 const char* const expected_new_password_value;
260 const char* const expected_old_password_element;
261 const char* const expected_old_password_value;
262 };
263
264 TEST_F(PasswordControllerTest, PopulatePasswordFormWithDictionary) {
Eugene But (OOO till 7-30) 2016/07/13 22:30:31 If you understand the purpose if these tests, coul
vabr (Chromium) 2016/07/14 07:36:31 Done.
265 // clang-format off
266 PasswordFormTestData test_data[] = {
267 // One username element, one password element. URLs contain extra
268 // parts: username/password, query, reference, which are all expected
269 // to be stripped off. The password is recognized as an old password.
270 {
271 "http://john:doe@fakedomain.com/foo/bar?baz=quz#foobar",
272 "{ \"action\": \"some/action?to=be&or=not#tobe\","
273 "\"usernameElement\": \"account\","
274 "\"usernameValue\": \"fakeaccount\","
275 "\"name\": \"signup\","
276 "\"origin\": \"http://john:doe@fakedomain.com/foo/bar\","
277 "\"passwords\": ["
278 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
279 "]}",
280 "http://fakedomain.com/foo/bar",
281 "http://fakedomain.com/foo/some/action",
282 "account",
283 "fakeaccount",
284 "",
285 "",
286 "secret",
287 "fakesecret",
288 },
289 // One username element, one password element. Population should fail
290 // due to an origin mismatch.
291 {
292 "http://john:doe@fakedomain.com/foo/bar?baz=quz#foobar",
293 "{ \"action\": \"some/action?to=be&or=not#tobe\","
294 "\"usernameElement\": \"account\","
295 "\"usernameValue\": \"fakeaccount\","
296 "\"name\": \"signup\","
297 "\"origin\": \"http://john:doe@realdomainipromise.com/foo/bar\","
298 "\"passwords\": ["
299 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
300 "]}",
301 "",
302 "",
303 "",
304 "",
305 "",
306 "",
307 "",
308 "",
309 },
310 // One username element, two password elements. Since both password
311 // values are the same, we are assuming that the webpage asked the user
312 // to enter the password twice for confirmation.
313 {
314 "http://fakedomain.com/foo",
315 "{ \"action\": \"http://anotherdomain.com/some_action\","
316 "\"usernameElement\": \"account\","
317 "\"usernameValue\": \"fakeaccount\","
318 "\"name\": \"signup\","
319 "\"origin\": \"http://fakedomain.com/foo\","
320 "\"passwords\": ["
321 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
322 "{ \"element\": \"confirm\"," "\"value\": \"fakesecret\" },"
323 "]}",
324 "http://fakedomain.com/foo",
325 "http://anotherdomain.com/some_action",
326 "account",
327 "fakeaccount",
328 "secret",
329 "fakesecret",
330 "",
331 "",
332 },
333 // One username element, two password elements. The password
334 // values are different, so we are assuming that the webpage asked the user
335 // to enter the old password and new password.
336 {
337 "http://fakedomain.com/foo",
338 "{ \"action\": \"\","
339 "\"usernameElement\": \"account\","
340 "\"usernameValue\": \"fakeaccount\","
341 "\"name\": \"signup\","
342 "\"origin\": \"http://fakedomain.com/foo\","
343 "\"passwords\": ["
344 "{ \"element\": \"old\"," "\"value\": \"oldsecret\" },"
345 "{ \"element\": \"new\"," "\"value\": \"newsecret\" },"
346 "]}",
347 "http://fakedomain.com/foo",
348 "http://fakedomain.com/foo",
349 "account",
350 "fakeaccount",
351 "new",
352 "newsecret",
353 "old",
354 "oldsecret",
355 },
356 // One username element, three password elements. All passwords
357 // are the same. Password population should fail because this configuration
358 // does not make sense.
359 {
360 "http://fakedomain.com",
361 "{ \"action\": \"\","
362 "\"usernameElement\": \"account\","
363 "\"usernameValue\": \"fakeaccount\","
364 "\"name\": \"signup\","
365 "\"origin\": \"http://fakedomain.com/foo\","
366 "\"passwords\": ["
367 "{ \"element\": \"pass1\"," "\"value\": \"word\" },"
368 "{ \"element\": \"pass2\"," "\"value\": \"word\" },"
369 "{ \"element\": \"pass3\"," "\"value\": \"word\" },"
370 "]}",
371 "http://fakedomain.com/",
372 "http://fakedomain.com/",
373 "account",
374 "fakeaccount",
375 "",
376 "",
377 "",
378 "",
379 },
380 // One username element, three password elements. Two passwords are
381 // the same followed by a different one. Assuming that the duplicated
382 // password is the old one.
383 {
384 "http://fakedomain.com",
385 "{ \"action\": \"\","
386 "\"usernameElement\": \"account\","
387 "\"usernameValue\": \"fakeaccount\","
388 "\"name\": \"signup\","
389 "\"origin\": \"http://fakedomain.com/foo\","
390 "\"passwords\": ["
391 "{ \"element\": \"pass1\"," "\"value\": \"word1\" },"
392 "{ \"element\": \"pass2\"," "\"value\": \"word1\" },"
393 "{ \"element\": \"pass3\"," "\"value\": \"word3\" },"
394 "]}",
395 "http://fakedomain.com/",
396 "http://fakedomain.com/",
397 "account",
398 "fakeaccount",
399 "pass3",
400 "word3",
401 "pass1",
402 "word1",
403 },
404 // One username element, three password elements. A password is
405 // follwed by two duplicate ones. Assuming that the duplicated
406 // password is the new one.
407 {
408 "http://fakedomain.com",
409 "{ \"action\": \"\","
410 "\"usernameElement\": \"account\","
411 "\"usernameValue\": \"fakeaccount\","
412 "\"name\": \"signup\","
413 "\"origin\": \"http://fakedomain.com/foo\","
414 "\"passwords\": ["
415 "{ \"element\": \"pass1\"," "\"value\": \"word1\" },"
416 "{ \"element\": \"pass2\"," "\"value\": \"word2\" },"
417 "{ \"element\": \"pass3\"," "\"value\": \"word2\" },"
418 "]}",
419 "http://fakedomain.com/",
420 "http://fakedomain.com/",
421 "account",
422 "fakeaccount",
423 "pass2",
424 "word2",
425 "pass1",
426 "word1",
427 },
428 };
429 // clang-format on
430
431 for (const PasswordFormTestData& data : test_data) {
432 SCOPED_TRACE(testing::Message()
433 << "for page_location=" << data.page_location
434 << " and json_string=" << data.json_string);
435 std::unique_ptr<base::Value> json_data(
436 base::JSONReader::Read(data.json_string, true));
437 const base::DictionaryValue* json_dict = nullptr;
438 ASSERT_TRUE(json_data->GetAsDictionary(&json_dict));
439 PasswordForm form;
440 [passwordController_ getPasswordForm:&form
441 fromDictionary:json_dict
442 pageURL:GURL(data.page_location)];
443 EXPECT_STREQ(data.expected_origin, form.origin.spec().c_str());
444 EXPECT_STREQ(data.expected_action, form.action.spec().c_str());
445 EXPECT_EQ(base::ASCIIToUTF16(data.expected_username_element),
446 form.username_element);
447 EXPECT_EQ(base::ASCIIToUTF16(data.expected_username_value),
448 form.username_value);
449 EXPECT_EQ(base::ASCIIToUTF16(data.expected_new_password_element),
450 form.new_password_element);
451 EXPECT_EQ(base::ASCIIToUTF16(data.expected_new_password_value),
452 form.new_password_value);
453 EXPECT_EQ(base::ASCIIToUTF16(data.expected_old_password_element),
454 form.password_element);
455 EXPECT_EQ(base::ASCIIToUTF16(data.expected_old_password_value),
456 form.password_value);
457 }
458 };
459
460 struct FindPasswordFormTestData {
461 NSString* html_string;
462 const bool expected_form_found;
463 const char* const expected_username_element;
464 const char* const expected_password_element;
465 };
466
467 // TODO(crbug.com/403705) This test is flaky.
468 TEST_F(PasswordControllerTest, FLAKY_FindPasswordFormsInView) {
469 // clang-format off
470 FindPasswordFormTestData test_data[] = {
471 // Normal form: a username and a password element.
472 {
473 @"<form>"
474 "<input type='text' name='user0'>"
475 "<input type='password' name='pass0'>"
476 "</form>",
477 true, "user0", "pass0"
478 },
479 // User name is captured as an email address (HTML5).
480 {
481 @"<form>"
482 "<input type='email' name='email1'>"
483 "<input type='password' name='pass1'>"
484 "</form>",
485 true, "email1", "pass1"
486 },
487 // No username element.
488 {
489 @"<form>"
490 "<input type='password' name='user2'>"
491 "<input type='password' name='pass2'>"
492 "</form>",
493 true, "", "user2"
494 },
495 // No username element before password.
496 {
497 @"<form>"
498 "<input type='password' name='pass3'>"
499 "<input type='text' name='user3'>"
500 "</form>",
501 true, "", "pass3"
502 },
503 // Disabled username element.
504 {
505 @"<form>"
506 "<input type='text' name='user4' disabled='disabled'>"
507 "<input type='password' name='pass4'>"
508 "</form>",
509 true, "", "pass4"
510 },
511 // Username element has autocomplete='off'.
512 {
513 @"<form>"
514 "<input type='text' name='user5' AUTOCOMPLETE='off'>"
515 "<input type='password' name='pass5'>"
516 "</form>",
517 true, "user5", "pass5"
518 },
519 // No password element.
520 {
521 @"<form>"
522 "<input type='text' name='user6'>"
523 "<input type='text' name='pass6'>"
524 "</form>",
525 false, nullptr, nullptr
526 },
527 // Disabled password element.
528 {
529 @"<form>"
530 "<input type='text' name='user7'>"
531 "<input type='password' name='pass7' disabled='disabled'>"
532 "</form>",
533 false, nullptr, nullptr
534 },
535 // Password element has autocomplete='off'.
536 {
537 @"<form>"
538 "<input type='text' name='user8'>"
539 "<input type='password' name='pass8' AUTOCOMPLETE='OFF'>"
540 "</form>",
541 true, "user8", "pass8"
542 },
543 // Form element has autocomplete='off'.
544 {
545 @"<form autocomplete='off'>"
546 "<input type='text' name='user9'>"
547 "<input type='password' name='pass9'>"
548 "</form>",
549 true, "user9", "pass9"
550 },
551 };
552 // clang-format on
553
554 for (const FindPasswordFormTestData& data : test_data) {
555 SCOPED_TRACE(testing::Message() << "for html_string=" << data.html_string);
556 LoadHtml(data.html_string);
557 __block std::vector<PasswordForm> forms;
558 __block BOOL block_was_called = NO;
559 [passwordController_ findPasswordFormsWithCompletionHandler:^(
560 const std::vector<PasswordForm>& result) {
561 block_was_called = YES;
562 forms = result;
563 }];
564 base::test::ios::WaitUntilCondition(^bool() {
565 return block_was_called;
566 });
567 if (data.expected_form_found) {
568 ASSERT_EQ(1U, forms.size());
569 EXPECT_EQ(base::ASCIIToUTF16(data.expected_username_element),
570 forms[0].username_element);
571 EXPECT_EQ(base::ASCIIToUTF16(data.expected_password_element),
572 forms[0].password_element);
573 } else {
574 ASSERT_TRUE(forms.empty());
575 }
576 }
577 }
578
579 struct GetSubmittedPasswordFormTestData {
580 NSString* html_string;
581 NSString* java_script;
582 const int number_of_forms_to_submit;
583 const bool expected_form_found;
584 const char* expected_username_element;
585 };
586
587 // TODO(crbug.com/403705) This test is flaky.
588 TEST_F(PasswordControllerTest, FLAKY_GetSubmittedPasswordForm) {
589 // clang-format off
590 GetSubmittedPasswordFormTestData test_data[] = {
591 // Two forms with no explicit names.
592 {
593 @"<form action='javascript:;'>"
594 "<input type='text' name='user1'>"
595 "<input type='password' name='pass1'>"
596 "</form>"
597 "<form action='javascript:;'>"
598 "<input type='text' name='user2'>"
599 "<input type='password' name='pass2'>"
600 "<input type='submit' id='s2'>"
601 "</form>",
602 @"document.getElementById('s2').click()",
603 1, true, "user2"
604 },
605 // Two forms with explicit names.
606 {
607 @"<form name='test2a' action='javascript:;'>"
608 "<input type='text' name='user1'>"
609 "<input type='password' name='pass1'>"
610 "<input type='submit' id='s1'>"
611 "</form>"
612 "<form name='test2b' action='javascript:;'>"
613 "<input type='text' name='user2'>"
614 "<input type='password' name='pass2'>"
615 "</form>",
616 @"document.getElementById('s1').click()",
617 0, true, "user1"
618 },
619 // No password forms.
620 {
621 @"<form action='javascript:;'>"
622 "<input type='text' name='user1'>"
623 "<input type='text' name='pass1'>"
624 "<input type='submit' id='s1'>"
625 "</form>",
626 @"document.getElementById('s1').click()",
627 0, false, nullptr
628 },
629 // Form with quotes in the form and field names.
630 {
631 @"<form name=\"foo'\" action='javascript:;'>"
632 "<input type='text' name=\"user1'\">"
633 "<input type='password' id='s1' name=\"pass1'\">"
634 "</form>",
635 @"document.getElementById('s1').click()",
636 0, true, "user1'"
637 },
638 };
639 // clang-format on
640
641 for (const GetSubmittedPasswordFormTestData& data : test_data) {
642 SCOPED_TRACE(testing::Message() << "for html_string=" << data.html_string
643 << " and java_script=" << data.java_script
644 << " and number_of_forms_to_submit="
645 << data.number_of_forms_to_submit);
646 LoadHtml(data.html_string);
647 EvaluateJavaScriptAsString(data.java_script);
648 __block BOOL block_was_called = NO;
649 id completion_handler = ^(BOOL found, const PasswordForm& form) {
650 block_was_called = YES;
651 ASSERT_EQ(data.expected_form_found, found);
652 if (data.expected_form_found) {
653 EXPECT_EQ(base::ASCIIToUTF16(data.expected_username_element),
654 form.username_element);
655 }
656 };
657 [passwordController_
658 extractSubmittedPasswordForm:FormName(data.number_of_forms_to_submit)
659 completionHandler:completion_handler];
660 base::test::ios::WaitUntilCondition(^bool() {
661 return block_was_called;
662 });
663 }
664 }
665
666 // Populates |form_data| with test values.
667 void SetPasswordFormFillData(PasswordFormFillData& form_data,
668 const std::string& origin,
669 const std::string& action,
670 const char* username_field,
671 const char* username_value,
672 const char* password_field,
673 const char* password_value,
674 const char* additional_username,
675 const char* additional_password,
676 bool wait_for_username) {
677 form_data.origin = GURL(origin);
678 form_data.action = GURL(action);
679 autofill::FormFieldData username;
680 username.name = base::UTF8ToUTF16(username_field);
681 username.value = base::UTF8ToUTF16(username_value);
682 form_data.username_field = username;
683 autofill::FormFieldData password;
684 password.name = base::UTF8ToUTF16(password_field);
685 password.value = base::UTF8ToUTF16(password_value);
686 form_data.password_field = password;
687 if (additional_username) {
688 autofill::PasswordAndRealm additional_password_data;
689 additional_password_data.password = base::UTF8ToUTF16(additional_password);
690 additional_password_data.realm.clear();
691 form_data.additional_logins.insert(
692 std::pair<base::string16, autofill::PasswordAndRealm>(
693 base::UTF8ToUTF16(additional_username), additional_password_data));
694 }
695 form_data.wait_for_username = wait_for_username;
696 }
697
698 // Test HTML page. It contains several password forms. Tests autofill
699 // them and verify that the right ones are autofilled.
700 static NSString* kHtmlWithMultiplePasswordForms =
701 @"<form>"
702 "<input id='un0' type='text' name='u0'>"
703 "<input id='pw0' type='password' name='p0'>"
704 "</form>"
705 "<form action='action?query=yes#reference'>"
706 "<input id='un1' type='text' name='u1'>"
707 "<input id='pw1' type='password' name='p1'>"
708 "</form>"
709 "<form action='http://some_other_action'>"
710 "<input id='un2' type='text' name='u2'>"
711 "<input id='pw2' type='password' name='p2'>"
712 "</form>"
713 "<form>"
714 "<input id='un3' type='text' name='u3'>"
715 "<input id='pw3' type='password' name='p3'>"
716 "<input id='pw3' type='password' name='p3'>"
717 "</form>"
718 "<form>"
719 "<input id='un4' type='text' name='u4'>"
720 "<input id='pw4' type='password' name='p4'>"
721 "</form>"
722 "<form>"
723 "<input id='un5' type='text' name='u4'>"
724 "<input id='pw5' type='password' name='p4'>"
725 "</form>"
726 "<form name=\"f6'\">"
727 "<input id=\"un6'\" type='text' name=\"u6'\">"
728 "<input id=\"pw6'\" type='password' name=\"p6'\">"
729 "</form>";
730
731 // A script that resets all text fields.
732 static NSString* kClearInputFieldsScript =
733 @"var inputs = document.getElementsByTagName('input');"
734 "for(var i = 0; i < inputs.length; i++){"
735 " inputs[i].value = '';"
736 "}";
737
738 // A script that we run after autofilling forms. It returns
739 // ids and values of all non-empty fields.
740 static NSString* kInputFieldValueVerificationScript =
741 @"var result='';"
742 "var inputs = document.getElementsByTagName('input');"
743 "for(var i = 0; i < inputs.length; i++){"
744 " var input = inputs[i];"
745 " if (input.value) {"
746 " result += input.id + '=' + input.value +';';"
747 " }"
748 "}; result";
749
750 struct FillPasswordFormTestData {
751 const std::string origin;
752 const std::string action;
753 const char* username_field;
754 const char* username_value;
755 const char* password_field;
756 const char* password_value;
757 const BOOL should_succeed;
758 NSString* expected_result;
759 };
760
761 TEST_F(PasswordControllerTest, FillPasswordForm) {
762 LoadHtml(kHtmlWithMultiplePasswordForms);
763
764 EXPECT_NSEQ(@"true",
765 EvaluateJavaScriptAsString(@"__gCrWeb.hasPasswordField()"));
766
767 const std::string base_url = BaseUrl();
768 // clang-format off
769 FillPasswordFormTestData test_data[] = {
770 // Basic test: one-to-one match on the first password form.
771 {
772 base_url,
773 base_url,
774 "u0",
775 "test_user",
776 "p0",
777 "test_password",
778 YES,
779 @"un0=test_user;pw0=test_password;"
780 },
781 // Multiple forms match: they should all be autofilled.
782 {
783 base_url,
784 base_url,
785 "u4",
786 "test_user",
787 "p4",
788 "test_password",
789 YES,
790 @"un4=test_user;pw4=test_password;un5=test_user;pw5=test_password;"
791 },
792 // The form matches despite a different action: the only difference
793 // is a query and reference.
794 {
795 base_url,
796 base_url,
797 "u1",
798 "test_user",
799 "p1",
800 "test_password",
801 YES,
802 @"un1=test_user;pw1=test_password;"
803 },
804 // No match because of a different origin.
805 {
806 "http://someotherfakedomain.com",
807 base_url,
808 "u0",
809 "test_user",
810 "p0",
811 "test_password",
812 NO,
813 @""
814 },
815 // No match because of a different action.
816 {
817 base_url,
818 "http://someotherfakedomain.com",
819 "u0",
820 "test_user",
821 "p0",
822 "test_password",
823 NO,
824 @""
825 },
826 // No match because some inputs are not in the form.
827 {
828 base_url,
829 base_url,
830 "u0",
831 "test_user",
832 "p1",
833 "test_password",
834 NO,
835 @""
836 },
837 // No match because there are duplicate inputs in the form.
838 {
839 base_url,
840 base_url,
841 "u3",
842 "test_user",
843 "p3",
844 "test_password",
845 NO,
846 @""
847 },
848 // Basic test, but with quotes in the names and IDs.
849 {
850 base_url,
851 base_url,
852 "u6'",
853 "test_user",
854 "p6'",
855 "test_password",
856 YES,
857 @"un6'=test_user;pw6'=test_password;"
858 },
859 };
860 // clang-format on
861
862 for (const FillPasswordFormTestData& data : test_data) {
863 EvaluateJavaScriptAsString(kClearInputFieldsScript);
864
865 PasswordFormFillData form_data;
866 SetPasswordFormFillData(form_data, data.origin, data.action,
867 data.username_field, data.username_value,
868 data.password_field, data.password_value, nullptr,
869 nullptr, false);
870
871 __block BOOL block_was_called = NO;
872 [passwordController_ fillPasswordForm:form_data
873 completionHandler:^(BOOL success) {
874 block_was_called = YES;
875 EXPECT_EQ(data.should_succeed, success);
876 }];
877 base::test::ios::WaitUntilCondition(^bool() {
878 return block_was_called;
879 });
880
881 NSString* result =
882 EvaluateJavaScriptAsString(kInputFieldValueVerificationScript);
883 EXPECT_NSEQ(data.expected_result, result);
884 }
885 }
886
887 // Tests that a form is found and the found form is filled in with the given
888 // username and password.
889 TEST_F(PasswordControllerTest, FindAndFillOnePasswordForm) {
890 LoadHtml(@"<form><input id='un' type='text' name='u'>"
891 "<input id='pw' type='password' name='p'></form>");
892 __block int call_counter = 0;
893 __block int success_counter = 0;
894 [passwordController_ findAndFillPasswordForms:@"john.doe@gmail.com"
895 password:@"super!secret"
896 completionHandler:^(BOOL complete) {
897 ++call_counter;
898 if (complete)
899 ++success_counter;
900 }];
901 base::test::ios::WaitUntilCondition(^{
902 return call_counter == 1;
903 });
904 EXPECT_EQ(1, success_counter);
905 NSString* result =
906 EvaluateJavaScriptAsString(kInputFieldValueVerificationScript);
907 EXPECT_NSEQ(@"un=john.doe@gmail.com;pw=super!secret;", result);
908 }
909
910 // Tests that multiple forms on the same page are found and filled.
911 // This test includes an mock injected failure on form filling to verify
912 // that completion handler is called with the proper values.
913 TEST_F(PasswordControllerTest, FindAndFillMultiplePasswordForms) {
914 // Fails the first call to fill password form.
915 SetFillPasswordFormFailureCount(1);
916 LoadHtml(@"<form><input id='u1' type='text' name='un1'>"
917 "<input id='p1' type='password' name='pw1'></form>"
918 "<form><input id='u2' type='text' name='un2'>"
919 "<input id='p2' type='password' name='pw2'></form>"
920 "<form><input id='u3' type='text' name='un3'>"
921 "<input id='p3' type='password' name='pw3'></form>");
922 __block int call_counter = 0;
923 __block int success_counter = 0;
924 [passwordController_ findAndFillPasswordForms:@"john.doe@gmail.com"
925 password:@"super!secret"
926 completionHandler:^(BOOL complete) {
927 ++call_counter;
928 if (complete)
929 ++success_counter;
930 LOG(INFO) << "HANDLER call " << call_counter
931 << " success " << success_counter;
932 }];
933 // There should be 3 password forms and only 2 successfully filled forms.
934 base::test::ios::WaitUntilCondition(^{
935 return call_counter == 3;
936 });
937 EXPECT_EQ(2, success_counter);
938 NSString* result =
939 EvaluateJavaScriptAsString(kInputFieldValueVerificationScript);
940 EXPECT_NSEQ(@"u2=john.doe@gmail.com;p2=super!secret;"
941 "u3=john.doe@gmail.com;p3=super!secret;",
942 result);
943 }
944
945 BOOL PasswordControllerTest::BasicFormFill(NSString* html) {
946 LoadHtml(html);
947 EXPECT_NSEQ(@"true",
948 EvaluateJavaScriptAsString(@"__gCrWeb.hasPasswordField()"));
949 const std::string base_url = BaseUrl();
950 PasswordFormFillData form_data;
951 SetPasswordFormFillData(form_data, base_url, base_url, "u0", "test_user",
952 "p0", "test_password", nullptr, nullptr, false);
953 __block BOOL block_was_called = NO;
954 __block BOOL return_value = NO;
955 [passwordController_ fillPasswordForm:form_data
956 completionHandler:^(BOOL success) {
957 block_was_called = YES;
958 return_value = success;
959 }];
960 base::test::ios::WaitUntilCondition(^bool() {
961 return block_was_called;
962 });
963 return return_value;
964 }
965
966 // Check that |fillPasswordForm| is not filled if 'readonly' attribute is set
967 // on either username or password fields.
968 // TODO(crbug.com/503050): Test is flaky.
969 TEST_F(PasswordControllerTest, FLAKY_DontFillReadOnly) {
970 // Control check that the fill operation will succceed with well-formed form.
971 EXPECT_TRUE(BasicFormFill(@"<form>"
972 "<input id='un0' type='text' name='u0'>"
973 "<input id='pw0' type='password' name='p0'>"
974 "</form>"));
975 // Form fill should fail with 'readonly' attribute on username.
976 EXPECT_FALSE(BasicFormFill(
977 @"<form>"
978 "<input id='un0' type='text' name='u0' readonly='readonly'>"
979 "<input id='pw0' type='password' name='p0'>"
980 "</form>"));
981 // Form fill should fail with 'readonly' attribute on password.
982 EXPECT_FALSE(BasicFormFill(
983 @"<form>"
984 "<input id='un0' type='text' name='u0'>"
985 "<input id='pw0' type='password' name='p0' readonly='readonly'>"
986 "</form>"));
987 }
988
989 // An HTML page containing one password form. The username input field
990 // also has custom event handlers. We need to verify that those event
991 // handlers are still triggered even though we override them with our own.
992 static NSString* kHtmlWithPasswordForm =
993 @"<form>"
994 "<input id='un' type='text' name=\"u'\""
995 " onkeyup='window.onKeyUpCalled_=true'"
996 " onchange='window.onChangeCalled_=true'>"
997 "<input id='pw' type='password' name=\"p'\">"
998 "</form>";
999
1000 // A script that resets indicators used to verify that custom event
1001 // handlers are triggered. It also finds and the username and
1002 // password fields and caches them for future verification.
1003 static NSString* kUsernameAndPasswordTestPreparationScript =
1004 @"onKeyUpCalled_ = false;"
1005 "onChangeCalled_ = false;"
1006 "username_ = document.getElementById('un');"
1007 "username_.__gCrWebAutofilled = 'false';"
1008 "password_ = document.getElementById('pw');"
1009 "password_.__gCrWebAutofilled = 'false';";
1010
1011 // A script that we run after autofilling forms. It returns
1012 // all values for verification as a single concatenated string.
1013 static NSString* kUsernamePasswordVerificationScript =
1014 @"var value = username_.value;"
1015 "var from = username_.selectionStart;"
1016 "var to = username_.selectionEnd;"
1017 "value.substr(0, from) + '[' + value.substr(from, to) + ']'"
1018 " + value.substr(to, value.length) + '=' + password_.value"
1019 " + ', onkeyup=' + onKeyUpCalled_"
1020 " + ', onchange=' + onChangeCalled_;";
1021
1022 struct SuggestionTestData {
1023 std::string description;
1024 NSArray* eval_scripts;
1025 NSArray* expected_suggestions;
1026 NSString* expected_result;
1027 };
1028
1029 // Tests that form activity correctly sends suggestions to the suggestion
1030 // controller.
1031 TEST_F(PasswordControllerTest, SuggestionUpdateTests) {
1032 LoadHtml(kHtmlWithPasswordForm);
1033 const std::string base_url = BaseUrl();
1034 EvaluateJavaScriptAsString(kUsernameAndPasswordTestPreparationScript);
1035
1036 // Initialize |form_data| with test data and an indicator that autofill
1037 // should not be performed while the user is entering the username so that
1038 // we can test with an initially-empty username field. Testing with a
1039 // username field that contains input is performed by a specific test below.
1040 PasswordFormFillData form_data;
1041 SetPasswordFormFillData(form_data, base_url, base_url, "u'", "user0", "p'",
1042 "password0", "abc", "def", true);
1043 form_data.name = base::ASCIIToUTF16(FormName(0));
1044
1045 __block BOOL block_was_called = NO;
1046 [passwordController_ fillPasswordForm:form_data
1047 completionHandler:^(BOOL success) {
1048 block_was_called = YES;
1049 // Verify that the fill reports failed.
1050 EXPECT_FALSE(success);
1051 }];
1052 base::test::ios::WaitUntilCondition(^bool() {
1053 return block_was_called;
1054 });
1055
1056 // Verify that the form has not been autofilled.
1057 EXPECT_NSEQ(@"[]=, onkeyup=false, onchange=false",
1058 EvaluateJavaScriptAsString(kUsernamePasswordVerificationScript));
1059
1060 // clang-format off
1061 SuggestionTestData test_data[] = {
1062 {
1063 "Should show all suggestions when focusing empty username field",
1064 @[(@"var evt = document.createEvent('Events');"
1065 "evt.initEvent('focus', true, true, window, 1);"
1066 "username_.dispatchEvent(evt);"),
1067 @""],
1068 @[@"abc", @"user0"],
1069 @"[]=, onkeyup=false, onchange=false"
1070 },
1071 {
1072 "Should not show suggestions when focusing password field",
1073 @[(@"var evt = document.createEvent('Events');"
1074 "evt.initEvent('focus', true, true, window, 1);"
1075 "password_.dispatchEvent(evt);"),
1076 @""],
1077 @[],
1078 @"[]=, onkeyup=false, onchange=false"
1079 },
1080 {
1081 "Should filter suggestions when focusing username field with input",
1082 @[(@"username_.value='ab';"
1083 "var evt = document.createEvent('Events');"
1084 "evt.initEvent('focus', true, true, window, 1);"
1085 "username_.dispatchEvent(evt);"),
1086 @""],
1087 @[@"abc"],
1088 @"ab[]=, onkeyup=false, onchange=false"
1089 },
1090 {
1091 "Should filter suggestions while typing",
1092 @[(@"var evt = document.createEvent('Events');"
1093 "evt.initEvent('focus', true, true, window, 1);"
1094 "username_.dispatchEvent(evt);"),
1095 (@"username_.value='ab';"
1096 "evt = document.createEvent('Events');"
1097 "evt.initEvent('keyup', true, true, window, 1);"
1098 "evt.keyCode = 98;"
1099 "username_.dispatchEvent(evt);"),
1100 @""],
1101 @[@"abc"],
1102 @"ab[]=, onkeyup=true, onchange=false"
1103 },
1104 {
1105 "Should unfilter suggestions after backspacing",
1106 @[(@"var evt = document.createEvent('Events');"
1107 "evt.initEvent('focus', true, true, window, 1);"
1108 "username_.dispatchEvent(evt);"),
1109 (@"username_.value='ab';"
1110 "evt = document.createEvent('Events');"
1111 "evt.initEvent('keyup', true, true, window, 1);"
1112 "evt.keyCode = 98;"
1113 "username_.dispatchEvent(evt);"),
1114 (@"username_.value='';"
1115 "evt = document.createEvent('Events');"
1116 "evt.initEvent('keyup', true, true, window, 1);"
1117 "evt.keyCode = 8;"
1118 "username_.dispatchEvent(evt);"),
1119 @""],
1120 @[@"abc", @"user0"],
1121 @"[]=, onkeyup=true, onchange=false"
1122 },
1123 };
1124 // clang-format on
1125
1126 for (const SuggestionTestData& data : test_data) {
1127 SCOPED_TRACE(testing::Message()
1128 << "for description=" << data.description
1129 << " and eval_scripts=" << data.eval_scripts);
1130 // Prepare the test.
1131 EvaluateJavaScriptAsString(kUsernameAndPasswordTestPreparationScript);
1132
1133 for (NSString* script in data.eval_scripts) {
1134 // Trigger events.
1135 EvaluateJavaScriptAsString(script);
1136
1137 // Pump the run loop so that the host can respond.
1138 WaitForBackgroundTasks();
1139 }
1140
1141 EXPECT_NSEQ(data.expected_suggestions, GetSortedSuggestionValues());
1142 EXPECT_NSEQ(data.expected_result, EvaluateJavaScriptAsString(
1143 kUsernamePasswordVerificationScript));
1144 // Clear all suggestions.
1145 [suggestionController_ setSuggestions:nil];
1146 }
1147 }
1148
1149 // Tests that selecting a suggestion will fill the corresponding form and field.
1150 TEST_F(PasswordControllerTest, SelectingSuggestionShouldFillPasswordForm) {
1151 LoadHtml(kHtmlWithPasswordForm);
1152 const std::string base_url = BaseUrl();
1153 EvaluateJavaScriptAsString(kUsernameAndPasswordTestPreparationScript);
1154
1155 // Initialize |form_data| with test data and an indicator that autofill
1156 // should not be performed while the user is entering the username so that
1157 // we can test with an initially-empty username field.
1158 PasswordFormFillData form_data;
1159 SetPasswordFormFillData(form_data, base_url, base_url, "u'", "user0", "p'",
1160 "password0", "abc", "def", true);
1161 form_data.name = base::ASCIIToUTF16(FormName(0));
1162
1163 __block BOOL block_was_called = NO;
1164 [passwordController_ fillPasswordForm:form_data
1165 completionHandler:^(BOOL success) {
1166 block_was_called = YES;
1167 // Verify that the fill reports failed.
1168 EXPECT_FALSE(success);
1169 }];
1170 base::test::ios::WaitUntilCondition(^bool() {
1171 return block_was_called;
1172 });
1173
1174 // Verify that the form has not been autofilled.
1175 EXPECT_NSEQ(@"[]=, onkeyup=false, onchange=false",
1176 EvaluateJavaScriptAsString(kUsernamePasswordVerificationScript));
1177
1178 // Tell PasswordController that a suggestion was selected. It should fill
1179 // out the password form with the corresponding credentials.
1180 FormSuggestion* suggestion = [FormSuggestion suggestionWithValue:@"abc"
1181 displayDescription:nil
1182 icon:nil
1183 identifier:0];
1184
1185 block_was_called = NO;
1186 SuggestionHandledCompletion completion = ^{
1187 block_was_called = YES;
1188 EXPECT_NSEQ(@"abc[]=def, onkeyup=false, onchange=false",
1189 ExecuteJavaScript(kUsernamePasswordVerificationScript));
1190 };
1191 [passwordController_ didSelectSuggestion:suggestion
1192 forField:@"u"
1193 form:base::SysUTF8ToNSString(FormName(0))
1194 completionHandler:completion];
1195 base::test::ios::WaitUntilCondition(^bool() {
1196 return block_was_called;
1197 });
1198 }
1199
1200 // Tests with invalid inputs.
1201 TEST_F(PasswordControllerTest, CheckIncorrectData) {
1202 // clang-format off
1203 std::string invalid_data[] = {
1204 "{}",
1205
1206 "{ \"usernameValue\": \"fakeaccount\","
1207 "\"passwords\": ["
1208 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
1209 "]}",
1210
1211 "{ \"usernameElement\": \"account\","
1212 "\"passwords\": ["
1213 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
1214 "]}",
1215
1216 "{ \"usernameElement\": \"account\","
1217 "\"usernameValue\": \"fakeaccount\","
1218 "}",
1219
1220 "{ \"usernameElement\": \"account\","
1221 "\"usernameValue\": \"fakeaccount\","
1222 "\"passwords\": {},"
1223 "}",
1224
1225 "{ \"usernameElement\": \"account\","
1226 "\"usernameValue\": \"fakeaccount\","
1227 "\"passwords\": ["
1228 "]}",
1229
1230 "{ \"usernameElement\": \"account\","
1231 "\"usernameValue\": \"fakeaccount\","
1232 "\"passwords\": ["
1233 "{ \"value\": \"fakesecret\" },"
1234 "]}",
1235
1236 "{ \"usernameElement\": \"account\","
1237 "\"usernameValue\": \"fakeaccount\","
1238 "\"passwords\": ["
1239 "{ \"element\": \"secret\" },"
1240 "]}",
1241 };
1242 // clang-format on
1243
1244 for (const std::string& data : invalid_data) {
1245 SCOPED_TRACE(testing::Message() << "for data=" << data);
1246 std::unique_ptr<base::Value> json_data(base::JSONReader::Read(data, true));
1247 const base::DictionaryValue* json_dict = nullptr;
1248 ASSERT_TRUE(json_data->GetAsDictionary(&json_dict));
1249 PasswordForm form;
1250 BOOL res =
1251 [passwordController_ getPasswordForm:&form
1252 fromDictionary:json_dict
1253 pageURL:GURL("https://www.foo.com/")];
1254 EXPECT_FALSE(res);
1255 }
1256 }
1257
1258 // The test case below does not need the heavy fixture from above, but it
1259 // needs to use MockWebState.
1260 TEST(PasswordControllerTestSimple, SaveOnNonHTMLLandingPage) {
63 TestChromeBrowserState::Builder builder; 1261 TestChromeBrowserState::Builder builder;
64 auto pref_service =
65 base::WrapUnique(new syncable_prefs::TestingPrefServiceSyncable);
66 pref_service->registry()->RegisterBooleanPref(
67 password_manager::prefs::kPasswordManagerSavingEnabled, true);
68 builder.SetPrefService(std::move(pref_service));
69 std::unique_ptr<TestChromeBrowserState> browser_state(builder.Build()); 1262 std::unique_ptr<TestChromeBrowserState> browser_state(builder.Build());
70 MockWebState web_state; 1263 MockWebState web_state;
71 ON_CALL(web_state, GetBrowserState()) 1264 ON_CALL(web_state, GetBrowserState())
72 .WillByDefault(testing::Return(browser_state.get())); 1265 .WillByDefault(testing::Return(browser_state.get()));
73 auto client = base::WrapUnique(new MockPasswordManagerClient); 1266
74 MockPasswordManagerClient* weak_client = client.get(); 1267 MockPasswordManagerClient* weak_client = nullptr;
75 base::scoped_nsobject<PasswordController> passwordController( 1268 base::scoped_nsobject<PasswordController> passwordController =
76 [[PasswordController alloc] initWithWebState:&web_state 1269 CreatePasswordController(&web_state, nullptr, &weak_client);
77 passwordsUiDelegate:nil 1270 static_cast<TestingPrefServiceSimple*>(weak_client->GetPrefs())
78 client:std::move(client)]); 1271 ->registry()
1272 ->RegisterBooleanPref(
1273 password_manager::prefs::kPasswordManagerSavingEnabled, true);
79 1274
80 // Use a mock LogManager to detect that OnPasswordFormsRendered has been 1275 // Use a mock LogManager to detect that OnPasswordFormsRendered has been
81 // called. TODO(crbug.com/598672): this is a hack, we should modularize the 1276 // called. TODO(crbug.com/598672): this is a hack, we should modularize the
82 // code better to allow proper unit-testing. 1277 // code better to allow proper unit-testing.
83 MockLogManager log_manager; 1278 MockLogManager log_manager;
84 EXPECT_CALL(log_manager, IsLoggingActive()).WillRepeatedly(Return(true)); 1279 EXPECT_CALL(log_manager, IsLoggingActive()).WillRepeatedly(Return(true));
85 EXPECT_CALL(log_manager, 1280 EXPECT_CALL(log_manager,
86 LogSavePasswordProgress( 1281 LogSavePasswordProgress(
87 "Message: \"PasswordManager::OnPasswordFormsRendered\"\n")); 1282 "Message: \"PasswordManager::OnPasswordFormsRendered\"\n"));
88 EXPECT_CALL(log_manager, 1283 EXPECT_CALL(log_manager,
89 LogSavePasswordProgress(testing::Ne( 1284 LogSavePasswordProgress(testing::Ne(
90 "Message: \"PasswordManager::OnPasswordFormsRendered\"\n"))) 1285 "Message: \"PasswordManager::OnPasswordFormsRendered\"\n")))
91 .Times(testing::AnyNumber()); 1286 .Times(testing::AnyNumber());
92 EXPECT_CALL(*weak_client, GetLogManager()) 1287 EXPECT_CALL(*weak_client, GetLogManager())
93 .WillRepeatedly(Return(&log_manager)); 1288 .WillRepeatedly(Return(&log_manager));
94 1289
95 web_state.SetContentIsHTML(false); 1290 web_state.SetContentIsHTML(false);
96 web_state.SetCurrentURL(GURL("https://example.com")); 1291 web_state.SetCurrentURL(GURL("https://example.com"));
97 [passwordController webStateDidLoadPage:&web_state]; 1292 [passwordController webStateDidLoadPage:&web_state];
98 } 1293 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698