/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { DbConflictResolution, Guid } from "@itwin/core-bentley";
import { EditTxn } from "../../EditTxn";
import { IModel, SubCategoryAppearance } from "@itwin/core-common";
import * as chai from "chai";
import { assert } from "chai";
import * as chaiAsPromised from "chai-as-promised";
import { HubWrappers, KnownTestLocations } from "..";
import { ChannelControl, SpatialCategory, SqliteChangesetReader, } from "../../core-backend";
import { HubMock } from "../../internal/HubMock";
import { IModelTestUtils, TestUserType } from "../IModelTestUtils";
import { TestUtils } from "../TestUtils";
import { Point3d } from "@itwin/core-geometry";
function startTestTxn(briefcase) {
    const txn = new EditTxn(briefcase, "change merge");
    txn.start();
    return txn;
}
chai.use(chaiAsPromised);
async function assertThrowsAsync(test, msg) {
    try {
        await test();
    }
    catch (e) {
        if (e instanceof Error && msg) {
            assert.equal(e.message, msg);
        }
        return;
    }
    throw new Error(`Failed to throw error with message: "${msg}"`);
}
async function updatePhysicalObject(txn, el1, federationGuid) {
    const briefcase = txn.iModel;
    await briefcase.locks.acquireLocks({ exclusive: el1 });
    const props = briefcase.elements.getElement(el1);
    props.federationGuid = federationGuid;
    txn.updateElement(props.toJSON());
}
describe("Change merge method", () => {
    const ctx = {
        accessTokens: {
            user1: "",
            user2: "",
            user3: "",
        },
        iModelId: "",
        iTwinId: "",
        modelId: "",
        spatialCategoryId: "",
        iModelName: "TestIModel",
        rootSubject: "TestSubject",
        openBriefcase: async (user, noLock) => {
            const b = await HubWrappers.downloadAndOpenBriefcase({ accessToken: ctx.accessTokens[user], iTwinId: ctx.iTwinId, iModelId: ctx.iModelId, noLock });
            b.channels.addAllowedChannel(ChannelControl.sharedChannelName);
            return b;
        },
        openB1: async (noLock) => { return ctx.openBriefcase("user1", noLock); },
        openB2: async (noLock) => { return ctx.openBriefcase("user2", noLock); },
        openB3: async (noLock) => { return ctx.openBriefcase("user3", noLock); },
    };
    async function insertPhysicalObject(txn) {
        const briefcase = txn.iModel;
        await briefcase.locks.acquireLocks({ shared: ctx.modelId });
        return txn.insertElement(IModelTestUtils.createPhysicalObject(briefcase, ctx.modelId, ctx.spatialCategoryId).toJSON());
    }
    before(async () => {
        await TestUtils.startBackend();
        HubMock.startup("PullMergeMethod", KnownTestLocations.outputDir);
    });
    after(async () => {
        HubMock.shutdown();
    });
    beforeEach(async () => {
        ctx.iTwinId = HubMock.iTwinId;
        ctx.accessTokens.user1 = await HubWrappers.getAccessToken(TestUserType.SuperManager);
        ctx.accessTokens.user2 = await HubWrappers.getAccessToken(TestUserType.Regular);
        ctx.accessTokens.user3 = await HubWrappers.getAccessToken(TestUserType.Super);
        ctx.iModelId = await HubMock.createNewIModel({ accessToken: ctx.accessTokens.user1, iTwinId: ctx.iTwinId, iModelName: ctx.iModelName, description: ctx.rootSubject });
        assert.isNotEmpty(ctx.iModelId);
        const b1 = await ctx.openB1();
        const b1Txn = startTestTxn(b1);
        await b1.locks.acquireLocks({ shared: IModel.dictionaryId });
        [, ctx.modelId] = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(b1Txn, IModelTestUtils.getUniqueModelCode(b1, "newPhysicalModel"), true);
        const dictionary = b1.models.getModel(IModel.dictionaryId);
        const newCategoryCode = IModelTestUtils.getUniqueSpatialCategoryCode(dictionary, "ThisTestSpatialCategory");
        ctx.spatialCategoryId = SpatialCategory.insert(b1Txn, dictionary.id, newCategoryCode.value, new SubCategoryAppearance({ color: 0xff0000 }));
        b1Txn.saveChanges();
        await b1.pushChanges({ description: "" });
        b1.close();
    });
    it("rebase events (noFastForward:true)", async () => {
        /**
         * Fastforward will not trigger rebase events as rebase was not required to merge changes.
         * In this test we will test rebase events when noFastForward is set to true. Which mean rebase is required to merge changes.
         */
        const events = new Map();
        const b1 = await ctx.openB1();
        const b1Txn = startTestTxn(b1);
        events.set(b1.briefcaseId, []);
        b1.txns.rebaser.onRebaseTxnBegin.addListener((args) => {
            events.get(b1.briefcaseId)?.push({ args, event: "onRebaseTxnBegin" });
        });
        b1.txns.rebaser.onRebaseTxnEnd.addListener((args) => {
            events.get(b1.briefcaseId)?.push({ args, event: "onRebaseTxnEnd" });
        });
        const b2 = await ctx.openB2();
        const b2Txn = startTestTxn(b2);
        events.set(b2.briefcaseId, []);
        b2.txns.rebaser.onRebaseTxnBegin.addListener((args) => {
            events.get(b2.briefcaseId)?.push({ args, event: "onRebaseTxnBegin" });
        });
        b2.txns.rebaser.onRebaseTxnEnd.addListener((args) => {
            events.get(b2.briefcaseId)?.push({ args, event: "onRebaseTxnEnd" });
        });
        const e1 = await insertPhysicalObject(b1Txn);
        b1Txn.saveChanges(`inserted physical object [id=${e1}]`);
        events.set(b1.briefcaseId, []);
        await b1.pushChanges({ description: `inserted physical object [id=${e1}]` });
        assert.isDefined(b1.elements.getElement(e1));
        assert.equal(events.get(b1.briefcaseId)?.length, 0);
        const e2 = await insertPhysicalObject(b1Txn);
        b1Txn.saveChanges();
        events.set(b1.briefcaseId, []);
        await b1.pushChanges({ description: `inserted physical object [id=${e2}]` });
        assert.equal(events.get(b1.briefcaseId)?.length, 0);
        assert.isDefined(b1.elements.getElement(e2));
        const e3 = await insertPhysicalObject(b2Txn);
        b2Txn.saveChanges(`inserted physical object [id=${e3}]`);
        const e4 = await insertPhysicalObject(b2Txn);
        b2Txn.saveChanges(`inserted physical object [id=${e4}]`);
        events.set(b2.briefcaseId, []);
        // fast-forward has no effect its a deprecated flag
        await b2.pushChanges({ description: `inserted physical object [id=${e3},${e4}]`, noFastForward: true });
        assert.equal(events.get(b2.briefcaseId)?.length, 4);
        assert.equal(events.get(b2.briefcaseId)?.[0].event, "onRebaseTxnBegin");
        assert.equal(events.get(b2.briefcaseId)?.[0].args.id, "0x100000000");
        assert.equal(events.get(b2.briefcaseId)?.[0].args.props?.description, "inserted physical object [id=0x40000000001]");
        assert.equal(events.get(b2.briefcaseId)?.[0].args.type, "Data");
        assert.equal(events.get(b2.briefcaseId)?.[3].event, "onRebaseTxnEnd");
        assert.equal(events.get(b2.briefcaseId)?.[3].args.id, "0x100000001");
        assert.equal(events.get(b2.briefcaseId)?.[3].args.props?.description, "inserted physical object [id=0x40000000002]");
        assert.equal(events.get(b2.briefcaseId)?.[3].args.type, "Data");
        assert.isDefined(b2.elements.getElement(e1));
        assert.isDefined(b2.elements.getElement(e2));
        assert.isDefined(b2.elements.getElement(e3));
        assert.isDefined(b2.elements.getElement(e4));
        const e5 = await insertPhysicalObject(b1Txn);
        b1Txn.saveChanges(`inserted physical object [id=${e5}]`);
        const e6 = await insertPhysicalObject(b1Txn);
        b1Txn.saveChanges(`inserted physical object [id=${e6}]`);
        events.set(b1.briefcaseId, []);
        await b1.pushChanges({ description: `inserted physical object [id=${e5}, ${e6}]` });
        assert.equal(events.get(b1.briefcaseId)?.length, 4);
        assert.equal(events.get(b1.briefcaseId)?.[0].event, "onRebaseTxnBegin");
        assert.equal(events.get(b1.briefcaseId)?.[0].args.id, "0x100000000");
        assert.equal(events.get(b1.briefcaseId)?.[0].args.props?.description, "inserted physical object [id=0x30000000003]");
        assert.equal(events.get(b1.briefcaseId)?.[0].args.type, "Data");
        assert.equal(events.get(b1.briefcaseId)?.[3].event, "onRebaseTxnEnd");
        assert.equal(events.get(b1.briefcaseId)?.[3].args.id, "0x100000001");
        assert.equal(events.get(b1.briefcaseId)?.[3].args.props?.description, "inserted physical object [id=0x30000000004]");
        assert.equal(events.get(b1.briefcaseId)?.[3].args.type, "Data");
        assert.isDefined(b1.elements.getElement(e1));
        assert.isDefined(b1.elements.getElement(e2));
        assert.isDefined(b1.elements.getElement(e3)); // Not found
        assert.isDefined(b1.elements.getElement(e4));
        assert.isDefined(b1.elements.getElement(e5));
        assert.isDefined(b1.elements.getElement(e6));
        events.set(b2.briefcaseId, []);
        await b2.pullChanges();
        assert.equal(events.get(b2.briefcaseId)?.length, 0);
        assert.isDefined(b2.elements.getElement(e1));
        assert.isDefined(b2.elements.getElement(e2));
        assert.isDefined(b2.elements.getElement(e3));
        assert.isDefined(b2.elements.getElement(e4));
        assert.isDefined(b2.elements.getElement(e5));
        assert.isDefined(b2.elements.getElement(e6));
        await updatePhysicalObject(b1Txn, e3, Guid.createValue());
        b1Txn.saveChanges(`update physical object [id=${e3}]`);
        await updatePhysicalObject(b1Txn, e4, Guid.createValue());
        b1Txn.saveChanges(`update physical object [id=${e4}]`);
        events.set(b1.briefcaseId, []);
        await b1.pushChanges({ description: `update physical object [id=${e3},${e4}]` });
        assert.equal(events.get(b1.briefcaseId)?.length, 0);
        await updatePhysicalObject(b2Txn, e1, Guid.createValue());
        b2Txn.saveChanges(`update physical object [id=${e1}]`);
        await updatePhysicalObject(b2Txn, e2, Guid.createValue());
        b2Txn.saveChanges(`update physical object [id=${e2}]`);
        await updatePhysicalObject(b2Txn, e5, Guid.createValue());
        b2Txn.saveChanges(`update physical object [id=${e5}]`);
        await updatePhysicalObject(b2Txn, e6, Guid.createValue());
        b2Txn.saveChanges(`update physical object [id=${e6}]`);
        events.set(b2.briefcaseId, []);
        await b2.pushChanges({ description: `update physical object [id=${e1},${e2},${e5}]`, noFastForward: true });
        assert.equal(events.get(b2.briefcaseId)?.length, 8);
        assert.equal(events.get(b2.briefcaseId)?.[0].event, "onRebaseTxnBegin");
        assert.equal(events.get(b2.briefcaseId)?.[0].args.id, "0x100000000");
        assert.equal(events.get(b2.briefcaseId)?.[0].args.props?.description, "update physical object [id=0x30000000001]");
        assert.equal(events.get(b2.briefcaseId)?.[0].args.type, "Data");
        assert.equal(events.get(b2.briefcaseId)?.[2].event, "onRebaseTxnBegin");
        assert.equal(events.get(b2.briefcaseId)?.[2].args.id, "0x100000001");
        assert.equal(events.get(b2.briefcaseId)?.[2].args.props?.description, "update physical object [id=0x30000000002]");
        assert.equal(events.get(b2.briefcaseId)?.[2].args.type, "Data");
        assert.equal(events.get(b2.briefcaseId)?.[4].event, "onRebaseTxnBegin");
        assert.equal(events.get(b2.briefcaseId)?.[4].args.id, "0x100000002");
        assert.equal(events.get(b2.briefcaseId)?.[4].args.props?.description, "update physical object [id=0x30000000003]");
        assert.equal(events.get(b2.briefcaseId)?.[4].args.type, "Data");
        assert.equal(events.get(b2.briefcaseId)?.[6].event, "onRebaseTxnBegin");
        assert.equal(events.get(b2.briefcaseId)?.[6].args.id, "0x100000003");
        assert.equal(events.get(b2.briefcaseId)?.[6].args.props?.description, "update physical object [id=0x30000000004]");
        assert.equal(events.get(b2.briefcaseId)?.[6].args.type, "Data");
        assert.isDefined(b1.elements.getElement(e1).federationGuid);
        assert.isDefined(b1.elements.getElement(e2).federationGuid);
        assert.isDefined(b1.elements.getElement(e3).federationGuid);
        assert.isDefined(b1.elements.getElement(e4).federationGuid);
        assert.isDefined(b1.elements.getElement(e5).federationGuid);
        assert.isDefined(b1.elements.getElement(e6).federationGuid);
        assert.isDefined(b2.elements.getElement(e1).federationGuid);
        assert.isDefined(b2.elements.getElement(e2).federationGuid);
        assert.isDefined(b2.elements.getElement(e3).federationGuid);
        assert.isDefined(b2.elements.getElement(e4).federationGuid);
        assert.isDefined(b2.elements.getElement(e5).federationGuid);
        assert.isDefined(b2.elements.getElement(e6).federationGuid);
        b1.close();
        b2.close();
    });
    it("rebase events (noFastForward:false/default)", async () => {
        const events = new Map();
        const b1 = await ctx.openB1();
        const b1Txn = startTestTxn(b1);
        events.set(b1.briefcaseId, []);
        b1.txns.rebaser.onRebaseTxnBegin.addListener((args) => {
            events.get(b1.briefcaseId)?.push({ args, event: "onRebaseTxnBegin" });
        });
        b1.txns.rebaser.onRebaseTxnEnd.addListener((args) => {
            events.get(b1.briefcaseId)?.push({ args, event: "onRebaseTxnEnd" });
        });
        const b2 = await ctx.openB2();
        const b2Txn = startTestTxn(b2);
        events.set(b2.briefcaseId, []);
        b2.txns.rebaser.onRebaseTxnBegin.addListener((args) => {
            events.get(b2.briefcaseId)?.push({ args, event: "onRebaseTxnBegin" });
        });
        b2.txns.rebaser.onRebaseTxnEnd.addListener((args) => {
            events.get(b2.briefcaseId)?.push({ args, event: "onRebaseTxnEnd" });
        });
        const e1 = await insertPhysicalObject(b1Txn);
        b1Txn.saveChanges(`inserted physical object [id=${e1}]`);
        events.set(b1.briefcaseId, []);
        await b1.pushChanges({ description: `inserted physical object [id=${e1}]` });
        assert.isDefined(b1.elements.getElement(e1));
        assert.equal(events.get(b1.briefcaseId)?.length, 0);
        const e2 = await insertPhysicalObject(b1Txn);
        b1Txn.saveChanges();
        events.set(b1.briefcaseId, []);
        await b1.pushChanges({ description: `inserted physical object [id=${e2}]` });
        assert.equal(events.get(b1.briefcaseId)?.length, 0);
        assert.isDefined(b1.elements.getElement(e2));
        const e3 = await insertPhysicalObject(b2Txn);
        b2Txn.saveChanges(`inserted physical object [id=${e3}]`);
        const e4 = await insertPhysicalObject(b2Txn);
        b2Txn.saveChanges(`inserted physical object [id=${e4}]`);
        events.set(b2.briefcaseId, []);
        // fast-forward
        await b2.pushChanges({ description: `inserted physical object [id=${e3},${e4}]` });
        assert.equal(events.get(b2.briefcaseId)?.length, 4);
        assert.isDefined(b2.elements.getElement(e1));
        assert.isDefined(b2.elements.getElement(e2));
        assert.isDefined(b2.elements.getElement(e3));
        assert.isDefined(b2.elements.getElement(e4));
        const e5 = await insertPhysicalObject(b1Txn);
        b1Txn.saveChanges(`inserted physical object [id=${e5}]`);
        const e6 = await insertPhysicalObject(b1Txn);
        b1Txn.saveChanges(`inserted physical object [id=${e6}]`);
        events.set(b1.briefcaseId, []);
        await b1.pushChanges({ description: `inserted physical object [id=${e5}, ${e6}]` });
        assert.equal(events.get(b1.briefcaseId)?.length, 4);
        assert.isDefined(b1.elements.getElement(e1));
        assert.isDefined(b1.elements.getElement(e2));
        assert.isDefined(b1.elements.getElement(e3)); // Not found
        assert.isDefined(b1.elements.getElement(e4));
        assert.isDefined(b1.elements.getElement(e5));
        assert.isDefined(b1.elements.getElement(e6));
        events.set(b2.briefcaseId, []);
        await b2.pullChanges();
        assert.equal(events.get(b2.briefcaseId)?.length, 0);
        assert.isDefined(b2.elements.getElement(e1));
        assert.isDefined(b2.elements.getElement(e2));
        assert.isDefined(b2.elements.getElement(e3));
        assert.isDefined(b2.elements.getElement(e4));
        assert.isDefined(b2.elements.getElement(e5));
        assert.isDefined(b2.elements.getElement(e6));
        await updatePhysicalObject(b1Txn, e3, Guid.createValue());
        b1Txn.saveChanges(`update physical object [id=${e3}]`);
        await updatePhysicalObject(b1Txn, e4, Guid.createValue());
        b1Txn.saveChanges(`update physical object [id=${e4}]`);
        events.set(b1.briefcaseId, []);
        await b1.pushChanges({ description: `update physical object [id=${e3},${e4}]` });
        assert.equal(events.get(b1.briefcaseId)?.length, 0);
        await updatePhysicalObject(b2Txn, e1, Guid.createValue());
        b2Txn.saveChanges(`update physical object [id=${e1}]`);
        await updatePhysicalObject(b2Txn, e2, Guid.createValue());
        b2Txn.saveChanges(`update physical object [id=${e2}]`);
        await updatePhysicalObject(b2Txn, e5, Guid.createValue());
        b2Txn.saveChanges(`update physical object [id=${e5}]`);
        await updatePhysicalObject(b2Txn, e6, Guid.createValue());
        b2Txn.saveChanges(`update physical object [id=${e6}]`);
        events.set(b2.briefcaseId, []);
        await b2.pushChanges({ description: `update physical object [id=${e1},${e2},${e5}]` });
        assert.isDefined(b1.elements.getElement(e1).federationGuid);
        assert.isDefined(b1.elements.getElement(e2).federationGuid);
        assert.isDefined(b1.elements.getElement(e3).federationGuid);
        assert.isDefined(b1.elements.getElement(e4).federationGuid);
        assert.isDefined(b1.elements.getElement(e5).federationGuid);
        assert.isDefined(b1.elements.getElement(e6).federationGuid);
        assert.isDefined(b2.elements.getElement(e1).federationGuid);
        assert.isDefined(b2.elements.getElement(e2).federationGuid);
        assert.isDefined(b2.elements.getElement(e3).federationGuid);
        assert.isDefined(b2.elements.getElement(e4).federationGuid);
        assert.isDefined(b2.elements.getElement(e5).federationGuid);
        assert.isDefined(b2.elements.getElement(e6).federationGuid);
        b1.close();
        b2.close();
    });
    it("rebase with be_props (insert conflict)", async () => {
        const b1 = await ctx.openB1();
        const b1Txn = startTestTxn(b1);
        const b2 = await ctx.openB2();
        const b2Txn = startTestTxn(b2);
        b1Txn.saveFileProperty({ namespace: "test", name: "test" }, "test1");
        b1Txn.saveChanges("test");
        await b1.pushChanges({ description: "test" });
        b2Txn.saveFileProperty({ namespace: "test", name: "test" }, "test2");
        b2Txn.saveChanges("test2");
        await assertThrowsAsync(async () => b2.pushChanges({ description: "test2" }), "PRIMARY KEY insert conflict. Aborting rebase.");
        assert.equal(b2.queryFilePropertyString({ namespace: "test", name: "test" }), "test1");
        b2Txn.saveFileProperty({ namespace: "test", name: "test" }, "test3");
        chai.expect(() => b2Txn.saveChanges("test1")).throws("Could not save changes (test1)");
        b2Txn.abandonChanges();
        // set handler to resolve conflict
        b2.txns.rebaser.addConflictHandler({
            id: "my", handler: (args) => {
                if (args.cause === "Conflict") {
                    if (args.tableName === "be_Prop") {
                        if (args.opcode === "Inserted") {
                            chai.expect(args.getColumnNames()).to.be.deep.equal(["Namespace", "Name", "Id", "SubId", "TxnMode", "StrData", "RawSize", "Data"]);
                            chai.expect(args.txn.id).to.be.equal("0x100000000");
                            chai.expect(args.txn.descr).to.be.equal("test2");
                            chai.expect(args.txn.type).to.be.equal("Data");
                            const localChangedVal = args.getValueText(5, "New");
                            const tipValue = b2.queryFilePropertyString({ namespace: "test", name: "test" });
                            b2Txn.saveFileProperty({ namespace: "test", name: "test" }, `${tipValue} + ${localChangedVal}`);
                            return DbConflictResolution.Skip; // skip incoming value and continue
                        }
                    }
                }
                return undefined;
            }
        });
        // resume rebase see if it resolve the conflict
        await b2.txns.rebaser.resume();
        // use changeset api to read txn directly
        const reader = SqliteChangesetReader.openTxn({ db: b2, txnId: "0x100000000" });
        chai.expect(reader.step()).to.be.true;
        chai.expect(reader.tableName).to.be.equal("be_Prop");
        chai.expect(reader.getColumnNames(reader.tableName)[5]).to.be.equal("StrData");
        // note the operation changed from insert to update
        chai.expect(reader.op).to.be.equal("Updated");
        // note this old value is from master branch after changeset was recomputed
        chai.expect(reader.getChangeValueText(5, "Old")).to.be.equal("test1");
        // note this new value is from local branch after merger.
        chai.expect(reader.getChangeValueText(5, "New")).to.be.equal("test1 + test2");
        reader.close();
        assert.equal(b2.queryFilePropertyString({ namespace: "test", name: "test" }), "test1 + test2");
        await b2.pushChanges({ description: "test2" });
        await b1.pullChanges();
        assert.equal(b2.queryFilePropertyString({ namespace: "test", name: "test" }), "test1 + test2");
        assert.equal(b1.queryFilePropertyString({ namespace: "test", name: "test" }), "test1 + test2");
        b1.close();
        b2.close();
    });
    it("rebase with be_props (data conflict) ", async () => {
        const b1 = await ctx.openB1();
        const b1Txn = startTestTxn(b1);
        const b2 = await ctx.openB2();
        const b2Txn = startTestTxn(b2);
        b1Txn.saveFileProperty({ namespace: "test", name: "test" }, "test1");
        b1Txn.saveChanges("test");
        await b1.pushChanges({ description: "test" });
        await b2.pullChanges();
        b2Txn.saveFileProperty({ namespace: "test", name: "test" }, "test2");
        b2Txn.saveChanges("test2");
        b1Txn.saveFileProperty({ namespace: "test", name: "test" }, "test3");
        b1Txn.saveChanges("test");
        await b1.pushChanges({ description: "test" });
        // set handler to resolve conflict
        b2.txns.rebaser.addConflictHandler({
            id: "my", handler: (args) => {
                if (args.cause === "Data") {
                    if (args.tableName === "be_Prop") {
                        if (args.opcode === "Updated") {
                            const localChangedVal = args.getValueText(5, "New");
                            const tipValue = b2.queryFilePropertyString({ namespace: "test", name: "test" });
                            b2Txn.saveFileProperty({ namespace: "test", name: "test" }, `${tipValue} + ${localChangedVal}`);
                            return DbConflictResolution.Skip; // skip incomming value and continue
                        }
                    }
                }
                return undefined;
            }
        });
        await b2.pushChanges({ description: "test" });
        await b2.pullChanges();
        await b1.pullChanges();
        assert.equal(b2.queryFilePropertyString({ namespace: "test", name: "test" }), "test3 + test2");
        assert.equal(b1.queryFilePropertyString({ namespace: "test", name: "test" }), "test3 + test2");
        b1.close();
        b2.close();
    });
    it("should keep element deleted when local delete conflicts with remote update", async () => {
        const b1 = await ctx.openB1(true /* = noLock */);
        const b1Txn = startTestTxn(b1);
        const b2 = await ctx.openB2(true /* = noLock */);
        const b2Txn = startTestTxn(b2);
        const e1 = await insertPhysicalObject(b1Txn);
        b1Txn.saveChanges();
        await b1.pushChanges({ description: `inserted physical object [id=${e1}]` });
        await b2.pullChanges();
        const eb1 = b1.elements.getElementProps(e1);
        eb1.userLabel = "test1";
        eb1.placement = { origin: { x: 1, y: 1, z: 1 }, angles: { yaw: 1, pitch: 1, roll: 1 } };
        eb1.geom = IModelTestUtils.createBox(Point3d.create(3, 3, 3));
        b1Txn.updateElement(eb1);
        b1Txn.saveChanges();
        await b1.pushChanges({ description: `update physical object [id=${e1}]` });
        b2Txn.deleteElement(e1);
        b2Txn.saveChanges();
        await b2.pullChanges();
        const eb2 = b2.elements.tryGetElementProps(e1);
        // when placement is changed the element is not deleted after pullChanges();
        assert.isUndefined(eb2);
        b1.close();
        b2.close();
    });
});
//# sourceMappingURL=ChangeMerge.test.js.map