keycloak-aplcache
Changes
testsuite/integration/pom.xml 8(+8 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java 150(+0 -150)
testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java 132(+0 -132)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java 18(+14 -4)
testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory 1(+0 -1)
testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl 13(+13 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json 6(+6 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl 114(+114 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties 18(+18 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html 72(+72 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties 18(+18 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl 95(+95 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl 131(+131 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties 18(+18 -0)
Details
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
index 2b5d35c..16b155b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
@@ -43,6 +43,7 @@ import java.util.Set;
@NamedQuery(name="getAttributesByNameAndValue", query="select attr from UserAttributeEntity attr where attr.name = :name and attr.value = :value"),
@NamedQuery(name="deleteUserAttributesByRealm", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteUserAttributesByNameAndUser", query="delete from UserAttributeEntity attr where attr.user.id = :userId and attr.name = :name"),
+ @NamedQuery(name="deleteUserAttributesOtherThan", query="delete from UserAttributeEntity attr where attr.user.id = :userId and attr.id <> :attrId"),
@NamedQuery(name="deleteUserAttributesByRealmAndLink", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
})
@Table(name="USER_ATTRIBUTE")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 2ea12fd..49bcac6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -125,29 +125,32 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
@Override
public void setSingleAttribute(String name, String value) {
- boolean found = false;
+ String firstExistingAttrId = null;
List<UserAttributeEntity> toRemove = new ArrayList<>();
for (UserAttributeEntity attr : user.getAttributes()) {
if (attr.getName().equals(name)) {
- if (!found) {
+ if (firstExistingAttrId == null) {
attr.setValue(value);
- found = true;
+ firstExistingAttrId = attr.getId();
} else {
toRemove.add(attr);
}
}
}
- for (UserAttributeEntity attr : toRemove) {
- em.remove(attr);
- user.getAttributes().remove(attr);
- }
+ if (firstExistingAttrId != null) {
+ // Remove attributes through HQL to avoid StaleUpdateException
+ Query query = em.createNamedQuery("deleteUserAttributesOtherThan");
+ query.setParameter("attrId", firstExistingAttrId);
+ query.setParameter("userId", user.getId());
+ int numUpdated = query.executeUpdate();
- if (found) {
- return;
- }
+ // Remove attribute from local entity
+ user.getAttributes().removeAll(toRemove);
+ } else {
- persistAttributeValue(name, value);
+ persistAttributeValue(name, value);
+ }
}
@Override
@@ -178,6 +181,15 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
query.setParameter("name", name);
query.setParameter("userId", user.getId());
int numUpdated = query.executeUpdate();
+
+ // KEYCLOAK-3494 : Also remove attributes from local user entity
+ List<UserAttributeEntity> toRemove = new ArrayList<>();
+ for (UserAttributeEntity attr : user.getAttributes()) {
+ if (attr.getName().equals(name)) {
+ toRemove.add(attr);
+ }
+ }
+ user.getAttributes().removeAll(toRemove);
}
@Override
diff --git a/server-spi/src/main/java/org/keycloak/models/Constants.java b/server-spi/src/main/java/org/keycloak/models/Constants.java
index 916565a..42982f6 100755
--- a/server-spi/src/main/java/org/keycloak/models/Constants.java
+++ b/server-spi/src/main/java/org/keycloak/models/Constants.java
@@ -50,5 +50,5 @@ public interface Constants {
String KEY = "key";
// Prefix for user attributes used in various "context"data maps
- public static final String USER_ATTRIBUTES_PREFIX = "user.attributes.";
+ String USER_ATTRIBUTES_PREFIX = "user.attributes.";
}
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java
index acb1514..857bfc0 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java
@@ -18,6 +18,7 @@
package org.keycloak.forms.account.freemarker.model;
import org.jboss.logging.Logger;
+import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import javax.ws.rs.core.MultivaluedMap;
@@ -55,8 +56,8 @@ public class AccountBean {
if (profileFormData != null) {
for (String key : profileFormData.keySet()) {
- if (key.startsWith("user.attributes.")) {
- String attribute = key.substring("user.attributes.".length());
+ if (key.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) {
+ String attribute = key.substring(Constants.USER_ATTRIBUTES_PREFIX.length());
attributes.put(attribute, profileFormData.getFirst(key));
}
}
testsuite/integration/pom.xml 8(+8 -0)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index aca1d78..92187bc 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -166,6 +166,14 @@
<groupId>org.keycloak</groupId>
<artifactId>federation-properties-example</artifactId>
</dependency>
+
+ <!-- Dependency on services from integration-arquillian -->
+ <dependency>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-testsuite-providers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
index 7683d0c..f6a85e4 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -45,8 +45,8 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.services.Urls;
-import org.keycloak.testsuite.DummyUserFederationProviderFactory;
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
+import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java
index 5d327f4..3831787 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java
@@ -32,7 +32,7 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.testsuite.DummyUserFederationProviderFactory;
+import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java
index 84a901a..efa688c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java
@@ -36,7 +36,7 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.UsersSyncManager;
-import org.keycloak.testsuite.DummyUserFederationProviderFactory;
+import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.timer.TimerProvider;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java
index b3ad0c4..2fb2aaf 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.model;
+import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
@@ -151,14 +152,17 @@ public class ConcurrentTransactionsTest extends AbstractModelTest {
}
- // KEYCLOAK-3296
+ // KEYCLOAK-3296 , KEYCLOAK-3494
@Test
public void removeUserAttribute() throws Exception {
RealmModel realm = realmManager.createRealm("original");
KeycloakSession session = realmManager.getSession();
- UserModel user = session.users().addUser(realm, "john");
- user.setSingleAttribute("foo", "val1");
+ UserModel john = session.users().addUser(realm, "john");
+ john.setSingleAttribute("foo", "val1");
+
+ UserModel john2 = session.users().addUser(realm, "john2");
+ john2.setAttribute("foo", Arrays.asList("val1", "val2"));
final KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
commit();
@@ -182,12 +186,18 @@ public class ConcurrentTransactionsTest extends AbstractModelTest {
UserModel john = session.users().getUserByUsername("john", realm);
String attrVal = john.getFirstAttribute("foo");
+ UserModel john2 = session.users().getUserByUsername("john2", realm);
+ String attrVal2 = john2.getFirstAttribute("foo");
+
// Wait until it's read in both threads
readAttrLatch.countDown();
readAttrLatch.await();
- // Remove user attribute in both threads
+ // KEYCLOAK-3296 : Remove user attribute in both threads
john.removeAttribute("foo");
+
+ // KEYCLOAK-3494 : Set single attribute in both threads
+ john2.setSingleAttribute("foo", "bar");
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
index 4b3f78e..e2af241 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -211,6 +211,31 @@ public class UserModelTest extends AbstractModelTest {
Assert.assertEquals("val23", attrVals.get(0));
}
+ // KEYCLOAK-3494
+ @Test
+ public void testUpdateUserAttribute() throws Exception {
+ RealmModel realm = realmManager.createRealm("original");
+ UserModel user = session.users().addUser(realm, "user");
+
+ user.setSingleAttribute("key1", "value1");
+
+ commit();
+
+ realm = realmManager.getRealmByName("original");
+ user = session.users().getUserByUsername("user", realm);
+
+ // Update attribute
+ List<String> attrVals = new ArrayList<>(Arrays.asList( "val2" ));
+ user.setAttribute("key1", attrVals);
+ Map<String, List<String>> allAttrVals = user.getAttributes();
+
+ // Ensure same transaction is able to see updated value
+ Assert.assertEquals(1, allAttrVals.size());
+ Assert.assertEquals(allAttrVals.get("key1"), Arrays.asList("val2"));
+
+ commit();
+ }
+
@Test
public void testSearchByString() {
RealmModel realm = realmManager.createRealm("original");
diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
index 1b4de73..d4a16d3 100755
--- a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
+++ b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
@@ -15,5 +15,4 @@
# limitations under the License.
#
-org.keycloak.testsuite.DummyUserFederationProviderFactory
org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl
index f32c036..664eecb 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl
@@ -36,6 +36,11 @@
</provider>
</spi>
</xsl:variable>
+ <xsl:variable name="themeModuleDefinition">
+ <modules>
+ <module>org.keycloak.testsuite.integration-arquillian-testsuite-providers</module>
+ </modules>
+ </xsl:variable>
<!--inject provider-->
<xsl:template match="//*[local-name()='providers']/*[local-name()='provider']">
@@ -46,6 +51,14 @@
<xsl:text>module:org.keycloak.testsuite.integration-arquillian-testsuite-providers</xsl:text>
</provider>
</xsl:template>
+
+ <!--inject provider for themes -->
+ <xsl:template match="//*[local-name()='theme']">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ <xsl:copy-of select="$themeModuleDefinition"/>
+ </xsl:copy>
+ </xsl:template>
<!--inject truststore-->
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsKS)]">
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json
new file mode 100644
index 0000000..03978db
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json
@@ -0,0 +1,6 @@
+{
+ "themes": [{
+ "name" : "address",
+ "types": [ "admin", "account", "login" ]
+ }]
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl
new file mode 100755
index 0000000..d2a6af1
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl
@@ -0,0 +1,114 @@
+<#import "template.ftl" as layout>
+<@layout.mainLayout active='account' bodyClass='user'; section>
+
+ <div class="row">
+ <div class="col-md-10">
+ <h2>${msg("editAccountHtmlTtile")}</h2>
+ </div>
+ <div class="col-md-2 subtitle">
+ <span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
+ </div>
+ </div>
+
+ <form action="${url.accountUrl}" class="form-horizontal" method="post">
+
+ <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
+
+ <div class="form-group ${messagesPerField.printIfExists('username','has-error')}">
+ <div class="col-sm-2 col-md-2">
+ <label for="username" class="control-label">${msg("username")}</label> <#if realm.editUsernameAllowed><span class="required">*</span></#if>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="username" name="username" <#if !realm.editUsernameAllowed>disabled="disabled"</#if> value="${(account.username!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group ${messagesPerField.printIfExists('email','has-error')}">
+ <div class="col-sm-2 col-md-2">
+ <label for="email" class="control-label">${msg("email")}</label> <span class="required">*</span>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="email" name="email" autofocus value="${(account.email!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group ${messagesPerField.printIfExists('firstName','has-error')}">
+ <div class="col-sm-2 col-md-2">
+ <label for="firstName" class="control-label">${msg("firstName")}</label> <span class="required">*</span>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="firstName" name="firstName" value="${(account.firstName!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group ${messagesPerField.printIfExists('lastName','has-error')}">
+ <div class="col-sm-2 col-md-2">
+ <label for="lastName" class="control-label">${msg("lastName")}</label> <span class="required">*</span>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="lastName" name="lastName" value="${(account.lastName!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.street" class="control-label">${msg("street")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.street" name="user.attributes.street" value="${(account.attributes.street!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.locality" class="control-label">${msg("locality")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.locality" name="user.attributes.locality" value="${(account.attributes.locality!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.region" class="control-label">${msg("region")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.region" name="user.attributes.region" value="${(account.attributes.region!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.postal_code" class="control-label">${msg("postal_code")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(account.attributes.postal_code!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.country" class="control-label">${msg("country")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.country" name="user.attributes.country" value="${(account.attributes.country!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
+ <div class="">
+ <#if url.referrerURI??><a href="${url.referrerURI}">${msg("backToApplication")}/a></#if>
+ <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
+ <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
+ </div>
+ </div>
+ </div>
+ </form>
+
+</@layout.mainLayout>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties
new file mode 100644
index 0000000..3e50437
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+parent=keycloak
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html
new file mode 100755
index 0000000..af512de
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html
@@ -0,0 +1,72 @@
+<!--
+ ~ 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.
+ -->
+
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
+ <li>{{user.username}}</li>
+ </ol>
+
+ <kc-tabs-user></kc-tabs-user>
+
+ <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageUsers">
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="street">Street</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.street" class="form-control" type="text" name="street" id="street" />
+ </div>
+ <kc-tooltip>Street address.</kc-tooltip>
+ </div>
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="locality">City or Locality</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.locality" class="form-control" type="text" name="locality" id="locality" />
+ </div>
+ <kc-tooltip>City or locality.</kc-tooltip>
+ </div>
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="region">State, Province, or Region</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.region" class="form-control" type="text" name="region" id="region" />
+ </div>
+ <kc-tooltip>State, province, prefecture, or region.</kc-tooltip>
+ </div>
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="postal_code">Zip or Postal code</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.postal_code" class="form-control" type="text" name="postal_code" id="postal_code" />
+ </div>
+ <kc-tooltip>Zip code or postal code.</kc-tooltip>
+ </div>
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="country">Country</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.country" class="form-control" type="text" name="country" id="country" />
+ </div>
+ <kc-tooltip>Country name.</kc-tooltip>
+ </div>
+
+ <div class="form-group" data-ng-show="access.manageUsers">
+ <div class="col-md-10 col-md-offset-2">
+ <button kc-save data-ng-disabled="!changed">Save</button>
+ <button kc-reset data-ng-disabled="!changed">Cancel</button>
+ </div>
+ </div>
+ </form>
+</div>
+
+<kc-menu></kc-menu>
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties
new file mode 100644
index 0000000..3e50437
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+parent=keycloak
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl
new file mode 100755
index 0000000..e02a340
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl
@@ -0,0 +1,95 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+ <#if section = "title">
+ ${msg("loginProfileTitle")}
+ <#elseif section = "header">
+ ${msg("loginProfileTitle")}
+ <#elseif section = "form">
+ <form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="email" name="email" value="${(user.email!'')?html}" class="${properties.kcInputClass!}" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="firstName" name="firstName" value="${(user.firstName!'')?html}" class="${properties.kcInputClass!}" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="lastName" name="lastName" value="${(user.lastName!'')?html}" class="${properties.kcInputClass!}" />
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street" value="${(user.attributes.street!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality" value="${(user.attributes.locality!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region" value="${(user.attributes.region!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(user.attributes.postal_code!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country" value="${(user.attributes.country!'')?html}"/>
+ </div>
+ </div>
+
+
+ <div class="${properties.kcFormGroupClass!}">
+ <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
+ <div class="${properties.kcFormOptionsWrapperClass!}">
+ </div>
+ </div>
+
+ <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
+ <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
+ </div>
+ </div>
+ </form>
+ </#if>
+</@layout.registrationLayout>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl
new file mode 100755
index 0000000..3247305
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl
@@ -0,0 +1,131 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+ <#if section = "title">
+ ${msg("registerWithTitle",(realm.name!''))}
+ <#elseif section = "header">
+ ${msg("registerWithTitleHtml",(realm.name!''))}
+ <#elseif section = "form">
+ <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
+ <#if !realm.registrationEmailAsUsername>
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')?html}" />
+ </div>
+ </div>
+ </#if>
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName" value="${(register.formData.firstName!'')?html}" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName" value="${(register.formData.lastName!'')?html}" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="email" class="${properties.kcInputClass!}" name="email" value="${(register.formData.email!'')?html}" />
+ </div>
+ </div>
+
+ <#if passwordRequired>
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="password" id="password" class="${properties.kcInputClass!}" name="password" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="password" id="password-confirm" class="${properties.kcInputClass!}" name="password-confirm" />
+ </div>
+ </div>
+ </#if>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street" value="${(register.formData['user.attributes.street']!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality" value="${(register.formData['user.attributes.locality']!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region" value="${(register.formData['user.attributes.region']!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(register.formData['user.attributes.postal_code']!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country" value="${(register.formData['user.attributes.country']!'')?html}"/>
+ </div>
+ </div>
+ <#if recaptchaRequired??>
+ <div class="form-group">
+ <div class="${properties.kcInputWrapperClass!}">
+ <div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
+ </div>
+ </div>
+ </#if>
+
+ <div class="${properties.kcFormGroupClass!}">
+ <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
+ <div class="${properties.kcFormOptionsWrapperClass!}">
+ <span><a href="${url.loginUrl}">${msg("backToLogin")}</a></span>
+ </div>
+ </div>
+
+ <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
+ <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
+ </div>
+ </div>
+ </form>
+ </#if>
+</@layout.registrationLayout>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties
new file mode 100644
index 0000000..3e50437
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+parent=keycloak
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index 2159c2f..2a77847 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -17,7 +17,9 @@
package org.keycloak.testsuite.pages;
+import org.keycloak.models.Constants;
import org.keycloak.services.resources.RealmsResource;
+import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -96,6 +98,14 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
submitButton.click();
}
+ public void updateAttribute(String attrName, String attrValue) {
+ WebElement attrElement = findAttributeInputElement(attrName);
+ attrElement.clear();
+ attrElement.sendKeys(attrValue);
+ submitButton.click();
+ }
+
+
public void clickCancel() {
cancelButton.click();
}
@@ -117,6 +127,11 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
return emailInput.getAttribute("value");
}
+ public String getAttribute(String attrName) {
+ WebElement attrElement = findAttributeInputElement(attrName);
+ return attrElement.getAttribute("value");
+ }
+
public boolean isCurrent() {
return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Edit Account");
}
@@ -140,4 +155,9 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
public boolean isPasswordUpdateSupported() {
return driver.getPageSource().contains(getPath() + "/password");
}
+
+ private WebElement findAttributeInputElement(String attrName) {
+ String attrId = Constants.USER_ATTRIBUTES_PREFIX + attrName;
+ return driver.findElement(By.id(attrId));
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java
new file mode 100644
index 0000000..810d9c2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.testsuite.account.custom;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.TestRealmKeycloakTest;
+import org.keycloak.testsuite.account.AccountTest;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CustomThemeTest extends TestRealmKeycloakTest {
+
+ @Override
+ public void configureTestRealm(RealmRepresentation testRealm) {
+ testRealm.setAccountTheme("address");
+
+ UserRepresentation user2 = UserBuilder.create()
+ .enabled(true)
+ .username("test-user-no-access@localhost")
+ .email("test-user-no-access@localhost")
+ .password("password")
+ .build();
+
+ RealmBuilder.edit(testRealm)
+ .user(user2);
+ }
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ @Page
+ protected LoginPage loginPage;
+
+ @Page
+ protected AccountUpdateProfilePage profilePage;
+
+ // KEYCLOAK-3494
+ @Test
+ public void changeProfile() throws Exception {
+ profilePage.open();
+ loginPage.login("test-user@localhost", "password");
+
+ events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountTest.ACCOUNT_REDIRECT).assertEvent();
+
+ Assert.assertEquals("test-user@localhost", profilePage.getEmail());
+ Assert.assertEquals("", profilePage.getAttribute("street"));
+
+ profilePage.updateAttribute("street", "Elm 1");
+ Assert.assertEquals("Elm 1", profilePage.getAttribute("street"));
+
+ profilePage.updateAttribute("street", "Elm 2");
+ Assert.assertEquals("Elm 2", profilePage.getAttribute("street"));
+
+ events.expectAccount(EventType.UPDATE_PROFILE).assertEvent();
+ }
+
+
+}