@itwin/core-backend
Version:
iTwin.js backend components
250 lines • 12.7 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Guid } from "@itwin/core-bentley";
import { HubWrappers, IModelTestUtils } from "./IModelTestUtils";
import { ChannelControl } from "../ChannelControl";
import { Code, IModel, SubCategoryAppearance } from "@itwin/core-common";
import { DrawingCategory } from "../Category";
import { HubMock } from "../internal/HubMock";
import { KnownTestLocations } from "./KnownTestLocations";
import * as chai from "chai";
import { TestUtils } from "./TestUtils";
import { EditTxn, withEditTxn } from "../EditTxn";
const schemas = {
/** Base schema v01.00.00 with classes A, C, D */
v01x00x00: `
<ECSchema schemaName="TestDomain" alias="td" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
<ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
<ECEntityClass typeName="A">
<BaseClass>bis:GraphicalElement2d</BaseClass>
<ECProperty propertyName="PropA" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="C">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropC" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="D">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropD" typeName="string"/>
</ECEntityClass>
</ECSchema>`,
/** v01.00.01 - Adds PropC2 to class C (trivial additive change) */
v01x00x01AddPropC2: `
<ECSchema schemaName="TestDomain" alias="td" version="01.00.01" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
<ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
<ECEntityClass typeName="A">
<BaseClass>bis:GraphicalElement2d</BaseClass>
<ECProperty propertyName="PropA" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="C">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropC" typeName="string"/>
<ECProperty propertyName="PropC2" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="D">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropD" typeName="string"/>
</ECEntityClass>
</ECSchema>`,
/** v01.00.02 - Adds PropD2 to class D (trivial additive change) */
v01x00x02AddPropD2: `
<ECSchema schemaName="TestDomain" alias="td" version="01.00.02" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
<ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
<ECEntityClass typeName="A">
<BaseClass>bis:GraphicalElement2d</BaseClass>
<ECProperty propertyName="PropA" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="C">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropC" typeName="string"/>
<ECProperty propertyName="PropC2" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="D">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropD" typeName="string"/>
<ECProperty propertyName="PropD2" typeName="string"/>
</ECEntityClass>
</ECSchema>`,
/** v01.00.02 - Moves PropC from C to A (requires data transformation) on top of v01.00.01 */
v01x00x02MovePropCToA: `
<ECSchema schemaName="TestDomain" alias="td" version="01.00.02" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
<ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
<ECEntityClass typeName="A">
<BaseClass>bis:GraphicalElement2d</BaseClass>
<ECProperty propertyName="PropA" typeName="string"/>
<ECProperty propertyName="PropC" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="C">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropC2" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="D">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropD" typeName="string"/>
</ECEntityClass>
</ECSchema>`,
/** v01.00.03 - Builds on top of v01.00.02 and in addition moves PropD to base, so we can have incoming and local transforming changes */
v01x00x03MovePropCAndD: `
<ECSchema schemaName="TestDomain" alias="td" version="01.00.03" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
<ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
<ECEntityClass typeName="A">
<BaseClass>bis:GraphicalElement2d</BaseClass>
<ECProperty propertyName="PropA" typeName="string"/>
<ECProperty propertyName="PropC" typeName="string"/>
<ECProperty propertyName="PropD" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="C">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropC2" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="D">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropD2" typeName="string"/>
</ECEntityClass>
</ECSchema>`,
/** v01.00.01 (incompatible variant) - Adds PropC3 instead of PropC2 to class C (same version) */
v01x00x01AddPropC3Incompatible: `
<ECSchema schemaName="TestDomain" alias="td" version="01.00.01" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
<ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
<ECEntityClass typeName="A">
<BaseClass>bis:GraphicalElement2d</BaseClass>
<ECProperty propertyName="PropA" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="C">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropC" typeName="string"/>
<ECProperty propertyName="PropC3" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="D">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropD" typeName="string"/>
</ECEntityClass>
</ECSchema>`,
/** v01.00.02 (incompatible variant) - Adds PropC3 instead of PropC2 to class C (higher version) */
v01x00x02AddPropC3Incompatible: `
<ECSchema schemaName="TestDomain" alias="td" version="01.00.02" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
<ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
<ECEntityClass typeName="A">
<BaseClass>bis:GraphicalElement2d</BaseClass>
<ECProperty propertyName="PropA" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="C">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropC" typeName="string"/>
<ECProperty propertyName="PropC3" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="D">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropD" typeName="string"/>
</ECEntityClass>
</ECSchema>`,
/** v01.00.02 (incompatible variant) - Adds PropC2 (higher version, different type) */
v01x00x02AddPropC2Incompatible: `
<ECSchema schemaName="TestDomain" alias="td" version="01.00.02" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
<ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
<ECEntityClass typeName="A">
<BaseClass>bis:GraphicalElement2d</BaseClass>
<ECProperty propertyName="PropA" typeName="string"/>
</ECEntityClass>
<ECEntityClass typeName="C">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropC" typeName="string"/>
<ECProperty propertyName="PropC2" typeName="int"/>
</ECEntityClass>
<ECEntityClass typeName="D">
<BaseClass>A</BaseClass>
<ECProperty propertyName="PropD" typeName="string"/>
</ECEntityClass>
</ECSchema>`,
};
describe("SquashSchemaAndDataChanges", () => {
let imodel;
let iModelId;
let drawingModelId;
let drawingCategoryId;
const createModelAndCategory = async (db) => {
const modelCode = IModelTestUtils.getUniqueModelCode(db, "DrawingModel");
await db.locks.acquireLocks({ shared: IModel.dictionaryId });
return withEditTxn(db, (txn) => {
const [, newDrawingModelId] = IModelTestUtils.createAndInsertDrawingPartitionAndModel(txn, modelCode);
const newDrawingCategoryId = DrawingCategory.insert(txn, IModel.dictionaryId, "DrawingCategory", new SubCategoryAppearance());
return [newDrawingModelId, newDrawingCategoryId];
});
};
const insertElement = (txn, className, properties) => {
const elementProps = {
classFullName: className,
model: drawingModelId,
category: drawingCategoryId,
code: Code.createEmpty(),
...properties,
};
const element = txn.iModel.elements.createElement(elementProps);
return txn.insertElement(element.toJSON());
};
before(async () => {
HubMock.startup("MergeSchemaAndDataChanges", KnownTestLocations.outputDir);
await TestUtils.shutdownBackend();
await TestUtils.startBackend({ useSemanticRebase: true });
});
beforeEach(async () => {
iModelId = await HubWrappers.createIModel("user1", HubMock.iTwinId, `Test-${Guid.createValue()}`);
imodel = await HubWrappers.downloadAndOpenBriefcase({ accessToken: "user1", iTwinId: HubMock.iTwinId, iModelId });
imodel.channels.addAllowedChannel(ChannelControl.sharedChannelName);
[drawingModelId, drawingCategoryId] = await createModelAndCategory(imodel);
await imodel.importSchemaStrings([schemas.v01x00x00, schemas.v01x00x01AddPropC2]);
await imodel.pushChanges({ description: "create model and category and imported schemas" });
});
afterEach(async () => {
imodel.close();
await HubMock.deleteIModel({ accessToken: "user1", iTwinId: HubMock.iTwinId, iModelId });
});
after(async () => {
HubMock.shutdown();
await TestUtils.shutdownBackend();
await TestUtils.startBackend(); // restart normal backend so subsequent test suites aren't left without IModelHost
});
it("should allow schema import while an EditTxn is active", async () => {
await imodel.locks.acquireLocks({ shared: drawingModelId });
const txn = new EditTxn(imodel, "schema and data changes unsaved state");
txn.start();
try {
txn.saveChanges("prepare for schema import");
await imodel.importSchemaStrings([schemas.v01x00x02MovePropCToA]);
chai.assert(imodel.containsClass("TestDomain:C"));
}
finally {
if (txn.isActive)
txn.end("abandon");
}
});
it("should squash schema and data changes if useSemanticRebase flag is on", async () => {
await imodel.locks.acquireLocks({ shared: drawingModelId });
const txn = new EditTxn(imodel, "squash schema and data changes");
txn.start();
insertElement(txn, "TestDomain:C", {
propA: "local_value_a",
propC: "local_value_c",
});
txn.saveChanges("local data change");
await imodel.importSchemaStrings([schemas.v01x00x02MovePropCToA]); // transforming data change
const lastTxnProps = imodel.txns.getLastSavedTxnProps();
chai.assert(lastTxnProps !== undefined);
chai.assert(lastTxnProps?.type === "Schema");
chai.assert(lastTxnProps?.prevId !== undefined);
// both schema and data(migration) changes are merged into single txn
const secondLastTxnProps = imodel.txns.getTxnProps(lastTxnProps.prevId);
chai.assert(secondLastTxnProps !== undefined);
chai.assert(secondLastTxnProps?.type === "Ddl");
chai.assert(secondLastTxnProps?.prevId !== undefined);
const thirdLastTxnProps = imodel.txns.getTxnProps(secondLastTxnProps.prevId);
chai.assert(thirdLastTxnProps !== undefined);
chai.assert(thirdLastTxnProps?.type === "Data");
chai.assert(thirdLastTxnProps?.prevId === undefined);
if (txn.isActive)
txn.end("abandon");
});
});
//# sourceMappingURL=SquashSchemaAndDataChanges.test.js.map