UNPKG

@itwin/core-backend

Version:
893 lines • 73.9 kB
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { if (value !== null && value !== void 0) { if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); var dispose, inner; if (async) { if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); dispose = value[Symbol.asyncDispose]; } if (dispose === void 0) { if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); dispose = value[Symbol.dispose]; if (async) inner = dispose; } if (typeof dispose !== "function") throw new TypeError("Object not disposable."); if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; env.stack.push({ value: value, dispose: dispose, async: async }); } else if (async) { env.stack.push({ async: true }); } return value; }; var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { return function (env) { function fail(e) { env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; env.hasError = true; } var r, s = 0; function next() { while (r = env.stack.pop()) { try { if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); if (r.dispose) { var result = r.dispose.call(r.value); if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); } else s |= 1; } catch (e) { fail(e); } } if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); if (env.hasError) throw env.error; } return next(); }; })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { DbResult, Id64 } from "@itwin/core-bentley"; import { Code, ColorDef, IModel, SubCategoryAppearance } from "@itwin/core-common"; import { Arc3d, IModelJson, Point3d } from "@itwin/core-geometry"; import { assert, expect } from "chai"; import * as path from "node:path"; import { DrawingCategory } from "../../Category"; import { ChangesetECAdaptor as ECChangesetAdaptor, ECChangeUnifierCache, PartialECChangeUnifier } from "../../ChangesetECAdaptor"; import { HubMock } from "../../internal/HubMock"; import { SnapshotDb } from "../../IModelDb"; import { SqliteChangesetReader } from "../../SqliteChangesetReader"; import { HubWrappers, IModelTestUtils } from "../IModelTestUtils"; import { KnownTestLocations } from "../KnownTestLocations"; import { _nativeDb, ChannelControl } from "../../core-backend"; describe("Changeset Reader API", async () => { let iTwinId; before(() => { HubMock.startup("ChangesetReaderTest", KnownTestLocations.outputDir); iTwinId = HubMock.iTwinId; }); after(() => HubMock.shutdown()); it("Able to recover from when ExclusiveRootClassId is NULL for overflow table", async () => { /** * 1. Import schema with class that span overflow table. * 2. Insert a element for the class. * 3. Push changes to hub. * 4. Update the element. * 5. Push changes to hub. * 6. Delete the element. * 7. Set ExclusiveRootClassId to NULL for overflow table. (Simulate the issue) * 8. ECChangesetAdaptor should be able to read the changeset 2 in which element is updated against latest imodel where element is deleted. */ const adminToken = "super manager token"; const iModelName = "test"; const nProps = 36; const rwIModelId = await HubMock.createNewIModel({ iTwinId, iModelName, description: "TestSubject", accessToken: adminToken }); assert.isNotEmpty(rwIModelId); const rwIModel = await HubWrappers.downloadAndOpenBriefcase({ iTwinId, iModelId: rwIModelId, accessToken: adminToken }); // 1. Import schema with class that span overflow table. const schema = `<?xml version="1.0" encoding="UTF-8"?> <ECSchema schemaName="TestDomain" alias="ts" version="01.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.1"> <ECSchemaReference name="BisCore" version="01.00" alias="bis"/> <ECEntityClass typeName="Test2dElement"> <BaseClass>bis:GraphicalElement2d</BaseClass> ${Array(nProps).fill(undefined).map((_, i) => `<ECProperty propertyName="p${i}" typeName="string"/>`).join("\n")} </ECEntityClass> </ECSchema>`; await rwIModel.importSchemaStrings([schema]); rwIModel.channels.addAllowedChannel(ChannelControl.sharedChannelName); // Create drawing model and category await rwIModel.locks.acquireLocks({ shared: IModel.dictionaryId }); const codeProps = Code.createEmpty(); codeProps.value = "DrawingModel"; const [, drawingModelId] = IModelTestUtils.createAndInsertDrawingPartitionAndModel(rwIModel, codeProps, true); let drawingCategoryId = DrawingCategory.queryCategoryIdByName(rwIModel, IModel.dictionaryId, "MyDrawingCategory"); if (undefined === drawingCategoryId) drawingCategoryId = DrawingCategory.insert(rwIModel, IModel.dictionaryId, "MyDrawingCategory", new SubCategoryAppearance({ color: ColorDef.fromString("rgb(255,0,0)").toJSON() })); // Insert element with 100 properties const geomArray = [ Arc3d.createXY(Point3d.create(0, 0), 5), Arc3d.createXY(Point3d.create(5, 5), 2), Arc3d.createXY(Point3d.create(-5, -5), 20), ]; const geometryStream = []; for (const geom of geomArray) { const arcData = IModelJson.Writer.toIModelJson(geom); geometryStream.push(arcData); } const props = Array(nProps).fill(undefined).map((_, i) => { return { [`p${i}`]: `test_${i}` }; }).reduce((acc, curr) => { return { ...acc, ...curr }; }, {}); const geomElement = { classFullName: `TestDomain:Test2dElement`, model: drawingModelId, category: drawingCategoryId, code: Code.createEmpty(), geom: geometryStream, ...props, }; // 2. Insert a element for the class. const id = rwIModel.elements.insertElement(geomElement); assert.isTrue(Id64.isValidId64(id), "insert worked"); rwIModel.saveChanges(); // 3. Push changes to hub. await rwIModel.pushChanges({ description: "insert element", accessToken: adminToken }); // 4. Update the element. const updatedElementProps = Object.assign(rwIModel.elements.getElementProps(id), Array(nProps).fill(undefined).map((_, i) => { return { [`p${i}`]: `updated_${i}` }; }).reduce((acc, curr) => { return { ...acc, ...curr }; }, {})); await rwIModel.locks.acquireLocks({ exclusive: id }); rwIModel.elements.updateElement(updatedElementProps); rwIModel.saveChanges(); // 5. Push changes to hub. await rwIModel.pushChanges({ description: "update element", accessToken: adminToken }); await rwIModel.locks.acquireLocks({ exclusive: id }); // 6. Delete the element. rwIModel.elements.deleteElement(id); rwIModel.saveChanges(); await rwIModel.pushChanges({ description: "delete element", accessToken: adminToken }); const targetDir = path.join(KnownTestLocations.outputDir, rwIModelId, "changesets"); const changesets = await HubMock.downloadChangesets({ iModelId: rwIModelId, targetDir }); const reader = SqliteChangesetReader.openFile({ fileName: changesets[1].pathname, db: rwIModel, disableSchemaCheck: true }); // Set ExclusiveRootClassId to NULL for overflow table to simulate the issue expect(rwIModel[_nativeDb].executeSql("UPDATE ec_Table SET ExclusiveRootClassId=NULL WHERE Name='bis_GeometricElement2d_Overflow'")).to.be.eq(DbResult.BE_SQLITE_OK); const adaptor = new ECChangesetAdaptor(reader); let assertOnOverflowTable = false; const expectedInserted = { // eslint-disable-next-line @typescript-eslint/naming-convention ECClassId: undefined, // eslint-disable-next-line @typescript-eslint/naming-convention ECInstanceId: "", $meta: { tables: ["bis_GeometricElement2d_Overflow"], op: "Updated", classFullName: "BisCore:GeometricElement2d", fallbackClassId: "0x5e", changeIndexes: [3], stage: "New", }, }; const expectedDeleted = { // eslint-disable-next-line @typescript-eslint/naming-convention ECClassId: undefined, // eslint-disable-next-line @typescript-eslint/naming-convention ECInstanceId: "", $meta: { tables: ["bis_GeometricElement2d_Overflow"], op: "Updated", classFullName: "BisCore:GeometricElement2d", fallbackClassId: "0x5e", changeIndexes: [3], stage: "Old", }, }; while (adaptor.step()) { if (adaptor.op === "Updated" && adaptor.inserted?.$meta?.tables[0] === "bis_GeometricElement2d_Overflow") { assert.deepEqual(adaptor.inserted, expectedInserted); assert.deepEqual(adaptor.deleted, expectedDeleted); assertOnOverflowTable = true; } } assert.isTrue(assertOnOverflowTable); rwIModel.close(); }); function getClassIdByName(iModel, className) { // eslint-disable-next-line @typescript-eslint/no-deprecated return iModel.withPreparedStatement(`SELECT ECInstanceId from meta.ECClassDef where Name=?`, (stmt) => { stmt.bindString(1, className); assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW); return stmt.getValue(0).getId(); }); } it("Changeset reader / EC adaptor", async () => { const adminToken = "super manager token"; const iModelName = "test"; const rwIModelId = await HubMock.createNewIModel({ iTwinId, iModelName, description: "TestSubject", accessToken: adminToken }); assert.isNotEmpty(rwIModelId); const rwIModel = await HubWrappers.downloadAndOpenBriefcase({ iTwinId, iModelId: rwIModelId, accessToken: adminToken }); const schema = `<?xml version="1.0" encoding="UTF-8"?> <ECSchema schemaName="TestDomain" alias="ts" version="01.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.1"> <ECSchemaReference name="BisCore" version="01.00" alias="bis"/> <ECEntityClass typeName="Test2dElement"> <BaseClass>bis:GraphicalElement2d</BaseClass> <ECProperty propertyName="s" typeName="string"/> </ECEntityClass> </ECSchema>`; await rwIModel.importSchemaStrings([schema]); rwIModel.saveChanges("user 1: schema changeset"); if (true || "push changes") { // Push the changes to the hub const prePushChangeSetId = rwIModel.changeset.id; await rwIModel.pushChanges({ description: "push schema changeset", accessToken: adminToken }); const postPushChangeSetId = rwIModel.changeset.id; assert(!!postPushChangeSetId); expect(prePushChangeSetId !== postPushChangeSetId); rwIModel.channels.addAllowedChannel(ChannelControl.sharedChannelName); } await rwIModel.locks.acquireLocks({ shared: IModel.dictionaryId }); const codeProps = Code.createEmpty(); codeProps.value = "DrawingModel"; let totalEl = 0; const [, drawingModelId] = IModelTestUtils.createAndInsertDrawingPartitionAndModel(rwIModel, codeProps, true); let drawingCategoryId = DrawingCategory.queryCategoryIdByName(rwIModel, IModel.dictionaryId, "MyDrawingCategory"); if (undefined === drawingCategoryId) drawingCategoryId = DrawingCategory.insert(rwIModel, IModel.dictionaryId, "MyDrawingCategory", new SubCategoryAppearance({ color: ColorDef.fromString("rgb(255,0,0)").toJSON() })); rwIModel.saveChanges("user 1: create drawing partition"); if (true || "push changes") { // Push the changes to the hub const prePushChangeSetId = rwIModel.changeset.id; await rwIModel.pushChanges({ description: "user 1: create drawing partition", accessToken: adminToken }); const postPushChangeSetId = rwIModel.changeset.id; assert(!!postPushChangeSetId); expect(prePushChangeSetId !== postPushChangeSetId); } await rwIModel.locks.acquireLocks({ shared: drawingModelId }); const insertElements = (imodel, className = "Test2dElement", noOfElements = 10, userProp) => { for (let m = 0; m < noOfElements; ++m) { const geomArray = [ Arc3d.createXY(Point3d.create(0, 0), 5), Arc3d.createXY(Point3d.create(5, 5), 2), Arc3d.createXY(Point3d.create(-5, -5), 20), ]; const geometryStream = []; for (const geom of geomArray) { const arcData = IModelJson.Writer.toIModelJson(geom); geometryStream.push(arcData); } const prop = userProp(++totalEl); // Create props const geomElement = { classFullName: `TestDomain:${className}`, model: drawingModelId, category: drawingCategoryId, code: Code.createEmpty(), geom: geometryStream, ...prop, }; const id = imodel.elements.insertElement(geomElement); assert.isTrue(Id64.isValidId64(id), "insert worked"); } }; const generatedStr = new Array(10).join("x"); insertElements(rwIModel, "Test2dElement", 1, () => { return { s: generatedStr }; }); const updatedElements = async () => { await rwIModel.locks.acquireLocks({ exclusive: "0x20000000004" }); const updatedElement = rwIModel.elements.getElementProps("0x20000000004"); updatedElement.s = "updated property"; rwIModel.elements.updateElement(updatedElement); rwIModel.saveChanges("user 1: updated data"); await rwIModel.pushChanges({ description: "user 1: update property id=0x20000000004", accessToken: adminToken }); }; rwIModel.saveChanges("user 1: data"); if (true || "test local changes") { const testChanges = (changes) => { assert.equal(changes.length, 3); assert.equal(changes[0].ECInstanceId, "0x20000000001"); assert.equal(changes[0].$meta?.classFullName, "BisCore:DrawingModel"); assert.equal(changes[0].$meta?.op, "Updated"); assert.equal(changes[0].$meta?.stage, "New"); assert.isNotNull(changes[0].LastMod); assert.isNotNull(changes[0].GeometryGuid); assert.equal(changes[1].ECInstanceId, "0x20000000001"); assert.equal(changes[1].$meta?.classFullName, "BisCore:DrawingModel"); assert.equal(changes[1].$meta?.op, "Updated"); assert.equal(changes[1].$meta?.stage, "Old"); assert.isNull(changes[1].LastMod); assert.isNull(changes[1].GeometryGuid); assert.equal(changes[2].ECInstanceId, "0x20000000004"); assert.equal(changes[2].$meta?.classFullName, "TestDomain:Test2dElement"); assert.equal(changes[2].$meta?.op, "Inserted"); assert.equal(changes[2].$meta?.stage, "New"); const el = changes.filter((x) => x.ECInstanceId === "0x20000000004")[0]; assert.equal(el.Rotation, 0); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.Origin, { X: 0, Y: 0 }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.BBoxLow, { X: -25, Y: -25 }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.BBoxHigh, { X: 15, Y: 15 }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.Category, { Id: "0x20000000002", RelECClassId: "0x6d" }); assert.equal(el.s, "xxxxxxxxx"); assert.isNull(el.CodeValue); assert.isNull(el.UserLabel); assert.isNull(el.JsonProperties); assert.instanceOf(el.GeometryStream, Uint8Array); assert.typeOf(el.FederationGuid, "string"); assert.typeOf(el.LastMod, "string"); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.Parent, { Id: null, RelECClassId: null }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.TypeDefinition, { Id: null, RelECClassId: null }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.Category, { Id: "0x20000000002", RelECClassId: "0x6d" }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.CodeSpec, { Id: "0x1", RelECClassId: "0x69" }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.CodeScope, { Id: "0x1", RelECClassId: "0x6b" }); assert.deepEqual(el.$meta, { tables: [ "bis_GeometricElement2d", "bis_Element", ], op: "Inserted", classFullName: "TestDomain:Test2dElement", changeIndexes: [ 2, 1, ], stage: "New", }); }; if (true || "test with InMemoryInstanceCache") { const env_1 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_1, SqliteChangesetReader.openLocalChanges({ db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_1, new ECChangesetAdaptor(reader), false); const pcu = __addDisposableResource(env_1, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createInMemoryCache()), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_1) { env_1.error = e_1; env_1.hasError = true; } finally { __disposeResources(env_1); } } if (true || "test with SqliteBackedInstanceCache") { const env_2 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_2, SqliteChangesetReader.openLocalChanges({ db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_2, new ECChangesetAdaptor(reader), false); const pcu = __addDisposableResource(env_2, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createSqliteBackedCache(rwIModel)), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_2) { env_2.error = e_2; env_2.hasError = true; } finally { __disposeResources(env_2); } } } const targetDir = path.join(KnownTestLocations.outputDir, rwIModelId, "changesets"); await rwIModel.pushChanges({ description: "schema changeset", accessToken: adminToken }); await updatedElements(); const changesets = await HubMock.downloadChangesets({ iModelId: rwIModelId, targetDir }); if (true || "updated element") { const testChanges = (changes) => { assert.equal(changes.length, 4); const classId = getClassIdByName(rwIModel, "Test2dElement"); // new value assert.equal(changes[2].ECInstanceId, "0x20000000004"); assert.equal(changes[2].ECClassId, classId); assert.equal(changes[2].s, "updated property"); assert.equal(changes[2].$meta?.classFullName, "TestDomain:Test2dElement"); assert.equal(changes[2].$meta?.op, "Updated"); assert.equal(changes[2].$meta?.stage, "New"); // old value assert.equal(changes[3].ECInstanceId, "0x20000000004"); assert.equal(changes[3].ECClassId, classId); assert.equal(changes[3].s, "xxxxxxxxx"); assert.equal(changes[3].$meta?.classFullName, "TestDomain:Test2dElement"); assert.equal(changes[3].$meta?.op, "Updated"); assert.equal(changes[3].$meta?.stage, "Old"); }; if (true || "test with InMemoryInstanceCache") { const env_3 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_3, SqliteChangesetReader.openFile({ fileName: changesets[3].pathname, db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_3, new ECChangesetAdaptor(reader), false); const pcu = __addDisposableResource(env_3, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createInMemoryCache()), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_3) { env_3.error = e_3; env_3.hasError = true; } finally { __disposeResources(env_3); } } if (true || "test with SqliteBackedInstanceCache") { const env_4 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_4, SqliteChangesetReader.openFile({ fileName: changesets[3].pathname, db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_4, new ECChangesetAdaptor(reader), false); const pcu = __addDisposableResource(env_4, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createSqliteBackedCache(rwIModel)), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_4) { env_4.error = e_4; env_4.hasError = true; } finally { __disposeResources(env_4); } } } if (true || "updated element when no classId") { const otherDb = SnapshotDb.openFile(IModelTestUtils.resolveAssetFile("test.bim")); const testChanges = (changes) => { assert.equal(changes.length, 4); // new value assert.equal(changes[2].ECInstanceId, "0x20000000004"); assert.isUndefined(changes[2].ECClassId); assert.isDefined(changes[2].$meta?.fallbackClassId); assert.equal(changes[2].$meta?.fallbackClassId, "0x3d"); assert.isUndefined(changes[2].s); assert.equal(changes[2].$meta?.classFullName, "BisCore:GeometricElement2d"); assert.equal(changes[2].$meta?.op, "Updated"); assert.equal(changes[2].$meta?.stage, "New"); // old value assert.equal(changes[3].ECInstanceId, "0x20000000004"); assert.isUndefined(changes[3].ECClassId); assert.isDefined(changes[3].$meta?.fallbackClassId); assert.equal(changes[3].$meta?.fallbackClassId, "0x3d"); assert.isUndefined(changes[3].s); assert.equal(changes[3].$meta?.classFullName, "BisCore:GeometricElement2d"); assert.equal(changes[3].$meta?.op, "Updated"); assert.equal(changes[3].$meta?.stage, "Old"); }; if (true || "test with InMemoryInstanceCache") { const env_5 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_5, SqliteChangesetReader.openFile({ fileName: changesets[3].pathname, db: otherDb, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_5, new ECChangesetAdaptor(reader), false); const pcu = __addDisposableResource(env_5, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createInMemoryCache()), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_5) { env_5.error = e_5; env_5.hasError = true; } finally { __disposeResources(env_5); } } if (true || "test with SqliteBackedInstanceCache") { const env_6 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_6, SqliteChangesetReader.openFile({ fileName: changesets[3].pathname, db: otherDb, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_6, new ECChangesetAdaptor(reader), false); const pcu = __addDisposableResource(env_6, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createSqliteBackedCache(rwIModel)), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_6) { env_6.error = e_6; env_6.hasError = true; } finally { __disposeResources(env_6); } } } if (true || "test changeset file") { const testChanges = (changes) => { assert.equal(changes.length, 3); assert.equal(changes[0].ECInstanceId, "0x20000000001"); assert.equal(changes[0].$meta?.classFullName, "BisCore:DrawingModel"); assert.equal(changes[0].$meta?.op, "Updated"); assert.equal(changes[0].$meta?.stage, "New"); assert.isNotNull(changes[0].LastMod); assert.isNotNull(changes[0].GeometryGuid); assert.equal(changes[1].ECInstanceId, "0x20000000001"); assert.equal(changes[1].$meta?.classFullName, "BisCore:DrawingModel"); assert.equal(changes[1].$meta?.op, "Updated"); assert.equal(changes[1].$meta?.stage, "Old"); assert.isNull(changes[1].LastMod); assert.isNull(changes[1].GeometryGuid); assert.equal(changes[2].ECInstanceId, "0x20000000004"); assert.equal(changes[2].$meta?.classFullName, "TestDomain:Test2dElement"); assert.equal(changes[2].$meta?.op, "Inserted"); assert.equal(changes[2].$meta?.stage, "New"); const el = changes.filter((x) => x.ECInstanceId === "0x20000000004")[0]; assert.equal(el.Rotation, 0); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.Origin, { X: 0, Y: 0 }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.BBoxLow, { X: -25, Y: -25 }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.BBoxHigh, { X: 15, Y: 15 }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.Category, { Id: "0x20000000002", RelECClassId: "0x6d" }); assert.equal(el.s, "xxxxxxxxx"); assert.isNull(el.CodeValue); assert.isNull(el.UserLabel); assert.isNull(el.JsonProperties); assert.instanceOf(el.GeometryStream, Uint8Array); assert.typeOf(el.FederationGuid, "string"); assert.typeOf(el.LastMod, "string"); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.Parent, { Id: null, RelECClassId: null }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.TypeDefinition, { Id: null, RelECClassId: null }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.Category, { Id: "0x20000000002", RelECClassId: "0x6d" }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.CodeSpec, { Id: "0x1", RelECClassId: "0x69" }); // eslint-disable-next-line @typescript-eslint/naming-convention assert.deepEqual(el.CodeScope, { Id: "0x1", RelECClassId: "0x6b" }); assert.deepEqual(el.$meta, { tables: [ "bis_GeometricElement2d", "bis_Element", ], op: "Inserted", classFullName: "TestDomain:Test2dElement", changeIndexes: [ 2, 1, ], stage: "New", }); }; if (true || "test with InMemoryInstanceCache") { const env_7 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_7, SqliteChangesetReader.openFile({ fileName: changesets[2].pathname, db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_7, new ECChangesetAdaptor(reader), false); const pcu = __addDisposableResource(env_7, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createInMemoryCache()), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_7) { env_7.error = e_7; env_7.hasError = true; } finally { __disposeResources(env_7); } } if (true || "test with SqliteBackedInstanceCache") { const env_8 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_8, SqliteChangesetReader.openFile({ fileName: changesets[2].pathname, db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_8, new ECChangesetAdaptor(reader), false); const pcu = __addDisposableResource(env_8, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createSqliteBackedCache(rwIModel)), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_8) { env_8.error = e_8; env_8.hasError = true; } finally { __disposeResources(env_8); } } } if (true || "test ChangesetAdaptor.acceptClass()") { const testChanges = (changes) => { assert.equal(changes.length, 1); assert.equal(changes[0].$meta?.classFullName, "TestDomain:Test2dElement"); }; if (true || "test with InMemoryInstanceCache") { const env_9 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_9, SqliteChangesetReader.openFile({ fileName: changesets[2].pathname, db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_9, new ECChangesetAdaptor(reader), false); adaptor.acceptClass("TestDomain.Test2dElement"); const pcu = __addDisposableResource(env_9, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createInMemoryCache()), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_9) { env_9.error = e_9; env_9.hasError = true; } finally { __disposeResources(env_9); } } if (true || "test with SqliteBackedInstanceCache") { const env_10 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_10, SqliteChangesetReader.openFile({ fileName: changesets[2].pathname, db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_10, new ECChangesetAdaptor(reader), false); adaptor.acceptClass("TestDomain.Test2dElement"); const pcu = __addDisposableResource(env_10, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createSqliteBackedCache(rwIModel)), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_10) { env_10.error = e_10; env_10.hasError = true; } finally { __disposeResources(env_10); } } } if (true || "test ChangesetAdaptor.adaptor()") { const testChanges = (changes) => { assert.equal(changes.length, 2); assert.equal(changes[0].ECInstanceId, "0x20000000001"); assert.equal(changes[0].$meta?.classFullName, "BisCore:DrawingModel"); assert.equal(changes[0].$meta?.op, "Updated"); assert.equal(changes[0].$meta?.stage, "New"); assert.equal(changes[1].ECInstanceId, "0x20000000001"); assert.equal(changes[1].$meta?.classFullName, "BisCore:DrawingModel"); assert.equal(changes[1].$meta?.op, "Updated"); assert.equal(changes[1].$meta?.stage, "Old"); }; if (true || "test with InMemoryInstanceCache") { const env_11 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_11, SqliteChangesetReader.openFile({ fileName: changesets[2].pathname, db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_11, new ECChangesetAdaptor(reader), false); adaptor.acceptOp("Updated"); const pcu = __addDisposableResource(env_11, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createInMemoryCache()), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_11) { env_11.error = e_11; env_11.hasError = true; } finally { __disposeResources(env_11); } } if (true || "test with SqliteBackedInstanceCache") { const env_12 = { stack: [], error: void 0, hasError: false }; try { const reader = __addDisposableResource(env_12, SqliteChangesetReader.openFile({ fileName: changesets[2].pathname, db: rwIModel, disableSchemaCheck: true }), false); const adaptor = __addDisposableResource(env_12, new ECChangesetAdaptor(reader), false); adaptor.acceptOp("Updated"); const pcu = __addDisposableResource(env_12, new PartialECChangeUnifier(reader.db, ECChangeUnifierCache.createSqliteBackedCache(rwIModel)), false); while (adaptor.step()) { pcu.appendFrom(adaptor); } testChanges(Array.from(pcu.instances)); } catch (e_12) { env_12.error = e_12; env_12.hasError = true; } finally { __disposeResources(env_12); } } } rwIModel.close(); }); it("revert timeline changes", async () => { const adminToken = "super manager token"; const iModelName = "test"; const rwIModelId = await HubMock.createNewIModel({ iTwinId, iModelName, description: "TestSubject", accessToken: adminToken }); assert.isNotEmpty(rwIModelId); const rwIModel = await HubWrappers.downloadAndOpenBriefcase({ iTwinId, iModelId: rwIModelId, accessToken: adminToken }); let nProps = 0; // 1. Import schema with class that span overflow table. const addPropertyAndImportSchema = async () => { await rwIModel.acquireSchemaLock(); ++nProps; const schema = `<?xml version="1.0" encoding="UTF-8"?> <ECSchema schemaName="TestDomain" alias="ts" version="01.00.${nProps}" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.1"> <ECSchemaReference name="BisCore" version="01.00" alias="bis"/> <ECEntityClass typeName="Test2dElement"> <BaseClass>bis:GraphicalElement2d</BaseClass> ${Array(nProps).fill(undefined).map((_, i) => `<ECProperty propertyName="p${i + 1}" typeName="string"/>`).join("\n")} </ECEntityClass> </ECSchema>`; await rwIModel.importSchemaStrings([schema]); }; await addPropertyAndImportSchema(); rwIModel.channels.addAllowedChannel(ChannelControl.sharedChannelName); // Create drawing model and category await rwIModel.locks.acquireLocks({ shared: IModel.dictionaryId }); const codeProps = Code.createEmpty(); codeProps.value = "DrawingModel"; const [, drawingModelId] = IModelTestUtils.createAndInsertDrawingPartitionAndModel(rwIModel, codeProps, true); let drawingCategoryId = DrawingCategory.queryCategoryIdByName(rwIModel, IModel.dictionaryId, "MyDrawingCategory"); if (undefined === drawingCategoryId) drawingCategoryId = DrawingCategory.insert(rwIModel, IModel.dictionaryId, "MyDrawingCategory", new SubCategoryAppearance({ color: ColorDef.fromString("rgb(255,0,0)").toJSON() })); rwIModel.saveChanges(); await rwIModel.pushChanges({ description: "setup category", accessToken: adminToken }); const createEl = async (args) => { await rwIModel.locks.acquireLocks({ exclusive: drawingModelId }); const geomArray = [ Arc3d.createXY(Point3d.create(0, 0), 5), Arc3d.createXY(Point3d.create(5, 5), 2), Arc3d.createXY(Point3d.create(-5, -5), 20), ]; const geometryStream = []; for (const geom of geomArray) { const arcData = IModelJson.Writer.toIModelJson(geom); geometryStream.push(arcData); } const e1 = { classFullName: `TestDomain:Test2dElement`, model: drawingModelId, category: drawingCategoryId, code: Code.createEmpty(), geom: geometryStream, ...args, }; return rwIModel.elements.insertElement(e1); ; }; const updateEl = async (id, args) => { await rwIModel.locks.acquireLocks({ exclusive: id }); const updatedElementProps = Object.assign(rwIModel.elements.getElementProps(id), args); rwIModel.elements.updateElement(updatedElementProps); }; const deleteEl = async (id) => { await rwIModel.locks.acquireLocks({ exclusive: id }); rwIModel.elements.deleteElement(id); }; const getChanges = async () => { return HubMock.downloadChangesets({ iModelId: rwIModelId, targetDir: path.join(KnownTestLocations.outputDir, rwIModelId, "changesets") }); }; const findEl = (id) => { try { return rwIModel.elements.getElementProps(id); } catch { return undefined; } }; // 2. Insert a element for the class const el1 = await createEl({ p1: "test1" }); const el2 = await createEl({ p1: "test2" }); rwIModel.saveChanges(); await rwIModel.pushChanges({ description: "insert 2 elements" }); // 3. Update the element. await updateEl(el1, { p1: "test3" }); rwIModel.saveChanges(); await rwIModel.pushChanges({ description: "update element 1" }); // 4. Delete the element. await deleteEl(el2); const el3 = await createEl({ p1: "test4" }); rwIModel.saveChanges(); await rwIModel.pushChanges({ description: "delete element 2" }); // 5. import schema and insert element 4 & update element 3 await addPropertyAndImportSchema(); const el4 = await createEl({ p1: "test5", p2: "test6" }); await updateEl(el3, { p1: "test7", p2: "test8" }); rwIModel.saveChanges(); await rwIModel.pushChanges({ description: "import schema, insert element 4 & update element 3" }); assert.isDefined(findEl(el1)); assert.isUndefined(findEl(el2)); assert.isDefined(findEl(el3)); assert.isDefined(findEl(el4)); // eslint-disable-next-line @typescript-eslint/no-deprecated assert.deepEqual(Object.getOwnPropertyNames(rwIModel.getMetaData("TestDomain:Test2dElement").properties), ["p1", "p2"]); // 6. Revert to timeline 2 await rwIModel.revertAndPushChanges({ toIndex: 2, description: "revert to timeline 2" }); assert.equal((await getChanges()).at(-1).description, "revert to timeline 2"); assert.isUndefined(findEl(el1)); assert.isUndefined(findEl(el2)); assert.isUndefined(findEl(el3)); assert.isUndefined(findEl(el4)); // eslint-disable-next-line @typescript-eslint/no-deprecated assert.deepEqual(Object.getOwnPropertyNames(rwIModel.getMetaData("TestDomain:Test2dElement").properties), ["p1"]); await rwIModel.revertAndPushChanges({ toIndex: 6, description: "reinstate last reverted changeset" }); assert.equal((await getChanges()).at(-1).description, "reinstate last reverted changeset"); assert.isDefined(findEl(el1)); assert.isUndefined(findEl(el2)); assert.isDefined(findEl(el3)); assert.isDefined(findEl(el4)); // eslint-disable-next-line @typescript-eslint/no-deprecated assert.deepEqual(Object.getOwnPropertyNames(rwIModel.getMetaData("TestDomain:Test2dElement").properties), ["p1", "p2"]); await addPropertyAndImportSchema(); const el5 = await createEl({ p1: "test9", p2: "test10", p3: "test11" }); await updateEl(el1, { p1: "test12", p2: "test13", p3: "test114" }); rwIModel.saveChanges(); await rwIModel.pushChanges({ description: "import schema, insert element 5 & update element 1" }); // eslint-disable-next-line @typescript-eslint/no-deprecated assert.deepEqual(Object.getOwnPropertyNames(rwIModel.getMetaData("TestDomain:Test2dElement").properties), ["p1", "p2", "p3"]); // skip schema changes & auto generated comment await rwIModel.revertAndPushChanges({ toIndex: 1, skipSchemaChanges: true }); assert.equal((await getChanges()).at(-1).description, "Reverted changes from 8 to 1 (schema changes skipped)"); assert.isUndefined(findEl(el1)); assert.isUndefined(findEl(el2)); assert.isUndefined(findEl(el3)); assert.isUndefined(findEl(el4)); assert.isUndefined(findEl(el5)); // eslint-disable-next-line @typescript-eslint/no-deprecated assert.deepEqual(Object.getOwnPropertyNames(rwIModel.getMetaData("TestDomain:Test2dElement").properties), ["p1", "p2", "p3"]); await rwIModel.revertAndPushChanges({ toIndex: 9 }); assert.equal((await getChanges()).at(-1).description, "Reverted changes from 9 to 9"); assert.isDefined(findEl(el1)); assert.isUndefined(findEl(el2)); assert.isDefined(findEl(el3)); assert.isDefined(findEl(el4)); assert.isDefined(findEl(el5)); // eslint-disable-next-line @typescript-eslint/no-deprecated assert.deepEqual(Object.getOwnPropertyNames(rwIModel.getMetaData("TestDomain:Test2dElement").properties), ["p1", "p2", "p3"]); rwIModel.close(); }); it("openGroup() & writeToFile()", async () => { const adminToken = "super manager token"; const iModelName = "test"; const rwIModelId = await HubMock.createNewIModel({ iTwinId, iModelName, description: "TestSubject", accessToken: adminToken }); assert.isNotEmpty(rwIModelId); const rwIModel = await HubWrappers.downloadAndOpenBriefcase({ iTwinId, iModelId: rwIModelId, accessToken: adminToken }); // 1. Import schema with class that span overflow table. const schema = `<?xml version="1.0" encoding="UTF-8"?> <ECSchema schemaName="TestDomain" alias="ts" version="01.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.1"> <ECSchemaReference name="BisCore" version="01.00" alias="bis"/> <ECEntityClass typeName="Test2dElement"> <BaseClass>bis:GraphicalElement2d</BaseClass> <ECProperty propertyName="p1" typeName="string"/> </ECEntityClass> </ECSchema>`; await rwIModel.importSchemaStrings([schema]); rwIModel.channels.addAllowedChannel(ChannelControl.sharedChannelName); // Create drawing model and category await rwIModel.locks.acquireLocks({ shared: IModel.dictionaryId }); const codeProps = Code.createEmpty(); codeProps.value = "DrawingModel"; const [, drawingModelId] = IModelTestUtils.createAndInsertDrawingPartitionAndModel(rwIModel, codeProps, true); let drawingCategoryId = DrawingCategory.queryCategoryIdByName(rwIModel, IModel.dictionaryId, "MyDrawingCategory"); if (undefined === drawingCategoryId) drawingCategoryId = DrawingCategory.insert(rwIModel, IModel.dictionaryId, "MyDrawingCategory", new SubCategoryAppearance({ color: ColorDef.fromString("rgb(255,0,0)").toJSON() })); rwIModel.saveChanges(); await rwIModel.pushChanges({ description: "setup category", accessToken: adminToken }); const geomArray = [ Arc3d.createXY(Point3d.create(0, 0), 5), Arc3d.createXY(Point3d.create(5