package org.deephacks.confit.test.integration;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import junit.framework.AssertionFailedError;
import org.deephacks.confit.ConfigChanges;
import org.deephacks.confit.ConfigChanges.ConfigChange;
import org.deephacks.confit.ConfigContext;
import org.deephacks.confit.ConfigObserver;
import org.deephacks.confit.admin.AdminContext;
import org.deephacks.confit.model.AbortRuntimeException;
import org.deephacks.confit.model.Bean;
import org.deephacks.confit.model.BeanId;
import org.deephacks.confit.test.ConfigTestData.*;
import org.deephacks.confit.test.FeatureTestsRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.deephacks.confit.model.Events.*;
import static org.deephacks.confit.test.ConfigTestData.*;
import static org.deephacks.confit.test.ConversionUtils.toBean;
import static org.deephacks.confit.test.ConversionUtils.toBeans;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals;
import static org.unitils.reflectionassert.ReflectionComparatorMode.LENIENT_ORDER;
@RunWith(FeatureTestsRunner.class)
public class IntegrationConfigTests {
private TestConfigObserver observer = new TestConfigObserver();
private ConfigContext config = ConfigContext.lookup();
private AdminContext admin = AdminContext.lookup();
private Child c1;
private Child c2;
private Parent p1;
private Parent p2;
private Grandfather g1;
private Grandfather g2;
private SingletonParent sp1;
private Singleton s1;
private Collection<Bean> defaultBeans;
@Before
public void setupDefaultConfigData() {
sp1 = new SingletonParent();
s1 = new Singleton();
c1 = getChild("c1");
c2 = getChild("c2");
p1 = getParent("p1");
p1.add(c2, c1);
p1.set(c1);
p1.put(c1);
p1.put(c2);
p2 = getParent("p2");
p2.add(c1, c2);
p2.set(c2);
p2.put(c1);
p2.put(c2);
g1 = getGrandfather("g1");
g1.add(p1, p2);
g2 = getGrandfather("g2");
g2.add(p1, p2);
g2.put(p1);
config.registerObserver(observer);
config.register(Person.class, Grandfather.class, Parent.class, Child.class, Singleton.class,
SingletonParent.class, JSR303Validation.class);
if (defaultBeans == null) {
// toBeans steals quite a bit of performance when having larger hierarchies.
defaultBeans = ImmutableList.copyOf(toBeans(c1, c2, p1, p2, g1, g2));
}
}
@After
public void after() {
observer.clear();
}
@Test
public void test_create_set_merge_non_existing_property() {
createDefault();
observer.clear();
Bean bean = Bean.create(c1.getBeanId());
bean.addProperty("non_existing", "bogus");
try {
admin.create(bean);
fail("Not possible to set property names that does not exist in schema");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG110));
assertThat(observer.getChanges().size(), is(0));
}
try {
admin.set(bean);
fail("Not possible to set property names that does not exist in schema");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG110));
assertThat(observer.getChanges().size(), is(0));
}
try {
admin.merge(bean);
fail("Not possible to set property names that does not exist in schema");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG110));
assertThat(observer.getChanges().size(), is(0));
}
try {
bean = Bean.create(BeanId.create("c5", CHILD_SCHEMA_NAME));
bean.setReference("non_existing", c1.getBeanId());
admin.create(bean);
fail("Not possible to set property names that does not exist in schema");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG111));
assertThat(observer.getChanges().size(), is(0));
}
bean = Bean.create(c1.getBeanId());
bean.addProperty("non_existing", "bogus");
try {
admin.set(bean);
fail("Not possible to set property names that does not exist in schema");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG110));
assertThat(observer.getChanges().size(), is(0));
}
try {
admin.merge(bean);
fail("Not possible to set property names that does not exist in schema");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG110));
assertThat(observer.getChanges().size(), is(0));
}
}
/**
* Test the possibility for:
*
* 1) Creating individual beans that have references to eachother.
* 2) Created beans can be fetched individually.
* 3) That the config view sees the same result.
*/
@Test
public void test_create_single_then_get_list() {
createThenGet(c1);
createThenGet(c2);
listAndAssert(c1.getBeanId().getSchemaName(), c1, c2);
createThenGet(p1);
createThenGet(p2);
listAndAssert(p1.getBeanId().getSchemaName(), p1, p2);
createThenGet(g1);
createThenGet(g2);
listAndAssert(g1.getBeanId().getSchemaName(), g1, g2);
}
/**
* Test the possibility for:
*
* 1) Creating a collection of beans that have references to eachother.
* 2) Created beans can be fetched individually afterwards.
* 3) Created beans can be listed afterwards.
* 4) That the config view sees the same result as admin view.
*/
@Test
public void test_create_multiple_then_get_list() {
createDefault();
getAndAssert(c1);
getAndAssert(c2);
listAndAssert(c1.getBeanId().getSchemaName(), c1, c2);
getAndAssert(p1);
getAndAssert(p2);
listAndAssert(p1.getBeanId().getSchemaName(), p1, p2);
getAndAssert(g1);
getAndAssert(g2);
listAndAssert(g1.getBeanId().getSchemaName(), g1, g2);
}
/**
* Test that lookup beans have their default instance created after registration.
*/
@Test
public void test_register_singleton() {
Singleton singleton = config.get(Singleton.class);
assertNotNull(singleton);
}
/**
* Test that lookup references are automatically assigned.
*/
@Test
public void test_singleton_references() {
// provision a bean without the lookup reference.
Bean singletonParent = toBean(sp1);
admin.create(singletonParent);
// assert that the lookup reference is set for config
SingletonParent parent = config.get(SingletonParent.class);
assertNotNull(parent.getSingleton());
// assert that the lookup is available from admin but
// references, however, is not set because it does not exist
Bean result = admin.get(singletonParent.getId()).get();
assertNotNull(result);
}
/**
* Test the possibility for:
*
* 1) Setting an empty bean that will erase properties and references.
* 3) Bean that was set empty can be fetched individually.
* 4) That the config view sees the same result as admin view.
*/
@Test
public void test_set_get_single() {
createDefault();
Grandfather empty = new Grandfather("g1");
Bean empty_expect = toBean(empty);
admin.set(empty_expect);
Bean empty_result = admin.get(empty.getBeanId()).get();
assertReflectionEquals(empty_expect, empty_result, LENIENT_ORDER);
}
@Test
public void test_set_get_list() {
createDefault();
Grandfather empty_g1 = new Grandfather("g1");
Grandfather empty_g2 = new Grandfather("g2");
Collection<Bean> empty_expect = toBeans(empty_g1, empty_g2);
admin.set(empty_expect);
Collection<Bean> empty_result = admin.list(empty_g1.getBeanId().getSchemaName());
assertReflectionEquals(empty_expect, empty_result, LENIENT_ORDER);
runtimeAllAndAssert(empty_g1.getClass(), empty_g1, empty_g2);
}
@Test
public void test_set_empty_properties() {
createThenGet(c1);
Bean b = Bean.create(BeanId.create("c1", CHILD_SCHEMA_NAME));
admin.set(b);
Bean result = admin.get(b.getId()).get();
assertThat(result.getPropertyNames().size(), is(0));
}
@Test
public void test_merge_get_single() {
createDefault();
Grandfather merged = new Grandfather("g1");
merged.setProp14(TimeUnit.NANOSECONDS);
merged.setProp19(Arrays.asList(TimeUnit.DAYS, TimeUnit.HOURS));
merged.setProp1("newName");
merged.setProp8((byte) 0);
merged.setProp9(0);
merged.setProp10((short) 0);
merged.setProp11(0);
merged.setProp12(0);
merged.setProp13(false);
merged.setProp21(0);
Bean mergeBean = toBean(merged);
admin.merge(mergeBean);
// modify the original to fit the expected merge
g1.setProp1(merged.getProp1());
g1.setProp19(merged.getProp19());
g1.setProp14(merged.getProp14());
g1.setProp8(merged.getProp8());
g1.setProp9(merged.getProp9());
g1.setProp10(merged.getProp10());
g1.setProp11(merged.getProp11());
g1.setProp12(merged.getProp12());
g1.setProp13(merged.getProp13());
g1.setProp21(merged.getProp21());
getAndAssert(g1);
}
@Test
public void test_merge_get_list() {
createDefault();
Grandfather g1_merged = new Grandfather("g1");
g1_merged.setProp14(TimeUnit.NANOSECONDS);
g1_merged.setProp19(Arrays.asList(TimeUnit.DAYS, TimeUnit.HOURS));
g1_merged.setProp1("newName");
g1_merged.setProp1("newName");
g1_merged.setProp8((byte) 0);
g1_merged.setProp9(0);
g1_merged.setProp10((short) 0);
g1_merged.setProp11(0);
g1_merged.setProp12(0);
g1_merged.setProp13(false);
g1_merged.setProp21(0);
Grandfather g2_merged = new Grandfather("g2");
g2_merged.setProp14(TimeUnit.NANOSECONDS);
g2_merged.setProp19(Arrays.asList(TimeUnit.DAYS, TimeUnit.HOURS));
g2_merged.setProp1("newName");
g2_merged.setProp8((byte) 0);
g2_merged.setProp9(0);
g2_merged.setProp10((short) 0);
g2_merged.setProp11(0);
g2_merged.setProp12(0);
g2_merged.setProp13(false);
g2_merged.setProp21(0);
Collection<Bean> mergeBeans = toBeans(g1_merged, g2_merged);
admin.merge(mergeBeans);
// modify the original to fit the expected merge
g1.setProp1(g1_merged.getProp1());
g1.setProp19(g1_merged.getProp19());
g1.setProp14(g1_merged.getProp14());
g1.setProp8(g1_merged.getProp8());
g1.setProp9(g1_merged.getProp9());
g1.setProp10(g1_merged.getProp10());
g1.setProp11(g1_merged.getProp11());
g1.setProp12(g1_merged.getProp12());
g1.setProp13(g1_merged.getProp13());
g1.setProp21(g1_merged.getProp21());
g2.setProp1(g2_merged.getProp1());
g2.setProp19(g2_merged.getProp19());
g2.setProp14(g2_merged.getProp14());
g2.setProp8(g2_merged.getProp8());
g2.setProp9(g2_merged.getProp9());
g2.setProp10(g2_merged.getProp10());
g2.setProp11(g2_merged.getProp11());
g2.setProp12(g2_merged.getProp12());
g2.setProp13(g2_merged.getProp13());
g2.setProp21(g2_merged.getProp21());
listAndAssert(g1.getBeanId().getSchemaName(), g1, g2);
}
@Test
public void test_merge_and_set_broken_references() {
createDefault();
// try merge a invalid single reference
Bean b = Bean.create(BeanId.create("p1", PARENT_SCHEMA_NAME));
b.addReference("prop6", BeanId.create("non_existing_child_ref", CHILD_SCHEMA_NAME));
try {
admin.merge(b);
fail("Should not be possible to merge invalid reference");
} catch (AbortRuntimeException e) {
if (e.getEvent().getCode() != CFG301 && e.getEvent().getCode() != CFG304) {
e.printStackTrace();
fail("Should not be possible to merge invalid reference");
}
assertThat(observer.getChanges().size(), is(0));
}
// try merge a invalid reference on collection
b = Bean.create(BeanId.create("p2", PARENT_SCHEMA_NAME));
b.addReference("prop7", BeanId.create("non_existing_child_ref", CHILD_SCHEMA_NAME));
try {
admin.merge(b);
fail("Should not be possible to merge invalid reference");
assertThat(observer.getChanges().size(), is(0));
} catch (AbortRuntimeException e) {
if (e.getEvent().getCode() != CFG301 && e.getEvent().getCode() != CFG304) {
e.printStackTrace();
fail("Should not be possible to merge invalid reference");
}
}
// try set a invalid single reference
b = Bean.create(BeanId.create("parent4", PARENT_SCHEMA_NAME));
b.addReference("prop6", BeanId.create("non_existing_child_ref", CHILD_SCHEMA_NAME));
try {
admin.set(b);
fail("Should not be possible to merge beans that does not exist");
} catch (AbortRuntimeException e) {
if (e.getEvent().getCode() != CFG301 && e.getEvent().getCode() != CFG304) {
e.printStackTrace();
fail("Should not be possible to merge invalid reference");
}
assertThat(observer.getChanges().size(), is(0));
}
// try merge a invalid single reference
b = Bean.create(BeanId.create("p1", PARENT_SCHEMA_NAME));
b.addReference("prop6", BeanId.create("non_existing_child_ref", CHILD_SCHEMA_NAME));
try {
admin.set(b);
fail("Should not be possible to merge invalid reference");
} catch (AbortRuntimeException e) {
if (e.getEvent().getCode() != CFG301 && e.getEvent().getCode() != CFG304) {
e.printStackTrace();
fail("Should not be possible to merge invalid reference");
}
assertThat(observer.getChanges().size(), is(0));
}
}
@Test
public void test_merge_delete_check_predecessors() {
Grandfather g1 = new Grandfather("g1");
Parent p1 = new Parent("p1");
Child c1 = new Child("c1");
g1.add(p1);
admin.create(toBean(c1));
admin.create(toBean(p1));
admin.create(toBean(g1));
Bean gb1 = toBean(g1);
Bean pb1 = toBean(p1);
admin.get(pb1.getId());
// delete the parent reference by setting the property to empty list
gb1.setReferences("prop7", new ArrayList<BeanId>());
admin.merge(gb1);
try {
admin.delete(pb1.getId());
} catch (AbortRuntimeException e) {
fail("Should be possible to delete parent since grandfather "
+ "no longer have a reference to it");
}
}
@Test
public void test_set_delete_check_predecessors() {
Grandfather g1 = new Grandfather("g1");
Parent p1 = new Parent("p1");
Child c1 = new Child("c1");
g1.add(p1);
admin.create(toBean(c1));
admin.create(toBean(p1));
admin.create(toBean(g1));
Bean gb1 = toBean(g1);
Bean pb1 = toBean(p1);
admin.get(pb1.getId());
// delete the parent reference by setting the property to empty list
gb1.setReferences("prop7", new ArrayList<BeanId>());
admin.set(gb1);
try {
admin.delete(pb1.getId());
} catch (AbortRuntimeException e) {
fail("Should be possible to delete parent since grandfather "
+ "no longer have a reference to it");
}
}
@Test
public void test_delete_bean() {
createDefault();
admin.delete(g1.getBeanId());
Optional<Bean> optional = admin.get(g1.getBeanId());
assertFalse(optional.isPresent());
}
@Test
public void test_delete_beans() {
createDefault();
admin.delete(g1.getBeanId().getSchemaName(), Arrays.asList("g1", "g2"));
List<Bean> result = admin.list(g1.getBeanId().getSchemaName());
assertThat(result.size(), is(0));
}
@Test
public void test_delete_reference_violation() {
admin.create(toBeans(g1, g2, p1, p2, c1, c2));
observer.clear();
// test single
try {
admin.delete(BeanId.create("c1", CHILD_SCHEMA_NAME));
fail("Should not be possible to delete a bean with references");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG302));
assertThat(observer.getChanges().size(), is(0));
}
// test multiple
try {
admin.delete(CHILD_SCHEMA_NAME, Arrays.asList("c1", "c2"));
fail("Should not be possible to delete a bean with references");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG302));
assertThat(observer.getChanges().size(), is(0));
}
}
@Test
public void test_set_merge_without_schema() {
Bean b = Bean.create(BeanId.create("1", "missing_schema_name"));
try {
admin.create(b);
fail("Cant add beans without a schema.");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG101));
assertThat(observer.getChanges().size(), is(0));
}
try {
admin.merge(b);
fail("Cant add beans without a schema.");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG101));
assertThat(observer.getChanges().size(), is(0));
}
}
@Test
public void test_set_merge_violating_types() {
admin.create(toBeans(g1, g2, p1, p2, c1, c2));
observer.clear();
Bean child = Bean.create(BeanId.create("c1", CHILD_SCHEMA_NAME));
// child merge invalid byte
try {
child.setProperty("prop8", "100000");
admin.set(child);
fail("10000 does not fit java.lang.Byte");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG105));
assertThat(observer.getChanges().size(), is(0));
}
// child merge invalid integer
try {
child.addProperty("prop3", "2.2");
admin.merge(child);
fail("2.2 does not fit a collection of java.lang.Integer");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG105));
assertThat(observer.getChanges().size(), is(0));
}
// parent set invalid enum value
Bean parent = Bean.create(BeanId.create("g1", GRANDFATHER_SCHEMA_NAME));
try {
parent.setProperty("prop14", "not_a_enum");
admin.set(parent);
fail("not_a_enum is not a value of TimeUnit");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG105));
assertThat(observer.getChanges().size(), is(0));
}
// parent merge invalid value to enum list
parent = Bean.create(BeanId.create("p1", PARENT_SCHEMA_NAME));
try {
parent.addProperty("prop19", "not_a_enum");
admin.merge(parent);
fail("not_a_enum is not a value of TimeUnit");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG105));
assertThat(observer.getChanges().size(), is(0));
}
// grandfather merge invalid multiplicity type, i.e. single on multi value.
Bean grandfather = Bean.create(BeanId.create("g1", GRANDFATHER_SCHEMA_NAME));
try {
grandfather.addProperty("prop1", Arrays.asList("1", "2"));
admin.merge(grandfather);
fail("Cannot add mutiple values to a single valued property.");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG106));
assertThat(observer.getChanges().size(), is(0));
}
// grandfather set invalid multiplicity type, multi value on single.
grandfather = Bean.create(BeanId.create("p1", PARENT_SCHEMA_NAME));
try {
grandfather.addProperty("prop11", "2.0");
admin.set(parent);
fail("Cannot add a value to a single typed value.");
} catch (AbortRuntimeException e) {
assertThat(e.getEvent().getCode(), is(CFG105));
assertThat(observer.getChanges().size(), is(0));
}
}
@Test
public void test_circular_references() {
String personSchema = "person";
config.register(Person.class);
BeanId aId = BeanId.create("a", personSchema);
BeanId bId = BeanId.create("b", personSchema);
BeanId cId = BeanId.create("c", personSchema);
BeanId dId = BeanId.create("d", personSchema);
Bean a = Bean.create(aId);
Bean b = Bean.create(bId);
Bean c = Bean.create(cId);
Bean d = Bean.create(dId);
admin.create(Arrays.asList(a, b, c, d));
a.setReference("bestFriend", bId);
b.setReference("bestFriend", aId);
c.setReference("bestFriend", dId);
d.setReference("bestFriend", cId);
a.addReference("closeFriends", Arrays.asList(bId, cId, dId));
b.addReference("closeFriends", Arrays.asList(aId, cId, dId));
c.addReference("closeFriends", Arrays.asList(aId, bId, dId));
d.addReference("closeFriends", Arrays.asList(aId, bId, cId));
a.addReference("colleauges", Arrays.asList(bId, cId, dId));
b.addReference("colleauges", Arrays.asList(aId, cId, dId));
c.addReference("colleauges", Arrays.asList(aId, bId, dId));
d.addReference("colleauges", Arrays.asList(aId, bId, cId));
/**
* Now test list operations from admin and config to make
* sure that none of them lookup stuck in infinite recrusion.
*/
admin.merge(Arrays.asList(a, b, c, d));
admin.set(Arrays.asList(a, b, c, d));
admin.list("person");
admin.get(BeanId.create("b", "person"));
config.list(Person.class);
config.get("c", Person.class);
}
private void createThenGet(Object object) throws AssertionFailedError {
Bean bean = toBean(object);
observer.clear();
admin.create(bean);
assertThat(observer.getChanges().size(), is(1));
assertFalse(observer.isBeforePresent(object.getClass()));
getAndAssert(object);
}
private void getAndAssert(Object object) throws AssertionFailedError {
Bean bean = toBean(object);
Bean result = admin.get(bean.getId()).get();
assertReflectionEquals(bean, result, LENIENT_ORDER);
runtimeGetAndAssert(object, bean);
}
/**
* Create the default testdata structure.
*/
private void createDefault() {
observer.clear();
admin.create(defaultBeans);
assertThat(observer.getChanges().size(), is(defaultBeans.size()));
observer.clear();
}
private void listAndAssert(String schemaName, Object... objects) {
Collection<Bean> beans = admin.list(schemaName);
assertReflectionEquals(toBeans(objects), beans, LENIENT_ORDER);
runtimeAllAndAssert(objects[0].getClass(), objects);
}
private void runtimeGetAndAssert(Object object, Bean bean) throws AssertionFailedError {
Object o = config.get(bean.getId().getInstanceId(), object.getClass()).get();
assertReflectionEquals(object, o, LENIENT_ORDER);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void runtimeAllAndAssert(Class clazz, Object... objects) throws AssertionFailedError {
List<Object> reslut = config.list(clazz);
assertReflectionEquals(objects, reslut, LENIENT_ORDER);
}
public static class TestConfigObserver implements ConfigObserver {
private ConfigChanges changes = new ConfigChanges();
@Override
public void notify(ConfigChanges changes) {
this.changes = changes;
}
public ConfigChanges getChanges() {
return changes;
}
public <T> ConfigChange<T> getFirstChange(Class<T> cls) {
return changes.getChanges(cls).iterator().next();
}
public <T> T getFirstAfter(Class<T> cls) {
return getFirstChange(cls).after().get();
}
public <T> T getFirstBefore(Class<T> cls) {
return getFirstChange(cls).before().get();
}
public void clear() {
changes = new ConfigChanges();
}
public <T> boolean isBeforePresent(Class<T> cls) {
return getFirstChange(cls).before().isPresent();
}
public <T> boolean isAfterPresent(Class<T> cls) {
return getFirstChange(cls).after().isPresent();
}
}
}