From 4e2202271a174c23073da7f8cce902c3ae461bf3 Mon Sep 17 00:00:00 2001 From: Stefan Apfel <71712332+StefanApfel-Bentley@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:15:51 +0200 Subject: [PATCH] SchemaMerger shall only merge into dynamic schemas (#7114) --- ...only-dynamic-schemas_2024-08-28-09-55.json | 10 + .../src/Merging/SchemaMerger.ts | 13 +- .../src/test/Merging/ClassMerger.test.ts | 977 ------------------ .../src/test/Merging/ClassMergerOrder.test.ts | 15 +- .../src/test/Merging/ConstantMerger.test.ts | 55 +- .../CustomAttributeClassMerger.test.ts | 233 +++++ .../Merging/CustomAttributeMerger.test.ts | 12 +- .../src/test/Merging/Edits/RenameEdit.test.ts | 14 +- .../src/test/Merging/Edits/SchemaEdit.test.ts | 16 +- .../test/Merging/EntityClassMerger.test.ts | 521 ++++++++++ .../test/Merging/EnumerationMerger.test.ts | 132 +-- .../test/Merging/KindOfQuantityMerger.test.ts | 13 +- .../src/test/Merging/MixinMerger.test.ts | 163 +++ .../src/test/Merging/PhenomenonMerger.test.ts | 27 +- .../Merging/PropertyCategoryMerger.test.ts | 45 +- .../src/test/Merging/PropertyMerger.test.ts | 18 +- .../Merging/RelationshipClassMerger.test.ts | 26 +- .../src/test/Merging/SchemaMerger.test.ts | 70 +- .../Merging/SchemaReferenceMerger.test.ts | 124 +-- .../test/Merging/StructClassMerger.test.ts | 174 ++++ .../src/test/Merging/UnitSystemMerger.test.ts | 37 +- .../src/test/TestUtils/BisTestHelper.ts | 7 + 22 files changed, 1494 insertions(+), 1208 deletions(-) create mode 100644 common/changes/@itwin/ecschema-editing/schemaediting-merger-only-dynamic-schemas_2024-08-28-09-55.json delete mode 100644 core/ecschema-editing/src/test/Merging/ClassMerger.test.ts create mode 100644 core/ecschema-editing/src/test/Merging/CustomAttributeClassMerger.test.ts create mode 100644 core/ecschema-editing/src/test/Merging/EntityClassMerger.test.ts create mode 100644 core/ecschema-editing/src/test/Merging/MixinMerger.test.ts create mode 100644 core/ecschema-editing/src/test/Merging/StructClassMerger.test.ts diff --git a/common/changes/@itwin/ecschema-editing/schemaediting-merger-only-dynamic-schemas_2024-08-28-09-55.json b/common/changes/@itwin/ecschema-editing/schemaediting-merger-only-dynamic-schemas_2024-08-28-09-55.json new file mode 100644 index 000000000000..b0e7d3b96542 --- /dev/null +++ b/common/changes/@itwin/ecschema-editing/schemaediting-merger-only-dynamic-schemas_2024-08-28-09-55.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/ecschema-editing", + "comment": "", + "type": "none" + } + ], + "packageName": "@itwin/ecschema-editing" +} \ No newline at end of file diff --git a/core/ecschema-editing/src/Merging/SchemaMerger.ts b/core/ecschema-editing/src/Merging/SchemaMerger.ts index 38561c34a707..cdc6c5b712a1 100644 --- a/core/ecschema-editing/src/Merging/SchemaMerger.ts +++ b/core/ecschema-editing/src/Merging/SchemaMerger.ts @@ -13,6 +13,7 @@ import { getSchemaDifferences, type SchemaDifferenceResult } from "../Differenci import { SchemaMergingVisitor } from "./SchemaMergingVisitor"; import { SchemaMergingWalker } from "./SchemaMergingWalker"; import type { SchemaEdits } from "./Edits/SchemaEdits"; +import { ECEditingStatus, SchemaEditingError } from "../Editing/Exception"; /** * Defines the context of a Schema merging run. @@ -79,9 +80,15 @@ export class SchemaMerger { ); } - const schema = await this._editor.getSchema(targetSchemaKey); - if (schema === undefined) { - throw new Error(`The target schema '${targetSchemaKey.name}' could not be found in the editing context.`); + const schema = await this._editor.getSchema(targetSchemaKey).catch((error: Error) => { + if (error instanceof SchemaEditingError && error.errorNumber === ECEditingStatus.SchemaNotFound) { + throw new Error(`The target schema '${targetSchemaKey.name}' could not be found in the editing context.`); + } + throw error; + }); + + if (!schema.customAttributes || !schema.customAttributes.has("CoreCustomAttributes.DynamicSchema")) { + throw new Error(`The target schema '${targetSchemaKey.name}' is not dynamic. Only dynamic schemas are supported for merging.`); } const visitor = new SchemaMergingVisitor({ diff --git a/core/ecschema-editing/src/test/Merging/ClassMerger.test.ts b/core/ecschema-editing/src/test/Merging/ClassMerger.test.ts deleted file mode 100644 index 1228069df26a..000000000000 --- a/core/ecschema-editing/src/test/Merging/ClassMerger.test.ts +++ /dev/null @@ -1,977 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Bentley Systems, Incorporated. All rights reserved. -* See LICENSE.md in the project root for license terms and full copyright notice. -*--------------------------------------------------------------------------------------------*/ -import { CustomAttributeClass, EntityClass, Mixin, Schema, SchemaContext, SchemaItemType, StructClass } from "@itwin/ecschema-metadata"; -import { SchemaMerger } from "../../Merging/SchemaMerger"; -import { expect } from "chai"; -import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; -import { ECEditingStatus } from "../../Editing/Exception"; - -/* eslint-disable @typescript-eslint/naming-convention */ - -describe("Class merger tests", () => { - let targetContext: SchemaContext; - const targetJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", - name: "TargetSchema", - version: "1.0.0", - alias: "target", - }; - - beforeEach(() => { - targetContext = new SchemaContext(); - }); - - it("should merge missing struct class", async () => { - await Schema.fromJson(targetJson, targetContext); - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.StructClass, - itemName: "TestStruct", - difference: { - label: "Test Structure", - description: "Description for Test Structure", - }, - }, - ], - }); - const mergedItem = await mergedSchema.getItem("TestStruct"); - expect(mergedItem!.toJSON()).deep.eq({ - schemaItemType: "StructClass", - label: "Test Structure", - description: "Description for Test Structure", - }); - }); - - it("should merge missing custom attribute class", async () => { - await Schema.fromJson(targetJson, targetContext); - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.CustomAttributeClass, - itemName: "TestCAClass", - difference: { - label: "Test Custom Attribute Class", - appliesTo: "AnyClass", - }, - }, - ], - }); - - const mergedItem = await mergedSchema.getItem("TestCAClass"); - expect(mergedItem!.toJSON()).deep.eq({ - schemaItemType: "CustomAttributeClass", - label: "Test Custom Attribute Class", - appliesTo: "AnyClass", - }); - }); - - it("should merge missing entity class with baseClass", async () => { - await Schema.fromJson(targetJson, targetContext); - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "TestBase", - difference: { - modifier: "Abstract", - }, - }, - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - label: "Test Entity", - description: "Description for TestEntity", - baseClass: "SourceSchema.TestBase", - }, - }, - ], - }); - const mergedItem = await mergedSchema.getItem("TestEntity"); - expect(mergedItem!.toJSON()).deep.eq({ - schemaItemType: "EntityClass", - label: "Test Entity", - description: "Description for TestEntity", - baseClass: "TargetSchema.TestBase", - }); - }); - - it("should merge missing entity class with referenced baseClass", async () => { - const referencedSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", - name: "TestSchema", - version: "01.00.15", - alias: "test", - items: { - TestBase: { - schemaItemType: "EntityClass", - modifier: "Abstract", - }, - }, - }; - - await Schema.fromJson(referencedSchema, targetContext); - await Schema.fromJson(targetJson, targetContext); - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaOtherTypes.SchemaReference, - difference: { - name: "TestSchema", - version: "01.00.15", - }, - }, - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - label: "Test Entity", - description: "Description for TestEntity", - baseClass: "TestSchema.TestBase", - }, - }, - ], - }); - const mergedItem = await mergedSchema.getItem("TestEntity"); - expect(mergedItem!.toJSON()).deep.eq({ - schemaItemType: "EntityClass", - label: "Test Entity", - description: "Description for TestEntity", - baseClass: "TestSchema.TestBase", - }); - }); - - it("should merge missing entity class with referenced mixin", async () => { - const referencedSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", - name: "TestSchema", - version: "01.00.15", - alias: "test", - items: { - BaseClass: { - schemaItemType: "EntityClass", - }, - TestMixin: { - schemaItemType: "Mixin", - appliesTo: "TestSchema.BaseClass", - }, - }, - }; - - await Schema.fromJson(referencedSchema, targetContext); - await Schema.fromJson(targetJson, targetContext); - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaOtherTypes.SchemaReference, - difference: { - name: "TestSchema", - version: "01.00.15", - }, - }, - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - label: "Test Entity", - description: "Description for TestEntity", - baseClass: "TestSchema.BaseClass", - mixins: [ - "TestSchema.TestMixin", - ], - }, - }, - ], - }); - - const mergedItem = await mergedSchema.getItem("TestEntity"); - expect(mergedItem!.toJSON()).deep.eq({ - schemaItemType: "EntityClass", - label: "Test Entity", - description: "Description for TestEntity", - baseClass: "TestSchema.BaseClass", - mixins: [ - "TestSchema.TestMixin", - ], - }); - }); - - it("should merge missing mixin", async () => { - await Schema.fromJson(targetJson, targetContext); - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - modifier: "Abstract", - }, - }, - { - changeType: "add", - schemaType: SchemaItemType.Mixin, - itemName: "TestMixin", - difference: { - label: "Test Mixin", - description: "Description for TestMixin", - appliesTo: "SourceSchema.TestEntity", - }, - }, - ], - }); - const mergedItem = await mergedSchema.getItem("TestMixin"); - expect(mergedItem!.toJSON()).deep.eq({ - schemaItemType: "Mixin", - label: "Test Mixin", - description: "Description for TestMixin", - appliesTo: "TargetSchema.TestEntity", - }); - }); - - it("should merge mixin base class derived from the current base class", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - BaseEntity: { - schemaItemType: SchemaItemType.EntityClass, - modifier: "Abstract", - }, - TestEntity: { - schemaItemType: SchemaItemType.EntityClass, - baseClass: "TargetSchema.BaseEntity", - }, - BaseMixin: { - schemaItemType: "Mixin", - appliesTo: "TargetSchema.BaseEntity", - }, - TestMixin: { - schemaItemType: "Mixin", - baseClass: "TargetSchema.BaseMixin", - appliesTo: "TargetSchema.TestEntity", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.Mixin, - itemName: "TestBase", - difference: { - baseClass: "SourceSchema.BaseMixin", - appliesTo: "SourceSchema.BaseEntity", - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.Mixin, - itemName: "TestMixin", - difference: { - baseClass: "SourceSchema.TestBase", - }, - }, - ], - }); - const mergedItem = await mergedSchema.getItem("TestMixin"); - expect(mergedItem!.toJSON().baseClass).deep.eq("TargetSchema.TestBase"); - }); - - it("should merge struct class changes", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TestStruct: { - schemaItemType: "StructClass", - label: "Struct", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "modify", - schemaType: SchemaItemType.StructClass, - itemName: "TestStruct", - difference: { - description: "Description for Test Structure", - label: "Test Structure", - }, - }, - ], - }); - - const mergedItem = await mergedSchema.getItem("TestStruct"); - expect(mergedItem!.toJSON()).deep.eq({ - schemaItemType: "StructClass", - description: "Description for Test Structure", - label: "Test Structure", - }); - }); - - it("should merge struct base class derived from the existing base class", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - BaseStruct: { - schemaItemType: "StructClass", - }, - TestStruct: { - schemaItemType: "StructClass", - baseClass: "TargetSchema.BaseStruct", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.StructClass, - itemName: "TestBase", - difference: { - baseClass: "SourceSchema.BaseStruct", - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.StructClass, - itemName: "TestStruct", - difference: { - baseClass: "SourceSchema.TestBase", - }, - }, - ], - }); - const mergedItem = await mergedSchema.getItem("TestStruct"); - expect(mergedItem!.toJSON().baseClass).deep.eq("TargetSchema.TestBase"); - }); - - it("should merge custom attribute class changes", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TestCAClass: { - schemaItemType: "CustomAttributeClass", - label: "TestCustomAttributeClass", - appliesTo: "AnyProperty", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "modify", - schemaType: SchemaItemType.CustomAttributeClass, - itemName: "TestCAClass", - difference: { - label: "Test Custom Attribute Class", - appliesTo: "AnyClass", - }, - }, - ], - }); - - const mergedItem = await mergedSchema.getItem("TestCAClass"); - expect(mergedItem!.toJSON()).deep.eq({ - schemaItemType: "CustomAttributeClass", - label: "Test Custom Attribute Class", - appliesTo: "AnyClass, AnyProperty", - }); - }); - - it("should merge custom attribute base class derived from the current base class", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - BaseCAClass: { - schemaItemType: "CustomAttributeClass", - appliesTo: "AnyProperty", - }, - TestCAClass: { - schemaItemType: "CustomAttributeClass", - appliesTo: "AnyProperty", - baseClass: "TargetSchema.BaseCAClass", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.CustomAttributeClass, - itemName: "TestBase", - difference: { - baseClass: "SourceSchema.BaseCAClass", - appliesTo: "AnyProperty", - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.CustomAttributeClass, - itemName: "TestCAClass", - difference: { - baseClass: "SourceSchema.TestBase", - }, - }, - ], - }); - const mergedItem = await mergedSchema.getItem("TestCAClass"); - expect(mergedItem!.toJSON().baseClass).deep.eq("TargetSchema.TestBase"); - }); - - it("should merge class modifier changed from Sealed to None", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TestEntity: { - schemaItemType: "EntityClass", - modifier: "Sealed", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "modify", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - modifier: "None", - }, - }, - ], - }); - const mergedItem = await mergedSchema.getItem("TestEntity"); - expect(mergedItem!.toJSON()).deep.eq({ - schemaItemType: "EntityClass", - // If modifier is set to None, it won't appear in the JSON - }); - }); - - it("should merge entity base class derived from the existing base class", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - BaseEntity: { - schemaItemType: "EntityClass", - modifier: "Abstract", - }, - TestEntity: { - schemaItemType: "EntityClass", - baseClass: "TargetSchema.BaseEntity", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const mergedSchema = await merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "TestBase", - difference: { - baseClass: "SourceSchema.BaseEntity", - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - baseClass: "SourceSchema.TestBase", - }, - }, - ], - }); - const mergedItem = await mergedSchema.getItem("TestEntity"); - expect(mergedItem!.toJSON().baseClass).deep.eq("TargetSchema.TestBase"); - }); - - it("should throw an error when merging classes with different schema item types", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TestClass: { - schemaItemType: "StructClass", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "modify", - schemaType: SchemaItemType.StructClass, - itemName: "TestClass", - difference: { - schemaItemType: "EntityClass", - } as any, // difference needs to be any-fied to be able to set the schemaItemType property. - }, - ], - }); - await expect(merge).to.be.rejectedWith("Changing the type of item 'TestClass' not supported."); - }); - - it("should throw an error when merging class modifier changed from Abstract to Sealed", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TestEntity: { - schemaItemType: "EntityClass", - modifier: "Abstract", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "modify", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - modifier: "Sealed", - }, - }, - ], - }); - - await expect(merge).to.be.rejectedWith("Changing the class 'TestEntity' modifier is not supported."); - }); - - it("should throw an error when merging entity base class to one that doesn't derive from", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TargetBase: { - schemaItemType: "EntityClass", - }, - TestEntity: { - schemaItemType: "EntityClass", - baseClass: "TargetSchema.TargetBase", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "SourceBase", - difference: { - }, - }, - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "TestBase", - difference: { - baseClass: "SourceSchema.SourceBase", - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - baseClass: "SourceSchema.TestBase", - }, - }, - ], - }); - - await expect(merge).to.be.eventually.rejected.then(function (error) { - expect(error).to.have.property("errorNumber", ECEditingStatus.SetBaseClass); - expect(error).to.have.nested.property("innerError.message", `Base class TargetSchema.TestBase must derive from TargetSchema.TargetBase.`); - expect(error).to.have.nested.property("innerError.errorNumber", ECEditingStatus.InvalidBaseClass); - }); - }); - - it("should throw an error when merging entity base class changed from existing one to undefined", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - BaseEntity: { - schemaItemType: "EntityClass", - }, - TestEntity: { - schemaItemType: "EntityClass", - baseClass: "TargetSchema.BaseEntity", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "modify", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - baseClass: undefined, - }, - }, - ], - }); - - await expect(merge).to.be.rejectedWith("Changing the class 'TestEntity' baseClass is not supported."); - }); - - it("should throw an error when merging entity base class changed from undefined to existing one", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TestEntity: { - schemaItemType: "EntityClass", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "BaseEntity", - difference: { - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.EntityClass, - itemName: "TestEntity", - difference: { - baseClass: "SourceSchema.BaseEntity", - }, - }, - ], - }); - - await expect(merge).to.be.rejectedWith("Changing the class 'TestEntity' baseClass is not supported."); - }); - - it("should throw an error when merging mixins with different appliesTo values", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TargetEntity: { - schemaItemType: "EntityClass", - }, - TestMixin: { - schemaItemType: "Mixin", - appliesTo: "TargetSchema.TargetEntity", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.EntityClass, - itemName: "SourceEntity", - difference: { - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.Mixin, - itemName: "TestMixin", - difference: { - appliesTo: "SourceSchema.SourceEntity", - }, - }, - ], - }); - - await expect(merge).to.be.rejectedWith("Changing the mixin 'TestMixin' appliesTo is not supported."); - }); - - it("should throw an error when merging entity class with a unknown mixins", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TestEntity: { - schemaItemType: "EntityClass", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaOtherTypes.EntityClassMixin, - itemName: "TestEntity", - difference: [ - "SourceSchema.NotExistingMixin", - ], - }, - ], - }); - - await expect(merge).to.be.eventually.rejected.then(function (error) { - expect(error).to.have.property("errorNumber", ECEditingStatus.AddMixin); - expect(error).to.have.nested.property("innerError.message", `Mixin TargetSchema.NotExistingMixin could not be found in the schema context.`); - expect(error).to.have.nested.property("innerError.errorNumber", ECEditingStatus.SchemaItemNotFoundInContext); - }); - }); - - it("should throw an error when merging struct base class changed from undefined to existing one", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TestStruct: { - schemaItemType: "StructClass", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.StructClass, - itemName: "BaseStruct", - difference: { - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.StructClass, - itemName: "TestStruct", - difference: { - baseClass: "SourceSchema.BaseStruct", - }, - }, - ], - }); - - await expect(merge).to.be.rejectedWith("Changing the class 'TestStruct' baseClass is not supported."); - }); - - it("should throw an error when merging mixin base class to one that doesn't derive from", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TestEntity: { - schemaItemType: "EntityClass", - }, - BaseMixin: { - schemaItemType: "Mixin", - appliesTo: "TargetSchema.TestEntity", - }, - TestMixin: { - schemaItemType: "Mixin", - baseClass: "TargetSchema.BaseMixin", - appliesTo: "TargetSchema.TestEntity", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.Mixin, - itemName: "TestBase", - difference: { - appliesTo: "SourceSchema.TestEntity", - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.Mixin, - itemName: "TestMixin", - difference: { - baseClass: "SourceSchema.TestBase", - }, - }, - ], - }); - - await expect(merge).to.be.eventually.rejected.then(function (error) { - expect(error).to.have.property("errorNumber", ECEditingStatus.SetBaseClass); - expect(error).to.have.nested.property("innerError.message", `Base class TargetSchema.TestBase must derive from TargetSchema.BaseMixin.`); - expect(error).to.have.nested.property("innerError.errorNumber", ECEditingStatus.InvalidBaseClass); - }); - }); - - it("should throw an error when merging custom attribute base class changed from undefined to existing one", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - testCAClass: { - schemaItemType: "CustomAttributeClass", - appliesTo: "AnyClass", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.CustomAttributeClass, - itemName: "BaseCAClass", - difference: { - appliesTo: "AnyClass", - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.CustomAttributeClass, - itemName: "testCAClass", - difference: { - baseClass: "SourceSchema.BaseCAClass", - }, - }, - ], - }); - - await expect(merge).to.be.rejectedWith("Changing the class 'testCAClass' baseClass is not supported."); - }); - - it("should throw an error when merging custom attribute base class to one that doesn't derive from", async () => { - await Schema.fromJson({ - ...targetJson, - items: { - TargetBase: { - schemaItemType: "CustomAttributeClass", - appliesTo: "AnyProperty", - }, - TestCAClass: { - schemaItemType: "CustomAttributeClass", - baseClass: "TargetSchema.TargetBase", - appliesTo: "AnyProperty", - }, - }, - }, targetContext); - - const merger = new SchemaMerger(targetContext); - const merge = merger.merge({ - sourceSchemaName: "SourceSchema.01.02.03", - targetSchemaName: "TargetSchema.01.00.00", - differences: [ - { - changeType: "add", - schemaType: SchemaItemType.CustomAttributeClass, - itemName: "SourceBase", - difference: { - appliesTo: "AnyProperty", - }, - }, - { - changeType: "add", - schemaType: SchemaItemType.CustomAttributeClass, - itemName: "TestBase", - difference: { - baseClass: "SourceSchema.SourceBase", - appliesTo: "AnyProperty", - }, - }, - { - changeType: "modify", - schemaType: SchemaItemType.CustomAttributeClass, - itemName: "TestCAClass", - difference: { - baseClass: "SourceSchema.TestBase", - }, - }, - ], - }); - - await expect(merge).to.be.eventually.rejected.then(function (error) { - expect(error).to.have.property("errorNumber", ECEditingStatus.SetBaseClass); - expect(error).to.have.nested.property("innerError.message", `Base class TargetSchema.TestBase must derive from TargetSchema.TargetBase.`); - expect(error).to.have.nested.property("innerError.errorNumber", ECEditingStatus.InvalidBaseClass); - }); - }); -}); diff --git a/core/ecschema-editing/src/test/Merging/ClassMergerOrder.test.ts b/core/ecschema-editing/src/test/Merging/ClassMergerOrder.test.ts index 06a24a2ef3f0..f18446bfa86f 100644 --- a/core/ecschema-editing/src/test/Merging/ClassMergerOrder.test.ts +++ b/core/ecschema-editing/src/test/Merging/ClassMergerOrder.test.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { EntityClass, Mixin, Schema, SchemaContext, SchemaItemType, StructClass } from "@itwin/ecschema-metadata"; import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; -import { SchemaMerger } from "../../ecschema-editing"; +import { SchemaMerger } from "../../Merging/SchemaMerger"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; import { expect } from "chai"; describe("Class items merging order tests", () => { @@ -15,10 +16,16 @@ describe("Class items merging order tests", () => { name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; beforeEach(async () => { - context = new SchemaContext(); + context = await BisTestHelper.getNewContext(); }); it("should merge the missing entity class with base class before the base class item", async () => { @@ -103,7 +110,7 @@ describe("Class items merging order tests", () => { const mergedItem = await mergedSchema.getItem("testClass"); const classMixins = mergedItem?.mixins; - if(classMixins){ + if (classMixins) { expect((classMixins[0]).fullName).to.deep.equal("TargetSchema.mixinA"); expect((classMixins[1]).fullName).to.deep.equal("TargetSchema.mixinB"); } @@ -257,7 +264,7 @@ describe("Class items merging order tests", () => { }); }); - it("it should merge missing struct properties of existing entity class before the struct class item",async () => { + it("it should merge missing struct properties of existing entity class before the struct class item", async () => { await Schema.fromJson({ ...targetJson, items: { diff --git a/core/ecschema-editing/src/test/Merging/ConstantMerger.test.ts b/core/ecschema-editing/src/test/Merging/ConstantMerger.test.ts index 70b3f0922b4c..91920cafa2e8 100644 --- a/core/ecschema-editing/src/test/Merging/ConstantMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/ConstantMerger.test.ts @@ -2,9 +2,10 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { Constant, Schema, SchemaContext, SchemaItemType } from "@itwin/ecschema-metadata"; +import { Constant, Schema, SchemaItemType } from "@itwin/ecschema-metadata"; import { SchemaMerger } from "../../Merging/SchemaMerger"; import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; import { expect } from "chai"; describe("Constant merger tests", () => { @@ -13,6 +14,12 @@ describe("Constant merger tests", () => { name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; const referenceJson = { $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", @@ -33,17 +40,7 @@ describe("Constant merger tests", () => { definition: "Units.LENGTH(2)", }, }, - }, new SchemaContext()); - - const testConstant = { - schemaItemType: "Constant", - label: "Test Constant", - description: "testing a constant", - phenomenon: "TargetSchema.testPhenomenon", - definition: "PI", - numerator: 5.5, - denominator: 5.1, - }; + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const mergedSchema = await merger.merge({ @@ -66,14 +63,20 @@ describe("Constant merger tests", () => { ], }); - const mergedConstant = await mergedSchema.getItem("testConstant"); - const mergedConstantToJSON = mergedConstant!.toJSON(false, false); - - expect(mergedConstantToJSON).deep.eq(testConstant); + await expect(mergedSchema.getItem("testConstant")).to.be.eventually.not.undefined + .then((constant: Constant) => { + expect(constant).to.have.a.property("schemaItemType", SchemaItemType.Constant); + expect(constant).to.have.a.property("label", "Test Constant"); + expect(constant).to.have.a.property("description", "testing a constant"); + expect(constant).to.have.a.nested.property("phenomenon.fullName", "TargetSchema.testPhenomenon"); + expect(constant).to.have.a.property("definition", "PI"); + expect(constant).to.have.a.property("numerator", 5.5); + expect(constant).to.have.a.property("denominator", 5.1); + }); }); it("it should merge missing constant with referenced phenomenon", async () => { - const targetContext = new SchemaContext(); + const targetContext = await BisTestHelper.getNewContext(); await Schema.fromJson({ ...referenceJson, items: { @@ -118,8 +121,16 @@ describe("Constant merger tests", () => { ], }); - const mergedConstant = await mergedSchema.getItem("testConstant"); - expect((mergedConstant?.phenomenon)?.fullName).to.be.equals("ReferenceSchema.testPhenomenon"); + await expect(mergedSchema.getItem("testConstant")).to.be.eventually.not.undefined + .then((constant: Constant) => { + expect(constant).to.have.a.property("schemaItemType", SchemaItemType.Constant); + expect(constant).to.have.a.property("label", "Test Constant"); + expect(constant).to.have.a.property("description", "testing a constant"); + expect(constant).to.have.a.nested.property("phenomenon.fullName", "ReferenceSchema.testPhenomenon"); + expect(constant).to.have.a.property("definition", "PI"); + expect(constant).to.have.a.property("numerator", 5); + expect(constant).to.have.a.property("denominator", 5.1); + }); }); it("it should throw error if definition conflict exist", async () => { @@ -141,7 +152,7 @@ describe("Constant merger tests", () => { phenomenon: "TargetSchema.testPhenomenon", }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const merge = merger.merge({ @@ -184,7 +195,7 @@ describe("Constant merger tests", () => { denominator: 5.1, }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const merge = merger.merge({ @@ -226,7 +237,7 @@ describe("Constant merger tests", () => { denominator: 4.2, }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const merge = merger.merge({ diff --git a/core/ecschema-editing/src/test/Merging/CustomAttributeClassMerger.test.ts b/core/ecschema-editing/src/test/Merging/CustomAttributeClassMerger.test.ts new file mode 100644 index 000000000000..8da96b459349 --- /dev/null +++ b/core/ecschema-editing/src/test/Merging/CustomAttributeClassMerger.test.ts @@ -0,0 +1,233 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Bentley Systems, Incorporated. All rights reserved. +* See LICENSE.md in the project root for license terms and full copyright notice. +*--------------------------------------------------------------------------------------------*/ +import { CustomAttributeClass, CustomAttributeContainerType, Schema, SchemaContext, SchemaItemType } from "@itwin/ecschema-metadata"; +import { SchemaMerger } from "../../Merging/SchemaMerger"; +import { expect } from "chai"; +import { ECEditingStatus } from "../../Editing/Exception"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; + +/* eslint-disable @typescript-eslint/naming-convention */ + +describe("CustomAttributeClass merger tests", () => { + let targetContext: SchemaContext; + const targetJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", + name: "TargetSchema", + version: "1.0.0", + alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], + }; + + beforeEach(async () => { + targetContext = await BisTestHelper.getNewContext(); + }); + + it("should merge missing custom attribute class", async () => { + await Schema.fromJson(targetJson, targetContext); + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.CustomAttributeClass, + itemName: "TestCAClass", + difference: { + label: "Test Custom Attribute Class", + appliesTo: "AnyClass", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestCAClass")).to.be.eventually.not.undefined + .then((mergedItem: CustomAttributeClass) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.CustomAttributeClass); + expect(mergedItem).to.have.a.property("label", "Test Custom Attribute Class"); + expect(mergedItem).to.have.a.property("appliesTo", CustomAttributeContainerType.AnyClass); + }); + }); + + it("should merge custom attribute class changes", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TestCAClass: { + schemaItemType: "CustomAttributeClass", + label: "TestCustomAttributeClass", + appliesTo: "AnyProperty", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "modify", + schemaType: SchemaItemType.CustomAttributeClass, + itemName: "TestCAClass", + difference: { + label: "Test Custom Attribute Class", + appliesTo: "AnyClass", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestCAClass")).to.be.eventually.not.undefined + .then((mergedItem: CustomAttributeClass) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.CustomAttributeClass); + expect(mergedItem).to.have.a.property("label", "Test Custom Attribute Class"); + expect(mergedItem).to.have.a.property("appliesTo", CustomAttributeContainerType.AnyClass | CustomAttributeContainerType.AnyProperty); + }); + }); + + it("should merge custom attribute base class derived from the current base class", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + BaseCAClass: { + schemaItemType: "CustomAttributeClass", + appliesTo: "AnyProperty", + }, + TestCAClass: { + schemaItemType: "CustomAttributeClass", + appliesTo: "AnyProperty", + baseClass: "TargetSchema.BaseCAClass", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.CustomAttributeClass, + itemName: "TestBase", + difference: { + baseClass: "SourceSchema.BaseCAClass", + appliesTo: "AnyProperty", + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.CustomAttributeClass, + itemName: "TestCAClass", + difference: { + baseClass: "SourceSchema.TestBase", + }, + }, + ], + }); + const mergedItem = await mergedSchema.getItem("TestCAClass"); + expect(mergedItem!.toJSON().baseClass).deep.eq("TargetSchema.TestBase"); + }); + + it("should throw an error when merging custom attribute base class changed from undefined to existing one", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + testCAClass: { + schemaItemType: "CustomAttributeClass", + appliesTo: "AnyClass", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.CustomAttributeClass, + itemName: "BaseCAClass", + difference: { + appliesTo: "AnyClass", + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.CustomAttributeClass, + itemName: "testCAClass", + difference: { + baseClass: "SourceSchema.BaseCAClass", + }, + }, + ], + }); + + await expect(merge).to.be.rejectedWith("Changing the class 'testCAClass' baseClass is not supported."); + }); + + it("should throw an error when merging custom attribute base class to one that doesn't derive from", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TargetBase: { + schemaItemType: "CustomAttributeClass", + appliesTo: "AnyProperty", + }, + TestCAClass: { + schemaItemType: "CustomAttributeClass", + baseClass: "TargetSchema.TargetBase", + appliesTo: "AnyProperty", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.CustomAttributeClass, + itemName: "SourceBase", + difference: { + appliesTo: "AnyProperty", + }, + }, + { + changeType: "add", + schemaType: SchemaItemType.CustomAttributeClass, + itemName: "TestBase", + difference: { + baseClass: "SourceSchema.SourceBase", + appliesTo: "AnyProperty", + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.CustomAttributeClass, + itemName: "TestCAClass", + difference: { + baseClass: "SourceSchema.TestBase", + }, + }, + ], + }); + + await expect(merge).to.be.eventually.rejected.then(function (error) { + expect(error).to.have.property("errorNumber", ECEditingStatus.SetBaseClass); + expect(error).to.have.nested.property("innerError.message", `Base class TargetSchema.TestBase must derive from TargetSchema.TargetBase.`); + expect(error).to.have.nested.property("innerError.errorNumber", ECEditingStatus.InvalidBaseClass); + }); + }); +}); diff --git a/core/ecschema-editing/src/test/Merging/CustomAttributeMerger.test.ts b/core/ecschema-editing/src/test/Merging/CustomAttributeMerger.test.ts index 2c09218d1168..8c2931b36aa4 100644 --- a/core/ecschema-editing/src/test/Merging/CustomAttributeMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/CustomAttributeMerger.test.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { EntityClass, RelationshipClass, Schema, SchemaContext, SchemaItemType } from "@itwin/ecschema-metadata"; import { SchemaMerger } from "../../Merging/SchemaMerger"; -import { expect } from "chai"; import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; +import { expect } from "chai"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -16,10 +17,16 @@ describe("Custom Attribute merge", () => { name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; beforeEach(async () => { - targetContext = new SchemaContext(); + targetContext = await BisTestHelper.getNewContext(); await Schema.fromJson({ $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", name: "TestSchema", @@ -289,6 +296,7 @@ describe("Custom Attribute merge", () => { expect(mergedSchema.toJSON().customAttributes).deep.eq( [ + ...targetJson.customAttributes, { className: "TargetSchema.TestCA", }, diff --git a/core/ecschema-editing/src/test/Merging/Edits/RenameEdit.test.ts b/core/ecschema-editing/src/test/Merging/Edits/RenameEdit.test.ts index 2e0e73037bf1..c9713029a156 100644 --- a/core/ecschema-editing/src/test/Merging/Edits/RenameEdit.test.ts +++ b/core/ecschema-editing/src/test/Merging/Edits/RenameEdit.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CustomAttributeClass, EntityClass, Enumeration, EnumerationArrayProperty, EnumerationProperty, KindOfQuantity, Mixin, NavigationProperty, PropertyCategory, RelationshipClass, Schema, SchemaContext, StructArrayProperty, StructClass, StructProperty } from "@itwin/ecschema-metadata"; import { ConflictCode, getSchemaDifferences, SchemaEdits, SchemaMerger } from "../../../ecschema-editing"; +import { BisTestHelper } from "../../TestUtils/BisTestHelper"; import { expect } from "chai"; import "chai-as-promised"; @@ -18,6 +19,17 @@ describe("Rename change tests", () => { name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { + name: "CoreCustomAttributes", + version: "01.00.01", + }, + ], + customAttributes: [ + { + className: "CoreCustomAttributes.DynamicSchema", + }, + ], }; const sourceJson = { @@ -71,7 +83,7 @@ describe("Rename change tests", () => { }; beforeEach(async () => { - targetContext = new SchemaContext(); + targetContext = await BisTestHelper.getNewContext(); sourceContext = new SchemaContext(); }); diff --git a/core/ecschema-editing/src/test/Merging/Edits/SchemaEdit.test.ts b/core/ecschema-editing/src/test/Merging/Edits/SchemaEdit.test.ts index 909d2e2c4514..b5e8733a3909 100644 --- a/core/ecschema-editing/src/test/Merging/Edits/SchemaEdit.test.ts +++ b/core/ecschema-editing/src/test/Merging/Edits/SchemaEdit.test.ts @@ -2,9 +2,10 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { expect } from "chai"; import { EntityClass, PrimitiveProperty, PrimitiveType, Schema, SchemaContext, StructClass } from "@itwin/ecschema-metadata"; import { ConflictCode, getSchemaDifferences, SchemaEdits, SchemaMerger } from "../../../ecschema-editing"; +import { BisTestHelper } from "../../TestUtils/BisTestHelper"; +import { expect } from "chai"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -15,6 +16,17 @@ describe("Schema Edit tests", () => { name: "ConflictSchema", version: "1.0.0", alias: "conflict", + references: [ + { + name: "CoreCustomAttributes", + version: "01.00.01", + }, + ], + customAttributes: [ + { + className: "CoreCustomAttributes.DynamicSchema", + }, + ], items: { SameNameOtherItemType: { schemaItemType: "EntityClass", @@ -27,7 +39,7 @@ describe("Schema Edit tests", () => { ], }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const sourceSchemas: Schema[] = [ // 1st case: Conflicting name diff --git a/core/ecschema-editing/src/test/Merging/EntityClassMerger.test.ts b/core/ecschema-editing/src/test/Merging/EntityClassMerger.test.ts new file mode 100644 index 000000000000..2281062fdfc3 --- /dev/null +++ b/core/ecschema-editing/src/test/Merging/EntityClassMerger.test.ts @@ -0,0 +1,521 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Bentley Systems, Incorporated. All rights reserved. +* See LICENSE.md in the project root for license terms and full copyright notice. +*--------------------------------------------------------------------------------------------*/ +import { ECClassModifier, EntityClass, Schema, SchemaContext, SchemaItemKey, SchemaItemType } from "@itwin/ecschema-metadata"; +import { SchemaMerger } from "../../Merging/SchemaMerger"; +import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; +import { ECEditingStatus } from "../../Editing/Exception"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; +import { expect } from "chai"; + +/* eslint-disable @typescript-eslint/naming-convention */ + +describe("EntityClass merger tests", () => { + let targetContext: SchemaContext; + const targetJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", + name: "TargetSchema", + version: "1.0.0", + alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], + }; + + beforeEach(async () => { + targetContext = await BisTestHelper.getNewContext(); + }); + + it("should merge missing entity class with baseClass", async () => { + await Schema.fromJson(targetJson, targetContext); + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "TestBase", + difference: { + modifier: "Abstract", + }, + }, + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + label: "Test Entity", + description: "Description for TestEntity", + baseClass: "SourceSchema.TestBase", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestEntity")).to.be.eventually.not.undefined + .then((mergedItem: EntityClass) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.EntityClass); + expect(mergedItem).to.have.a.property("label", "Test Entity"); + expect(mergedItem).to.have.a.property("description", "Description for TestEntity"); + expect(mergedItem).to.have.a.nested.property("baseClass.fullName", "TargetSchema.TestBase"); + }); + }); + + it("should merge missing entity class with referenced baseClass", async () => { + const referencedSchema = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", + name: "TestSchema", + version: "01.00.15", + alias: "test", + items: { + TestBase: { + schemaItemType: "EntityClass", + modifier: "Abstract", + }, + }, + }; + + await Schema.fromJson(referencedSchema, targetContext); + await Schema.fromJson(targetJson, targetContext); + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaOtherTypes.SchemaReference, + difference: { + name: "TestSchema", + version: "01.00.15", + }, + }, + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + label: "Test Entity", + description: "Description for TestEntity", + baseClass: "TestSchema.TestBase", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestEntity")).to.be.eventually.not.undefined + .then((mergedItem: EntityClass) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.EntityClass); + expect(mergedItem).to.have.a.property("label", "Test Entity"); + expect(mergedItem).to.have.a.property("description", "Description for TestEntity"); + expect(mergedItem).to.have.a.nested.property("baseClass.fullName", "TestSchema.TestBase"); + }); + }); + + it("should merge missing entity class with referenced mixin", async () => { + const referencedSchema = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", + name: "TestSchema", + version: "01.00.15", + alias: "test", + items: { + BaseClass: { + schemaItemType: "EntityClass", + }, + TestMixin: { + schemaItemType: "Mixin", + appliesTo: "TestSchema.BaseClass", + }, + }, + }; + + await Schema.fromJson(referencedSchema, targetContext); + await Schema.fromJson(targetJson, targetContext); + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaOtherTypes.SchemaReference, + difference: { + name: "TestSchema", + version: "01.00.15", + }, + }, + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + label: "Test Entity", + description: "Description for TestEntity", + baseClass: "TestSchema.BaseClass", + mixins: [ + "TestSchema.TestMixin", + ], + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestEntity")).to.be.eventually.not.undefined + .then((mergedItem: EntityClass) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.EntityClass); + expect(mergedItem).to.have.a.property("label", "Test Entity"); + expect(mergedItem).to.have.a.property("description", "Description for TestEntity"); + expect(mergedItem).to.have.a.nested.property("baseClass.fullName", "TestSchema.BaseClass"); + expect(mergedItem).to.have.a.property("mixins").that.satisfies((mixins: SchemaItemKey[]) => { + return mixins.find((mixin) => mixin.fullName === "TestSchema.TestMixin") !== undefined; + }); + }); + }); + + it("should merge class modifier changed from Sealed to None", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TestEntity: { + schemaItemType: "EntityClass", + modifier: "Sealed", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "modify", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + modifier: "None", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestEntity")).to.be.eventually.not.undefined + .then((mergedItem: EntityClass) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.EntityClass); + expect(mergedItem).to.have.a.property("modifier", ECClassModifier.None); + }); + }); + + it("should merge entity base class derived from the existing base class", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + BaseEntity: { + schemaItemType: "EntityClass", + modifier: "Abstract", + }, + TestEntity: { + schemaItemType: "EntityClass", + baseClass: "TargetSchema.BaseEntity", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "TestBase", + difference: { + baseClass: "SourceSchema.BaseEntity", + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + baseClass: "SourceSchema.TestBase", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestEntity")).to.be.eventually.not.undefined + .then((mergedItem: EntityClass) => { + expect(mergedItem).to.have.a.nested.property("baseClass.fullName", "TargetSchema.TestBase"); + }); + }); + + it("should throw an error when merging classes with different schema item types", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TestClass: { + schemaItemType: "StructClass", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "modify", + schemaType: SchemaItemType.StructClass, + itemName: "TestClass", + difference: { + schemaItemType: "EntityClass", + } as any, // difference needs to be any-fied to be able to set the schemaItemType property. + }, + ], + }); + await expect(merge).to.be.rejectedWith("Changing the type of item 'TestClass' not supported."); + }); + + it("should throw an error when merging class modifier changed from Abstract to Sealed", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TestEntity: { + schemaItemType: "EntityClass", + modifier: "Abstract", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "modify", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + modifier: "Sealed", + }, + }, + ], + }); + + await expect(merge).to.be.rejectedWith("Changing the class 'TestEntity' modifier is not supported."); + }); + + it("should throw an error when merging entity base class changed from existing one to undefined", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + BaseEntity: { + schemaItemType: "EntityClass", + }, + TestEntity: { + schemaItemType: "EntityClass", + baseClass: "TargetSchema.BaseEntity", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "modify", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + baseClass: undefined, + }, + }, + ], + }); + + await expect(merge).to.be.rejectedWith("Changing the class 'TestEntity' baseClass is not supported."); + }); + + it("should throw an error when merging entity base class changed from undefined to existing one", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TestEntity: { + schemaItemType: "EntityClass", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "BaseEntity", + difference: { + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + baseClass: "SourceSchema.BaseEntity", + }, + }, + ], + }); + + await expect(merge).to.be.rejectedWith("Changing the class 'TestEntity' baseClass is not supported."); + }); + + it("should throw an error when merging entity base class to one that doesn't derive from", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TargetBase: { + schemaItemType: "EntityClass", + }, + TestEntity: { + schemaItemType: "EntityClass", + baseClass: "TargetSchema.TargetBase", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "SourceBase", + difference: { + }, + }, + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "TestBase", + difference: { + baseClass: "SourceSchema.SourceBase", + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + baseClass: "SourceSchema.TestBase", + }, + }, + ], + }); + + await expect(merge).to.be.eventually.rejected.then(function (error) { + expect(error).to.have.property("errorNumber", ECEditingStatus.SetBaseClass); + expect(error).to.have.nested.property("innerError.message", `Base class TargetSchema.TestBase must derive from TargetSchema.TargetBase.`); + expect(error).to.have.nested.property("innerError.errorNumber", ECEditingStatus.InvalidBaseClass); + }); + }); + + it("should throw an error when merging entity class with a unknown mixins", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TestEntity: { + schemaItemType: "EntityClass", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaOtherTypes.EntityClassMixin, + itemName: "TestEntity", + difference: [ + "SourceSchema.NotExistingMixin", + ], + }, + ], + }); + + await expect(merge).to.be.eventually.rejected.then(function (error) { + expect(error).to.have.property("errorNumber", ECEditingStatus.AddMixin); + expect(error).to.have.nested.property("innerError.message", `Mixin TargetSchema.NotExistingMixin could not be found in the schema context.`); + expect(error).to.have.nested.property("innerError.errorNumber", ECEditingStatus.SchemaItemNotFoundInContext); + }); + }); + + it("should throw an error when merging mixin base class to one that doesn't derive from", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TestEntity: { + schemaItemType: "EntityClass", + }, + BaseMixin: { + schemaItemType: "Mixin", + appliesTo: "TargetSchema.TestEntity", + }, + TestMixin: { + schemaItemType: "Mixin", + baseClass: "TargetSchema.BaseMixin", + appliesTo: "TargetSchema.TestEntity", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.Mixin, + itemName: "TestBase", + difference: { + appliesTo: "SourceSchema.TestEntity", + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.Mixin, + itemName: "TestMixin", + difference: { + baseClass: "SourceSchema.TestBase", + }, + }, + ], + }); + + await expect(merge).to.be.eventually.rejected.then(function (error) { + expect(error).to.have.property("errorNumber", ECEditingStatus.SetBaseClass); + expect(error).to.have.nested.property("innerError.message", `Base class TargetSchema.TestBase must derive from TargetSchema.BaseMixin.`); + expect(error).to.have.nested.property("innerError.errorNumber", ECEditingStatus.InvalidBaseClass); + }); + }); +}); diff --git a/core/ecschema-editing/src/test/Merging/EnumerationMerger.test.ts b/core/ecschema-editing/src/test/Merging/EnumerationMerger.test.ts index bca0a24d4e20..6ee2a2925905 100644 --- a/core/ecschema-editing/src/test/Merging/EnumerationMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/EnumerationMerger.test.ts @@ -2,10 +2,11 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { Enumeration, Schema, SchemaContext, SchemaItemType } from "@itwin/ecschema-metadata"; +import { Enumeration, Schema, SchemaItemType } from "@itwin/ecschema-metadata"; import { SchemaMerger } from "../../Merging/SchemaMerger"; -import { expect } from "chai"; import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; +import { expect } from "chai"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -16,12 +17,18 @@ describe("Enumeration merge tests", () => { name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; it("should merge missing enumeration", async () => { const targetSchema = await Schema.fromJson({ ...targetJson, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const mergedSchema = await merger.merge({ @@ -52,24 +59,25 @@ describe("Enumeration merge tests", () => { ], }); - const mergedEnumeration = await mergedSchema.getItem("TestEnumeration") as Enumeration; - expect(mergedEnumeration.toJSON()).deep.equals({ - type: "int", - schemaItemType: "Enumeration", - isStrict: false, - enumerators: [ - { + await expect(mergedSchema.getItem("TestEnumeration")).to.be.eventually.not.undefined + .then((mergedEnumeration: Enumeration) => { + expect(mergedEnumeration).to.have.a.property("schemaItemType", SchemaItemType.Enumeration); + expect(mergedEnumeration).to.have.a.property("isInt", true); + expect(mergedEnumeration).to.have.a.property("isStrict", false); + expect(mergedEnumeration).to.have.a.property("enumerators").that.has.lengthOf(2); + expect(mergedEnumeration.enumerators[0]).to.deep.equals({ + description: undefined, label: "first value", name: "FirstValue", value: 0, - }, - { + }); + expect(mergedEnumeration.enumerators[1]).to.deep.equals({ + description: undefined, label: "second value", name: "SecondValue", value: 1, - }, - ], - }); + }); + }); }); it("should merge missing enumerators of the same enumeration", async () => { @@ -87,7 +95,7 @@ describe("Enumeration merge tests", () => { }], }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const mergedSchema = await merger.merge({ @@ -109,22 +117,25 @@ describe("Enumeration merge tests", () => { ], }); - const mergedEnumeration = await mergedSchema.getItem("TestEnumeration") as Enumeration; - expect(mergedEnumeration.toJSON()).deep.equals({ - schemaItemType: "Enumeration", - type: "string", - isStrict: true, - enumerators: [{ - name: "AnotherValue", - label: "totally different value", - value: "T", - }, - { - name: "FirstValue", - label: "first value", - value: "F", - }], - }); + await expect(mergedSchema.getItem("TestEnumeration")).to.be.eventually.not.undefined + .then((mergedEnumeration: Enumeration) => { + expect(mergedEnumeration).to.have.a.property("schemaItemType", SchemaItemType.Enumeration); + expect(mergedEnumeration).to.have.a.property("isString", true); + expect(mergedEnumeration).to.have.a.property("isStrict", true); + expect(mergedEnumeration).to.have.a.property("enumerators").that.has.lengthOf(2); + expect(mergedEnumeration.enumerators[0]).to.deep.equals({ + description: undefined, + label: "totally different value", + name: "AnotherValue", + value: "T", + }); + expect(mergedEnumeration.enumerators[1]).to.deep.equals({ + description: undefined, + label: "first value", + name: "FirstValue", + value: "F", + }); + }); }); it("should merge a super-set enumeration", async () => { @@ -147,7 +158,7 @@ describe("Enumeration merge tests", () => { }], }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const mergedSchema = await merger.merge({ @@ -168,29 +179,31 @@ describe("Enumeration merge tests", () => { ], }); - const mergedEnumeration = await mergedSchema.getItem("TestEnumeration") as Enumeration; - expect(mergedEnumeration.toJSON()).deep.equals({ - type: "int", - schemaItemType: "Enumeration", - isStrict: false, - enumerators: [ - { + await expect(mergedSchema.getItem("TestEnumeration")).to.be.eventually.not.undefined + .then((mergedEnumeration: Enumeration) => { + expect(mergedEnumeration).to.have.a.property("schemaItemType", SchemaItemType.Enumeration); + expect(mergedEnumeration).to.have.a.property("isInt", true); + expect(mergedEnumeration).to.have.a.property("isStrict", false); + expect(mergedEnumeration).to.have.a.property("enumerators").that.has.lengthOf(3); + expect(mergedEnumeration.enumerators[0]).to.deep.equals({ + description: undefined, label: "first value", name: "FirstValue", value: 0, - }, - { + }); + expect(mergedEnumeration.enumerators[1]).to.deep.equals({ + description: undefined, label: "second value", name: "SecondValue", value: 1, - }, - { + }); + expect(mergedEnumeration.enumerators[2]).to.deep.equals({ + description: undefined, label: "Third value", name: "ThirdValue", value: 2, - }, - ], - }); + }); + }); }); it("should merge missing enumerator attributes", async () => { @@ -209,7 +222,7 @@ describe("Enumeration merge tests", () => { ], }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const mergedSchema = await merger.merge({ @@ -229,20 +242,19 @@ describe("Enumeration merge tests", () => { ], }); - const mergedEnumeration = await mergedSchema.getItem("TestEnumeration") as Enumeration; - expect(mergedEnumeration.toJSON()).deep.eq({ - type: "int", - schemaItemType: "Enumeration", - isStrict: true, - enumerators: [ - { + await expect(mergedSchema.getItem("TestEnumeration")).to.be.eventually.not.undefined + .then((mergedEnumeration: Enumeration) => { + expect(mergedEnumeration).to.have.a.property("schemaItemType", SchemaItemType.Enumeration); + expect(mergedEnumeration).to.have.a.property("isInt", true); + expect(mergedEnumeration).to.have.a.property("isStrict", true); + expect(mergedEnumeration).to.have.a.property("enumerators").that.has.lengthOf(1); + expect(mergedEnumeration.enumerators[0]).to.deep.equals({ description: "This is for enumerator one", label: "Enumerator One", name: "EnumeratorOne", value: 100, - }, - ], - }); + }); + }); }); it("should throw an error if source enumeration and target enumeration type mismatch", async () => { @@ -260,7 +272,7 @@ describe("Enumeration merge tests", () => { }], }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const merge = merger.merge({ @@ -298,7 +310,7 @@ describe("Enumeration merge tests", () => { ], }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const merge = merger.merge({ diff --git a/core/ecschema-editing/src/test/Merging/KindOfQuantityMerger.test.ts b/core/ecschema-editing/src/test/Merging/KindOfQuantityMerger.test.ts index 41e08ad14da8..3612662ba669 100644 --- a/core/ecschema-editing/src/test/Merging/KindOfQuantityMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/KindOfQuantityMerger.test.ts @@ -6,6 +6,7 @@ import { KindOfQuantity, Schema, SchemaContext, SchemaItemType } from "@itwin/ec import { SchemaMerger } from "../../Merging/SchemaMerger"; import { expect } from "chai"; import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -17,6 +18,12 @@ describe("KindOfQuantity merge tests", () => { name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; const referenceJson = { $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", @@ -74,7 +81,7 @@ describe("KindOfQuantity merge tests", () => { }; beforeEach(async () => { - targetContext = new SchemaContext(); + targetContext = await BisTestHelper.getNewContext(); await Schema.fromJson(referenceJson, targetContext); }); @@ -122,6 +129,7 @@ describe("KindOfQuantity merge tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "ReferenceSchema", version: "1.2.0", @@ -215,6 +223,7 @@ describe("KindOfQuantity merge tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "ReferenceSchema", version: "1.2.0", @@ -285,6 +294,7 @@ describe("KindOfQuantity merge tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "ReferenceSchema", version: "1.2.0", @@ -334,6 +344,7 @@ describe("KindOfQuantity merge tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "ReferenceSchema", version: "1.2.0", diff --git a/core/ecschema-editing/src/test/Merging/MixinMerger.test.ts b/core/ecschema-editing/src/test/Merging/MixinMerger.test.ts new file mode 100644 index 000000000000..b34a7cd53f0e --- /dev/null +++ b/core/ecschema-editing/src/test/Merging/MixinMerger.test.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Bentley Systems, Incorporated. All rights reserved. +* See LICENSE.md in the project root for license terms and full copyright notice. +*--------------------------------------------------------------------------------------------*/ +import { Mixin, Schema, SchemaContext, SchemaItemType } from "@itwin/ecschema-metadata"; +import { SchemaMerger } from "../../Merging/SchemaMerger"; +import { expect } from "chai"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; + +/* eslint-disable @typescript-eslint/naming-convention */ + +describe("Mixin merger tests", () => { + let targetContext: SchemaContext; + const targetJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", + name: "TargetSchema", + version: "1.0.0", + alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], + }; + + beforeEach(async () => { + targetContext = await BisTestHelper.getNewContext(); + }); + + it("should merge missing mixin", async () => { + await Schema.fromJson(targetJson, targetContext); + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "TestEntity", + difference: { + modifier: "Abstract", + }, + }, + { + changeType: "add", + schemaType: SchemaItemType.Mixin, + itemName: "TestMixin", + difference: { + label: "Test Mixin", + description: "Description for TestMixin", + appliesTo: "SourceSchema.TestEntity", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestMixin")).to.be.eventually.not.undefined + .then((mergedItem: Mixin) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.Mixin); + expect(mergedItem).to.have.a.property("label", "Test Mixin"); + expect(mergedItem).to.have.a.property("description", "Description for TestMixin"); + expect(mergedItem).to.have.a.nested.property("appliesTo.fullName", "TargetSchema.TestEntity"); + }); + }); + + it("should merge mixin base class derived from the current base class", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + BaseEntity: { + schemaItemType: SchemaItemType.EntityClass, + modifier: "Abstract", + }, + TestEntity: { + schemaItemType: SchemaItemType.EntityClass, + baseClass: "TargetSchema.BaseEntity", + }, + BaseMixin: { + schemaItemType: "Mixin", + appliesTo: "TargetSchema.BaseEntity", + }, + TestMixin: { + schemaItemType: "Mixin", + baseClass: "TargetSchema.BaseMixin", + appliesTo: "TargetSchema.TestEntity", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.Mixin, + itemName: "TestBase", + difference: { + baseClass: "SourceSchema.BaseMixin", + appliesTo: "SourceSchema.BaseEntity", + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.Mixin, + itemName: "TestMixin", + difference: { + baseClass: "SourceSchema.TestBase", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestMixin")).to.be.eventually.not.undefined + .then((mergedItem: Mixin) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.Mixin); + expect(mergedItem).to.have.a.nested.property("baseClass.fullName", "TargetSchema.TestBase"); + }); + }); + + it("should throw an error when merging mixins with different appliesTo values", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TargetEntity: { + schemaItemType: "EntityClass", + }, + TestMixin: { + schemaItemType: "Mixin", + appliesTo: "TargetSchema.TargetEntity", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.EntityClass, + itemName: "SourceEntity", + difference: { + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.Mixin, + itemName: "TestMixin", + difference: { + appliesTo: "SourceSchema.SourceEntity", + }, + }, + ], + }); + + await expect(merge).to.be.rejectedWith("Changing the mixin 'TestMixin' appliesTo is not supported."); + }); +}); diff --git a/core/ecschema-editing/src/test/Merging/PhenomenonMerger.test.ts b/core/ecschema-editing/src/test/Merging/PhenomenonMerger.test.ts index af08fd68808a..4c691e40fff7 100644 --- a/core/ecschema-editing/src/test/Merging/PhenomenonMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/PhenomenonMerger.test.ts @@ -2,8 +2,9 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { Phenomenon, Schema, SchemaContext, SchemaItemType } from "@itwin/ecschema-metadata"; +import { Phenomenon, Schema, SchemaItemType } from "@itwin/ecschema-metadata"; import { SchemaMerger } from "../../Merging/SchemaMerger"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; import { expect } from "chai"; describe("Phenomenon merger tests", () => { @@ -12,10 +13,16 @@ describe("Phenomenon merger tests", () => { name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; it("should merge missing phenomenon item", async () => { - const targetSchema = await Schema.fromJson(targetJson, new SchemaContext()); + const targetSchema = await Schema.fromJson(targetJson, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const mergedSchema = await merger.merge({ @@ -35,13 +42,13 @@ describe("Phenomenon merger tests", () => { ], }); - const mergedPhenomenon = await mergedSchema.getItem("testPhenomenon"); - expect(mergedPhenomenon!.toJSON()).deep.equals({ - schemaItemType: "Phenomenon", - label: "Area", - description: "Area description", - definition: "Units.LENGTH(2)", - }); + await expect(mergedSchema.getItem("testPhenomenon")).to.be.eventually.not.undefined + .then((phenomenon: Phenomenon) => { + expect(phenomenon).to.have.a.property("schemaItemType", SchemaItemType.Phenomenon); + expect(phenomenon).to.have.a.property("label").to.equal("Area"); + expect(phenomenon).to.have.a.property("description").to.equal("Area description"); + expect(phenomenon).to.have.a.property("definition").to.equal("Units.LENGTH(2)"); + }); }); it("should throw error for definition conflict", async () => { @@ -56,7 +63,7 @@ describe("Phenomenon merger tests", () => { definition: "Units.LENGTH(4)", }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); await expect(merger.merge({ diff --git a/core/ecschema-editing/src/test/Merging/PropertyCategoryMerger.test.ts b/core/ecschema-editing/src/test/Merging/PropertyCategoryMerger.test.ts index 7892ce6c11d6..edbbce11c955 100644 --- a/core/ecschema-editing/src/test/Merging/PropertyCategoryMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/PropertyCategoryMerger.test.ts @@ -2,22 +2,29 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { PropertyCategory, Schema, SchemaContext, SchemaItemType } from "@itwin/ecschema-metadata"; +import { PropertyCategory, Schema, SchemaItemType } from "@itwin/ecschema-metadata"; import { SchemaMerger } from "../../Merging/SchemaMerger"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; import { expect } from "chai"; /* eslint-disable @typescript-eslint/naming-convention */ describe("PropertyCategory merge tests", () => { - const targetJson = { + const targetJson = { $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; it("should merge missing PropertyCategory", async () => { - const targetSchema = await Schema.fromJson(targetJson, new SchemaContext()); + const targetSchema = await Schema.fromJson(targetJson, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const mergedSchema = await merger.merge({ sourceSchemaName: "SourceSchema.01.02.03", @@ -35,12 +42,12 @@ describe("PropertyCategory merge tests", () => { ], }); - const mergedCategory = await mergedSchema.getItem("TestPropertyCategory"); - expect(mergedCategory!.toJSON()).deep.equals({ - schemaItemType: "PropertyCategory", - label: "ValueTrack Metadata", - priority: 100000, - }); + await expect(mergedSchema.getItem("TestPropertyCategory")).to.be.eventually.not.undefined + .then((propertyCategory: PropertyCategory) => { + expect(propertyCategory).to.have.a.property("schemaItemType", SchemaItemType.PropertyCategory); + expect(propertyCategory).to.have.a.property("label", "ValueTrack Metadata"); + expect(propertyCategory).to.have.a.property("priority", 100000); + }); }); it("should override PropertyCategory", async () => { @@ -48,12 +55,12 @@ describe("PropertyCategory merge tests", () => { ...targetJson, items: { TestPropertyCategory: { - schemaItemType:"PropertyCategory", - label:"ValueTrack Metadata", - priority:100000, + schemaItemType: "PropertyCategory", + label: "ValueTrack Metadata", + priority: 100000, }, }, - }, new SchemaContext()); + }, await BisTestHelper.getNewContext()); const merger = new SchemaMerger(targetSchema.context); const mergedSchema = await merger.merge({ @@ -71,11 +78,11 @@ describe("PropertyCategory merge tests", () => { ], }); - const mergedCategory = await mergedSchema.getItem("TestPropertyCategory"); - expect(mergedCategory!.toJSON()).deep.eq({ - schemaItemType:"PropertyCategory", - label:"ValueTrack Metadata", - priority:99, - }); + await expect(mergedSchema.getItem("TestPropertyCategory")).to.be.eventually.not.undefined + .then((propertyCategory: PropertyCategory) => { + expect(propertyCategory).to.have.a.property("schemaItemType", SchemaItemType.PropertyCategory); + expect(propertyCategory).to.have.a.property("label", "ValueTrack Metadata"); + expect(propertyCategory).to.have.a.property("priority", 99); + }); }); }); diff --git a/core/ecschema-editing/src/test/Merging/PropertyMerger.test.ts b/core/ecschema-editing/src/test/Merging/PropertyMerger.test.ts index db3975ccf690..9082af38b79c 100644 --- a/core/ecschema-editing/src/test/Merging/PropertyMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/PropertyMerger.test.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { CustomAttributeClass, EntityClass, Mixin, Schema, SchemaContext, SchemaItemType, StructClass } from "@itwin/ecschema-metadata"; import { SchemaMerger } from "../../Merging/SchemaMerger"; -import { expect } from "chai"; import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; +import { expect } from "chai"; + /* eslint-disable @typescript-eslint/naming-convention */ describe("Property merger tests", () => { @@ -15,6 +17,12 @@ describe("Property merger tests", () => { name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; const testJson = { @@ -87,7 +95,7 @@ describe("Property merger tests", () => { }; beforeEach(async () => { - targetContext = new SchemaContext(); + targetContext = await BisTestHelper.getNewContext(); await Schema.fromJson(testJson, targetContext); }); @@ -546,6 +554,7 @@ describe("Property merger tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -666,6 +675,7 @@ describe("Property merger tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -835,6 +845,7 @@ describe("Property merger tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -899,6 +910,7 @@ describe("Property merger tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -1303,6 +1315,7 @@ describe("Property merger tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -1345,6 +1358,7 @@ describe("Property merger tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", diff --git a/core/ecschema-editing/src/test/Merging/RelationshipClassMerger.test.ts b/core/ecschema-editing/src/test/Merging/RelationshipClassMerger.test.ts index 62e5effad852..43e0237293fd 100644 --- a/core/ecschema-editing/src/test/Merging/RelationshipClassMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/RelationshipClassMerger.test.ts @@ -8,12 +8,13 @@ import { expect } from "chai"; import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; import { ECEditingStatus } from "../../Editing/Exception"; import { AnyDiagnostic } from "../../ecschema-editing"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; /* eslint-disable @typescript-eslint/naming-convention */ function getRuleViolationMessage(ruleViolations: AnyDiagnostic[]) { let violations = ""; - for (const diagnostic of ruleViolations){ + for (const diagnostic of ruleViolations) { violations += `${diagnostic.code}: ${diagnostic.messageText}\r\n`; } return violations; @@ -26,7 +27,14 @@ describe("Relationship Class merger tests", () => { name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; + const testJson = { $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", name: "TestSchema", @@ -114,8 +122,8 @@ describe("Relationship Class merger tests", () => { }; } - beforeEach(() => { - targetContext = new SchemaContext(); + beforeEach(async () => { + targetContext = await BisTestHelper.getNewContext(); }); it("should merge missing relationship class with added constraint classes", async () => { @@ -285,6 +293,7 @@ describe("Relationship Class merger tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -357,6 +366,7 @@ describe("Relationship Class merger tests", () => { const targetSchema = await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -460,6 +470,7 @@ describe("Relationship Class merger tests", () => { const targetSchema = await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -541,6 +552,7 @@ describe("Relationship Class merger tests", () => { const targetSchema = await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -606,6 +618,7 @@ describe("Relationship Class merger tests", () => { const targetSchema = await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -657,6 +670,7 @@ describe("Relationship Class merger tests", () => { const targetSchema = await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -709,6 +723,7 @@ describe("Relationship Class merger tests", () => { const targetSchema = await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -760,6 +775,7 @@ describe("Relationship Class merger tests", () => { const targetSchema = await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -816,6 +832,7 @@ describe("Relationship Class merger tests", () => { const targetSchema = await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -872,6 +889,7 @@ describe("Relationship Class merger tests", () => { const targetSchema = await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -929,6 +947,7 @@ describe("Relationship Class merger tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", @@ -1004,6 +1023,7 @@ describe("Relationship Class merger tests", () => { await Schema.fromJson({ ...targetJson, references: [ + ...targetJson.references, { name: "TestSchema", version: "01.00.15", diff --git a/core/ecschema-editing/src/test/Merging/SchemaMerger.test.ts b/core/ecschema-editing/src/test/Merging/SchemaMerger.test.ts index 2ffc58d6eb45..f4bc741cf544 100644 --- a/core/ecschema-editing/src/test/Merging/SchemaMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/SchemaMerger.test.ts @@ -3,12 +3,13 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { Schema, SchemaContext, SchemaItemType } from "@itwin/ecschema-metadata"; +import { SchemaConflictsError } from "../../Differencing/Errors"; import { SchemaMerger } from "../../Merging/SchemaMerger"; import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; import { ConflictCode, SchemaDifferenceConflict } from "../../Differencing/SchemaConflicts"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; import { expect } from "chai"; import "chai-as-promised"; -import { SchemaConflictsError } from "../../Differencing/Errors"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -34,19 +35,55 @@ describe("Schema merge tests", () => { await expect(merge).to.be.rejectedWith(SchemaConflictsError, "Schema's can't be merged if there are unresolved conflicts.") .then((error: SchemaConflictsError) => { - expect(error.sourceSchema.name).equals("SourceSchema", "Unexpected source schema name"); - expect(error.targetSchema.name).equals("TargetSchema", "Unexpected target schema name"); + expect(error).to.have.a.nested.property("sourceSchema.name", "SourceSchema", "Unexpected source schema name"); + expect(error).to.have.a.nested.property("targetSchema.name", "TargetSchema", "Unexpected target schema name"); expect(error.conflicts).includes(conflict); }); }); + it("should throw an error if the target schema cannot be located", async () => { + const merger = new SchemaMerger(new SchemaContext()); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + conflicts: [], + differences: [], + }); + + await expect(merge).to.be.rejectedWith("The target schema 'TargetSchema' could not be found in the editing context."); + }); + + it("should throw an error if the target schema cannot be located", async () => { + const targetSchema = await Schema.fromJson({ + $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", + name: "TargetSchema", + version: "1.0.0", + alias: "target", + }, new SchemaContext()); + const merger = new SchemaMerger(targetSchema.context); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + conflicts: [], + differences: [], + }); + + await expect(merge).to.be.rejectedWith("The target schema 'TargetSchema' is not dynamic. Only dynamic schemas are supported for merging."); + }); + it("should merge label and description from schema", async () => { - const targetContext = new SchemaContext(); + const targetContext = await BisTestHelper.getNewContext(); await Schema.fromJson({ $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }, targetContext); const newDescription = "This is the new description"; @@ -56,7 +93,7 @@ describe("Schema merge tests", () => { const mergedSchema = await merger.merge({ sourceSchemaName: "SourceSchema.01.02.03", targetSchemaName: "TargetSchema.01.00.00", - differences:[{ + differences: [{ changeType: "modify", schemaType: SchemaOtherTypes.Schema, difference: { @@ -65,17 +102,23 @@ describe("Schema merge tests", () => { }, }], }); - expect(mergedSchema.label).equals(newLabel, "unexpected source label"); - expect(mergedSchema.description).equals(newDescription, "unexpected source description"); + expect(mergedSchema).to.have.a.property("label", newLabel, "unexpected source label"); + expect(mergedSchema).to.have.a.property("description", newDescription, "unexpected source description"); }); it("should merge Schema Items case insensitive", async () => { - const targetContext = new SchemaContext(); + const targetContext = await BisTestHelper.getNewContext(); await Schema.fromJson({ $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], items: { TestCustomAttribute: { schemaItemType: "CustomAttributeClass", @@ -88,7 +131,7 @@ describe("Schema merge tests", () => { const mergedSchema = await merger.merge({ sourceSchemaName: "SourceSchema.01.02.03", targetSchemaName: "TargetSchema.01.00.00", - differences:[{ + differences: [{ changeType: "add", schemaType: SchemaOtherTypes.CustomAttributeInstance, appliedTo: "Schema", @@ -98,10 +141,9 @@ describe("Schema merge tests", () => { }], }); - expect(mergedSchema.toJSON().customAttributes).deep.equals( - [{ - className: "TargetSchema.TestCustomAttribute", - }], - ); + expect(mergedSchema).to.have.a.property("customAttributes").is.not.undefined; + expect(mergedSchema).to.have.a.property("customAttributes").satisfies((customAttributes: any) => { + return customAttributes.has("TargetSchema.TestCustomAttribute"); + }); }); }); diff --git a/core/ecschema-editing/src/test/Merging/SchemaReferenceMerger.test.ts b/core/ecschema-editing/src/test/Merging/SchemaReferenceMerger.test.ts index 706e247bee2e..b6aac5d56317 100644 --- a/core/ecschema-editing/src/test/Merging/SchemaReferenceMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/SchemaReferenceMerger.test.ts @@ -2,43 +2,38 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { Schema, SchemaContext } from "@itwin/ecschema-metadata"; +import { Schema } from "@itwin/ecschema-metadata"; import { SchemaMerger } from "../../Merging/SchemaMerger"; -import { expect } from "chai"; import { SchemaOtherTypes } from "../../Differencing/SchemaDifference"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; +import { expect } from "chai"; describe("Schema reference merging tests", () => { - const sourceJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", - name: "SourceSchema", - version: "1.2.3", - alias: "source", - }; const targetJson = { $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }; it("should merge missing schema references", async () => { - const targetSchemaContext = new SchemaContext(); + const targetSchemaContext = await BisTestHelper.getNewContext(); const targetSchema = await Schema.fromJson({ ...targetJson, }, targetSchemaContext); await Schema.fromJson({ $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", - name: "BisCore", + name: "TestSchema", version: "01.00.15", - alias: "bis", - }, targetSchemaContext); - await Schema.fromJson({ - $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", - name: "CoreCustomAttributes", - version: "01.00.03", - alias: "ca", + alias: "ts", }, targetSchemaContext); const merger = new SchemaMerger(targetSchema.context); @@ -49,86 +44,61 @@ describe("Schema reference merging tests", () => { changeType: "add", schemaType: SchemaOtherTypes.SchemaReference, difference: { - name: "BisCore", + name: "TestSchema", version: "01.00.15", }, - }, { - changeType: "add", - schemaType: SchemaOtherTypes.SchemaReference, - difference: { - name: "CoreCustomAttributes", - version: "01.00.03", - }, }], }); - expect(mergedSchema.toJSON().references).deep.equals([ - { name: "BisCore", version: "01.00.15" }, - { name: "CoreCustomAttributes", version: "01.00.03" }, - ]); + await expect(mergedSchema.getReference("TestSchema")).to.be.eventually.not.undefined + .then((reference: Schema) => { + expect(reference).to.have.a.property("name", "TestSchema", "unexpected schema name"); + expect(reference).to.have.a.property("readVersion", 1, "unexpected read version"); + expect(reference).to.have.a.property("writeVersion", 0, "unexpected write version"); + expect(reference).to.have.a.property("minorVersion", 15, "unexpected minor version"); + }); }); it("should not merge if target has more recent schema references", async () => { - const sourceSchemaContext = new SchemaContext(); - const targetSchemaContext = new SchemaContext(); - - // For this test case we need schema mocks we reference. - // they can be empty, it's just there to get resolved by the schema context. - await Schema.fromJson({ - $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", - name: "BisCore", - version: "01.00.15", - alias: "bis", - }, sourceSchemaContext); - await Schema.fromJson({ - $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", - name: "BisCore", - version: "01.00.16", - alias: "bis", - }, targetSchemaContext); - - const sourceSchema = await Schema.fromJson({ - ...sourceJson, - references: [ - { - name: "BisCore", - version: "01.00.15", - }, - ], - }, sourceSchemaContext); - + const targetSchemaContext = await BisTestHelper.getNewContext(); const targetSchema = await Schema.fromJson({ ...targetJson, references: [ - { - name: "BisCore", - version: "01.00.16", - }, + ...targetJson.references, + { name: "BisCore", version: "01.00.01" }, ], }, targetSchemaContext); const merger = new SchemaMerger(targetSchema.context); - const mergedSchema = await merger.mergeSchemas(targetSchema, sourceSchema); - const bisCoreReference = await mergedSchema.getReference("BisCore"); - expect(bisCoreReference?.schemaKey.toString()).equals("BisCore.01.00.16"); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [{ + changeType: "modify", + schemaType: SchemaOtherTypes.SchemaReference, + difference: { + name: "BisCore", + version: "01.00.00", + }, + }], + }); + + await expect(mergedSchema.getReference("BisCore")).to.be.eventually.not.undefined + .then((reference: Schema) => { + expect(reference).to.have.a.property("name", "BisCore", "unexpected schema name"); + expect(reference).to.have.a.property("readVersion", 1, "unexpected read version"); + expect(reference).to.have.a.property("writeVersion", 0, "unexpected write version"); + expect(reference).to.have.a.property("minorVersion", 1, "unexpected minor version"); + }); }); it("should fail if schema references are incompatible", async () => { - const targetSchemaContext = new SchemaContext(); - await Schema.fromJson({ - $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", - name: "BisCore", - version: "01.00.15", - alias: "bis", - }, targetSchemaContext); - + const targetSchemaContext = await BisTestHelper.getNewContext(); const targetSchema = await Schema.fromJson({ ...targetJson, references: [ - { - name: "BisCore", - version: "01.00.15", - }, + ...targetJson.references, + { name: "BisCore", version: "01.00.01" }, ], }, targetSchemaContext); @@ -145,6 +115,6 @@ describe("Schema reference merging tests", () => { }, }], }); - await expect(merge).to.eventually.rejectedWith("Schemas references of BisCore have incompatible versions: 01.00.15 and 01.01.01"); + await expect(merge).to.eventually.rejectedWith("Schemas references of BisCore have incompatible versions: 01.00.01 and 01.01.01"); }); }); diff --git a/core/ecschema-editing/src/test/Merging/StructClassMerger.test.ts b/core/ecschema-editing/src/test/Merging/StructClassMerger.test.ts new file mode 100644 index 000000000000..c92b27a38328 --- /dev/null +++ b/core/ecschema-editing/src/test/Merging/StructClassMerger.test.ts @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Bentley Systems, Incorporated. All rights reserved. +* See LICENSE.md in the project root for license terms and full copyright notice. +*--------------------------------------------------------------------------------------------*/ +import { Schema, SchemaContext, SchemaItemType, StructClass } from "@itwin/ecschema-metadata"; +import { SchemaMerger } from "../../Merging/SchemaMerger"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; +import { expect } from "chai"; + +/* eslint-disable @typescript-eslint/naming-convention */ + +describe("StructClass merger tests", () => { + let targetContext: SchemaContext; + const targetJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", + name: "TargetSchema", + version: "1.0.0", + alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], + }; + + beforeEach(async () => { + targetContext = await BisTestHelper.getNewContext(); + }); + + it("should merge missing struct class", async () => { + await Schema.fromJson(targetJson, targetContext); + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.StructClass, + itemName: "TestStruct", + difference: { + label: "Test Structure", + description: "Description for Test Structure", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestStruct")).to.be.eventually.not.undefined + .then((mergedItem: StructClass) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.StructClass); + expect(mergedItem).to.have.a.property("label", "Test Structure"); + expect(mergedItem).to.have.a.property("description", "Description for Test Structure"); + }); + }); + + it("should merge struct class changes", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TestStruct: { + schemaItemType: "StructClass", + label: "Struct", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "modify", + schemaType: SchemaItemType.StructClass, + itemName: "TestStruct", + difference: { + description: "Description for Test Structure", + label: "Test Structure", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestStruct")).to.be.eventually.not.undefined + .then((mergedItem: StructClass) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.StructClass); + expect(mergedItem).to.have.a.property("label", "Test Structure"); + expect(mergedItem).to.have.a.property("description", "Description for Test Structure"); + }); + }); + + it("should merge struct base class derived from the existing base class", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + BaseStruct: { + schemaItemType: "StructClass", + }, + TestStruct: { + schemaItemType: "StructClass", + baseClass: "TargetSchema.BaseStruct", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const mergedSchema = await merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.StructClass, + itemName: "TestBase", + difference: { + baseClass: "SourceSchema.BaseStruct", + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.StructClass, + itemName: "TestStruct", + difference: { + baseClass: "SourceSchema.TestBase", + }, + }, + ], + }); + + await expect(mergedSchema.getItem("TestStruct")).to.be.eventually.not.undefined + .then((mergedItem: StructClass) => { + expect(mergedItem).to.have.a.property("schemaItemType", SchemaItemType.StructClass); + expect(mergedItem).to.have.a.nested.property("baseClass.fullName", "TargetSchema.TestBase"); + }); + }); + + it("should throw an error when merging struct base class changed from undefined to existing one", async () => { + await Schema.fromJson({ + ...targetJson, + items: { + TestStruct: { + schemaItemType: "StructClass", + }, + }, + }, targetContext); + + const merger = new SchemaMerger(targetContext); + const merge = merger.merge({ + sourceSchemaName: "SourceSchema.01.02.03", + targetSchemaName: "TargetSchema.01.00.00", + differences: [ + { + changeType: "add", + schemaType: SchemaItemType.StructClass, + itemName: "BaseStruct", + difference: { + }, + }, + { + changeType: "modify", + schemaType: SchemaItemType.StructClass, + itemName: "TestStruct", + difference: { + baseClass: "SourceSchema.BaseStruct", + }, + }, + ], + }); + + await expect(merge).to.be.rejectedWith("Changing the class 'TestStruct' baseClass is not supported."); + }); +}); diff --git a/core/ecschema-editing/src/test/Merging/UnitSystemMerger.test.ts b/core/ecschema-editing/src/test/Merging/UnitSystemMerger.test.ts index 035d9824a058..7d2af89f28a4 100644 --- a/core/ecschema-editing/src/test/Merging/UnitSystemMerger.test.ts +++ b/core/ecschema-editing/src/test/Merging/UnitSystemMerger.test.ts @@ -2,18 +2,25 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import { Schema, SchemaContext, SchemaItemType, UnitSystem } from "@itwin/ecschema-metadata"; +import { Schema, SchemaItemType, UnitSystem } from "@itwin/ecschema-metadata"; import { SchemaMerger } from "../../Merging/SchemaMerger"; import { expect } from "chai"; +import { BisTestHelper } from "../TestUtils/BisTestHelper"; describe("Unit system merger tests", () => { it("should merge missing unit system", async () => { - const targetContext = new SchemaContext(); + const targetContext = await BisTestHelper.getNewContext(); await Schema.fromJson({ $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], }, targetContext); const merger = new SchemaMerger(targetContext); @@ -31,19 +38,26 @@ describe("Unit system merger tests", () => { }], }); - const mergedUnitSystem = await mergedSchema.getItem("testUnitSystem") as UnitSystem; - expect(mergedUnitSystem).is.not.undefined; - expect(mergedUnitSystem.label).equals("Imperial"); - expect(mergedUnitSystem.description).equals("Imperial Unit System"); + await expect(mergedSchema.getItem("testUnitSystem")).to.be.eventually.not.undefined + .then((mergedUnitSystem: UnitSystem) => { + expect(mergedUnitSystem).to.have.a.property("label", "Imperial"); + expect(mergedUnitSystem).to.have.a.property("description", "Imperial Unit System"); + }); }); it("should merge unit system with new label and description", async () => { - const targetContext = new SchemaContext(); + const targetContext = await BisTestHelper.getNewContext(); await Schema.fromJson({ $schema: "https://dev.bentley.com/json_schemas/ec/32/ecschema", name: "TargetSchema", version: "1.0.0", alias: "target", + references: [ + { name: "CoreCustomAttributes", version: "01.00.01" }, + ], + customAttributes: [ + { className: "CoreCustomAttributes.DynamicSchema" }, + ], items: { testUnitSystem: { schemaItemType: "UnitSystem", @@ -66,9 +80,10 @@ describe("Unit system merger tests", () => { }], }); - const mergedUnitSystem = await mergedSchema.getItem("testUnitSystem") as UnitSystem; - expect(mergedUnitSystem).is.not.undefined; - expect(mergedUnitSystem.label).equals("New Imperial"); - expect(mergedUnitSystem.description).equals("New Imperial Unit System"); + await expect(mergedSchema.getItem("testUnitSystem")).to.be.eventually.not.undefined + .then((mergedUnitSystem: UnitSystem) => { + expect(mergedUnitSystem).to.have.a.property("label", "New Imperial"); + expect(mergedUnitSystem).to.have.a.property("description", "New Imperial Unit System"); + }); }); }); diff --git a/core/ecschema-editing/src/test/TestUtils/BisTestHelper.ts b/core/ecschema-editing/src/test/TestUtils/BisTestHelper.ts index 7ba3bc9b7461..acd744926fc5 100644 --- a/core/ecschema-editing/src/test/TestUtils/BisTestHelper.ts +++ b/core/ecschema-editing/src/test/TestUtils/BisTestHelper.ts @@ -30,6 +30,13 @@ const coreCustomAttributesSchema = { modifier: "sealed", schemaItemType: "CustomAttributeClass", }, + DynamicSchema: { + schemaItemType: "CustomAttributeClass", + label: "Dynamic Schema", + description: "Identifies a schema as dynamically generated by an application. Like versions of a schema may differ and are not guaranteed to merge without conflicts.", + modifier: "Sealed", + appliesTo: "Schema", + }, }, };