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

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 GYP 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
10 #include "base/mac/scoped_nsobject.h" 12 #include "base/json/json_reader.h"
13 #include "base/mac/bind_objc_block.h"
14 #import "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 // Check that given a serialization of a PasswordForm, the controller is able
265 // to create the corresponding PasswordForm object.
266 TEST_F(PasswordControllerTest, PopulatePasswordFormWithDictionary) {
267 // clang-format off
268 PasswordFormTestData test_data[] = {
269 // One username element, one password element. URLs contain extra
270 // parts: username/password, query, reference, which are all expected
271 // to be stripped off. The password is recognized as an old password.
272 {
273 "http://john:doe@fakedomain.com/foo/bar?baz=quz#foobar",
274 "{ \"action\": \"some/action?to=be&or=not#tobe\","
275 "\"usernameElement\": \"account\","
276 "\"usernameValue\": \"fakeaccount\","
277 "\"name\": \"signup\","
278 "\"origin\": \"http://john:doe@fakedomain.com/foo/bar\","
279 "\"passwords\": ["
280 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
281 "]}",
282 "http://fakedomain.com/foo/bar",
283 "http://fakedomain.com/foo/some/action",
284 "account",
285 "fakeaccount",
286 "",
287 "",
288 "secret",
289 "fakesecret",
290 },
291 // One username element, one password element. Population should fail
292 // due to an origin mismatch.
293 {
294 "http://john:doe@fakedomain.com/foo/bar?baz=quz#foobar",
295 "{ \"action\": \"some/action?to=be&or=not#tobe\","
296 "\"usernameElement\": \"account\","
297 "\"usernameValue\": \"fakeaccount\","
298 "\"name\": \"signup\","
299 "\"origin\": \"http://john:doe@realdomainipromise.com/foo/bar\","
300 "\"passwords\": ["
301 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
302 "]}",
303 "",
304 "",
305 "",
306 "",
307 "",
308 "",
309 "",
310 "",
311 },
312 // One username element, two password elements. Since both password
313 // values are the same, we are assuming that the webpage asked the user
314 // to enter the password twice for confirmation.
315 {
316 "http://fakedomain.com/foo",
317 "{ \"action\": \"http://anotherdomain.com/some_action\","
318 "\"usernameElement\": \"account\","
319 "\"usernameValue\": \"fakeaccount\","
320 "\"name\": \"signup\","
321 "\"origin\": \"http://fakedomain.com/foo\","
322 "\"passwords\": ["
323 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
324 "{ \"element\": \"confirm\"," "\"value\": \"fakesecret\" },"
325 "]}",
326 "http://fakedomain.com/foo",
327 "http://anotherdomain.com/some_action",
328 "account",
329 "fakeaccount",
330 "secret",
331 "fakesecret",
332 "",
333 "",
334 },
335 // One username element, two password elements. The password
336 // values are different, so we are assuming that the webpage asked the user
337 // to enter the old password and new password.
338 {
339 "http://fakedomain.com/foo",
340 "{ \"action\": \"\","
341 "\"usernameElement\": \"account\","
342 "\"usernameValue\": \"fakeaccount\","
343 "\"name\": \"signup\","
344 "\"origin\": \"http://fakedomain.com/foo\","
345 "\"passwords\": ["
346 "{ \"element\": \"old\"," "\"value\": \"oldsecret\" },"
347 "{ \"element\": \"new\"," "\"value\": \"newsecret\" },"
348 "]}",
349 "http://fakedomain.com/foo",
350 "http://fakedomain.com/foo",
351 "account",
352 "fakeaccount",
353 "new",
354 "newsecret",
355 "old",
356 "oldsecret",
357 },
358 // One username element, three password elements. All passwords
359 // are the same. Password population should fail because this configuration
360 // does not make sense.
361 {
362 "http://fakedomain.com",
363 "{ \"action\": \"\","
364 "\"usernameElement\": \"account\","
365 "\"usernameValue\": \"fakeaccount\","
366 "\"name\": \"signup\","
367 "\"origin\": \"http://fakedomain.com/foo\","
368 "\"passwords\": ["
369 "{ \"element\": \"pass1\"," "\"value\": \"word\" },"
370 "{ \"element\": \"pass2\"," "\"value\": \"word\" },"
371 "{ \"element\": \"pass3\"," "\"value\": \"word\" },"
372 "]}",
373 "http://fakedomain.com/",
374 "http://fakedomain.com/",
375 "account",
376 "fakeaccount",
377 "",
378 "",
379 "",
380 "",
381 },
382 // One username element, three password elements. Two passwords are
383 // the same followed by a different one. Assuming that the duplicated
384 // password is the old one.
385 {
386 "http://fakedomain.com",
387 "{ \"action\": \"\","
388 "\"usernameElement\": \"account\","
389 "\"usernameValue\": \"fakeaccount\","
390 "\"name\": \"signup\","
391 "\"origin\": \"http://fakedomain.com/foo\","
392 "\"passwords\": ["
393 "{ \"element\": \"pass1\"," "\"value\": \"word1\" },"
394 "{ \"element\": \"pass2\"," "\"value\": \"word1\" },"
395 "{ \"element\": \"pass3\"," "\"value\": \"word3\" },"
396 "]}",
397 "http://fakedomain.com/",
398 "http://fakedomain.com/",
399 "account",
400 "fakeaccount",
401 "pass3",
402 "word3",
403 "pass1",
404 "word1",
405 },
406 // One username element, three password elements. A password is
407 // follwed by two duplicate ones. Assuming that the duplicated
408 // password is the new one.
409 {
410 "http://fakedomain.com",
411 "{ \"action\": \"\","
412 "\"usernameElement\": \"account\","
413 "\"usernameValue\": \"fakeaccount\","
414 "\"name\": \"signup\","
415 "\"origin\": \"http://fakedomain.com/foo\","
416 "\"passwords\": ["
417 "{ \"element\": \"pass1\"," "\"value\": \"word1\" },"
418 "{ \"element\": \"pass2\"," "\"value\": \"word2\" },"
419 "{ \"element\": \"pass3\"," "\"value\": \"word2\" },"
420 "]}",
421 "http://fakedomain.com/",
422 "http://fakedomain.com/",
423 "account",
424 "fakeaccount",
425 "pass2",
426 "word2",
427 "pass1",
428 "word1",
429 },
430 };
431 // clang-format on
432
433 for (const PasswordFormTestData& data : test_data) {
434 SCOPED_TRACE(testing::Message()
435 << "for page_location=" << data.page_location
436 << " and json_string=" << data.json_string);
437 std::unique_ptr<base::Value> json_data(
438 base::JSONReader::Read(data.json_string, true));
439 const base::DictionaryValue* json_dict = nullptr;
440 ASSERT_TRUE(json_data->GetAsDictionary(&json_dict));
441 PasswordForm form;
442 [passwordController_ getPasswordForm:&form
443 fromDictionary:json_dict
444 pageURL:GURL(data.page_location)];
445 EXPECT_STREQ(data.expected_origin, form.origin.spec().c_str());
446 EXPECT_STREQ(data.expected_action, form.action.spec().c_str());
447 EXPECT_EQ(base::ASCIIToUTF16(data.expected_username_element),
448 form.username_element);
449 EXPECT_EQ(base::ASCIIToUTF16(data.expected_username_value),
450 form.username_value);
451 EXPECT_EQ(base::ASCIIToUTF16(data.expected_new_password_element),
452 form.new_password_element);
453 EXPECT_EQ(base::ASCIIToUTF16(data.expected_new_password_value),
454 form.new_password_value);
455 EXPECT_EQ(base::ASCIIToUTF16(data.expected_old_password_element),
456 form.password_element);
457 EXPECT_EQ(base::ASCIIToUTF16(data.expected_old_password_value),
458 form.password_value);
459 }
460 };
461
462 struct FindPasswordFormTestData {
463 NSString* html_string;
464 const bool expected_form_found;
465 const char* const expected_username_element;
466 const char* const expected_password_element;
467 };
468
469 // TODO(crbug.com/403705) This test is flaky.
470 // Check that HTML forms are converted correctly into PasswordForms.
471 TEST_F(PasswordControllerTest, FLAKY_FindPasswordFormsInView) {
472 // clang-format off
473 FindPasswordFormTestData test_data[] = {
474 // Normal form: a username and a password element.
475 {
476 @"<form>"
477 "<input type='text' name='user0'>"
478 "<input type='password' name='pass0'>"
479 "</form>",
480 true, "user0", "pass0"
481 },
482 // User name is captured as an email address (HTML5).
483 {
484 @"<form>"
485 "<input type='email' name='email1'>"
486 "<input type='password' name='pass1'>"
487 "</form>",
488 true, "email1", "pass1"
489 },
490 // No username element.
491 {
492 @"<form>"
493 "<input type='password' name='user2'>"
494 "<input type='password' name='pass2'>"
495 "</form>",
496 true, "", "user2"
497 },
498 // No username element before password.
499 {
500 @"<form>"
501 "<input type='password' name='pass3'>"
502 "<input type='text' name='user3'>"
503 "</form>",
504 true, "", "pass3"
505 },
506 // Disabled username element.
507 {
508 @"<form>"
509 "<input type='text' name='user4' disabled='disabled'>"
510 "<input type='password' name='pass4'>"
511 "</form>",
512 true, "", "pass4"
513 },
514 // Username element has autocomplete='off'.
515 {
516 @"<form>"
517 "<input type='text' name='user5' AUTOCOMPLETE='off'>"
518 "<input type='password' name='pass5'>"
519 "</form>",
520 true, "user5", "pass5"
521 },
522 // No password element.
523 {
524 @"<form>"
525 "<input type='text' name='user6'>"
526 "<input type='text' name='pass6'>"
527 "</form>",
528 false, nullptr, nullptr
529 },
530 // Disabled password element.
531 {
532 @"<form>"
533 "<input type='text' name='user7'>"
534 "<input type='password' name='pass7' disabled='disabled'>"
535 "</form>",
536 false, nullptr, nullptr
537 },
538 // Password element has autocomplete='off'.
539 {
540 @"<form>"
541 "<input type='text' name='user8'>"
542 "<input type='password' name='pass8' AUTOCOMPLETE='OFF'>"
543 "</form>",
544 true, "user8", "pass8"
545 },
546 // Form element has autocomplete='off'.
547 {
548 @"<form autocomplete='off'>"
549 "<input type='text' name='user9'>"
550 "<input type='password' name='pass9'>"
551 "</form>",
552 true, "user9", "pass9"
553 },
554 };
555 // clang-format on
556
557 for (const FindPasswordFormTestData& data : test_data) {
558 SCOPED_TRACE(testing::Message() << "for html_string=" << data.html_string);
559 LoadHtml(data.html_string);
560 __block std::vector<PasswordForm> forms;
561 __block BOOL block_was_called = NO;
562 [passwordController_ findPasswordFormsWithCompletionHandler:^(
563 const std::vector<PasswordForm>& result) {
564 block_was_called = YES;
565 forms = result;
566 }];
567 base::test::ios::WaitUntilCondition(^bool() {
568 return block_was_called;
569 });
570 if (data.expected_form_found) {
571 ASSERT_EQ(1U, forms.size());
572 EXPECT_EQ(base::ASCIIToUTF16(data.expected_username_element),
573 forms[0].username_element);
574 EXPECT_EQ(base::ASCIIToUTF16(data.expected_password_element),
575 forms[0].password_element);
576 } else {
577 ASSERT_TRUE(forms.empty());
578 }
579 }
580 }
581
582 struct GetSubmittedPasswordFormTestData {
583 NSString* html_string;
584 NSString* java_script;
585 const int number_of_forms_to_submit;
586 const bool expected_form_found;
587 const char* expected_username_element;
588 };
589
590 // TODO(crbug.com/403705) This test is flaky.
591 // Check that HTML forms are captured and converted correctly into
592 // PasswordForms on submission.
593 TEST_F(PasswordControllerTest, FLAKY_GetSubmittedPasswordForm) {
594 // clang-format off
595 GetSubmittedPasswordFormTestData test_data[] = {
596 // Two forms with no explicit names.
597 {
598 @"<form action='javascript:;'>"
599 "<input type='text' name='user1'>"
600 "<input type='password' name='pass1'>"
601 "</form>"
602 "<form action='javascript:;'>"
603 "<input type='text' name='user2'>"
604 "<input type='password' name='pass2'>"
605 "<input type='submit' id='s2'>"
606 "</form>",
607 @"document.getElementById('s2').click()",
608 1, true, "user2"
609 },
610 // Two forms with explicit names.
611 {
612 @"<form name='test2a' action='javascript:;'>"
613 "<input type='text' name='user1'>"
614 "<input type='password' name='pass1'>"
615 "<input type='submit' id='s1'>"
616 "</form>"
617 "<form name='test2b' action='javascript:;'>"
618 "<input type='text' name='user2'>"
619 "<input type='password' name='pass2'>"
620 "</form>",
621 @"document.getElementById('s1').click()",
622 0, true, "user1"
623 },
624 // No password forms.
625 {
626 @"<form action='javascript:;'>"
627 "<input type='text' name='user1'>"
628 "<input type='text' name='pass1'>"
629 "<input type='submit' id='s1'>"
630 "</form>",
631 @"document.getElementById('s1').click()",
632 0, false, nullptr
633 },
634 // Form with quotes in the form and field names.
635 {
636 @"<form name=\"foo'\" action='javascript:;'>"
637 "<input type='text' name=\"user1'\">"
638 "<input type='password' id='s1' name=\"pass1'\">"
639 "</form>",
640 @"document.getElementById('s1').click()",
641 0, true, "user1'"
642 },
643 };
644 // clang-format on
645
646 for (const GetSubmittedPasswordFormTestData& data : test_data) {
647 SCOPED_TRACE(testing::Message() << "for html_string=" << data.html_string
648 << " and java_script=" << data.java_script
649 << " and number_of_forms_to_submit="
650 << data.number_of_forms_to_submit);
651 LoadHtml(data.html_string);
652 EvaluateJavaScriptAsString(data.java_script);
653 __block BOOL block_was_called = NO;
654 id completion_handler = ^(BOOL found, const PasswordForm& form) {
655 block_was_called = YES;
656 ASSERT_EQ(data.expected_form_found, found);
657 if (data.expected_form_found) {
658 EXPECT_EQ(base::ASCIIToUTF16(data.expected_username_element),
659 form.username_element);
660 }
661 };
662 [passwordController_
663 extractSubmittedPasswordForm:FormName(data.number_of_forms_to_submit)
664 completionHandler:completion_handler];
665 base::test::ios::WaitUntilCondition(^bool() {
666 return block_was_called;
667 });
668 }
669 }
670
671 // Populates |form_data| with test values.
672 void SetPasswordFormFillData(PasswordFormFillData& form_data,
673 const std::string& origin,
674 const std::string& action,
675 const char* username_field,
676 const char* username_value,
677 const char* password_field,
678 const char* password_value,
679 const char* additional_username,
680 const char* additional_password,
681 bool wait_for_username) {
682 form_data.origin = GURL(origin);
683 form_data.action = GURL(action);
684 autofill::FormFieldData username;
685 username.name = base::UTF8ToUTF16(username_field);
686 username.value = base::UTF8ToUTF16(username_value);
687 form_data.username_field = username;
688 autofill::FormFieldData password;
689 password.name = base::UTF8ToUTF16(password_field);
690 password.value = base::UTF8ToUTF16(password_value);
691 form_data.password_field = password;
692 if (additional_username) {
693 autofill::PasswordAndRealm additional_password_data;
694 additional_password_data.password = base::UTF8ToUTF16(additional_password);
695 additional_password_data.realm.clear();
696 form_data.additional_logins.insert(
697 std::pair<base::string16, autofill::PasswordAndRealm>(
698 base::UTF8ToUTF16(additional_username), additional_password_data));
699 }
700 form_data.wait_for_username = wait_for_username;
701 }
702
703 // Test HTML page. It contains several password forms. Tests autofill
704 // them and verify that the right ones are autofilled.
705 static NSString* kHtmlWithMultiplePasswordForms =
706 @"<form>"
707 "<input id='un0' type='text' name='u0'>"
708 "<input id='pw0' type='password' name='p0'>"
709 "</form>"
710 "<form action='action?query=yes#reference'>"
711 "<input id='un1' type='text' name='u1'>"
712 "<input id='pw1' type='password' name='p1'>"
713 "</form>"
714 "<form action='http://some_other_action'>"
715 "<input id='un2' type='text' name='u2'>"
716 "<input id='pw2' type='password' name='p2'>"
717 "</form>"
718 "<form>"
719 "<input id='un3' type='text' name='u3'>"
720 "<input id='pw3' type='password' name='p3'>"
721 "<input id='pw3' type='password' name='p3'>"
722 "</form>"
723 "<form>"
724 "<input id='un4' type='text' name='u4'>"
725 "<input id='pw4' type='password' name='p4'>"
726 "</form>"
727 "<form>"
728 "<input id='un5' type='text' name='u4'>"
729 "<input id='pw5' type='password' name='p4'>"
730 "</form>"
731 "<form name=\"f6'\">"
732 "<input id=\"un6'\" type='text' name=\"u6'\">"
733 "<input id=\"pw6'\" type='password' name=\"p6'\">"
734 "</form>";
735
736 // A script that resets all text fields.
737 static NSString* kClearInputFieldsScript =
738 @"var inputs = document.getElementsByTagName('input');"
739 "for(var i = 0; i < inputs.length; i++){"
740 " inputs[i].value = '';"
741 "}";
742
743 // A script that we run after autofilling forms. It returns
744 // ids and values of all non-empty fields.
745 static NSString* kInputFieldValueVerificationScript =
746 @"var result='';"
747 "var inputs = document.getElementsByTagName('input');"
748 "for(var i = 0; i < inputs.length; i++){"
749 " var input = inputs[i];"
750 " if (input.value) {"
751 " result += input.id + '=' + input.value +';';"
752 " }"
753 "}; result";
754
755 struct FillPasswordFormTestData {
756 const std::string origin;
757 const std::string action;
758 const char* username_field;
759 const char* username_value;
760 const char* password_field;
761 const char* password_value;
762 const BOOL should_succeed;
763 NSString* expected_result;
764 };
765
766 // Test that filling password forms works correctly.
767 TEST_F(PasswordControllerTest, FillPasswordForm) {
768 LoadHtml(kHtmlWithMultiplePasswordForms);
769
770 EXPECT_NSEQ(@"true",
771 EvaluateJavaScriptAsString(@"__gCrWeb.hasPasswordField()"));
772
773 const std::string base_url = BaseUrl();
774 // clang-format off
775 FillPasswordFormTestData test_data[] = {
776 // Basic test: one-to-one match on the first password form.
777 {
778 base_url,
779 base_url,
780 "u0",
781 "test_user",
782 "p0",
783 "test_password",
784 YES,
785 @"un0=test_user;pw0=test_password;"
786 },
787 // Multiple forms match: they should all be autofilled.
788 {
789 base_url,
790 base_url,
791 "u4",
792 "test_user",
793 "p4",
794 "test_password",
795 YES,
796 @"un4=test_user;pw4=test_password;un5=test_user;pw5=test_password;"
797 },
798 // The form matches despite a different action: the only difference
799 // is a query and reference.
800 {
801 base_url,
802 base_url,
803 "u1",
804 "test_user",
805 "p1",
806 "test_password",
807 YES,
808 @"un1=test_user;pw1=test_password;"
809 },
810 // No match because of a different origin.
811 {
812 "http://someotherfakedomain.com",
813 base_url,
814 "u0",
815 "test_user",
816 "p0",
817 "test_password",
818 NO,
819 @""
820 },
821 // No match because of a different action.
822 {
823 base_url,
824 "http://someotherfakedomain.com",
825 "u0",
826 "test_user",
827 "p0",
828 "test_password",
829 NO,
830 @""
831 },
832 // No match because some inputs are not in the form.
833 {
834 base_url,
835 base_url,
836 "u0",
837 "test_user",
838 "p1",
839 "test_password",
840 NO,
841 @""
842 },
843 // No match because there are duplicate inputs in the form.
844 {
845 base_url,
846 base_url,
847 "u3",
848 "test_user",
849 "p3",
850 "test_password",
851 NO,
852 @""
853 },
854 // Basic test, but with quotes in the names and IDs.
855 {
856 base_url,
857 base_url,
858 "u6'",
859 "test_user",
860 "p6'",
861 "test_password",
862 YES,
863 @"un6'=test_user;pw6'=test_password;"
864 },
865 };
866 // clang-format on
867
868 for (const FillPasswordFormTestData& data : test_data) {
869 EvaluateJavaScriptAsString(kClearInputFieldsScript);
870
871 PasswordFormFillData form_data;
872 SetPasswordFormFillData(form_data, data.origin, data.action,
873 data.username_field, data.username_value,
874 data.password_field, data.password_value, nullptr,
875 nullptr, false);
876
877 __block BOOL block_was_called = NO;
878 [passwordController_ fillPasswordForm:form_data
879 completionHandler:^(BOOL success) {
880 block_was_called = YES;
881 EXPECT_EQ(data.should_succeed, success);
882 }];
883 base::test::ios::WaitUntilCondition(^bool() {
884 return block_was_called;
885 });
886
887 NSString* result =
888 EvaluateJavaScriptAsString(kInputFieldValueVerificationScript);
889 EXPECT_NSEQ(data.expected_result, result);
890 }
891 }
892
893 // Tests that a form is found and the found form is filled in with the given
894 // username and password.
895 TEST_F(PasswordControllerTest, FindAndFillOnePasswordForm) {
896 LoadHtml(@"<form><input id='un' type='text' name='u'>"
897 "<input id='pw' type='password' name='p'></form>");
898 __block int call_counter = 0;
899 __block int success_counter = 0;
900 [passwordController_ findAndFillPasswordForms:@"john.doe@gmail.com"
901 password:@"super!secret"
902 completionHandler:^(BOOL complete) {
903 ++call_counter;
904 if (complete)
905 ++success_counter;
906 }];
907 base::test::ios::WaitUntilCondition(^{
908 return call_counter == 1;
909 });
910 EXPECT_EQ(1, success_counter);
911 NSString* result =
912 EvaluateJavaScriptAsString(kInputFieldValueVerificationScript);
913 EXPECT_NSEQ(@"un=john.doe@gmail.com;pw=super!secret;", result);
914 }
915
916 // Tests that multiple forms on the same page are found and filled.
917 // This test includes an mock injected failure on form filling to verify
918 // that completion handler is called with the proper values.
919 TEST_F(PasswordControllerTest, FindAndFillMultiplePasswordForms) {
920 // Fails the first call to fill password form.
921 SetFillPasswordFormFailureCount(1);
922 LoadHtml(@"<form><input id='u1' type='text' name='un1'>"
923 "<input id='p1' type='password' name='pw1'></form>"
924 "<form><input id='u2' type='text' name='un2'>"
925 "<input id='p2' type='password' name='pw2'></form>"
926 "<form><input id='u3' type='text' name='un3'>"
927 "<input id='p3' type='password' name='pw3'></form>");
928 __block int call_counter = 0;
929 __block int success_counter = 0;
930 [passwordController_ findAndFillPasswordForms:@"john.doe@gmail.com"
931 password:@"super!secret"
932 completionHandler:^(BOOL complete) {
933 ++call_counter;
934 if (complete)
935 ++success_counter;
936 LOG(INFO) << "HANDLER call " << call_counter
937 << " success " << success_counter;
938 }];
939 // There should be 3 password forms and only 2 successfully filled forms.
940 base::test::ios::WaitUntilCondition(^{
941 return call_counter == 3;
942 });
943 EXPECT_EQ(2, success_counter);
944 NSString* result =
945 EvaluateJavaScriptAsString(kInputFieldValueVerificationScript);
946 EXPECT_NSEQ(@"u2=john.doe@gmail.com;p2=super!secret;"
947 "u3=john.doe@gmail.com;p3=super!secret;",
948 result);
949 }
950
951 BOOL PasswordControllerTest::BasicFormFill(NSString* html) {
952 LoadHtml(html);
953 EXPECT_NSEQ(@"true",
954 EvaluateJavaScriptAsString(@"__gCrWeb.hasPasswordField()"));
955 const std::string base_url = BaseUrl();
956 PasswordFormFillData form_data;
957 SetPasswordFormFillData(form_data, base_url, base_url, "u0", "test_user",
958 "p0", "test_password", nullptr, nullptr, false);
959 __block BOOL block_was_called = NO;
960 __block BOOL return_value = NO;
961 [passwordController_ fillPasswordForm:form_data
962 completionHandler:^(BOOL success) {
963 block_was_called = YES;
964 return_value = success;
965 }];
966 base::test::ios::WaitUntilCondition(^bool() {
967 return block_was_called;
968 });
969 return return_value;
970 }
971
972 // Check that |fillPasswordForm| is not filled if 'readonly' attribute is set
973 // on either username or password fields.
974 // TODO(crbug.com/503050): Test is flaky.
975 TEST_F(PasswordControllerTest, FLAKY_DontFillReadOnly) {
976 // Control check that the fill operation will succceed with well-formed form.
977 EXPECT_TRUE(BasicFormFill(@"<form>"
978 "<input id='un0' type='text' name='u0'>"
979 "<input id='pw0' type='password' name='p0'>"
980 "</form>"));
981 // Form fill should fail with 'readonly' attribute on username.
982 EXPECT_FALSE(BasicFormFill(
983 @"<form>"
984 "<input id='un0' type='text' name='u0' readonly='readonly'>"
985 "<input id='pw0' type='password' name='p0'>"
986 "</form>"));
987 // Form fill should fail with 'readonly' attribute on password.
988 EXPECT_FALSE(BasicFormFill(
989 @"<form>"
990 "<input id='un0' type='text' name='u0'>"
991 "<input id='pw0' type='password' name='p0' readonly='readonly'>"
992 "</form>"));
993 }
994
995 // An HTML page containing one password form. The username input field
996 // also has custom event handlers. We need to verify that those event
997 // handlers are still triggered even though we override them with our own.
998 static NSString* kHtmlWithPasswordForm =
999 @"<form>"
1000 "<input id='un' type='text' name=\"u'\""
1001 " onkeyup='window.onKeyUpCalled_=true'"
1002 " onchange='window.onChangeCalled_=true'>"
1003 "<input id='pw' type='password' name=\"p'\">"
1004 "</form>";
1005
1006 // A script that resets indicators used to verify that custom event
1007 // handlers are triggered. It also finds and the username and
1008 // password fields and caches them for future verification.
1009 static NSString* kUsernameAndPasswordTestPreparationScript =
1010 @"onKeyUpCalled_ = false;"
1011 "onChangeCalled_ = false;"
1012 "username_ = document.getElementById('un');"
1013 "username_.__gCrWebAutofilled = 'false';"
1014 "password_ = document.getElementById('pw');"
1015 "password_.__gCrWebAutofilled = 'false';";
1016
1017 // A script that we run after autofilling forms. It returns
1018 // all values for verification as a single concatenated string.
1019 static NSString* kUsernamePasswordVerificationScript =
1020 @"var value = username_.value;"
1021 "var from = username_.selectionStart;"
1022 "var to = username_.selectionEnd;"
1023 "value.substr(0, from) + '[' + value.substr(from, to) + ']'"
1024 " + value.substr(to, value.length) + '=' + password_.value"
1025 " + ', onkeyup=' + onKeyUpCalled_"
1026 " + ', onchange=' + onChangeCalled_;";
1027
1028 struct SuggestionTestData {
1029 std::string description;
1030 NSArray* eval_scripts;
1031 NSArray* expected_suggestions;
1032 NSString* expected_result;
1033 };
1034
1035 // Tests that form activity correctly sends suggestions to the suggestion
1036 // controller.
1037 TEST_F(PasswordControllerTest, SuggestionUpdateTests) {
1038 LoadHtml(kHtmlWithPasswordForm);
1039 const std::string base_url = BaseUrl();
1040 EvaluateJavaScriptAsString(kUsernameAndPasswordTestPreparationScript);
1041
1042 // Initialize |form_data| with test data and an indicator that autofill
1043 // should not be performed while the user is entering the username so that
1044 // we can test with an initially-empty username field. Testing with a
1045 // username field that contains input is performed by a specific test below.
1046 PasswordFormFillData form_data;
1047 SetPasswordFormFillData(form_data, base_url, base_url, "u'", "user0", "p'",
1048 "password0", "abc", "def", true);
1049 form_data.name = base::ASCIIToUTF16(FormName(0));
1050
1051 __block BOOL block_was_called = NO;
1052 [passwordController_ fillPasswordForm:form_data
1053 completionHandler:^(BOOL success) {
1054 block_was_called = YES;
1055 // Verify that the fill reports failed.
1056 EXPECT_FALSE(success);
1057 }];
1058 base::test::ios::WaitUntilCondition(^bool() {
1059 return block_was_called;
1060 });
1061
1062 // Verify that the form has not been autofilled.
1063 EXPECT_NSEQ(@"[]=, onkeyup=false, onchange=false",
1064 EvaluateJavaScriptAsString(kUsernamePasswordVerificationScript));
1065
1066 // clang-format off
1067 SuggestionTestData test_data[] = {
1068 {
1069 "Should show all suggestions when focusing empty username field",
1070 @[(@"var evt = document.createEvent('Events');"
1071 "evt.initEvent('focus', true, true, window, 1);"
1072 "username_.dispatchEvent(evt);"),
1073 @""],
1074 @[@"abc", @"user0"],
1075 @"[]=, onkeyup=false, onchange=false"
1076 },
1077 {
1078 "Should not show suggestions when focusing password field",
1079 @[(@"var evt = document.createEvent('Events');"
1080 "evt.initEvent('focus', true, true, window, 1);"
1081 "password_.dispatchEvent(evt);"),
1082 @""],
1083 @[],
1084 @"[]=, onkeyup=false, onchange=false"
1085 },
1086 {
1087 "Should filter suggestions when focusing username field with input",
1088 @[(@"username_.value='ab';"
1089 "var evt = document.createEvent('Events');"
1090 "evt.initEvent('focus', true, true, window, 1);"
1091 "username_.dispatchEvent(evt);"),
1092 @""],
1093 @[@"abc"],
1094 @"ab[]=, onkeyup=false, onchange=false"
1095 },
1096 {
1097 "Should filter suggestions while typing",
1098 @[(@"var evt = document.createEvent('Events');"
1099 "evt.initEvent('focus', true, true, window, 1);"
1100 "username_.dispatchEvent(evt);"),
1101 (@"username_.value='ab';"
1102 "evt = document.createEvent('Events');"
1103 "evt.initEvent('keyup', true, true, window, 1);"
1104 "evt.keyCode = 98;"
1105 "username_.dispatchEvent(evt);"),
1106 @""],
1107 @[@"abc"],
1108 @"ab[]=, onkeyup=true, onchange=false"
1109 },
1110 {
1111 "Should unfilter suggestions after backspacing",
1112 @[(@"var evt = document.createEvent('Events');"
1113 "evt.initEvent('focus', true, true, window, 1);"
1114 "username_.dispatchEvent(evt);"),
1115 (@"username_.value='ab';"
1116 "evt = document.createEvent('Events');"
1117 "evt.initEvent('keyup', true, true, window, 1);"
1118 "evt.keyCode = 98;"
1119 "username_.dispatchEvent(evt);"),
1120 (@"username_.value='';"
1121 "evt = document.createEvent('Events');"
1122 "evt.initEvent('keyup', true, true, window, 1);"
1123 "evt.keyCode = 8;"
1124 "username_.dispatchEvent(evt);"),
1125 @""],
1126 @[@"abc", @"user0"],
1127 @"[]=, onkeyup=true, onchange=false"
1128 },
1129 };
1130 // clang-format on
1131
1132 for (const SuggestionTestData& data : test_data) {
1133 SCOPED_TRACE(testing::Message()
1134 << "for description=" << data.description
1135 << " and eval_scripts=" << data.eval_scripts);
1136 // Prepare the test.
1137 EvaluateJavaScriptAsString(kUsernameAndPasswordTestPreparationScript);
1138
1139 for (NSString* script in data.eval_scripts) {
1140 // Trigger events.
1141 EvaluateJavaScriptAsString(script);
1142
1143 // Pump the run loop so that the host can respond.
1144 WaitForBackgroundTasks();
1145 }
1146
1147 EXPECT_NSEQ(data.expected_suggestions, GetSortedSuggestionValues());
1148 EXPECT_NSEQ(data.expected_result, EvaluateJavaScriptAsString(
1149 kUsernamePasswordVerificationScript));
1150 // Clear all suggestions.
1151 [suggestionController_ setSuggestions:nil];
1152 }
1153 }
1154
1155 // Tests that selecting a suggestion will fill the corresponding form and field.
1156 TEST_F(PasswordControllerTest, SelectingSuggestionShouldFillPasswordForm) {
1157 LoadHtml(kHtmlWithPasswordForm);
1158 const std::string base_url = BaseUrl();
1159 EvaluateJavaScriptAsString(kUsernameAndPasswordTestPreparationScript);
1160
1161 // Initialize |form_data| with test data and an indicator that autofill
1162 // should not be performed while the user is entering the username so that
1163 // we can test with an initially-empty username field.
1164 PasswordFormFillData form_data;
1165 SetPasswordFormFillData(form_data, base_url, base_url, "u'", "user0", "p'",
1166 "password0", "abc", "def", true);
1167 form_data.name = base::ASCIIToUTF16(FormName(0));
1168
1169 __block BOOL block_was_called = NO;
1170 [passwordController_ fillPasswordForm:form_data
1171 completionHandler:^(BOOL success) {
1172 block_was_called = YES;
1173 // Verify that the fill reports failed.
1174 EXPECT_FALSE(success);
1175 }];
1176 base::test::ios::WaitUntilCondition(^bool() {
1177 return block_was_called;
1178 });
1179
1180 // Verify that the form has not been autofilled.
1181 EXPECT_NSEQ(@"[]=, onkeyup=false, onchange=false",
1182 EvaluateJavaScriptAsString(kUsernamePasswordVerificationScript));
1183
1184 // Tell PasswordController that a suggestion was selected. It should fill
1185 // out the password form with the corresponding credentials.
1186 FormSuggestion* suggestion = [FormSuggestion suggestionWithValue:@"abc"
1187 displayDescription:nil
1188 icon:nil
1189 identifier:0];
1190
1191 block_was_called = NO;
1192 SuggestionHandledCompletion completion = ^{
1193 block_was_called = YES;
1194 EXPECT_NSEQ(@"abc[]=def, onkeyup=false, onchange=false",
1195 ExecuteJavaScript(kUsernamePasswordVerificationScript));
1196 };
1197 [passwordController_ didSelectSuggestion:suggestion
1198 forField:@"u"
1199 form:base::SysUTF8ToNSString(FormName(0))
1200 completionHandler:completion];
1201 base::test::ios::WaitUntilCondition(^bool() {
1202 return block_was_called;
1203 });
1204 }
1205
1206 // Tests with invalid inputs.
1207 TEST_F(PasswordControllerTest, CheckIncorrectData) {
1208 // clang-format off
1209 std::string invalid_data[] = {
1210 "{}",
1211
1212 "{ \"usernameValue\": \"fakeaccount\","
1213 "\"passwords\": ["
1214 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
1215 "]}",
1216
1217 "{ \"usernameElement\": \"account\","
1218 "\"passwords\": ["
1219 "{ \"element\": \"secret\"," "\"value\": \"fakesecret\" },"
1220 "]}",
1221
1222 "{ \"usernameElement\": \"account\","
1223 "\"usernameValue\": \"fakeaccount\","
1224 "}",
1225
1226 "{ \"usernameElement\": \"account\","
1227 "\"usernameValue\": \"fakeaccount\","
1228 "\"passwords\": {},"
1229 "}",
1230
1231 "{ \"usernameElement\": \"account\","
1232 "\"usernameValue\": \"fakeaccount\","
1233 "\"passwords\": ["
1234 "]}",
1235
1236 "{ \"usernameElement\": \"account\","
1237 "\"usernameValue\": \"fakeaccount\","
1238 "\"passwords\": ["
1239 "{ \"value\": \"fakesecret\" },"
1240 "]}",
1241
1242 "{ \"usernameElement\": \"account\","
1243 "\"usernameValue\": \"fakeaccount\","
1244 "\"passwords\": ["
1245 "{ \"element\": \"secret\" },"
1246 "]}",
1247 };
1248 // clang-format on
1249
1250 for (const std::string& data : invalid_data) {
1251 SCOPED_TRACE(testing::Message() << "for data=" << data);
1252 std::unique_ptr<base::Value> json_data(base::JSONReader::Read(data, true));
1253 const base::DictionaryValue* json_dict = nullptr;
1254 ASSERT_TRUE(json_data->GetAsDictionary(&json_dict));
1255 PasswordForm form;
1256 BOOL res =
1257 [passwordController_ getPasswordForm:&form
1258 fromDictionary:json_dict
1259 pageURL:GURL("https://www.foo.com/")];
1260 EXPECT_FALSE(res);
1261 }
1262 }
1263
1264 // The test case below does not need the heavy fixture from above, but it
1265 // needs to use MockWebState.
1266 TEST(PasswordControllerTestSimple, SaveOnNonHTMLLandingPage) {
63 TestChromeBrowserState::Builder builder; 1267 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()); 1268 std::unique_ptr<TestChromeBrowserState> browser_state(builder.Build());
70 MockWebState web_state; 1269 MockWebState web_state;
71 ON_CALL(web_state, GetBrowserState()) 1270 ON_CALL(web_state, GetBrowserState())
72 .WillByDefault(testing::Return(browser_state.get())); 1271 .WillByDefault(testing::Return(browser_state.get()));
73 auto client = base::WrapUnique(new MockPasswordManagerClient); 1272
74 MockPasswordManagerClient* weak_client = client.get(); 1273 MockPasswordManagerClient* weak_client = nullptr;
75 base::scoped_nsobject<PasswordController> passwordController( 1274 base::scoped_nsobject<PasswordController> passwordController =
76 [[PasswordController alloc] initWithWebState:&web_state 1275 CreatePasswordController(&web_state, nullptr, &weak_client);
77 passwordsUiDelegate:nil 1276 static_cast<TestingPrefServiceSimple*>(weak_client->GetPrefs())
78 client:std::move(client)]); 1277 ->registry()
1278 ->RegisterBooleanPref(
1279 password_manager::prefs::kPasswordManagerSavingEnabled, true);
79 1280
80 // Use a mock LogManager to detect that OnPasswordFormsRendered has been 1281 // Use a mock LogManager to detect that OnPasswordFormsRendered has been
81 // called. TODO(crbug.com/598672): this is a hack, we should modularize the 1282 // called. TODO(crbug.com/598672): this is a hack, we should modularize the
82 // code better to allow proper unit-testing. 1283 // code better to allow proper unit-testing.
83 MockLogManager log_manager; 1284 MockLogManager log_manager;
84 EXPECT_CALL(log_manager, IsLoggingActive()).WillRepeatedly(Return(true)); 1285 EXPECT_CALL(log_manager, IsLoggingActive()).WillRepeatedly(Return(true));
85 EXPECT_CALL(log_manager, 1286 EXPECT_CALL(log_manager,
86 LogSavePasswordProgress( 1287 LogSavePasswordProgress(
87 "Message: \"PasswordManager::OnPasswordFormsRendered\"\n")); 1288 "Message: \"PasswordManager::OnPasswordFormsRendered\"\n"));
88 EXPECT_CALL(log_manager, 1289 EXPECT_CALL(log_manager,
89 LogSavePasswordProgress(testing::Ne( 1290 LogSavePasswordProgress(testing::Ne(
90 "Message: \"PasswordManager::OnPasswordFormsRendered\"\n"))) 1291 "Message: \"PasswordManager::OnPasswordFormsRendered\"\n")))
91 .Times(testing::AnyNumber()); 1292 .Times(testing::AnyNumber());
92 EXPECT_CALL(*weak_client, GetLogManager()) 1293 EXPECT_CALL(*weak_client, GetLogManager())
93 .WillRepeatedly(Return(&log_manager)); 1294 .WillRepeatedly(Return(&log_manager));
94 1295
95 web_state.SetContentIsHTML(false); 1296 web_state.SetContentIsHTML(false);
96 web_state.SetCurrentURL(GURL("https://example.com")); 1297 web_state.SetCurrentURL(GURL("https://example.com"));
97 [passwordController webStateDidLoadPage:&web_state]; 1298 [passwordController webStateDidLoadPage:&web_state];
98 } 1299 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698