From ebcf17c012d1ee1c10d706e30d30a7054c31665f Mon Sep 17 00:00:00 2001 From: christophermlawson <32881725+christophermlawson@users.noreply.github.com> Date: Mon, 16 Aug 2021 07:57:37 -0400 Subject: [PATCH] Fix NavigationClassMustBeAConstraintClassOfRel (#2015) * Fix NavigationClassMustBeAConstraintClassOfRel * change log * PR Updates --- ...igationprop-rule-fix_2021-08-09-16-41.json | 11 + .../src/Validation/ECRules.ts | 24 +- .../Validation/ECRules/PropertyRules.test.ts | 306 ++++++++++++++++-- 3 files changed, 319 insertions(+), 22 deletions(-) create mode 100644 common/changes/@bentley/ecschema-editing/navigationprop-rule-fix_2021-08-09-16-41.json diff --git a/common/changes/@bentley/ecschema-editing/navigationprop-rule-fix_2021-08-09-16-41.json b/common/changes/@bentley/ecschema-editing/navigationprop-rule-fix_2021-08-09-16-41.json new file mode 100644 index 000000000000..8ec8a595d11d --- /dev/null +++ b/common/changes/@bentley/ecschema-editing/navigationprop-rule-fix_2021-08-09-16-41.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@bentley/ecschema-editing", + "comment": "", + "type": "none" + } + ], + "packageName": "@bentley/ecschema-editing", + "email": "32881725+christophermlawson@users.noreply.github.com" +} \ No newline at end of file diff --git a/core/ecschema-editing/src/Validation/ECRules.ts b/core/ecschema-editing/src/Validation/ECRules.ts index 0c75f0d6e6f9..bc52f2069c24 100644 --- a/core/ecschema-editing/src/Validation/ECRules.ts +++ b/core/ecschema-editing/src/Validation/ECRules.ts @@ -453,15 +453,31 @@ export async function* validateNavigationProperty(property: AnyProperty): AsyncI yield new Diagnostics.NavigationRelationshipAbstractConstraintEntityOrMixin(property, [property.fullName, relationship.fullName]); } - let concreteClass = false; + const isClassSupported = async (ecClass: ECClass, propertyName: string, constraintName: string): Promise => { + if (constraintName === ecClass.fullName && undefined !== await ecClass.getProperty(propertyName)) + return true; + + const inheritedProp = await ecClass.getInheritedProperty(propertyName); + if (inheritedProp && constraintName === inheritedProp.class.fullName) + return true; + + const baseClass = await ecClass.baseClass; + if (!baseClass) + return false; + + return isClassSupported(baseClass, propertyName, constraintName); + }; + + let classSupported = false; if (thisConstraint.constraintClasses) { for (const constraintClass of thisConstraint.constraintClasses) { - if (constraintClass.fullName === property.class.fullName) - concreteClass = true; + classSupported = await isClassSupported(property.class, property.name, constraintClass.fullName); + if (classSupported) + break; } } - if (!concreteClass) + if (!classSupported) yield new Diagnostics.NavigationClassMustBeAConstraintClassOfRelationship(property, [property.class.name, property.name, relationship.fullName, navigationClassSide]); if (thatConstraint.multiplicity === RelationshipMultiplicity.oneMany || thatConstraint.multiplicity === RelationshipMultiplicity.zeroMany) { diff --git a/core/ecschema-editing/src/test/Validation/ECRules/PropertyRules.test.ts b/core/ecschema-editing/src/test/Validation/ECRules/PropertyRules.test.ts index 5d9668b7de58..753f1de1caee 100644 --- a/core/ecschema-editing/src/test/Validation/ECRules/PropertyRules.test.ts +++ b/core/ecschema-editing/src/test/Validation/ECRules/PropertyRules.test.ts @@ -459,7 +459,24 @@ describe("PropertyRule tests", () => { }); describe("Validate NavigationProperty Tests", () => { - function createSchemaJson(baseRelationship: any, sourceConst: any, targetConst: any, sourceProperties: any, targetProperties: any, relationshipProperties?: any) { + function createSchemaJson(baseRelationship: any, sourceConst: any, targetConst: any, sourceProperties: any, targetProperties: any, inheritanceLevel: any, relationshipProperties?: any) { + let sourcePropsInherited1: any; + let targetPropsInherited1: any; + let sourcePropsInherited2: any; + let targetPropsInherited2: any; + if (inheritanceLevel === 1) { + sourcePropsInherited1 = sourceProperties; + targetPropsInherited1 = targetProperties; + } else if (inheritanceLevel === 2) { + sourcePropsInherited2 = sourceProperties; + targetPropsInherited2 = targetProperties; + } else if (inheritanceLevel === 3) { + sourcePropsInherited1 = sourceProperties; + targetPropsInherited1 = targetProperties; + sourcePropsInherited2 = sourceProperties; + targetPropsInherited2 = targetProperties; + } + return createSchemaJsonWithItems({ TestRelationship: { ...baseRelationship, @@ -473,17 +490,36 @@ describe("PropertyRule tests", () => { ...targetConst, }, }, - TestBaseEntity: { + SourceBaseEntity1: { schemaItemType: "EntityClass", modifier: "abstract", + ...sourcePropsInherited1, + }, + SourceBaseEntity2: { + schemaItemType: "EntityClass", + baseClass: "TestSchema.SourceBaseEntity1", + modifier: "abstract", + ...sourcePropsInherited2, }, TestSourceEntity: { schemaItemType: "EntityClass", - baseClass: "TestSchema.TestBaseEntity", + baseClass: "TestSchema.SourceBaseEntity2", ...sourceProperties, }, + TargetBaseEntity1: { + schemaItemType: "EntityClass", + modifier: "abstract", + ...targetPropsInherited1, + }, + TargetBaseEntity2: { + schemaItemType: "EntityClass", + baseClass: "TestSchema.TargetBaseEntity1", + modifier: "abstract", + ...targetPropsInherited2, + }, TestTargetEntity: { schemaItemType: "EntityClass", + baseClass: "TestSchema.TargetBaseEntity2", ...targetProperties, }, TestRelationship2: { @@ -557,7 +593,7 @@ describe("PropertyRule tests", () => { baseClass: "TestSchema.TestRelationship2", }; - const testSchema = await Schema.fromJson(createSchemaJson(baseRelationshipJson, sourceJson, targetJson, propertyJson, undefined), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(baseRelationshipJson, sourceJson, targetJson, propertyJson, undefined, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -605,7 +641,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -646,7 +682,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -694,7 +730,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -733,7 +769,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestTargetEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -781,7 +817,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestTargetEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -822,7 +858,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -870,7 +906,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, undefined, propertyJson), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, undefined, 0, propertyJson), new SchemaContext()); const testProperty = (await testSchema.getItem("TestRelationship2"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -909,7 +945,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestTargetEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -957,7 +993,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, undefined, propertyJson), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, undefined, 0, propertyJson), new SchemaContext()); const testProperty = (await testSchema.getItem("TestRelationship2"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -998,7 +1034,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -1046,8 +1082,8 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined), new SchemaContext()); - const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined, 0), new SchemaContext()); + const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -1085,7 +1121,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestTargetEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -1133,7 +1169,7 @@ describe("PropertyRule tests", () => { ], }; - const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson), new SchemaContext()); + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson, 0), new SchemaContext()); const testProperty = (await testSchema.getItem("TestTargetEntity"))?.getPropertySync("TestProperty") as AnyProperty; const results = Rules.validateNavigationProperty(testProperty); @@ -1141,6 +1177,240 @@ describe("PropertyRule tests", () => { for await (const _diagnostic of results) expect(false, "Rule should have passed").true; }); + + it("Forward direction, relationship source constraint does include inherited (grandparent) property class, rule passes.", async () => { + const sourceJson = { + polymorphic: false, + multiplicity: "(0..*)", + roleLabel: "Test Source roleLabel", + constraintClasses: [ + "TestSchema.SourceBaseEntity1", + ], + }; + + const targetJson = { + polymorphic: false, + multiplicity: "(0..1)", + roleLabel: "Test Target roleLabel", + constraintClasses: [ + "TestSchema.TargetBaseEntity1", + ], + }; + + const propertyJson = { + properties: [ + { + name: "TestProperty", + type: "NavigationProperty", + relationshipName: "TestSchema.TestRelationship", + direction: "Forward", + }, + ], + }; + + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined, 1), new SchemaContext()); + const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; + + const results = Rules.validateNavigationProperty(testProperty); + + for await (const _diagnostic of results) + expect(false, "Rule should have passed").true; + }); + + it("Forward direction, relationship source constraint does include inherited (parent) property class, rule passes.", async () => { + const sourceJson = { + polymorphic: false, + multiplicity: "(0..*)", + roleLabel: "Test Source roleLabel", + constraintClasses: [ + "TestSchema.SourceBaseEntity2", + ], + }; + + const targetJson = { + polymorphic: false, + multiplicity: "(0..1)", + roleLabel: "Test Target roleLabel", + constraintClasses: [ + "TestSchema.TargetBaseEntity2", + ], + }; + + const propertyJson = { + properties: [ + { + name: "TestProperty", + type: "NavigationProperty", + relationshipName: "TestSchema.TestRelationship", + direction: "Forward", + }, + ], + }; + + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined, 2), new SchemaContext()); + const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; + + const results = Rules.validateNavigationProperty(testProperty); + + for await (const _diagnostic of results) + expect(false, "Rule should have passed").true; + }); + + it("Forward direction, relationship source constraint does include inherited (parent and grandparent) property class, rule passes.", async () => { + const sourceJson = { + polymorphic: false, + multiplicity: "(0..*)", + roleLabel: "Test Source roleLabel", + constraintClasses: [ + "TestSchema.SourceBaseEntity1", + ], + }; + + const targetJson = { + polymorphic: false, + multiplicity: "(0..1)", + roleLabel: "Test Target roleLabel", + constraintClasses: [ + "TestSchema.TargetBaseEntity1", + ], + }; + + const propertyJson = { + properties: [ + { + name: "TestProperty", + type: "NavigationProperty", + relationshipName: "TestSchema.TestRelationship", + direction: "Forward", + }, + ], + }; + + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, propertyJson, undefined, 3), new SchemaContext()); + const testProperty = (await testSchema.getItem("TestSourceEntity"))?.getPropertySync("TestProperty") as AnyProperty; + + const results = Rules.validateNavigationProperty(testProperty); + + for await (const _diagnostic of results) + expect(false, "Rule should have passed").true; + }); + + it("Backward direction, relationship target constraint does include inherited (grandparent) property class, rule passes.", async () => { + const sourceJson = { + polymorphic: false, + multiplicity: "(0..1)", + roleLabel: "Test Source roleLabel", + constraintClasses: [ + "TestSchema.SourceBaseEntity1", + ], + }; + + const targetJson = { + polymorphic: false, + multiplicity: "(0..*)", + roleLabel: "Test Target roleLabel", + constraintClasses: [ + "TestSchema.TargetBaseEntity1", + ], + }; + + const propertyJson = { + properties: [ + { + name: "TestProperty", + type: "NavigationProperty", + relationshipName: "TestSchema.TestRelationship", + direction: "Backward", + }, + ], + }; + + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson, 1), new SchemaContext()); + const testProperty = (await testSchema.getItem("TestTargetEntity"))?.getPropertySync("TestProperty") as AnyProperty; + + const results = Rules.validateNavigationProperty(testProperty); + + for await (const _diagnostic of results) + expect(false, "Rule should have passed").true; + }); + + it("Backward direction, relationship target constraint does include inherited (parent) property class, rule passes.", async () => { + const sourceJson = { + polymorphic: false, + multiplicity: "(0..1)", + roleLabel: "Test Source roleLabel", + constraintClasses: [ + "TestSchema.SourceBaseEntity2", + ], + }; + + const targetJson = { + polymorphic: false, + multiplicity: "(0..*)", + roleLabel: "Test Target roleLabel", + constraintClasses: [ + "TestSchema.TargetBaseEntity2", + ], + }; + + const propertyJson = { + properties: [ + { + name: "TestProperty", + type: "NavigationProperty", + relationshipName: "TestSchema.TestRelationship", + direction: "Backward", + }, + ], + }; + + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson, 2), new SchemaContext()); + const testProperty = (await testSchema.getItem("TestTargetEntity"))?.getPropertySync("TestProperty") as AnyProperty; + + const results = Rules.validateNavigationProperty(testProperty); + + for await (const _diagnostic of results) + expect(false, "Rule should have passed").true; + }); + + it("Backward direction, relationship target constraint does include inherited (parent and grandparent) property class, rule passes.", async () => { + const sourceJson = { + polymorphic: false, + multiplicity: "(0..1)", + roleLabel: "Test Source roleLabel", + constraintClasses: [ + "TestSchema.SourceBaseEntity1", + ], + }; + + const targetJson = { + polymorphic: false, + multiplicity: "(0..*)", + roleLabel: "Test Target roleLabel", + constraintClasses: [ + "TestSchema.TargetBaseEntity1", + ], + }; + + const propertyJson = { + properties: [ + { + name: "TestProperty", + type: "NavigationProperty", + relationshipName: "TestSchema.TestRelationship", + direction: "Backward", + }, + ], + }; + + const testSchema = await Schema.fromJson(createSchemaJson(undefined, sourceJson, targetJson, undefined, propertyJson, 3), new SchemaContext()); + const testProperty = (await testSchema.getItem("TestTargetEntity"))?.getPropertySync("TestProperty") as AnyProperty; + + const results = Rules.validateNavigationProperty(testProperty); + + for await (const _diagnostic of results) + expect(false, "Rule should have passed").true; + }); }); }); });