From edec6d64e30ab20f13650d46bd89020fb8457bda Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 13 Dec 2024 10:54:29 -0600 Subject: [PATCH] Add more validation of the join and associated table, in table associations. --- .../core/instances/QInstanceValidator.java | 28 ++++++++++++- .../instances/QInstanceValidatorTest.java | 41 +++++++++++++++---- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index 58618c3e..4d5e8c51 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -70,6 +70,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior; import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType; import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; @@ -780,14 +781,37 @@ public class QInstanceValidator if(assertCondition(StringUtils.hasContent(association.getName()), "missing a name for an Association on table " + table.getName())) { String messageSuffix = " for Association " + association.getName() + " on table " + table.getName(); + boolean recognizedTable = false; if(assertCondition(StringUtils.hasContent(association.getAssociatedTableName()), "missing associatedTableName" + messageSuffix)) { - assertCondition(qInstance.getTable(association.getAssociatedTableName()) != null, "unrecognized associatedTableName " + association.getAssociatedTableName() + messageSuffix); + if(assertCondition(qInstance.getTable(association.getAssociatedTableName()) != null, "unrecognized associatedTableName " + association.getAssociatedTableName() + messageSuffix)) + { + recognizedTable = true; + } } if(assertCondition(StringUtils.hasContent(association.getJoinName()), "missing joinName" + messageSuffix)) { - assertCondition(qInstance.getJoin(association.getJoinName()) != null, "unrecognized joinName " + association.getJoinName() + messageSuffix); + QJoinMetaData join = qInstance.getJoin(association.getJoinName()); + if(assertCondition(join != null, "unrecognized joinName " + association.getJoinName() + messageSuffix)) + { + assert join != null; // covered by the assertCondition + + if(recognizedTable) + { + boolean isLeftToRight = join.getLeftTable().equals(table.getName()) && join.getRightTable().equals(association.getAssociatedTableName()); + boolean isRightToLeft = join.getRightTable().equals(table.getName()) && join.getLeftTable().equals(association.getAssociatedTableName()); + assertCondition(isLeftToRight || isRightToLeft, "join [" + association.getJoinName() + "] does not connect tables [" + table.getName() + "] and [" + association.getAssociatedTableName() + "]" + messageSuffix); + if(isLeftToRight) + { + assertCondition(join.getType().equals(JoinType.ONE_TO_MANY) || join.getType().equals(JoinType.ONE_TO_ONE), "Join type does not have 'one' on this table's side side (left)" + messageSuffix); + } + else if(isRightToLeft) + { + assertCondition(join.getType().equals(JoinType.MANY_TO_ONE) || join.getType().equals(JoinType.ONE_TO_ONE), "Join type does not have 'one' on this table's side (right)" + messageSuffix); + } + } + } } } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index 89c69734..65198111 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -2054,16 +2054,41 @@ public class QInstanceValidatorTest extends BaseTest assertValidationFailureReasons((qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withAssociation(new Association().withName("myAssociation"))), "missing joinName for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER, - "missing associatedTableName for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER - ); + "missing associatedTableName for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER); assertValidationFailureReasons((qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withAssociation(new Association().withName("myAssociation").withJoinName("notAJoin").withAssociatedTableName(TestUtils.TABLE_NAME_LINE_ITEM))), - "unrecognized joinName notAJoin for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER - ); + "unrecognized joinName notAJoin for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER); assertValidationFailureReasons((qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withAssociation(new Association().withName("myAssociation").withJoinName("orderLineItem").withAssociatedTableName("notATable"))), - "unrecognized associatedTableName notATable for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER - ); + "unrecognized associatedTableName notATable for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER); + + ////////////////////////////////// + // wrong join on an association // + ////////////////////////////////// + assertValidationFailureReasons((qInstance -> + { + Association association = qInstance.getTable(TestUtils.TABLE_NAME_ORDER).getAssociationByName("orderLine").orElseThrow(); + association.setJoinName("orderOrderExtrinsic"); + }), + "join [orderOrderExtrinsic] does not connect tables [order] and [orderLine]"); + + ////////////////////////////////////////// + // wrong table (doesn't match the join) // + ////////////////////////////////////////// + assertValidationFailureReasons((qInstance -> + { + Association association = qInstance.getTable(TestUtils.TABLE_NAME_ORDER).getAssociationByName("orderLine").orElseThrow(); + association.setAssociatedTableName(TestUtils.TABLE_NAME_ORDER_EXTRINSIC); + }), + "join [orderLineItem] does not connect tables [order] and [orderExtrinsic]"); + + ////////////////////////////// + // invalid type on the join // + ////////////////////////////// + assertValidationFailureReasons((qInstance -> qInstance.getJoin("orderLineItem").setType(JoinType.MANY_TO_MANY)), + "Join type does not have 'one' on this table's side side (left)"); + assertValidationFailureReasons((qInstance -> qInstance.getJoin("orderLineItem").setType(JoinType.MANY_TO_ONE)), + "Join type does not have 'one' on this table's side side (left)"); } @@ -2323,7 +2348,7 @@ public class QInstanceValidatorTest extends BaseTest { int noOfReasons = actualReasons == null ? 0 : actualReasons.size(); assertEquals(expectedReasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", expectedReasons) - + "\nActual reasons: " + (noOfReasons > 0 ? String.join("\n", actualReasons) : "--")); + + "\nActual reasons: " + (noOfReasons > 0 ? String.join("\n", actualReasons) : "--")); } for(String reason : expectedReasons) @@ -2451,6 +2476,7 @@ public class QInstanceValidatorTest extends BaseTest public static class ValidAuthCustomizer implements QAuthenticationModuleCustomizerInterface {} + /*************************************************************************** ** ***************************************************************************/ @@ -2468,6 +2494,7 @@ public class QInstanceValidatorTest extends BaseTest } + /*************************************************************************** ** ***************************************************************************/