Index: ios/chrome/browser/passwords/password_controller_js_unittest.mm |
diff --git a/ios/chrome/browser/passwords/password_controller_js_unittest.mm b/ios/chrome/browser/passwords/password_controller_js_unittest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..966edc5e91ce98cea16d70331dc494bf17fc2592 |
--- /dev/null |
+++ b/ios/chrome/browser/passwords/password_controller_js_unittest.mm |
@@ -0,0 +1,300 @@ |
+// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import <Foundation/Foundation.h> |
+ |
+#import "ios/chrome/browser/passwords/js_password_manager.h" |
+#import "ios/web/public/test/web_js_test.h" |
+#import "ios/web/public/test/web_test_with_web_state.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "testing/gtest_mac.h" |
+ |
+// Unit tests for ios/chrome/browser/web/resources/password_controller.js |
+namespace { |
+ |
+// Text fixture to test password controller. |
+class PasswordControllerJsTest |
+ : public web::WebJsTest<web::WebTestWithWebState> { |
+ public: |
+ PasswordControllerJsTest() |
+ : web::WebJsTest<web::WebTestWithWebState>(@[ @"password_controller" ]) {} |
+}; |
+ |
+// IDs used in the Username and Password <input> elements. |
+NSString* const kEmailInputID = @"Email"; |
+NSString* const kPasswordInputID = @"Passwd"; |
+ |
+// Returns an autoreleased string of an HTML form that is similar to the |
+// Google Accounts sign in form. |email| may be nil if the form does not |
+// need to be pre-filled with the username. Use |isReadOnly| flag to indicate |
+// if the email field should be read-only. |
+NSString* GAIASignInForm(NSString* formAction, |
+ NSString* email, |
+ BOOL isReadOnly) { |
+ return [NSString |
+ stringWithFormat: |
+ @"<html><body>" |
+ "<form novalidate method=\"post\" action=\"%@\" " |
+ "id=\"gaia_loginform\">" |
+ " <input name=\"GALX\" type=\"hidden\" value=\"abcdefghij\">" |
+ " <input name=\"service\" type=\"hidden\" value=\"mail\">" |
+ " <input id=\"%@\" name=\"Email\" type=\"email\" value=\"%@\" %@>" |
+ " <input id=\"%@\" name=\"Passwd\" type=\"password\" " |
+ " placeholder=\"Password\">" |
+ "</form></body></html>", |
+ formAction, kEmailInputID, email ? email : @"", |
+ isReadOnly ? @"readonly" : @"", kPasswordInputID]; |
+} |
+ |
+// Returns an autoreleased string of JSON for a parsed form. |
+NSString* GAIASignInFormData(NSString* formAction) { |
+ return [NSString stringWithFormat:@"{" |
+ " \"action\":\"%@\"," |
+ " \"origin\":\"%@\"," |
+ " \"fields\":[" |
+ " {\"name\":\"%@\", \"value\":\"\"}," |
+ " {\"name\":\"%@\",\"value\":\"\"}" |
+ " ]" |
+ "}", |
+ formAction, formAction, kEmailInputID, |
+ kPasswordInputID]; |
+} |
+ |
+// Loads a page with a password form containing a username value already. |
+// Checks that an attempt to fill in credentials with the same username |
+// succeeds. |
+TEST_F(PasswordControllerJsTest, |
+ FillPasswordFormWithPrefilledUsername_SucceedsWhenUsernameMatches) { |
+ NSString* const formAction = @"https://accounts.google.com/ServiceLoginAuth"; |
+ NSString* const username = @"john.doe@gmail.com"; |
+ NSString* const password = @"super!secret"; |
+ LoadHtmlAndInject(GAIASignInForm(formAction, username, YES)); |
+ EXPECT_NSEQ(@"true", EvaluateJavaScriptWithFormat( |
+ @"__gCrWeb.fillPasswordForm(%@, '%@', '%@', '%@')", |
+ GAIASignInFormData(formAction), username, password, |
+ formAction)); |
+ // Verifies that the sign-in form has been filled with username/password. |
+ EvaluateJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value", |
+ @[ kEmailInputID, kPasswordInputID ], |
+ @[ username, password ]); |
+} |
+ |
+// Loads a page with a password form containing a username value already. |
+// Checks that an attempt to fill in credentials with a different username |
+// fails, as long as the field is read-only. |
+TEST_F(PasswordControllerJsTest, |
+ FillPasswordFormWithPrefilledUsername_FailsWhenUsernameMismatched) { |
+ NSString* const formAction = @"https://accounts.google.com/ServiceLoginAuth"; |
+ NSString* const username1 = @"john.doe@gmail.com"; |
+ NSString* const username2 = @"jean.dubois@gmail.com"; |
+ NSString* const password = @"super!secret"; |
+ LoadHtmlAndInject(GAIASignInForm(formAction, username1, YES)); |
+ EXPECT_NSEQ(@"false", EvaluateJavaScriptWithFormat( |
+ @"__gCrWeb.fillPasswordForm(%@, '%@', '%@', '%@')", |
+ GAIASignInFormData(formAction), username2, password, |
+ formAction)); |
+ // Verifies that the sign-in form has not been filled. |
+ EvaluateJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value", |
+ @[ kEmailInputID, kPasswordInputID ], |
+ @[ username1, @"" ]); |
+} |
+ |
+// Loads a page with a password form containing a username value already. |
+// Checks that an attempt to fill in credentials with a different username |
+// succeeds, as long as the field is writeable. |
+TEST_F(PasswordControllerJsTest, |
+ FillPasswordFormWithPrefilledUsername_SucceedsByOverridingUsername) { |
+ NSString* const formAction = @"https://accounts.google.com/ServiceLoginAuth"; |
+ NSString* const username1 = @"john.doe@gmail.com"; |
+ NSString* const username2 = @"jane.doe@gmail.com"; |
+ NSString* const password = @"super!secret"; |
+ LoadHtmlAndInject(GAIASignInForm(formAction, username1, NO)); |
+ EXPECT_NSEQ(@"true", EvaluateJavaScriptWithFormat( |
+ @"__gCrWeb.fillPasswordForm(%@, '%@', '%@', '%@')", |
+ GAIASignInFormData(formAction), username2, password, |
+ formAction)); |
+ // Verifies that the sign-in form has been filled with the new username |
+ // and password. |
+ EvaluateJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value", |
+ @[ kEmailInputID, kPasswordInputID ], |
+ @[ username2, password ]); |
+} |
+ |
+// Check that when instructed to fill a form named "bar", a form named "foo" |
+// is not filled with generated password. |
+TEST_F(PasswordControllerJsTest, |
+ FillPasswordFormWithGeneratedPassword_FailsWhenFormNotFound) { |
+ LoadHtmlAndInject(@"<html>" |
+ " <body>" |
+ " <form name=\"foo\">" |
+ " <input type=\"password\" name=\"ps\">" |
+ " </form>" |
+ " </body" |
+ "</html>"); |
+ NSString* const formName = @"bar"; |
+ NSString* const password = @"abc"; |
+ EXPECT_NSEQ(@"false", |
+ EvaluateJavaScriptWithFormat( |
+ @"__gCrWeb.fillPasswordFormWithGeneratedPassword('%@', '%@')", |
+ formName, password)); |
+} |
+ |
+// Check that filling a form without password fields fails. |
+TEST_F(PasswordControllerJsTest, |
+ FillPasswordFormWithGeneratedPassword_FailsWhenNoFieldsFilled) { |
+ LoadHtmlAndInject(@"<html>" |
+ " <body>" |
+ " <form name=\"foo\">" |
+ " <input type=\"text\" name=\"user\">" |
+ " <input type=\"submit\" name=\"go\">" |
+ " </form>" |
+ " </body" |
+ "</html>"); |
+ NSString* const formName = @"foo"; |
+ NSString* const password = @"abc"; |
+ EXPECT_NSEQ(@"false", |
+ EvaluateJavaScriptWithFormat( |
+ @"__gCrWeb.fillPasswordFormWithGeneratedPassword('%@', '%@')", |
+ formName, password)); |
+} |
+ |
+// Check that a matching and complete password form is successfully filled |
+// with the generated password. |
+TEST_F(PasswordControllerJsTest, |
+ FillPasswordFormWithGeneratedPassword_SucceedsWhenFieldsFilled) { |
+ LoadHtmlAndInject(@"<html>" |
+ " <body>" |
+ " <form name=\"foo\">" |
+ " <input type=\"text\" id=\"user\" name=\"user\">" |
+ " <input type=\"password\" id=\"ps1\" name=\"ps1\">" |
+ " <input type=\"password\" id=\"ps2\" name=\"ps2\">" |
+ " <input type=\"submit\" name=\"go\">" |
+ " </form>" |
+ " </body" |
+ "</html>"); |
+ NSString* const formName = @"foo"; |
+ NSString* const password = @"abc"; |
+ EXPECT_NSEQ(@"true", |
+ EvaluateJavaScriptWithFormat( |
+ @"__gCrWeb.fillPasswordFormWithGeneratedPassword('%@', '%@')", |
+ formName, password)); |
+ EXPECT_NSEQ(@"true", |
+ EvaluateJavaScriptWithFormat( |
+ @"document.getElementById('ps1').value == '%@'", password)); |
+ EXPECT_NSEQ(@"true", |
+ EvaluateJavaScriptWithFormat( |
+ @"document.getElementById('ps2').value == '%@'", password)); |
+ EXPECT_NSEQ(@"false", |
+ EvaluateJavaScriptWithFormat( |
+ @"document.getElementById('user').value == '%@'", password)); |
+} |
+ |
+// Check that one password form is identified and serialized correctly. |
+TEST_F(PasswordControllerJsTest, |
+ FindAndPreparePasswordFormsSingleFrameSingleForm) { |
+ LoadHtmlAndInject( |
+ @"<html><body>" |
+ "<form action='/generic_submit' method='post' name='login_form'>" |
+ " Name: <input type='text' name='name'>" |
+ " Password: <input type='password' name='password'>" |
+ " <input type='submit' value='Submit'>" |
+ "</form>" |
+ "</body></html>"); |
+ |
+ const std::string base_url = BaseUrl(); |
+ NSString* result = [NSString |
+ stringWithFormat: |
+ @"[{\"action\":\"/generic_submit\"," |
+ "\"method\":\"post\"," |
+ "\"name\":\"login_form\"," |
+ "\"origin\":\"%s\"," |
+ "\"fields\":[{\"element\":\"name\",\"type\":\"text\"}," |
+ "{\"element\":\"password\",\"type\":\"password\"}," |
+ "{\"element\":\"\",\"type\":\"submit\"}]," |
+ "\"usernameElement\":\"name\"," |
+ "\"usernameValue\":\"\"," |
+ "\"passwords\":[{\"element\":\"password\",\"value\":\"\"}]}]", |
+ base_url.c_str()]; |
+ EXPECT_NSEQ(result, |
+ EvaluateJavaScriptWithFormat(@"__gCrWeb.findPasswordForms()")); |
+}; |
+ |
+// Check that multiple password forms are identified and serialized correctly. |
+TEST_F(PasswordControllerJsTest, |
+ FindAndPreparePasswordFormsSingleFrameMultipleForms) { |
+ LoadHtmlAndInject( |
+ @"<html><body>" |
+ "<form action='/generic_submit' method='post' id='login_form1'>" |
+ " Name: <input type='text' name='name'>" |
+ " Password: <input type='password' name='password'>" |
+ " <input type='submit' value='Submit'>" |
+ "</form>" |
+ "<form action='/generic_s2' method='post' name='login_form2'>" |
+ " Name: <input type='text' name='name2'>" |
+ " Password: <input type='password' name='password2'>" |
+ " <input type='submit' value='Submit'>" |
+ "</form>" |
+ "</body></html>"); |
+ |
+ const std::string base_url = BaseUrl(); |
+ NSString* result = [NSString |
+ stringWithFormat: |
+ @"[{\"action\":\"/generic_submit\"," |
+ "\"method\":\"post\"," |
+ "\"name\":\"login_form1\"," |
+ "\"origin\":\"%s\"," |
+ "\"fields\":[{\"element\":\"name\",\"type\":\"text\"}," |
+ "{\"element\":\"password\",\"type\":\"password\"}," |
+ "{\"element\":\"\",\"type\":\"submit\"}]," |
+ "\"usernameElement\":\"name\"," |
+ "\"usernameValue\":\"\"," |
+ "\"passwords\":[{\"element\":\"password\",\"value\":\"\"}]}," |
+ "{\"action\":\"/generic_s2\"," |
+ "\"method\":\"post\"," |
+ "\"name\":\"login_form2\"," |
+ "\"origin\":\"%s\"," |
+ "\"fields\":[{\"element\":\"name2\",\"type\":\"text\"}," |
+ "{\"element\":\"password2\",\"type\":\"password\"}," |
+ "{\"element\":\"\",\"type\":\"submit\"}]," |
+ "\"usernameElement\":\"name2\"," |
+ "\"usernameValue\":\"\"," |
+ "\"passwords\":[{\"element\":\"password2\",\"value\":\"\"}]}]", |
+ base_url.c_str(), base_url.c_str()]; |
+ EXPECT_NSEQ(result, |
+ EvaluateJavaScriptWithFormat(@"__gCrWeb.findPasswordForms()")); |
+}; |
+ |
+// Test serializing of password forms. |
+TEST_F(PasswordControllerJsTest, GetPasswordFormData) { |
+ LoadHtmlAndInject( |
+ @"<html><body>" |
+ "<form name='np' id='np1' action='/generic_submit' method='post'>" |
+ " Name: <input type='text' name='name'>" |
+ " Password: <input type='password' name='password'>" |
+ " <input type='submit' value='Submit'>" |
+ "</form>" |
+ "</body></html>"); |
+ |
+ const std::string base_url = BaseUrl(); |
+ NSString* parameter = @"window.document.getElementsByTagName('form')[0]"; |
+ NSString* result = [NSString |
+ stringWithFormat: |
+ @"{\"action\":\"/generic_submit\"," |
+ "\"method\":\"post\"," |
+ "\"name\":\"np\"," |
+ "\"origin\":\"%s\"," |
+ "\"fields\":[{\"element\":\"name\",\"type\":\"text\"}," |
+ "{\"element\":\"password\",\"type\":\"password\"}," |
+ "{\"element\":\"\",\"type\":\"submit\"}]," |
+ "\"usernameElement\":\"name\"," |
+ "\"usernameValue\":\"\"," |
+ "\"passwords\":[{\"element\":\"password\",\"value\":\"\"}]}", |
+ base_url.c_str()]; |
+ EXPECT_NSEQ( |
+ result, |
+ EvaluateJavaScriptWithFormat( |
+ @"__gCrWeb.stringify(__gCrWeb.getPasswordFormData(%@))", parameter)); |
+}; |
+ |
+} // namespace |