diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginUpdateProfilePage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginUpdateProfilePage.java new file mode 100644 index 00000000000..f03b6164ba0 --- /dev/null +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginUpdateProfilePage.java @@ -0,0 +1,334 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testframework.ui.page; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; + +import org.openqa.selenium.By; +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +public class LoginUpdateProfilePage extends AbstractLoginPage { + + @FindBy(id = "username") + private WebElement usernameInput; + + @FindBy(name = "firstName") + private WebElement firstNameInput; + + @FindBy(name = "lastName") + private WebElement lastNameInput; + + @FindBy(name = "email") + private WebElement emailInput; + + @FindBy(name = "department") + private WebElement departmentInput; + + @FindBy(css = "input[type=\"submit\"]") + private WebElement submitButton; + + @FindBy(name = "cancel-aia") + private WebElement cancelAIAButton; + + @FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']") + private WebElement loginAlertErrorMessage; + + private final UpdateProfileErrors errorsPage; + + public LoginUpdateProfilePage(ManagedWebDriver driver) { + super(driver); + this.errorsPage = new UpdateProfileErrors(driver); + } + + public void update(String firstName, String lastName) { + prepareUpdate().firstName(firstName).lastName(lastName).submit(); + } + + public void update(String firstName, String lastName, String email) { + prepareUpdate().firstName(firstName).lastName(lastName).email(email).submit(); + } + + public void update(Map attributes) { + prepareUpdate().otherProfileAttribute(attributes).submit(); + } + + public Update prepareUpdate() { + return new Update(this); + } + + public void cancel() { + cancelAIAButton.click(); + } + + public String getAlertError() { + try { + return loginAlertErrorMessage.getText(); + } catch (NoSuchElementException e) { + return null; + } + } + + public String getUsername() { + return readValue(By.id("username")); + } + + public String getFirstName() { + return readValue(By.name("firstName")); + } + + public String getLastName() { + return readValue(By.name("lastName")); + } + + public String getEmail() { + return readValue(By.name("email")); + } + + public String getDepartment() { + return readValue(By.name("department")); + } + + private String readValue(By locator) { + return driver.waiting().until(d -> { + try { + return d.findElement(locator).getAttribute("value"); + } catch (StaleElementReferenceException e) { + return null; + } + }); + } + + public boolean isDepartmentEnabled() { + return departmentInput.isEnabled(); + } + + public UpdateProfileErrors getInputErrors() { + return errorsPage; + } + + public String getLabelForField(String fieldId) { + return driver.findElement(By.cssSelector("label[for=" + fieldId + "]")).getText().replaceAll("\\s\\*$", ""); + } + + public WebElement getElementById(String fieldId) { + try { + return driver.findElement(By.id(fieldId)); + } catch (NoSuchElementException ignore) { + return null; + } + } + + public boolean isUsernamePresent() { + try { + return usernameInput.isDisplayed(); + } catch (NoSuchElementException nse) { + return false; + } + } + + public boolean isEmailInputPresent() { + try { + return emailInput.isDisplayed(); + } catch (NoSuchElementException e) { + return false; + } + } + + public boolean isDepartmentPresent() { + try { + return departmentInput.isDisplayed(); + } catch (NoSuchElementException e) { + return false; + } + } + + public boolean isCancelDisplayed() { + try { + return cancelAIAButton.isDisplayed(); + } catch (NoSuchElementException e) { + return false; + } + } + + public void setAttribute(String elementId, String value) { + WebElement element = getElementById(elementId); + + if (element != null) { + element.clear(); + element.sendKeys(value); + } + } + + public void clickAddAttributeValue(String elementId) { + WebElement element = getElementById("kc-add-" + elementId); + + if (element != null) { + element.click(); + } + } + + public void clickRemoveAttributeValue(String elementId) { + WebElement element = getElementById("kc-remove-" + elementId); + + if (element != null) { + element.click(); + } + } + + public String getAttribute(String elementId) { + WebElement element = getElementById(elementId); + + if (element != null) { + return element.getAttribute("value"); + } + + return null; + } + + @Override + public String getExpectedPageId() { + return "login-login-update-profile"; + } + + public static class Update { + private final LoginUpdateProfilePage page; + private String username; + private String firstName; + private String lastName; + private String department; + private String email; + private final Map other = new LinkedHashMap<>(); + + protected Update(LoginUpdateProfilePage page) { + this.page = page; + } + + public Update username(String username) { + this.username = username; + return this; + } + + public Update firstName(String firstName) { + this.firstName = firstName; + return this; + } + + public Update lastName(String lastName) { + this.lastName = lastName; + return this; + } + + public Update department(String department) { + this.department = department; + return this; + } + + public Update email(String email) { + this.email = email; + return this; + } + + public Update otherProfileAttribute(Map attributes) { + other.putAll(attributes); + return this; + } + + public void submit() { + if (username != null) { + page.usernameInput.clear(); + page.usernameInput.sendKeys(username); + } + if (firstName != null) { + page.firstNameInput.clear(); + page.firstNameInput.sendKeys(firstName); + } + if (lastName != null) { + page.lastNameInput.clear(); + page.lastNameInput.sendKeys(lastName); + } + + if (department != null) { + page.departmentInput.clear(); + page.departmentInput.sendKeys(department); + } + + if (email != null) { + page.emailInput.clear(); + page.emailInput.sendKeys(email); + } + + for (Map.Entry entry : other.entrySet()) { + WebElement el = page.driver.findElement(By.id(entry.getKey())); + if (el != null) { + el.clear(); + el.sendKeys(entry.getValue()); + } + } + + page.submitButton.submit(); + } + } + + // For managing input errors + public static class UpdateProfileErrors { + + private final ManagedWebDriver driver; + + public UpdateProfileErrors(ManagedWebDriver driver) { + this.driver = driver; + } + + private String getTextById(String id) { + try { + return driver.findElement(By.id(id)).getText(); + } catch (NoSuchElementException e) { + return null; + } + } + + public String getFirstNameError() { + String text = getTextById("input-error-firstname"); + if (text != null) { + return text; + } + return getTextById("input-error-firstName"); + } + + public String getLastNameError() { + String text = getTextById("input-error-lastname"); + if (text != null) { + return text; + } + return getTextById("input-error-lastName"); + } + + public String getEmailError() { + return getTextById("input-error-email"); + } + + public String getUsernameError() { + return getTextById("input-error-username"); + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/tests/base/src/test/java/org/keycloak/tests/actions/RequiredActionUpdateProfileTest.java old mode 100755 new mode 100644 similarity index 66% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java rename to tests/base/src/test/java/org/keycloak/tests/actions/RequiredActionUpdateProfileTest.java index 6a54479c8c8..bc51b0d7a76 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/actions/RequiredActionUpdateProfileTest.java @@ -14,7 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.testsuite.actions; + +package org.keycloak.tests.actions; import java.util.Arrays; import java.util.HashMap; @@ -29,116 +30,114 @@ import org.keycloak.admin.client.resource.UserResource; import org.keycloak.events.Details; import org.keycloak.events.EventType; import org.keycloak.models.UserModel; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.userprofile.config.UPAttribute; import org.keycloak.representations.userprofile.config.UPAttributePermissions; import org.keycloak.representations.userprofile.config.UPConfig; +import org.keycloak.testframework.annotations.InjectEvents; +import org.keycloak.testframework.annotations.InjectRealm; +import org.keycloak.testframework.annotations.InjectUser; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; import org.keycloak.testframework.events.EventAssertion; +import org.keycloak.testframework.events.Events; +import org.keycloak.testframework.injection.LifeCycle; +import org.keycloak.testframework.oauth.OAuthClient; +import org.keycloak.testframework.oauth.annotations.InjectOAuthClient; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.ManagedUser; +import org.keycloak.testframework.realm.RealmBuilder; +import org.keycloak.testframework.realm.RealmConfig; import org.keycloak.testframework.realm.UserBuilder; -import org.keycloak.testsuite.AbstractChangeImportedUserPasswordsTest; -import org.keycloak.testsuite.AssertEvents; -import org.keycloak.testsuite.admin.AdminApiUtil; -import org.keycloak.testsuite.arquillian.annotation.IgnoreBrowserDriver; -import org.keycloak.testsuite.pages.AppPage; -import org.keycloak.testsuite.pages.AppPage.RequestType; -import org.keycloak.testsuite.pages.ErrorPage; -import org.keycloak.testsuite.pages.LoginPage; -import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage; +import org.keycloak.testframework.realm.UserConfig; +import org.keycloak.testframework.ui.annotations.InjectPage; +import org.keycloak.testframework.ui.annotations.InjectWebDriver; +import org.keycloak.testframework.ui.page.ErrorPage; +import org.keycloak.testframework.ui.page.LoginPage; +import org.keycloak.testframework.ui.page.LoginUpdateProfilePage; +import org.keycloak.testframework.ui.webdriver.BrowserType; +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; +import org.keycloak.tests.suites.DatabaseTest; +import org.keycloak.tests.utils.PasswordGenerateUtil; +import org.keycloak.tests.utils.admin.AdminApiUtil; import org.keycloak.userprofile.UserProfileContext; import org.keycloak.utils.StringUtil; import org.hamcrest.Matchers; -import org.jboss.arquillian.graphene.page.Page; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; import org.openqa.selenium.ElementClickInterceptedException; -import org.openqa.selenium.htmlunit.HtmlUnitDriver; import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN; import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; -/** - * @author Stian Thorgersen - */ -public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserPasswordsTest { +@KeycloakIntegrationTest +@DatabaseTest +public class RequiredActionUpdateProfileTest { - @Rule - public AssertEvents events = new AssertEvents(this); + private static final String PASSWORD = PasswordGenerateUtil.generatePassword(); - @Page - protected AppPage appPage; + @InjectRealm(config = RequiredActionUpdateProfileRealmConfig.class) + ManagedRealm realm; - @Page + @InjectWebDriver + ManagedWebDriver driver; + + @InjectOAuthClient + OAuthClient oauth; + + @InjectEvents + Events events; + + @InjectUser(ref = "testUser", config = TestUserConfig.class, lifecycle = LifeCycle.METHOD) + ManagedUser testUser; + + @InjectUser(ref = "johnDoh", config = JohnDohUserConfig.class, lifecycle = LifeCycle.METHOD) + ManagedUser johnDohUser; + + @InjectPage protected LoginPage loginPage; - @Page - protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage; + @InjectPage + protected LoginUpdateProfilePage updateProfilePage; - @Page + @InjectPage protected ErrorPage errorPage; - @Override - public void configureTestRealm(RealmRepresentation testRealm) { - super.configureTestRealm(testRealm); - ActionUtil.addRequiredActionForUser(testRealm, "test-user@localhost", UserModel.RequiredAction.UPDATE_PROFILE.name()); - ActionUtil.addRequiredActionForUser(testRealm, "john-doh@localhost", UserModel.RequiredAction.UPDATE_PROFILE.name()); - } - - @Before - public void beforeTest() { - AdminApiUtil.removeUserByUsername(managedRealm.admin(), "test-user@localhost"); - UserRepresentation user = UserBuilder.create().enabled(true) - .username("test-user@localhost") - .email("test-user@localhost") - .firstName("Tom") - .lastName("Brady") - .emailVerified(true) - .requiredActions(UserModel.RequiredAction.UPDATE_PROFILE.name()).build(); - AdminApiUtil.createUserAndResetPasswordWithAdminClient(managedRealm.admin(), user, generatePassword("test-user@localhost")); - - AdminApiUtil.removeUserByUsername(managedRealm.admin(), "john-doh@localhost"); - user = UserBuilder.create().enabled(true) - .username("john-doh@localhost") - .email("john-doh@localhost") - .firstName("John") - .lastName("Doh") - .emailVerified(true) - .requiredActions(UserModel.RequiredAction.UPDATE_PROFILE.name()).build(); - AdminApiUtil.createUserAndResetPasswordWithAdminClient(managedRealm.admin(), user, generatePassword("john-doh@localhost")); - } - @Test public void updateProfile() { oauth.openLoginForm(); - UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost"); - user.setEmailVerified(true); - adminClient.realm("test").users().get(user.getId()).update(user); - loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + + loginPage.fillLogin("test-user@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); assertFalse(updateProfilePage.isCancelDisplayed()); updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit(); - events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "New first") - .detail(Details.PREVIOUS_LAST_NAME, "Brady").detail(Details.UPDATED_LAST_NAME, "New last") - .detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com") - .assertEvent(); - Assertions.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + EventAssertion.assertSuccess(pollEvent()) + .type(EventType.UPDATE_PROFILE) + .details(Details.PREVIOUS_FIRST_NAME, "Tom") + .details(Details.UPDATED_FIRST_NAME, "New first") + .details(Details.PREVIOUS_LAST_NAME, "Brady") + .details(Details.UPDATED_LAST_NAME, "New last") + .details(Details.PREVIOUS_EMAIL, "test-user@localhost") + .details(Details.UPDATED_EMAIL, "new@email.com"); + assertThat(oauth.parseLoginResponse().getCode(), notNullValue()); - EventAssertion.expectLoginSuccess(events.poll()); + EventAssertion.expectLoginSuccess(pollEvent()); // assert user is really updated in persistent store - user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost"); + UserRepresentation user = testUser.admin().toRepresentation(); Assertions.assertEquals("New first", user.getFirstName()); Assertions.assertEquals("New last", user.getLastName()); Assertions.assertEquals("new@email.com", user.getEmail()); @@ -151,15 +150,16 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP public void updateUsername() { oauth.openLoginForm(); - loginPage.login("john-doh@localhost", getPassword("john-doh@localhost")); + loginPage.fillLogin("john-doh@localhost", PASSWORD); + loginPage.submit(); - String userId = ActionUtil.findUserWithAdminClient(adminClient, "john-doh@localhost").getId(); + String userId = johnDohUser.getId(); updateProfilePage.assertCurrent(); updateProfilePage.prepareUpdate().username("new").firstName("New first").lastName("New last").email("john-doh@localhost").submit(); - EventAssertion.assertSuccess(events.poll()) + EventAssertion.assertSuccess(pollEvent()) .type(EventType.UPDATE_PROFILE) .isCodeId() .sessionId(null) @@ -169,26 +169,26 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP .details(Details.UPDATED_LAST_NAME, "New last") .withoutDetails(Details.CONSENT); - Assertions.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + assertThat(oauth.parseLoginResponse().getCode(), notNullValue()); - EventAssertion.expectLoginSuccess(events.poll()).details(Details.USERNAME, "john-doh@localhost").userId(userId); + EventAssertion.expectLoginSuccess(pollEvent()).details(Details.USERNAME, "john-doh@localhost").userId(userId); // assert user is really updated in persistent store - UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "new"); + UserRepresentation user = johnDohUser.admin().toRepresentation(); Assertions.assertEquals("New first", user.getFirstName()); Assertions.assertEquals("New last", user.getLastName()); Assertions.assertEquals("john-doh@localhost", user.getEmail()); Assertions.assertEquals("new", user.getUsername()); // email not changed so verify that emailVerified flag is NOT reset Assertions.assertEquals(true, user.isEmailVerified()); - getCleanup().addUserId(user.getId()); } @Test public void updateProfileMissingFirstName() { oauth.openLoginForm(); - loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + loginPage.fillLogin("test-user@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); @@ -202,14 +202,15 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP Assertions.assertEquals("new@email.com", updateProfilePage.getEmail()); Assertions.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getFirstNameError()); - events.assertEmpty(); + assertNoMoreEvents(); } @Test public void updateProfileMissingLastName() { oauth.openLoginForm(); - loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + loginPage.fillLogin("test-user@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); @@ -224,14 +225,15 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP Assertions.assertEquals("Please specify this field.", updateProfilePage.getInputErrors().getLastNameError()); - events.assertEmpty(); + assertNoMoreEvents(); } @Test public void updateProfileMissingEmail() { oauth.openLoginForm(); - loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + loginPage.fillLogin("test-user@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); @@ -250,19 +252,20 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP containsString("Please specify this field") )); - events.assertEmpty(); + assertNoMoreEvents(); } @Test public void updateProfileInvalidEmail() { oauth.openLoginForm(); - loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + loginPage.fillLogin("test-user@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); updateProfilePage.prepareUpdate().username("invalid").firstName("New first").lastName("New last") - .email("invalidemail").submit(); + .email("invalidemail").submit(); updateProfilePage.assertCurrent(); @@ -273,14 +276,15 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP Assertions.assertEquals("Invalid email address.", updateProfilePage.getInputErrors().getEmailError()); - events.assertEmpty(); + assertNoMoreEvents(); } @Test public void updateProfileMissingUsername() { oauth.openLoginForm(); - loginPage.login("john-doh@localhost", getPassword("john-doh@localhost")); + loginPage.fillLogin("john-doh@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); @@ -296,14 +300,15 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP Assertions.assertEquals("Please specify username.", updateProfilePage.getInputErrors().getUsernameError()); - events.assertEmpty(); + assertNoMoreEvents(); } @Test public void updateProfileDuplicateUsername() { oauth.openLoginForm(); - loginPage.login("john-doh@localhost", getPassword("john-doh@localhost")); + loginPage.fillLogin("john-doh@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); @@ -319,14 +324,15 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP Assertions.assertEquals("Username already exists.", updateProfilePage.getInputErrors().getUsernameError()); - events.assertEmpty(); + assertNoMoreEvents(); } @Test public void updateProfileDuplicatedEmail() { oauth.openLoginForm(); - loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + loginPage.fillLogin("test-user@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); @@ -342,16 +348,18 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP Assertions.assertEquals("Email already exists.", updateProfilePage.getInputErrors().getEmailError()); - events.assertEmpty(); + assertNoMoreEvents(); } @Test public void updateProfileDuplicateUsernameWithEmail() { - getCleanup().addUserId(createUser(TEST_REALM_NAME, "user1@local.com", generatePassword("user1@local.com"), "user1", "user1", "user1@local.org")); + String userId = createUser("user1@local.com", "user1", "user1", "user1@local.org"); + realm.cleanup().add(r -> r.users().get(userId).remove()); oauth.openLoginForm(); - loginPage.login("john-doh@localhost", getPassword("john-doh@localhost")); + loginPage.fillLogin("john-doh@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); @@ -367,16 +375,18 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP Assertions.assertEquals("Username already exists.", updateProfilePage.getInputErrors().getUsernameError()); - events.assertEmpty(); + assertNoMoreEvents(); } @Test public void updateProfileDuplicatedEmailWithUsername() { - getCleanup().addUserId(createUser(TEST_REALM_NAME, "user1@local.com", generatePassword("user1@local.com"), "user1", "user1", "user1@local.org")); + String userId = createUser("user1@local.com", "user1", "user1", "user1@local.org"); + realm.cleanup().add(r -> r.users().get(userId).remove()); oauth.openLoginForm(); - loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + loginPage.fillLogin("test-user@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); @@ -392,38 +402,34 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP Assertions.assertEquals("Email already exists.", updateProfilePage.getInputErrors().getEmailError()); - events.assertEmpty(); + assertNoMoreEvents(); } @Test public void updateProfileExpiredCookies() { oauth.openLoginForm(); - loginPage.login("john-doh@localhost", getPassword("john-doh@localhost")); + loginPage.fillLogin("john-doh@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); // Expire cookies and assert the page with "back to application" link present - driver.manage().deleteAllCookies(); + driver.cookies().deleteAll(); updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("keycloak-user@localhost").submit(); errorPage.assertCurrent(); - - String backToAppLink = errorPage.getBackToApplicationLink(); - - ClientRepresentation client = AdminApiUtil.findClientByClientId(adminClient.realm("test"), "test-app").toRepresentation(); - Assertions.assertEquals(backToAppLink, client.getBaseUrl()); } @Test public void updateProfileWithoutRemoveCustomAttributes() { - UserProfileResource upResource = adminClient.realm("test").users().userProfile(); + UserProfileResource upResource = realm.admin().users().userProfile(); UPConfig upConfig = upResource.getConfiguration(); upConfig.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ADMIN_EDIT); upResource.update(upConfig); try { - UserRepresentation userRep = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost"); - UserResource user = adminClient.realm("test").users().get(userRep.getId()); + UserResource user = testUser.admin(); + UserRepresentation userRep = user.toRepresentation(); userRep.setAttributes(new HashMap<>()); userRep.getAttributes().put("custom", Arrays.asList("custom")); @@ -432,21 +438,26 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP oauth.openLoginForm(); - loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + loginPage.fillLogin("test-user@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); assertFalse(updateProfilePage.isCancelDisplayed()); updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit(); - events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.UPDATE_PROFILE.name()).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent(); + EventAssertion.assertSuccess(pollEvent()) + .type(EventType.UPDATE_PROFILE) + .details(Details.CONTEXT, UserProfileContext.UPDATE_PROFILE.name()) + .details(Details.PREVIOUS_EMAIL, "test-user@localhost") + .details(Details.UPDATED_EMAIL, "new@email.com"); - Assertions.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + assertThat(oauth.parseLoginResponse().getCode(), notNullValue()); - EventAssertion.expectLoginSuccess(events.poll()); + EventAssertion.expectLoginSuccess(pollEvent()); // assert user is really updated in persistent store - userRep = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost"); + userRep = testUser.admin().toRepresentation(); Assertions.assertEquals("New first", userRep.getFirstName()); Assertions.assertEquals("New last", userRep.getLastName()); Assertions.assertEquals("new@email.com", userRep.getEmail()); @@ -460,9 +471,10 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP } @Test - @IgnoreBrowserDriver(HtmlUnitDriver.class) // we can't yet run modern JavaScript using HtmlUnit public void testMultivaluedAttributes() { - UserProfileResource userProfile = managedRealm.admin().users().userProfile(); + Assumptions.assumeFalse(driver.getBrowserType().equals(BrowserType.HTML_UNIT)); // we can't yet run modern JavaScript using HtmlUnit + + UserProfileResource userProfile = realm.admin().users().userProfile(); UPConfig configuration = userProfile.getConfiguration(); try { @@ -480,7 +492,8 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP userProfile.update(testUpConfig); oauth.openLoginForm(); - loginPage.login("john-doh@localhost", getPassword("john-doh@localhost")); + loginPage.fillLogin("john-doh@localhost", PASSWORD); + loginPage.submit(); updateProfilePage.assertCurrent(); for (String attribute : attributes) { @@ -493,12 +506,13 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP updateProfilePage.clickAddAttributeValue(elementId); } updateProfilePage.update("f", "l", "e@keycloak.org"); - UserRepresentation userRep = ActionUtil.findUserWithAdminClient(adminClient, "john-doh@localhost"); + driver.waiting().waitForOAuthCallback(); + UserRepresentation userRep = johnDohUser.admin().toRepresentation(); assertThat(userRep.getAttributes().get(attribute), Matchers.containsInAnyOrder(values.toArray())); // make sure multiple values are properly rendered userRep.setRequiredActions(List.of(UserModel.RequiredAction.UPDATE_PROFILE.name())); - managedRealm.admin().users().get(userRep.getId()).update(userRep); + johnDohUser.admin().update(userRep); oauth.openLoginForm(); assertThat(IntStream.range(0, 5).mapToObj(value -> updateProfilePage.getAttribute(attribute + "-" + value)).collect(Collectors.toSet()), Matchers.equalTo(valuesSet)); @@ -515,13 +529,14 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP } } updateProfilePage.update("f", "l", "e@keycloak.org"); - userRep = ActionUtil.findUserWithAdminClient(adminClient, "john-doh@localhost"); + driver.waiting().waitForOAuthCallback(); + userRep = johnDohUser.admin().toRepresentation(); assertThat(userRep.getAttributes().get(attribute), Matchers.hasSize(1)); assertThat(userRep.getAttributes().get(attribute).get(0), Matchers.in(values)); // make sure adding/removing within the same context works userRep.setRequiredActions(List.of(UserModel.RequiredAction.UPDATE_PROFILE.name())); - managedRealm.admin().users().get(userRep.getId()).update(userRep); + johnDohUser.admin().update(userRep); oauth.openLoginForm(); for (String value : values) { String elementId = attribute + "-" + value; @@ -545,13 +560,14 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP updateProfilePage.setAttribute(attribute + "-0", lastValue); } updateProfilePage.update("f", "l", "e@keycloak.org"); - userRep = ActionUtil.findUserWithAdminClient(adminClient, "john-doh@localhost"); + driver.waiting().waitForOAuthCallback(); + userRep = johnDohUser.admin().toRepresentation(); assertThat(userRep.getAttributes().get(attribute), Matchers.hasSize(1)); assertThat(userRep.getAttributes().get(attribute).get(0), Matchers.in(values)); // at the end the attribute is set with multiple values userRep.setRequiredActions(List.of(UserModel.RequiredAction.UPDATE_PROFILE.name())); - managedRealm.admin().users().get(userRep.getId()).update(userRep); + johnDohUser.admin().update(userRep); oauth.openLoginForm(); for (String value : values) { String elementId = attribute + "-" + value; @@ -559,15 +575,16 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP updateProfilePage.clickAddAttributeValue(elementId); } updateProfilePage.update("f", "l", "e@keycloak.org"); + driver.waiting().waitForOAuthCallback(); // restart the update profile flow - userRep = ActionUtil.findUserWithAdminClient(adminClient, "john-doh@localhost"); + userRep = johnDohUser.admin().toRepresentation(); userRep.setRequiredActions(List.of(UserModel.RequiredAction.UPDATE_PROFILE.name())); - managedRealm.admin().users().get(userRep.getId()).update(userRep); + johnDohUser.admin().update(userRep); oauth.openLoginForm(); } - UserRepresentation userRep = ActionUtil.findUserWithAdminClient(adminClient, "john-doh@localhost"); + UserRepresentation userRep = johnDohUser.admin().toRepresentation(); // all attributes should be set with multiple values for (String attribute : attributes) { @@ -578,4 +595,64 @@ public class RequiredActionUpdateProfileTest extends AbstractChangeImportedUserP } } + private String createUser(String username, String firstName, String lastName, String email) { + UserRepresentation user = UserBuilder.create().enabled(true) + .username(username) + .email(email) + .firstName(firstName) + .lastName(lastName) + .emailVerified(true) + .build(); + return AdminApiUtil.createUserAndResetPasswordWithAdminClient(realm.admin(), user, PASSWORD); + } + + private void assertNoMoreEvents() { + EventRepresentation event = events.poll(); + assertNull(event, "Expected no more events but got: " + (event != null ? event.getType() : null)); + } + + private EventRepresentation pollEvent() { + return driver.waiting().until(d -> events.poll()); + } + + public static class RequiredActionUpdateProfileRealmConfig implements RealmConfig { + + @Override + public RealmBuilder configure(RealmBuilder realm) { + realm.editUsernameAllowed(true); + realm.users(UserBuilder.create("keycloak-user@localhost") + .email("keycloak-user@localhost") + .name("keycloak", "User") + .password(PASSWORD) + .emailVerified(true)); + return realm; + } + } + + public static class TestUserConfig implements UserConfig { + + @Override + public UserBuilder configure(UserBuilder user) { + return user.username("test-user@localhost") + .email("test-user@localhost") + .name("Tom", "Brady") + .password(PASSWORD) + .emailVerified(true) + .requiredActions(UserModel.RequiredAction.UPDATE_PROFILE.name()); + } + } + + public static class JohnDohUserConfig implements UserConfig { + + @Override + public UserBuilder configure(UserBuilder user) { + return user.username("john-doh@localhost") + .email("john-doh@localhost") + .name("John", "Doh") + .password(PASSWORD) + .emailVerified(true) + .requiredActions(UserModel.RequiredAction.UPDATE_PROFILE.name()); + } + } + } diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java index 21537461d92..30c8fa9c09d 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java @@ -6,6 +6,7 @@ import org.junit.platform.suite.api.Suite; @Suite @SelectPackages({ "org.keycloak.tests.account", + "org.keycloak.tests.actions", "org.keycloak.tests.authz", "org.keycloak.tests.broker", "org.keycloak.tests.client", diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/FormsTestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/FormsTestSuite.java index de650b1cc81..e4e1c8e3edb 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/FormsTestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/FormsTestSuite.java @@ -1,5 +1,6 @@ package org.keycloak.tests.suites; +import org.keycloak.tests.actions.RequiredActionUpdateProfileTest; import org.keycloak.tests.i18n.LoginPageTest; import org.junit.platform.suite.api.SelectClasses; @@ -8,7 +9,8 @@ import org.junit.platform.suite.api.Suite; @Suite // TODO: Select relevant test classes or packages once they have been migrated @SelectClasses({ - LoginPageTest.class + LoginPageTest.class, + RequiredActionUpdateProfileTest.class }) public class FormsTestSuite { } diff --git a/testsuite/integration-arquillian/tests/base/testsuites/database-suite b/testsuite/integration-arquillian/tests/base/testsuites/database-suite index a36cef720ce..477ef73a623 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/database-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/database-suite @@ -6,7 +6,6 @@ ExportImportTest KcOidcBrokerTest LDAPUserLoginTest LoginTest -RequiredActionUpdateProfileTest SamlClientTest UserProfileTest OidcAdvancedClaimToGroupMapperTest diff --git a/testsuite/integration-arquillian/tests/base/testsuites/forms-suite b/testsuite/integration-arquillian/tests/base/testsuites/forms-suite index dd7aa8cb081..0015b385607 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/forms-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/forms-suite @@ -1,3 +1,2 @@ org.keycloak.testsuite.forms.** -org.keycloak.testsuite.actions.RequiredActionUpdateProfileTest org.keycloak.testsuite.actions.TermsAndConditionsTest