/* * Copyright (c) 2008, SQL Power Group Inc. * * This file is part of Power*Architect. * * Power*Architect is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Power*Architect is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.sqlpower.sqlobject; import java.beans.PropertyChangeEvent; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; import ca.sqlpower.object.ObjectDependentException; import ca.sqlpower.object.PersistedSPObjectTest; import ca.sqlpower.object.SPObject; import ca.sqlpower.object.undo.SPObjectUndoManager; import ca.sqlpower.sql.JDBCDataSource; import ca.sqlpower.sql.SPDataSource; import ca.sqlpower.sqlobject.SQLTypePhysicalProperties.SQLTypeConstraint; import ca.sqlpower.sqlobject.SQLTypePhysicalPropertiesProvider.BasicSQLType; import ca.sqlpower.sqlobject.SQLTypePhysicalPropertiesProvider.PropertyType; import ca.sqlpower.testutil.GenericNewValueMaker; import ca.sqlpower.testutil.MockJDBCDriver; import ca.sqlpower.testutil.NewValueMaker; import ca.sqlpower.util.SQLPowerUtils; /** * Extends the basic database-connected test class with some test methods that * should be applied to every SQLObject implementation. If you are making a test * for a new SQLObject implementation, this is the test class you should extend! */ public abstract class BaseSQLObjectTestCase extends PersistedSPObjectTest { Set<String>propertiesToIgnoreForUndo = new HashSet<String>(); Set<String>propertiesToIgnoreForEventGeneration = new HashSet<String>(); public BaseSQLObjectTestCase(String name) throws Exception { super(name); } protected abstract SQLObject getSQLObjectUnderTest() throws SQLObjectException; @Override public SPObject getSPObjectUnderTest() { try { return getSQLObjectUnderTest(); } catch (SQLObjectException e) { throw new RuntimeException("This should not happen!", e); } } public Set<String> getPropertiesToIgnoreForEvents() { Set<String> ignored = new HashSet<String>(); ignored.add("referenceCount"); ignored.add("populated"); ignored.add("SQLObjectListeners"); ignored.add("children"); ignored.add("parent"); ignored.add("parentDatabase"); ignored.add("class"); ignored.add("childCount"); ignored.add("undoEventListeners"); ignored.add("connection"); ignored.add("typeMap"); ignored.add("secondaryChangeMode"); ignored.add("zoomInAction"); ignored.add("zoomOutAction"); ignored.add("magicEnabled"); ignored.add("tableContainer"); ignored.add("session"); ignored.add("workspaceContainer"); ignored.add("runnableDispatcher"); ignored.add("foregroundThread"); return ignored; } /** * Returns a class that is one of the child types of the object under test. An * object of this type must be able to be added as a child to the object without * error. If the object under test does not allow children or all of the children * of the object are final so none can be added, null will be returned. */ protected abstract Class<? extends SPObject> getChildClassType(); /** * XXX This test should use the {@link GenericNewValueMaker} as it has it's own mini * version inside it. This test should also be using the annotations to decide which * setters can fire events. * * @throws IllegalArgumentException * @throws IllegalAccessException * @throws InvocationTargetException * @throws NoSuchMethodException * @throws SQLObjectException */ public void testAllSettersGenerateEvents() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLObjectException { SQLObject so = getSQLObjectUnderTest(); so.populate(); propertiesToIgnoreForEventGeneration.addAll(getPropertiesToIgnoreForEvents()); //Ignored because we expect the object to be populated. propertiesToIgnoreForEventGeneration.add("exportedKeysPopulated"); propertiesToIgnoreForEventGeneration.add("importedKeysPopulated"); propertiesToIgnoreForEventGeneration.add("columnsPopulated"); propertiesToIgnoreForEventGeneration.add("indicesPopulated"); CountingSQLObjectListener listener = new CountingSQLObjectListener(); SQLPowerUtils.listenToHierarchy(so, listener); if (so instanceof SQLDatabase) { // should be handled in the Datasource propertiesToIgnoreForEventGeneration.add("name"); } List<PropertyDescriptor> settableProperties; settableProperties = Arrays.asList(PropertyUtils.getPropertyDescriptors(so.getClass())); for (PropertyDescriptor property : settableProperties) { Object oldVal; if (propertiesToIgnoreForEventGeneration.contains(property.getName())) continue; try { oldVal = PropertyUtils.getSimpleProperty(so, property.getName()); // check for a setter if (property.getWriteMethod() == null) { continue; } } catch (NoSuchMethodException e) { System.out.println("Skipping non-settable property "+property.getName()+" on "+so.getClass().getName()); continue; } Object newVal; // don't init here so compiler can warn if the following code doesn't always give it a value if (property.getPropertyType() == Integer.TYPE ||property.getPropertyType() == Integer.class ) { if (oldVal != null) { newVal = ((Integer)oldVal)+1; } else { newVal = 1; } } else if (property.getPropertyType() == String.class) { // make sure it's unique newVal ="new " + oldVal; } else if (property.getPropertyType() == Boolean.TYPE || property.getPropertyType() == Boolean.class) { if (oldVal == null) { newVal = Boolean.TRUE; } else { newVal = new Boolean(! ((Boolean) oldVal).booleanValue()); } } else if (property.getPropertyType() == SQLCatalog.class) { newVal = new SQLCatalog(new SQLDatabase(),"This is a new catalog"); } else if (property.getPropertyType() == SPDataSource.class) { newVal = new JDBCDataSource(getPLIni()); ((SPDataSource)newVal).setName("test"); ((SPDataSource)newVal).setDisplayName("test"); ((JDBCDataSource)newVal).setUser("a"); ((JDBCDataSource)newVal).setPass("b"); ((JDBCDataSource)newVal).getParentType().setJdbcDriver(MockJDBCDriver.class.getName()); ((JDBCDataSource)newVal).setUrl("jdbc:mock:tables=tab1"); } else if (property.getPropertyType() == JDBCDataSource.class) { newVal = new JDBCDataSource(getPLIni()); ((SPDataSource)newVal).setName("test"); ((SPDataSource)newVal).setDisplayName("test"); ((JDBCDataSource)newVal).setUser("a"); ((JDBCDataSource)newVal).setPass("b"); ((JDBCDataSource)newVal).getParentType().setJdbcDriver(MockJDBCDriver.class.getName()); ((JDBCDataSource)newVal).setUrl("jdbc:mock:tables=tab1"); } else if (property.getPropertyType() == SQLTable.class) { newVal = new SQLTable(); } else if ( property.getPropertyType() == SQLColumn.class){ newVal = new SQLColumn(); } else if ( property.getPropertyType() == SQLIndex.class){ newVal = new SQLIndex(); } else if (property.getPropertyType() == SQLRelationship.SQLImportedKey.class) { SQLRelationship rel = new SQLRelationship(); newVal = rel.getForeignKey(); } else if ( property.getPropertyType() == SQLRelationship.Deferrability.class){ if (oldVal == SQLRelationship.Deferrability.INITIALLY_DEFERRED) { newVal = SQLRelationship.Deferrability.NOT_DEFERRABLE; } else { newVal = SQLRelationship.Deferrability.INITIALLY_DEFERRED; } } else if ( property.getPropertyType() == SQLRelationship.UpdateDeleteRule.class){ if (oldVal == SQLRelationship.UpdateDeleteRule.CASCADE) { newVal = SQLRelationship.UpdateDeleteRule.RESTRICT; } else { newVal = SQLRelationship.UpdateDeleteRule.CASCADE; } } else if (property.getPropertyType() == SQLIndex.AscendDescend.class) { if (oldVal == SQLIndex.AscendDescend.ASCENDING) { newVal = SQLIndex.AscendDescend.DESCENDING; } else { newVal = SQLIndex.AscendDescend.ASCENDING; } } else if (property.getPropertyType() == Throwable.class) { newVal = new Throwable(); } else if (property.getPropertyType() == BasicSQLType.class) { if (oldVal != BasicSQLType.OTHER) { newVal = BasicSQLType.OTHER; } else { newVal = BasicSQLType.TEXT; } } else if (property.getPropertyType() == UserDefinedSQLType.class) { newVal = new UserDefinedSQLType(); } else if (property.getPropertyType() == SQLTypeConstraint.class) { if (oldVal != SQLTypeConstraint.NONE) { newVal = SQLTypeConstraint.NONE; } else { newVal = SQLTypeConstraint.CHECK; } } else if (property.getPropertyType() == String[].class) { newVal = new String[3]; } else if (property.getPropertyType() == PropertyType.class) { if (oldVal != PropertyType.NOT_APPLICABLE) { newVal = PropertyType.NOT_APPLICABLE; } else { newVal = PropertyType.VARIABLE; } } else if (property.getPropertyType() == List.class) { newVal = Arrays.asList("one", "two"); } else { throw new RuntimeException("This test case lacks a value for "+ property.getName()+ " (type "+property.getPropertyType().getName()+") from "+so.getClass()+" on property "+property.getDisplayName()); } int oldChangeCount = listener.getChangedCount(); try { System.out.println("Setting property '"+property.getName()+"' to '"+newVal+"' ("+newVal.getClass().getName()+")"); BeanUtils.copyProperty(so, property.getName(), newVal); // some setters fire multiple events (they change more than one property) assertTrue("Event for set "+property.getName()+" on "+so.getClass().getName()+" didn't fire!", listener.getChangedCount() > oldChangeCount); if (listener.getChangedCount() == oldChangeCount + 1) { assertEquals("Property name mismatch for "+property.getName()+ " in "+so.getClass(), property.getName(), ((PropertyChangeEvent) listener.getLastEvent()).getPropertyName()); assertEquals("New value for "+property.getName()+" was wrong", newVal, ((PropertyChangeEvent) listener.getLastEvent()).getNewValue()); } } catch (InvocationTargetException e) { System.out.println("(non-fatal) Failed to write property '"+property.getName()+" to type "+so.getClass().getName()); } } } /** * @throws IllegalArgumentException * @throws IllegalAccessException * @throws InvocationTargetException * @throws NoSuchMethodException * @throws SQLObjectException */ public void testAllSettersAreUndoable() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLObjectException { SQLObject so = getSQLObjectUnderTest(); propertiesToIgnoreForUndo.add("referenceCount"); propertiesToIgnoreForUndo.add("populated"); propertiesToIgnoreForUndo.add("exportedKeysPopulated"); propertiesToIgnoreForUndo.add("importedKeysPopulated"); propertiesToIgnoreForUndo.add("columnsPopulated"); propertiesToIgnoreForUndo.add("indicesPopulated"); propertiesToIgnoreForUndo.add("SQLObjectListeners"); propertiesToIgnoreForUndo.add("children"); propertiesToIgnoreForUndo.add("parent"); propertiesToIgnoreForUndo.add("parentDatabase"); propertiesToIgnoreForUndo.add("class"); propertiesToIgnoreForUndo.add("childCount"); propertiesToIgnoreForUndo.add("undoEventListeners"); propertiesToIgnoreForUndo.add("connection"); propertiesToIgnoreForUndo.add("typeMap"); propertiesToIgnoreForUndo.add("secondaryChangeMode"); propertiesToIgnoreForUndo.add("zoomInAction"); propertiesToIgnoreForUndo.add("zoomOutAction"); propertiesToIgnoreForUndo.add("magicEnabled"); propertiesToIgnoreForUndo.add("deleteRule"); propertiesToIgnoreForUndo.add("updateRule"); propertiesToIgnoreForUndo.add("tableContainer"); propertiesToIgnoreForUndo.add("session"); propertiesToIgnoreForUndo.add("workspaceContainer"); propertiesToIgnoreForUndo.add("runnableDispatcher"); propertiesToIgnoreForUndo.add("foregroundThread"); if(so instanceof SQLDatabase) { // should be handled in the Datasource propertiesToIgnoreForUndo.add("name"); } SPObjectUndoManager undoManager= new SPObjectUndoManager(so); List<PropertyDescriptor> settableProperties; settableProperties = Arrays.asList(PropertyUtils.getPropertyDescriptors(so.getClass())); if(so instanceof SQLDatabase) { // should be handled in the Datasource settableProperties.remove("name"); } for (PropertyDescriptor property : settableProperties) { Object oldVal; if (propertiesToIgnoreForUndo.contains(property.getName())) continue; try { oldVal = PropertyUtils.getSimpleProperty(so, property.getName()); if (property.getWriteMethod() == null) { continue; } } catch (NoSuchMethodException e) { System.out.println("Skipping non-settable property "+property.getName()+" on "+so.getClass().getName()); continue; } Object newVal; // don't init here so compiler can warn if the following code doesn't always give it a value if (property.getPropertyType() == Integer.TYPE || property.getPropertyType() == Integer.class) { if (oldVal != null) { newVal = ((Integer) oldVal) + 1; } else { newVal = 1; } } else if (property.getPropertyType() == String.class) { // make sure it's unique newVal ="new " + oldVal; } else if (property.getPropertyType() == Boolean.TYPE || property.getPropertyType() == Boolean.class) { if (oldVal == null) { newVal = Boolean.TRUE; } else { newVal = new Boolean(! ((Boolean) oldVal).booleanValue()); } } else if (property.getPropertyType() == SQLCatalog.class) { newVal = new SQLCatalog(new SQLDatabase(),"This is a new catalog"); } else if (property.getPropertyType() == SPDataSource.class) { newVal = new JDBCDataSource(getPLIni()); ((SPDataSource)newVal).setName("test"); ((SPDataSource)newVal).setDisplayName("test"); ((JDBCDataSource)newVal).setUser("a"); ((JDBCDataSource)newVal).setPass("b"); ((JDBCDataSource)newVal).getParentType().setJdbcDriver(MockJDBCDriver.class.getName()); ((JDBCDataSource)newVal).setUrl("jdbc:mock:tables=tab1,tab2"); } else if (property.getPropertyType() == JDBCDataSource.class) { newVal = new JDBCDataSource(getPLIni()); ((SPDataSource)newVal).setName("test"); ((SPDataSource)newVal).setDisplayName("test"); ((JDBCDataSource)newVal).setUser("a"); ((JDBCDataSource)newVal).setPass("b"); ((JDBCDataSource)newVal).getParentType().setJdbcDriver(MockJDBCDriver.class.getName()); ((JDBCDataSource)newVal).setUrl("jdbc:mock:tables=tab1,tab2"); } else if (property.getPropertyType() == SQLTable.class) { newVal = new SQLTable(); } else if (property.getPropertyType() == SQLColumn.class) { newVal = new SQLColumn(); } else if (property.getPropertyType() == SQLIndex.class) { newVal = new SQLIndex(); } else if (property.getPropertyType() == SQLRelationship.SQLImportedKey.class) { SQLRelationship rel = new SQLRelationship(); newVal = rel.getForeignKey(); } else if ( property.getPropertyType() == SQLRelationship.Deferrability.class){ if (oldVal == SQLRelationship.Deferrability.INITIALLY_DEFERRED) { newVal = SQLRelationship.Deferrability.NOT_DEFERRABLE; } else { newVal = SQLRelationship.Deferrability.INITIALLY_DEFERRED; } } else if (property.getPropertyType() == SQLIndex.AscendDescend.class) { if (oldVal == SQLIndex.AscendDescend.ASCENDING) { newVal = SQLIndex.AscendDescend.DESCENDING; } else { newVal = SQLIndex.AscendDescend.ASCENDING; } } else if (property.getPropertyType() == Throwable.class) { newVal = new Throwable(); } else if (property.getPropertyType() == BasicSQLType.class) { if (oldVal != BasicSQLType.OTHER) { newVal = BasicSQLType.OTHER; } else { newVal = BasicSQLType.TEXT; } } else if (property.getPropertyType() == UserDefinedSQLType.class) { newVal = new UserDefinedSQLType(); } else if (property.getPropertyType() == SQLTypeConstraint.class) { if (oldVal != SQLTypeConstraint.NONE) { newVal = SQLTypeConstraint.NONE; } else { newVal = SQLTypeConstraint.CHECK; } } else if (property.getPropertyType() == SQLCheckConstraint.class) { newVal = new SQLCheckConstraint("check constraint name", "check constraint condition"); } else if (property.getPropertyType() == SQLEnumeration.class) { newVal = new SQLEnumeration("some enumeration"); } else if (property.getPropertyType() == String[].class) { newVal = new String[3]; } else if (property.getPropertyType() == PropertyType.class) { if (oldVal != PropertyType.NOT_APPLICABLE) { newVal = PropertyType.NOT_APPLICABLE; } else { newVal = PropertyType.VARIABLE; } } else { throw new RuntimeException("This test case lacks a value for "+ property.getName()+ " (type "+property.getPropertyType().getName()+") from "+so.getClass()); } int oldChangeCount = undoManager.getUndoableEditCount(); try { BeanUtils.copyProperty(so, property.getName(), newVal); // some setters fire multiple events (they change more than one property) but only register one as an undo assertEquals("Event for set "+property.getName()+" on "+so.getClass().getName() + " added multiple ("+undoManager.printUndoVector()+") undos!", oldChangeCount+1,undoManager.getUndoableEditCount()); } catch (InvocationTargetException e) { System.out.println("(non-fatal) Failed to write property '"+property.getName()+" to type "+so.getClass().getName()); } } } /** * The child list should never be null for any SQL Object, even if * that object's type is childless. */ public void testChildrenNotNull() throws SQLObjectException { assertNotNull(getSQLObjectUnderTest().getChildren()); } /** * Adding a child to any SQL Object should not force the object to populate. */ public void testAddChildDoesNotPopulate() throws Exception { SQLObject o = getSQLObjectUnderTest(); if (!o.allowsChildren()) return; o.setPopulated(false); //isPopulated always returns true, skip this test. if (o.isPopulated()) return; Class<?> childClassType = getChildClassType(); if (childClassType == null) return; NewValueMaker valueMaker = new GenericNewValueMaker(getRootObject()); SQLObject newChild = (SQLObject) valueMaker.makeNewValue(childClassType, null, "child"); o.addChild(newChild); assertFalse(o.isPopulated()); } /* * Test method for 'ca.sqlpower.sqlobject.SQLObject.setPopulated(boolean)' */ public void testSetPopulated() throws Exception { getSQLObjectUnderTest().setPopulated(true); assertTrue(getSQLObjectUnderTest().isPopulated()); } /* * Test method for 'ca.sqlpower.sqlobject.SQLObject.setChildren(List)' * Note that setChildren copies elements, does not assign the list, and * getChildren returns an unmodifiable copy of the current list. */ public void testAllChildHandlingMethods() throws SQLObjectException, IllegalArgumentException, ObjectDependentException { if (!getSQLObjectUnderTest().allowsChildren()) return; getSQLObjectUnderTest().populate(); NewValueMaker newValueMaker = new GenericNewValueMaker(getRootObject()); Class<? extends SPObject> childType = getSQLObjectUnderTest().getAllowedChildTypes().get(0); int childCount = getSQLObjectUnderTest().getChildCount(); List<SPObject> children = new ArrayList<SPObject>(); children.addAll(getSQLObjectUnderTest().getChildren(childType)); SQLObject x = (SQLObject) newValueMaker.makeNewValue(childType, null, ""); getSQLObjectUnderTest().addChild(x); assertEquals(childCount + 1, getSQLObjectUnderTest().getChildCount()); assertEquals(x, getSQLObjectUnderTest().getChildren(childType).get( getSQLObjectUnderTest().getChildren(childType).size() - 1)); SQLObject y = (SQLObject) newValueMaker.makeNewValue(childType, null, ""); // Test addChild(SQLObject, int) getSQLObjectUnderTest().addChild(y, 0); assertEquals(y, getSQLObjectUnderTest().getChildren(y.getClass()).get(0)); assertEquals(x, getSQLObjectUnderTest().getChildren(childType).get( getSQLObjectUnderTest().getChildren(childType).size() - 1)); getSQLObjectUnderTest().removeChild(x); children.add(0, y); assertTrue(getSQLObjectUnderTest().getChildren(childType).containsAll(children)); getSQLObjectUnderTest().removeChild(y); assertEquals(childCount, getSQLObjectUnderTest().getChildCount()); } public void testFiresAddEvent() throws SQLObjectException { if (!getSQLObjectUnderTest().allowsChildren()) return; CountingSQLObjectListener l = new CountingSQLObjectListener(); getSQLObjectUnderTest().addSPListener(l); Class<? extends SPObject> childType = getSQLObjectUnderTest().getAllowedChildTypes().get(0); NewValueMaker newValueMaker = new GenericNewValueMaker(getRootObject()); SQLObject x = (SQLObject) newValueMaker.makeNewValue(childType, null, ""); getSQLObjectUnderTest().addChild(x); assertEquals(1, l.getInsertedCount()); assertEquals(0, l.getRemovedCount()); assertEquals(0, l.getChangedCount()); assertEquals(0, l.getStructureChangedCount()); } public void testFireChangeEvent() throws Exception { CountingSQLObjectListener l = new CountingSQLObjectListener(); class NewStubSQLObject extends StubSQLObject { public void fakeObjectChanged(String string,Object oldValue, Object newValue) { firePropertyChange(string,oldValue,newValue); } }; NewStubSQLObject stub = new NewStubSQLObject(); stub.addSPListener(l); stub.fakeObjectChanged("fred","old value","new value"); assertEquals(0, l.getInsertedCount()); assertEquals(0, l.getRemovedCount()); assertEquals(1, l.getChangedCount()); assertEquals(0, l.getStructureChangedCount()); } /** make sure "change" to same value doesn't fire useless event */ public void testDontFireChangeEvent() throws Exception { CountingSQLObjectListener l = new CountingSQLObjectListener(); class NewStubSQLObject extends StubSQLObject { public void fakeObjectChanged(String string,Object oldValue, Object newValue) { firePropertyChange(string,oldValue,newValue); } }; NewStubSQLObject stub = new NewStubSQLObject(); stub.addSPListener(l); stub.fakeObjectChanged("fred","old value","old value"); assertEquals(0, l.getInsertedCount()); assertEquals(0, l.getRemovedCount()); assertEquals(0, l.getChangedCount()); assertEquals(0, l.getStructureChangedCount()); } public void testFireStructureChangeEvent() throws Exception { CountingSQLObjectListener l = new CountingSQLObjectListener(); getSQLObjectUnderTest().addSPListener(l); assertEquals(0, l.getInsertedCount()); assertEquals(0, l.getRemovedCount()); assertEquals(0, l.getChangedCount()); } public void testAddRemoveListener() throws Exception { CountingSQLObjectListener l = new CountingSQLObjectListener(); int listenerSize = getSQLObjectUnderTest().getSPListeners().size(); getSQLObjectUnderTest().addSPListener(l); assertEquals(listenerSize + 1, getSQLObjectUnderTest().getSPListeners().size()); getSQLObjectUnderTest().removeSPListener(l); assertEquals(listenerSize, getSQLObjectUnderTest().getSPListeners().size()); } public void testAllowMixedChildrenThatAreSubclassesOfEachOther() throws Exception { SQLObject stub = new StubSQLObject(); SQLObject subImpl = new StubSQLObject() {}; stub.addChild(new StubSQLObject()); stub.addChild(subImpl); // now test the other direction stub.removeChild(stub.getChild(0)); stub.addChild(new StubSQLObject()); // test passes if no exceptions were thrown } public void testPreRemoveEventNoVeto() throws Exception { if (!getSQLObjectUnderTest().allowsChildren()) return; getSQLObjectUnderTest().populate(); Class<? extends SPObject> childType = getSQLObjectUnderTest().getAllowedChildTypes().get(0); NewValueMaker newValueMaker = new GenericNewValueMaker(getRootObject()); SQLObject x = (SQLObject) newValueMaker.makeNewValue(childType, null, ""); int childCount = getSQLObjectUnderTest().getChildCount(); getSQLObjectUnderTest().addChild(x); CountingSQLObjectPreEventListener l = new CountingSQLObjectPreEventListener(); getSQLObjectUnderTest().addSQLObjectPreEventListener(l); l.setVetoing(false); getSQLObjectUnderTest().removeChild(getSQLObjectUnderTest().getChild(0)); assertEquals("Event fired", 1, l.getPreRemoveCount()); assertEquals("Child removed", childCount, getSQLObjectUnderTest().getChildren().size()); } public void testPreRemoveEventVeto() throws Exception { if (!getSQLObjectUnderTest().allowsChildren()) return; getSQLObjectUnderTest().populate(); Class<? extends SPObject> childType = getSQLObjectUnderTest().getAllowedChildTypes().get(0); NewValueMaker newValueMaker = new GenericNewValueMaker(getRootObject()); SQLObject x = (SQLObject) newValueMaker.makeNewValue(childType, null, ""); int childCount = getSQLObjectUnderTest().getChildCount(); getSQLObjectUnderTest().addChild(x); CountingSQLObjectPreEventListener l = new CountingSQLObjectPreEventListener(); getSQLObjectUnderTest().addSQLObjectPreEventListener(l); l.setVetoing(true); getSQLObjectUnderTest().removeChild(getSQLObjectUnderTest().getChild(0)); assertEquals("Event fired", 1, l.getPreRemoveCount()); assertEquals("Child not removed", childCount + 1, getSQLObjectUnderTest().getChildren().size()); } public void testClientPropertySetAndGet() throws Exception { getSQLObjectUnderTest().putClientProperty(this.getClass(), "testProperty", "test me"); assertEquals("test me", getSQLObjectUnderTest().getClientProperty(this.getClass(), "testProperty")); } public void testClientPropertyFiresEvent() throws Exception { CountingSQLObjectListener listener = new CountingSQLObjectListener(); getSQLObjectUnderTest().addSPListener(listener); getSQLObjectUnderTest().putClientProperty(this.getClass(), "testProperty", "test me"); assertEquals(1, listener.getChangedCount()); } public void testChildrenInaccessibleReasonSetOnPopulateError() throws Exception { final RuntimeException e = new RuntimeException("freaky!"); SQLObject o = new StubSQLObject() { @Override protected void populateImpl() throws SQLObjectException { throw e; } }; try { o.populate(); fail("Failing on populate should throw an exception as well as store it in the children inaccessible reason."); } catch (RuntimeException ex) { assertEquals(e, ex); } assertEquals(e, o.getChildrenInaccessibleReason(SQLObject.class)); } }