package org.keycloak.testsuite.cluster;
import org.apache.commons.lang.RandomStringUtils;
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 org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.ContainerInfo;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertFalse;
/**
*
* @author tkyjovsk
* @param <T> entity representation
* @param <TR> entity resource
*/
public abstract class AbstractInvalidationClusterTest<T, TR> extends AbstractClusterTest {
protected RealmRepresentation createTestRealmRepresentation() {
RealmRepresentation testRealm = new RealmRepresentation();
testRealm.setRealm("test_" + RandomStringUtils.randomAlphabetic(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 TR entityResource(T testEntity, ContainerInfo node);
protected abstract TR entityResource(String idOrName, ContainerInfo node);
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 TR entityResourceOnCurrentFailNode(T testEntity) {
return entityResource(testEntity, getCurrentFailNode());
}
protected String getEntityType(T entity) {
return entity.getClass().getSimpleName().replace("Representation", "");
}
protected T createEntityOnCurrentFailNode(T entity) {
log.info("Creating " + getEntityType(entity) + " on " + getCurrentFailNode());
return createEntity(entity, getCurrentFailNode());
}
protected T readEntityOnCurrentFailNode(T entity) {
log.debug("Reading " + getEntityType(entity) + " on " + getCurrentFailNode());
return readEntity(entity, getCurrentFailNode());
}
protected T updateEntityOnCurrentFailNode(T entity) {
return updateEntityOnCurrentFailNode(entity, "");
}
protected T updateEntityOnCurrentFailNode(T entity, String updateType) {
log.info("Updating " + getEntityType(entity) + " " + updateType + " on " + getCurrentFailNode());
return updateEntity(entity, getCurrentFailNode());
}
protected void deleteEntityOnCurrentFailNode(T entity) {
log.info("Creating " + getEntityType(entity) + " on " + getCurrentFailNode());
deleteEntity(entity, 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(String.format("Verification of %s on survivor %s PASSED", getEntityType(testEntityOnFailNode), survivorNode));
} else {
entityDiffers = true;
log.error(String.format("Verification of %s on survivor %s FAILED", getEntityType(testEntityOnFailNode), survivorNode));
String tf = ReflectionToStringBuilder.reflectionToString(testEntityOnFailNode, ToStringStyle.SHORT_PREFIX_STYLE);
String ts = ReflectionToStringBuilder.reflectionToString(testEntityOnSurvivorNode, ToStringStyle.SHORT_PREFIX_STYLE);
log.error(String.format(
"\nEntity on fail node: \n%s\n"
+ "\nEntity on survivor node: \n%s\n"
+ "\nDifference: \n%s\n",
tf, ts, StringUtils.difference(tf, ts)));
}
}
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(String.format("Verification of %s deletion on survivor %s PASSED", getEntityType(testEntityOnFailNode), survivorNode));
} else {
entityExists = true;
log.error(String.format("Verification of %s deletion on survivor %s FAILED", getEntityType(testEntityOnFailNode), survivorNode));
}
}
assertFalse(entityExists);
}
}