keycloak-aplcache

KEYCLOAK-1678 generalized invalidation tests, added test

2/16/2016 2:49:43 AM

Details

diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java
index 80ed20f..f54e15b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java
@@ -2,6 +2,7 @@ package org.keycloak.testsuite.cluster;
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.jboss.arquillian.container.test.api.ContainerController;
@@ -10,6 +11,7 @@ import static org.junit.Assert.assertTrue;
 import org.junit.Before;
 import org.keycloak.admin.client.Keycloak;
 import org.keycloak.models.Constants;
+import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.arquillian.ContainerInfo;
 import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
@@ -38,7 +40,7 @@ public abstract class AbstractClusterTest extends AbstractKeycloakTest {
         if (currentFailNodeIndex >= getClusterSize()) {
             currentFailNodeIndex = 0;
         }
-        logCurrentState();
+        logFailoverSetup();
     }
 
     protected ContainerInfo getCurrentFailNode() {
@@ -51,7 +53,8 @@ public abstract class AbstractClusterTest extends AbstractKeycloakTest {
         return survivors;
     }
 
-    protected void logCurrentState() {
+    protected void logFailoverSetup() {
+        log.info("Current failover setup");
         boolean started = controller.isStarted(getCurrentFailNode().getQualifier());
         log.info("Fail node: " + getCurrentFailNode() + (started ? "" : " (stopped)"));
         for (ContainerInfo survivor : getCurrentSurvivorNodes()) {
@@ -72,6 +75,10 @@ public abstract class AbstractClusterTest extends AbstractKeycloakTest {
         }
     }
 
+    protected ContainerInfo frontendNode() {
+        return suiteContext.getAuthServerInfo();
+    }
+    
     protected ContainerInfo backendNode(int i) {
         return suiteContext.getAuthServerBackendsInfo().get(i);
     }
@@ -101,15 +108,22 @@ public abstract class AbstractClusterTest extends AbstractKeycloakTest {
         controller.kill(node.getQualifier());
     }
 
-    protected Keycloak getAdminClientFor(ContainerInfo backendNode) {
-        return backendAdminClients.get(backendNode);
+    protected Keycloak getAdminClientFor(ContainerInfo node) {
+        return node.equals(suiteContext.getAuthServerInfo())
+                ? adminClient // frontend client
+                : backendAdminClients.get(node);
     }
 
     @Before
     public void beforeClusterTest() {
         failback();
-        logCurrentState();
+        logFailoverSetup();
         pause(3000);
     }
 
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        // no test realms will be created by the default 
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractInvalidationClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractInvalidationClusterTest.java
new file mode 100644
index 0000000..c129bbe
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractInvalidationClusterTest.java
@@ -0,0 +1,148 @@
+package org.keycloak.testsuite.cluster;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import static org.junit.Assert.assertFalse;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public abstract class AbstractInvalidationClusterTest<T> extends AbstractClusterTest {
+
+    private final SecureRandom random = new SecureRandom();
+
+    protected String randomString(int length) {
+        return new BigInteger(130, random).toString(length);
+    }
+
+    protected RealmRepresentation createTestRealmRepresentation() {
+        RealmRepresentation testRealm = new RealmRepresentation();
+        testRealm.setRealm("test_" + randomString(5));
+        testRealm.setEnabled(true);
+        return testRealm;
+    }
+
+    protected abstract T createTestEntityRepresentation();
+
+    @Test
+    public void crudWithoutFailover() {
+        crud(false);
+    }
+
+    @Test
+    public void crudWithFailover() {
+        crud(true);
+    }
+
+    public void crud(boolean backendFailover) {
+        T testEntity = createTestEntityRepresentation();
+
+        // CREATE 
+        testEntity = createEntityOnCurrentFailNode(testEntity);
+
+        if (backendFailover) {
+            failure();
+        }
+
+        assertEntityOnSurvivorNodesEqualsTo(testEntity);
+
+        failback();
+        iterateCurrentFailNode();
+
+        // UPDATE(s)
+        testEntity = testEntityUpdates(testEntity, backendFailover);
+
+        // DELETE 
+        deleteEntityOnCurrentFailNode(testEntity);
+
+        if (backendFailover) {
+            failure();
+        }
+
+        assertEntityOnSurvivorNodesIsDeleted(testEntity);
+    }
+
+    protected abstract T createEntity(T testEntity, ContainerInfo node);
+
+    protected abstract T readEntity(T entity, ContainerInfo node);
+
+    protected abstract T updateEntity(T entity, ContainerInfo node);
+
+    protected abstract void deleteEntity(T testEntity, ContainerInfo node);
+
+    protected T createEntityOnCurrentFailNode(T testEntity) {
+        return createEntity(testEntity, getCurrentFailNode());
+    }
+
+    protected T readEntityOnCurrentFailNode(T entity) {
+        return readEntity(entity, getCurrentFailNode());
+    }
+
+    protected T updateEntityOnCurrentFailNode(T entity) {
+        return updateEntity(entity, getCurrentFailNode());
+    }
+
+    protected void deleteEntityOnCurrentFailNode(T testEntity) {
+        deleteEntity(testEntity, getCurrentFailNode());
+    }
+
+    protected abstract T testEntityUpdates(T testEntity, boolean backendFailover);
+
+    protected void verifyEntityUpdateDuringFailover(T testEntity, boolean backendFailover) {
+        if (backendFailover) {
+            failure();
+        }
+
+        assertEntityOnSurvivorNodesEqualsTo(testEntity);
+
+        failback();
+        iterateCurrentFailNode();
+    }
+
+    protected List<String> excludedComparisonFields = new ArrayList<>();
+
+    protected void assertEntityOnSurvivorNodesEqualsTo(T testEntityOnFailNode) {
+        boolean entityDiffers = false;
+        for (ContainerInfo survivorNode : getCurrentSurvivorNodes()) {
+            T testEntityOnSurvivorNode = readEntity(testEntityOnFailNode, survivorNode);
+            if (EqualsBuilder.reflectionEquals(testEntityOnSurvivorNode, testEntityOnFailNode, excludedComparisonFields)) {
+                log.info("Verification on survivor " + survivorNode + " PASSED");
+            } else {
+                entityDiffers = true;
+                log.error("Verification on survivor " + survivorNode + " FAILED");
+                String tf = ReflectionToStringBuilder.reflectionToString(testEntityOnFailNode, ToStringStyle.SHORT_PREFIX_STYLE);
+                String ts = ReflectionToStringBuilder.reflectionToString(testEntityOnSurvivorNode, ToStringStyle.SHORT_PREFIX_STYLE);
+                log.error("\nEntity on fail node: \n\n" + tf + "\n"
+                        + "\nEntity on survivor node: \n" + ts + "\n"
+                        + "\nDifference: \n" + StringUtils.difference(tf, ts) + "\n");
+            }
+        }
+        assertFalse(entityDiffers);
+    }
+
+    private void assertEntityOnSurvivorNodesIsDeleted(T testEntityOnFailNode) {
+        // check if deleted from all survivor nodes
+        boolean entityExists = false;
+        for (ContainerInfo survivorNode : getCurrentSurvivorNodes()) {
+            T testEntityOnSurvivorNode = readEntity(testEntityOnFailNode, survivorNode);
+            if (testEntityOnSurvivorNode == null) {
+                log.info("Verification of deletion on survivor " + survivorNode + " PASSED");
+            } else {
+                entityExists = true;
+                log.error("Verification of deletion on survivor " + survivorNode + " FAILED");
+            }
+        }
+        assertFalse(entityExists);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractInvalidationClusterTestWithTestRealm.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractInvalidationClusterTestWithTestRealm.java
new file mode 100644
index 0000000..a2e2013
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractInvalidationClusterTestWithTestRealm.java
@@ -0,0 +1,26 @@
+package org.keycloak.testsuite.cluster;
+
+import org.junit.Before;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public abstract class AbstractInvalidationClusterTestWithTestRealm<T> extends AbstractInvalidationClusterTest<T> {
+
+    protected String testRealmName = null;
+    
+    @Before
+    public void createTestRealm() {
+        createTestRealm(frontendNode());
+    }
+    
+    protected void createTestRealm(ContainerInfo node) {
+        RealmRepresentation r = createTestRealmRepresentation();
+        getAdminClientFor(node).realms().create(r);
+        testRealmName = r.getRealm();
+    }
+    
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/ClientInvalidationClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/ClientInvalidationClusterTest.java
new file mode 100644
index 0000000..f654260
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/ClientInvalidationClusterTest.java
@@ -0,0 +1,84 @@
+package org.keycloak.testsuite.cluster;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+import static org.junit.Assert.assertNull;
+import org.junit.Before;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class ClientInvalidationClusterTest extends AbstractInvalidationClusterTestWithTestRealm<ClientRepresentation> {
+
+    @Before
+    public void setExcludedComparisonFields() {
+        excludedComparisonFields.add("protocolMappers");
+    }
+
+    @Override
+    protected ClientRepresentation createTestEntityRepresentation() {
+        ClientRepresentation client = new ClientRepresentation();
+        String s = randomString(5);
+        client.setClientId("client_" + s);
+        client.setName("name_" + s);
+        return client;
+    }
+
+    protected ClientsResource clients(ContainerInfo node) {
+        return getAdminClientFor(node).realm(testRealmName).clients();
+    }
+
+    @Override
+    protected ClientRepresentation createEntity(ClientRepresentation client, ContainerInfo node) {
+        Response response = clients(node).create(client);
+        String id = ApiUtil.getCreatedId(response);
+        response.close();
+        client.setId(id);
+        return readEntity(client, node);
+    }
+
+    @Override
+    protected ClientRepresentation readEntity(ClientRepresentation client, ContainerInfo node) {
+        ClientRepresentation u = null;
+        try {
+            u = clients(node).get(client.getId()).toRepresentation();
+        } catch (NotFoundException nfe) {
+            // exoected when client doesn't exist
+        }
+        return u;
+    }
+
+    @Override
+    protected ClientRepresentation updateEntity(ClientRepresentation client, ContainerInfo node) {
+        clients(node).get(client.getId()).update(client);
+        return readEntity(client, node);
+    }
+
+    @Override
+    protected void deleteEntity(ClientRepresentation client, ContainerInfo node) {
+        clients(node).get(client.getId()).remove();
+        assertNull(readEntity(client, node));
+    }
+
+    @Override
+    protected ClientRepresentation testEntityUpdates(ClientRepresentation client, boolean backendFailover) {
+
+        // clientId
+        client.setClientId(client.getClientId() + "_updated");
+        client = updateEntity(client, getCurrentFailNode());
+        verifyEntityUpdateDuringFailover(client, backendFailover);
+
+        // name
+        client.setName(client.getName() + "_updated");
+        client = updateEntity(client, getCurrentFailNode());
+        verifyEntityUpdateDuringFailover(client, backendFailover);
+
+        return client;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/RealmInvalidationClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/RealmInvalidationClusterTest.java
new file mode 100644
index 0000000..af66a3e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/RealmInvalidationClusterTest.java
@@ -0,0 +1,79 @@
+package org.keycloak.testsuite.cluster;
+
+import javax.ws.rs.NotFoundException;
+import static org.junit.Assert.assertNull;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class RealmInvalidationClusterTest extends AbstractInvalidationClusterTest<RealmRepresentation> {
+
+    @Override
+    protected RealmRepresentation createTestEntityRepresentation() {
+        return createTestRealmRepresentation();
+    }
+
+    @Override
+    protected RealmRepresentation createEntity(RealmRepresentation realm, ContainerInfo node) {
+        log.info("Creating realm on : " + getCurrentFailNode());
+        getAdminClientFor(getCurrentFailNode()).realms().create(realm);
+        // get created entity
+        return readEntity(realm, node);
+    }
+
+    @Override
+    protected RealmRepresentation readEntity(RealmRepresentation realm, ContainerInfo node) {
+        RealmRepresentation realmOnNode = null;
+        try {
+            realmOnNode = getAdminClientFor(node).realm(realm.getRealm()).toRepresentation();
+        } catch (NotFoundException nfe) {
+            // expected if realm not found
+        }
+        return realmOnNode;
+    }
+
+    @Override
+    protected RealmRepresentation updateEntity(RealmRepresentation realm, ContainerInfo node) {
+        getAdminClientFor(node).realms().realm(realm.getRealm()).update(realm);
+        return readEntity(realm, node);
+    }
+
+    @Override
+    protected void deleteEntity(RealmRepresentation realm, ContainerInfo node) {
+        log.info("Deleting realm on: " + getCurrentFailNode());
+        getAdminClientFor(node).realms().realm(realm.getRealm()).remove();
+        // check if deleted
+        assertNull(readEntity(realm, node));
+    }
+
+    @Override
+    protected RealmRepresentation testEntityUpdates(RealmRepresentation realm, boolean backendFailover) {
+
+        realm = updateRealmName(realm, realm.getRealm() + "_updated");
+        verifyEntityUpdateDuringFailover(realm, backendFailover);
+
+        realm = updateRealmEnabled(realm);
+        verifyEntityUpdateDuringFailover(realm, backendFailover);
+        
+        return realm;
+    }
+
+    protected RealmRepresentation updateRealmName(RealmRepresentation realm, String newName) {
+        log.info("Updating realm on: " + getCurrentFailNode());
+        String originalName = realm.getRealm();
+        realm.setRealm(newName);
+        
+        getAdminClientFor(getCurrentFailNode()).realms().realm(originalName).update(realm);
+        return readEntity(realm, getCurrentFailNode());
+    }
+
+    protected RealmRepresentation updateRealmEnabled(RealmRepresentation realm) {
+        log.info("Updating realm on: " + getCurrentFailNode());
+        realm.setEnabled(!realm.isEnabled());
+        return updateEntity(realm, getCurrentFailNode());
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/UserInvalidationClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/UserInvalidationClusterTest.java
new file mode 100644
index 0000000..3df6498
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/UserInvalidationClusterTest.java
@@ -0,0 +1,82 @@
+package org.keycloak.testsuite.cluster;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+import static org.junit.Assert.assertNull;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class UserInvalidationClusterTest extends AbstractInvalidationClusterTestWithTestRealm<UserRepresentation> {
+
+    @Override
+    protected UserRepresentation createTestEntityRepresentation() {
+        String firstName = "user";
+        String lastName = randomString(5);
+        UserRepresentation user = new UserRepresentation();
+        user.setUsername(firstName + "_" + lastName);
+        user.setEmail(user.getUsername() + "@email.test");
+        user.setFirstName(firstName);
+        user.setLastName(lastName);
+        return user;
+    }
+
+    protected UsersResource users(ContainerInfo node) {
+        return getAdminClientFor(node).realm(testRealmName).users();
+    }
+
+    @Override
+    protected UserRepresentation createEntity(UserRepresentation user, ContainerInfo node) {
+        Response response = users(node).create(user);
+        String id = ApiUtil.getCreatedId(response);
+        response.close();
+        user.setId(id);
+        return readEntity(user, node);
+    }
+
+    @Override
+    protected UserRepresentation readEntity(UserRepresentation user, ContainerInfo node) {
+        UserRepresentation u = null;
+        try {
+            u = users(node).get(user.getId()).toRepresentation();
+        } catch (NotFoundException nfe) {
+            // exoected when user doesn't exist
+        }
+        return u;
+    }
+
+    @Override
+    protected UserRepresentation updateEntity(UserRepresentation user, ContainerInfo node) {
+        users(node).get(user.getId()).update(user);
+        return readEntity(user, node);
+    }
+
+    @Override
+    protected void deleteEntity(UserRepresentation user, ContainerInfo node) {
+        users(node).get(user.getId()).remove();
+        assertNull(readEntity(user, node));
+    }
+
+    @Override
+    protected UserRepresentation testEntityUpdates(UserRepresentation user, boolean backendFailover) {
+        
+        // username
+        user.setUsername(user.getUsername() + "_updated");
+        user = updateEntity(user, getCurrentFailNode());
+        verifyEntityUpdateDuringFailover(user, backendFailover);
+
+        // first+lastName
+        user.setFirstName(user.getFirstName() + "_updated");
+        user.setLastName(user.getLastName() + "_updated");
+        user = updateEntity(user, getCurrentFailNode());
+        verifyEntityUpdateDuringFailover(user, backendFailover);
+
+        return user;
+    }
+
+}