@itwin/core-backend 5.7.0-dev.9 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/lib/cjs/BriefcaseManager.d.ts +138 -1
  3. package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
  4. package/lib/cjs/BriefcaseManager.js +336 -1
  5. package/lib/cjs/BriefcaseManager.js.map +1 -1
  6. package/lib/cjs/CloudSqlite.js +1 -1
  7. package/lib/cjs/CloudSqlite.js.map +1 -1
  8. package/lib/cjs/IModelDb.d.ts +71 -0
  9. package/lib/cjs/IModelDb.d.ts.map +1 -1
  10. package/lib/cjs/IModelDb.js +122 -6
  11. package/lib/cjs/IModelDb.js.map +1 -1
  12. package/lib/cjs/IModelHost.d.ts +15 -0
  13. package/lib/cjs/IModelHost.d.ts.map +1 -1
  14. package/lib/cjs/IModelHost.js +12 -0
  15. package/lib/cjs/IModelHost.js.map +1 -1
  16. package/lib/cjs/IModelJsFs.d.ts +2 -0
  17. package/lib/cjs/IModelJsFs.d.ts.map +1 -1
  18. package/lib/cjs/IModelJsFs.js +2 -0
  19. package/lib/cjs/IModelJsFs.js.map +1 -1
  20. package/lib/cjs/TileStorage.js +1 -1
  21. package/lib/cjs/TileStorage.js.map +1 -1
  22. package/lib/cjs/TxnManager.d.ts +39 -0
  23. package/lib/cjs/TxnManager.d.ts.map +1 -1
  24. package/lib/cjs/TxnManager.js +149 -0
  25. package/lib/cjs/TxnManager.js.map +1 -1
  26. package/lib/cjs/ViewDefinition.d.ts.map +1 -1
  27. package/lib/cjs/ViewDefinition.js +2 -0
  28. package/lib/cjs/ViewDefinition.js.map +1 -1
  29. package/lib/cjs/internal/IModelDbFontsImpl.js +1 -1
  30. package/lib/cjs/internal/IModelDbFontsImpl.js.map +1 -1
  31. package/lib/cjs/internal/IntegrityCheck.d.ts +240 -0
  32. package/lib/cjs/internal/IntegrityCheck.d.ts.map +1 -0
  33. package/lib/cjs/internal/IntegrityCheck.js +193 -0
  34. package/lib/cjs/internal/IntegrityCheck.js.map +1 -0
  35. package/lib/cjs/internal/annotations/fields.js +2 -2
  36. package/lib/cjs/internal/annotations/fields.js.map +1 -1
  37. package/lib/cjs/rpc/tracing.js +2 -2
  38. package/lib/cjs/rpc/tracing.js.map +1 -1
  39. package/lib/cjs/workspace/Workspace.js +1 -1
  40. package/lib/cjs/workspace/Workspace.js.map +1 -1
  41. package/lib/esm/BriefcaseManager.d.ts +138 -1
  42. package/lib/esm/BriefcaseManager.d.ts.map +1 -1
  43. package/lib/esm/BriefcaseManager.js +336 -1
  44. package/lib/esm/BriefcaseManager.js.map +1 -1
  45. package/lib/esm/CloudSqlite.js +1 -1
  46. package/lib/esm/CloudSqlite.js.map +1 -1
  47. package/lib/esm/IModelDb.d.ts +71 -0
  48. package/lib/esm/IModelDb.d.ts.map +1 -1
  49. package/lib/esm/IModelDb.js +122 -6
  50. package/lib/esm/IModelDb.js.map +1 -1
  51. package/lib/esm/IModelHost.d.ts +15 -0
  52. package/lib/esm/IModelHost.d.ts.map +1 -1
  53. package/lib/esm/IModelHost.js +12 -0
  54. package/lib/esm/IModelHost.js.map +1 -1
  55. package/lib/esm/IModelJsFs.d.ts +2 -0
  56. package/lib/esm/IModelJsFs.d.ts.map +1 -1
  57. package/lib/esm/IModelJsFs.js +2 -0
  58. package/lib/esm/IModelJsFs.js.map +1 -1
  59. package/lib/esm/TileStorage.js +1 -1
  60. package/lib/esm/TileStorage.js.map +1 -1
  61. package/lib/esm/TxnManager.d.ts +39 -0
  62. package/lib/esm/TxnManager.d.ts.map +1 -1
  63. package/lib/esm/TxnManager.js +150 -1
  64. package/lib/esm/TxnManager.js.map +1 -1
  65. package/lib/esm/ViewDefinition.d.ts.map +1 -1
  66. package/lib/esm/ViewDefinition.js +2 -0
  67. package/lib/esm/ViewDefinition.js.map +1 -1
  68. package/lib/esm/internal/IModelDbFontsImpl.js +1 -1
  69. package/lib/esm/internal/IModelDbFontsImpl.js.map +1 -1
  70. package/lib/esm/internal/IntegrityCheck.d.ts +240 -0
  71. package/lib/esm/internal/IntegrityCheck.d.ts.map +1 -0
  72. package/lib/esm/internal/IntegrityCheck.js +187 -0
  73. package/lib/esm/internal/IntegrityCheck.js.map +1 -0
  74. package/lib/esm/internal/annotations/fields.js +2 -2
  75. package/lib/esm/internal/annotations/fields.js.map +1 -1
  76. package/lib/esm/rpc/tracing.js +2 -2
  77. package/lib/esm/rpc/tracing.js.map +1 -1
  78. package/lib/esm/test/SquashSchemaAndDataChanges.test.d.ts +2 -0
  79. package/lib/esm/test/SquashSchemaAndDataChanges.test.d.ts.map +1 -0
  80. package/lib/esm/test/SquashSchemaAndDataChanges.test.js +241 -0
  81. package/lib/esm/test/SquashSchemaAndDataChanges.test.js.map +1 -0
  82. package/lib/esm/test/hubaccess/Rebase.test.js +1575 -1568
  83. package/lib/esm/test/hubaccess/Rebase.test.js.map +1 -1
  84. package/lib/esm/test/hubaccess/SemanticRebase.test.d.ts +2 -0
  85. package/lib/esm/test/hubaccess/SemanticRebase.test.d.ts.map +1 -0
  86. package/lib/esm/test/hubaccess/SemanticRebase.test.js +1206 -0
  87. package/lib/esm/test/hubaccess/SemanticRebase.test.js.map +1 -0
  88. package/lib/esm/test/imodel/IModel.test.js +1 -1
  89. package/lib/esm/test/imodel/IModel.test.js.map +1 -1
  90. package/lib/esm/test/standalone/ChangesetReader.test.js +173 -2
  91. package/lib/esm/test/standalone/ChangesetReader.test.js.map +1 -1
  92. package/lib/esm/test/standalone/IntegrityCheck.test.d.ts +2 -0
  93. package/lib/esm/test/standalone/IntegrityCheck.test.d.ts.map +1 -0
  94. package/lib/esm/test/standalone/IntegrityCheck.test.js +385 -0
  95. package/lib/esm/test/standalone/IntegrityCheck.test.js.map +1 -0
  96. package/lib/esm/test/standalone/ViewDefinition.test.js +14 -2
  97. package/lib/esm/test/standalone/ViewDefinition.test.js.map +1 -1
  98. package/lib/esm/test/standalone/Workspace.test.js +5 -0
  99. package/lib/esm/test/standalone/Workspace.test.js.map +1 -1
  100. package/lib/esm/workspace/Workspace.js +1 -1
  101. package/lib/esm/workspace/Workspace.js.map +1 -1
  102. package/package.json +14 -14
@@ -7,9 +7,10 @@ import { Code, GeometryStreamBuilder, IModel, QueryBinder, SubCategoryAppearance
7
7
  import * as chai from "chai";
8
8
  import * as chaiAsPromised from "chai-as-promised";
9
9
  import { HubWrappers, IModelTestUtils, KnownTestLocations } from "..";
10
- import { _nativeDb, BriefcaseManager, ChangesetECAdaptor, ChannelControl, DrawingCategory, ElementGroupsMembers, IModelHost, SqliteChangesetReader } from "../../core-backend";
10
+ import { _nativeDb, BriefcaseManager, ChangesetECAdaptor, ChannelControl, DrawingCategory, ElementGroupsMembers, SqliteChangesetReader } from "../../core-backend";
11
11
  import { HubMock } from "../../internal/HubMock";
12
12
  import { StashManager } from "../../StashManager";
13
+ import { TestUtils } from "../TestUtils";
13
14
  import { existsSync, unlinkSync, writeFileSync } from "fs";
14
15
  import * as path from "path";
15
16
  import { LineSegment3d, Point3d } from "@itwin/core-geometry";
@@ -196,269 +197,274 @@ const removePropertyRecursive = (obj, prop) => {
196
197
  });
197
198
  }
198
199
  };
199
- describe("rebase changes & stashing api", function () {
200
- let testIModel;
201
- before(async () => {
202
- if (!IModelHost.isValid)
203
- await IModelHost.startup();
204
- });
205
- this.beforeEach(async () => {
206
- testIModel = new TestIModel();
207
- await testIModel.startup();
208
- });
209
- this.afterEach(async () => {
210
- await testIModel.shutdown();
211
- });
212
- it("save changes args", async () => {
213
- const b1 = await testIModel.openBriefcase();
214
- await testIModel.insertElement(b1);
215
- b1.saveChanges({
216
- source: "test",
217
- description: "test description",
218
- appData: {
219
- test: "test",
220
- foo: [1, 2, 3],
221
- bar: { baz: "qux" }
222
- }
200
+ for (const enableSemanticRebase of [false, true]) {
201
+ describe(`rebase changes & stashing api (useSemanticRebase=${enableSemanticRebase})`, function () {
202
+ let testIModel;
203
+ before(async () => {
204
+ await TestUtils.shutdownBackend();
205
+ await TestUtils.startBackend({ useSemanticRebase: enableSemanticRebase });
223
206
  });
224
- let lastTxn = b1.txns.getLastSavedTxnProps();
225
- chai.assert.isDefined(lastTxn);
226
- if (lastTxn) {
227
- chai.expect(lastTxn.props.source).to.be.equals("test");
228
- chai.expect(lastTxn.props.description).to.be.equals("test description");
229
- chai.expect(lastTxn.props.appData).to.not.be.undefined;
230
- chai.expect(lastTxn.props.appData?.test).to.be.eq("test");
231
- chai.expect(lastTxn.props.appData?.foo).to.be.deep.eq([1, 2, 3]);
232
- chai.expect(lastTxn.props.appData?.bar).to.be.deep.eq({ baz: "qux" });
233
- chai.expect(lastTxn.nextId).to.be.undefined;
234
- chai.expect(lastTxn.prevId).to.be.undefined;
235
- chai.expect(lastTxn.type).to.be.eq("Data");
236
- chai.expect(lastTxn.id).to.be.eq('0x100000000');
237
- chai.expect(lastTxn.reversed).to.be.false;
238
- chai.expect(lastTxn.grouped).to.be.false;
239
- }
240
- await testIModel.insertElement(b1);
241
- b1.saveChanges({
242
- source: "test2",
243
- description: "test description 2",
244
- appData: {
245
- test: "test 2",
246
- foo: [11, 12, 13],
247
- bar: { baz: "qux2" }
248
- }
207
+ after(async () => {
208
+ await TestUtils.shutdownBackend();
209
+ await TestUtils.startBackend(); // restart normal backend so subsequent test suites aren't left without IModelHost
249
210
  });
250
- lastTxn = b1.txns.getLastSavedTxnProps();
251
- chai.assert.isDefined(lastTxn);
252
- if (lastTxn) {
253
- chai.expect(lastTxn.props.source).to.be.equals("test2");
254
- chai.expect(lastTxn.props.description).to.be.equals("test description 2");
255
- chai.expect(lastTxn.props.appData).to.not.be.undefined;
256
- chai.expect(lastTxn.props.appData?.test).to.be.eq("test 2");
257
- chai.expect(lastTxn.props.appData?.foo).to.be.deep.eq([11, 12, 13]);
258
- chai.expect(lastTxn.props.appData?.bar).to.be.deep.eq({ baz: "qux2" });
259
- chai.expect(lastTxn.nextId).to.be.undefined;
260
- chai.expect(lastTxn.prevId).to.be.equal('0x100000000');
261
- chai.expect(lastTxn.type).to.be.eq("Data");
262
- chai.expect(lastTxn.id).to.be.eq('0x100000001');
263
- chai.expect(lastTxn.reversed).to.be.false;
264
- chai.expect(lastTxn.grouped).to.be.false;
265
- }
266
- await testIModel.insertElement(b1);
267
- b1.saveChanges("new element");
268
- lastTxn = b1.txns.getLastSavedTxnProps();
269
- chai.assert.isDefined(lastTxn);
270
- if (lastTxn) {
271
- chai.expect(lastTxn.props.source).is.undefined;
272
- chai.expect(lastTxn.props.description).to.be.equals("new element");
273
- chai.expect(lastTxn.props.appData).to.be.undefined;
274
- chai.expect(lastTxn.nextId).to.be.undefined;
275
- chai.expect(lastTxn.prevId).to.be.equal('0x100000001');
276
- chai.expect(lastTxn.type).to.be.eq("Data");
277
- chai.expect(lastTxn.id).to.be.eq('0x100000002');
278
- chai.expect(lastTxn.reversed).to.be.false;
279
- chai.expect(lastTxn.grouped).to.be.false;
280
- }
281
- await b1.pushChanges({ description: "new element" });
282
- chai.expect(b1.txns.isUndoPossible).is.false;
283
- chai.expect(b1.txns.isRedoPossible).is.false;
284
- lastTxn = b1.txns.getLastSavedTxnProps();
285
- chai.assert.isUndefined(lastTxn);
286
- });
287
- it("direct / indirect", async () => {
288
- const b1 = await testIModel.openBriefcase();
289
- const directElId = await testIModel.insertElement(b1);
290
- const indirectElId = await testIModel.insertElement(b1, true);
291
- chai.expect(directElId).to.not.be.undefined;
292
- chai.expect(indirectElId).to.not.be.undefined;
293
- b1.saveChanges({ description: "insert element 1 direct and 1 indirect" });
294
- const txn = b1.txns.getLastSavedTxnProps();
295
- chai.assert.isDefined(txn);
296
- if (txn) {
297
- let checkCount = 0;
298
- const reader = SqliteChangesetReader.openTxn({ txnId: txn?.id, db: b1 });
299
- while (reader.step()) {
300
- if (reader.primaryKeyValues.length === 0)
301
- continue;
302
- if (reader.tableName !== "bis_Element")
303
- continue;
304
- const iid = reader.primaryKeyValues[0];
305
- if (iid === directElId) {
306
- chai.expect(reader.isIndirect).to.be.false;
211
+ this.beforeEach(async () => {
212
+ testIModel = new TestIModel();
213
+ await testIModel.startup();
214
+ });
215
+ this.afterEach(async () => {
216
+ await testIModel.shutdown();
217
+ });
218
+ it("save changes args", async () => {
219
+ const b1 = await testIModel.openBriefcase();
220
+ await testIModel.insertElement(b1);
221
+ b1.saveChanges({
222
+ source: "test",
223
+ description: "test description",
224
+ appData: {
225
+ test: "test",
226
+ foo: [1, 2, 3],
227
+ bar: { baz: "qux" }
307
228
  }
308
- if (iid === indirectElId) {
309
- chai.expect(reader.isIndirect).to.be.true;
229
+ });
230
+ let lastTxn = b1.txns.getLastSavedTxnProps();
231
+ chai.assert.isDefined(lastTxn);
232
+ if (lastTxn) {
233
+ chai.expect(lastTxn.props.source).to.be.equals("test");
234
+ chai.expect(lastTxn.props.description).to.be.equals("test description");
235
+ chai.expect(lastTxn.props.appData).to.not.be.undefined;
236
+ chai.expect(lastTxn.props.appData?.test).to.be.eq("test");
237
+ chai.expect(lastTxn.props.appData?.foo).to.be.deep.eq([1, 2, 3]);
238
+ chai.expect(lastTxn.props.appData?.bar).to.be.deep.eq({ baz: "qux" });
239
+ chai.expect(lastTxn.nextId).to.be.undefined;
240
+ chai.expect(lastTxn.prevId).to.be.undefined;
241
+ chai.expect(lastTxn.type).to.be.eq("Data");
242
+ chai.expect(lastTxn.id).to.be.eq('0x100000000');
243
+ chai.expect(lastTxn.reversed).to.be.false;
244
+ chai.expect(lastTxn.grouped).to.be.false;
245
+ }
246
+ await testIModel.insertElement(b1);
247
+ b1.saveChanges({
248
+ source: "test2",
249
+ description: "test description 2",
250
+ appData: {
251
+ test: "test 2",
252
+ foo: [11, 12, 13],
253
+ bar: { baz: "qux2" }
310
254
  }
311
- checkCount++;
255
+ });
256
+ lastTxn = b1.txns.getLastSavedTxnProps();
257
+ chai.assert.isDefined(lastTxn);
258
+ if (lastTxn) {
259
+ chai.expect(lastTxn.props.source).to.be.equals("test2");
260
+ chai.expect(lastTxn.props.description).to.be.equals("test description 2");
261
+ chai.expect(lastTxn.props.appData).to.not.be.undefined;
262
+ chai.expect(lastTxn.props.appData?.test).to.be.eq("test 2");
263
+ chai.expect(lastTxn.props.appData?.foo).to.be.deep.eq([11, 12, 13]);
264
+ chai.expect(lastTxn.props.appData?.bar).to.be.deep.eq({ baz: "qux2" });
265
+ chai.expect(lastTxn.nextId).to.be.undefined;
266
+ chai.expect(lastTxn.prevId).to.be.equal('0x100000000');
267
+ chai.expect(lastTxn.type).to.be.eq("Data");
268
+ chai.expect(lastTxn.id).to.be.eq('0x100000001');
269
+ chai.expect(lastTxn.reversed).to.be.false;
270
+ chai.expect(lastTxn.grouped).to.be.false;
312
271
  }
313
- chai.expect(checkCount).to.be.equals(2);
314
- }
315
- await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
316
- chai.expect(b1.txns.isUndoPossible).is.false;
317
- chai.expect(b1.txns.isRedoPossible).is.false;
318
- const lastTxn = b1.txns.getLastSavedTxnProps();
319
- chai.assert.isUndefined(lastTxn);
320
- });
321
- it("rebase handler", async () => {
322
- const b1 = await testIModel.openBriefcase();
323
- const b2 = await testIModel.openBriefcase();
324
- const e1 = await testIModel.insertElement(b1);
325
- const e2 = await testIModel.insertElement(b1, true);
326
- b1.saveChanges();
327
- await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
328
- await b2.pullChanges();
329
- await testIModel.updateElement(b1, e1);
330
- await testIModel.updateElement(b1, e2, true);
331
- b1.saveChanges();
332
- await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
333
- await testIModel.insertElement(b2);
334
- await testIModel.insertElement(b2, true);
335
- b2.saveChanges("first change");
336
- await testIModel.insertElement(b2);
337
- await testIModel.insertElement(b2, true);
338
- b2.saveChanges("second change");
339
- await testIModel.insertElement(b2);
340
- await testIModel.insertElement(b2, true);
341
- b2.saveChanges("third change");
342
- b2.txns.rebaser.setCustomHandler({
343
- shouldReinstate: (_txn) => {
344
- return true;
345
- },
346
- recompute: async (_txn) => {
347
- await testIModel.insertElement(b2);
348
- await testIModel.insertElement(b2, true);
349
- },
272
+ await testIModel.insertElement(b1);
273
+ b1.saveChanges("new element");
274
+ lastTxn = b1.txns.getLastSavedTxnProps();
275
+ chai.assert.isDefined(lastTxn);
276
+ if (lastTxn) {
277
+ chai.expect(lastTxn.props.source).is.undefined;
278
+ chai.expect(lastTxn.props.description).to.be.equals("new element");
279
+ chai.expect(lastTxn.props.appData).to.be.undefined;
280
+ chai.expect(lastTxn.nextId).to.be.undefined;
281
+ chai.expect(lastTxn.prevId).to.be.equal('0x100000001');
282
+ chai.expect(lastTxn.type).to.be.eq("Data");
283
+ chai.expect(lastTxn.id).to.be.eq('0x100000002');
284
+ chai.expect(lastTxn.reversed).to.be.false;
285
+ chai.expect(lastTxn.grouped).to.be.false;
286
+ }
287
+ await b1.pushChanges({ description: "new element" });
288
+ chai.expect(b1.txns.isUndoPossible).is.false;
289
+ chai.expect(b1.txns.isRedoPossible).is.false;
290
+ lastTxn = b1.txns.getLastSavedTxnProps();
291
+ chai.assert.isUndefined(lastTxn);
350
292
  });
351
- await b1.pullChanges();
352
- });
353
- it("stash & drop", async () => {
354
- const b1 = await testIModel.openBriefcase();
355
- const e1 = await testIModel.insertElement(b1);
356
- b1.saveChanges();
357
- await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
358
- const e2 = await testIModel.insertElement(b1);
359
- b1.saveChanges();
360
- await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
361
- await testIModel.insertElement(b1);
362
- b1.saveChanges(`first`);
363
- await testIModel.updateElement(b1, e1);
364
- b1.saveChanges(`second`);
365
- await testIModel.deleteElement(b1, e2);
366
- b1.saveChanges(`third`);
367
- await testIModel.insertElement(b1);
368
- b1.saveChanges(`fourth`);
369
- const stash1 = await StashManager.stash({ db: b1, description: "stash test 1" });
370
- chai.expect(stash1).to.exist;
371
- chai.assert(Guid.isGuid(stash1.id));
372
- chai.expect(stash1.description).to.equals("stash test 1");
373
- chai.expect(stash1.briefcaseId).equals(b1.briefcaseId);
374
- chai.expect(stash1.iModelId).to.equals(b1.iModelId);
375
- chai.expect(stash1.timestamp).to.exist;
376
- chai.expect(stash1.description).to.exist;
377
- chai.expect(stash1.hash).length(64);
378
- chai.expect(stash1.parentChangeset).to.exist;
379
- chai.expect(stash1.idSequences.element).to.equals("0x30000000004");
380
- chai.expect(stash1.idSequences.instance).to.equals("0x30000000000");
381
- chai.expect(stash1.acquiredLocks).equals(4);
382
- chai.expect(stash1.txns).to.exist;
383
- chai.expect(stash1.txns).to.have.lengthOf(4);
384
- chai.expect(stash1.txns[0].props.description).to.equal("first");
385
- chai.expect(stash1.txns[1].props.description).to.equal("second");
386
- chai.expect(stash1.txns[2].props.description).to.equal("third");
387
- chai.expect(stash1.txns[3].props.description).to.equal("fourth");
388
- chai.expect(stash1.txns[0].id).to.equals("0x100000000");
389
- chai.expect(stash1.txns[1].id).to.equals("0x100000001");
390
- chai.expect(stash1.txns[2].id).to.equals("0x100000002");
391
- chai.expect(stash1.txns[3].id).to.equals("0x100000003");
392
- await testIModel.insertElement(b1);
393
- b1.saveChanges(`fifth`);
394
- await testIModel.updateElement(b1, e1);
395
- b1.saveChanges(`sixth`);
396
- await testIModel.insertElement(b1);
397
- b1.saveChanges(`seventh`);
398
- const stash2 = await StashManager.stash({ db: b1, description: "stash test 2" });
399
- chai.expect(stash2).to.exist;
400
- chai.expect(stash2.description).to.equals("stash test 2");
401
- chai.expect(stash2.hash).length(64);
402
- chai.expect(stash2.parentChangeset).to.exist;
403
- chai.expect(stash2.idSequences.element).to.equals("0x30000000006");
404
- chai.expect(stash2.idSequences.instance).to.equals("0x30000000000");
405
- chai.expect(stash2.acquiredLocks).equals(4);
406
- chai.expect(stash2.txns).to.exist;
407
- chai.expect(stash2.txns).to.have.lengthOf(7);
408
- chai.expect(stash2.txns[0].props.description).to.equal("first");
409
- chai.expect(stash2.txns[1].props.description).to.equal("second");
410
- chai.expect(stash2.txns[2].props.description).to.equal("third");
411
- chai.expect(stash2.txns[3].props.description).to.equal("fourth");
412
- chai.expect(stash2.txns[4].props.description).to.equal("fifth");
413
- chai.expect(stash2.txns[5].props.description).to.equal("sixth");
414
- chai.expect(stash2.txns[6].props.description).to.equal("seventh");
415
- chai.expect(stash2.txns[0].id).to.equals("0x100000000");
416
- chai.expect(stash2.txns[1].id).to.equals("0x100000001");
417
- chai.expect(stash2.txns[2].id).to.equals("0x100000002");
418
- chai.expect(stash2.txns[3].id).to.equals("0x100000003");
419
- chai.expect(stash2.txns[4].id).to.equals("0x100000004");
420
- chai.expect(stash2.txns[5].id).to.equals("0x100000005");
421
- chai.expect(stash2.txns[6].id).to.equals("0x100000006");
422
- const stashes = StashManager.getStashes(b1);
423
- chai.expect(stashes).to.have.lengthOf(2);
424
- chai.expect(stashes[0].description).to.equals("stash test 2");
425
- chai.expect(stashes[1].description).to.equals("stash test 1");
426
- chai.expect(stashes[0]).to.deep.equal(stash2);
427
- chai.expect(stashes[1]).to.deep.equal(stash1);
428
- StashManager.dropAllStashes(b1);
429
- chai.expect(StashManager.getStashes(b1)).to.have.lengthOf(0);
430
- });
431
- it("recursively calling withIndirectTxnMode()", async () => {
432
- const b1 = await testIModel.openBriefcase();
433
- chai.expect(b1.txns.getMode()).to.equal("direct");
434
- b1.txns.withIndirectTxnMode(() => {
435
- chai.expect(b1.txns.getMode()).to.equal("indirect");
293
+ it("direct / indirect", async () => {
294
+ const b1 = await testIModel.openBriefcase();
295
+ const directElId = await testIModel.insertElement(b1);
296
+ const indirectElId = await testIModel.insertElement(b1, true);
297
+ chai.expect(directElId).to.not.be.undefined;
298
+ chai.expect(indirectElId).to.not.be.undefined;
299
+ b1.saveChanges({ description: "insert element 1 direct and 1 indirect" });
300
+ const txn = b1.txns.getLastSavedTxnProps();
301
+ chai.assert.isDefined(txn);
302
+ if (txn) {
303
+ let checkCount = 0;
304
+ const reader = SqliteChangesetReader.openTxn({ txnId: txn?.id, db: b1 });
305
+ while (reader.step()) {
306
+ if (reader.primaryKeyValues.length === 0)
307
+ continue;
308
+ if (reader.tableName !== "bis_Element")
309
+ continue;
310
+ const iid = reader.primaryKeyValues[0];
311
+ if (iid === directElId) {
312
+ chai.expect(reader.isIndirect).to.be.false;
313
+ }
314
+ if (iid === indirectElId) {
315
+ chai.expect(reader.isIndirect).to.be.true;
316
+ }
317
+ checkCount++;
318
+ }
319
+ chai.expect(checkCount).to.be.equals(2);
320
+ }
321
+ await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
322
+ chai.expect(b1.txns.isUndoPossible).is.false;
323
+ chai.expect(b1.txns.isRedoPossible).is.false;
324
+ const lastTxn = b1.txns.getLastSavedTxnProps();
325
+ chai.assert.isUndefined(lastTxn);
326
+ });
327
+ it("rebase handler", async () => {
328
+ const b1 = await testIModel.openBriefcase();
329
+ const b2 = await testIModel.openBriefcase();
330
+ const e1 = await testIModel.insertElement(b1);
331
+ const e2 = await testIModel.insertElement(b1, true);
332
+ b1.saveChanges();
333
+ await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
334
+ await b2.pullChanges();
335
+ await testIModel.updateElement(b1, e1);
336
+ await testIModel.updateElement(b1, e2, true);
337
+ b1.saveChanges();
338
+ await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
339
+ await testIModel.insertElement(b2);
340
+ await testIModel.insertElement(b2, true);
341
+ b2.saveChanges("first change");
342
+ await testIModel.insertElement(b2);
343
+ await testIModel.insertElement(b2, true);
344
+ b2.saveChanges("second change");
345
+ await testIModel.insertElement(b2);
346
+ await testIModel.insertElement(b2, true);
347
+ b2.saveChanges("third change");
348
+ b2.txns.rebaser.setCustomHandler({
349
+ shouldReinstate: (_txn) => {
350
+ return true;
351
+ },
352
+ recompute: async (_txn) => {
353
+ await testIModel.insertElement(b2);
354
+ await testIModel.insertElement(b2, true);
355
+ },
356
+ });
357
+ await b1.pullChanges();
358
+ });
359
+ it("stash & drop", async () => {
360
+ const b1 = await testIModel.openBriefcase();
361
+ const e1 = await testIModel.insertElement(b1);
362
+ b1.saveChanges();
363
+ await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
364
+ const e2 = await testIModel.insertElement(b1);
365
+ b1.saveChanges();
366
+ await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
367
+ await testIModel.insertElement(b1);
368
+ b1.saveChanges(`first`);
369
+ await testIModel.updateElement(b1, e1);
370
+ b1.saveChanges(`second`);
371
+ await testIModel.deleteElement(b1, e2);
372
+ b1.saveChanges(`third`);
373
+ await testIModel.insertElement(b1);
374
+ b1.saveChanges(`fourth`);
375
+ const stash1 = await StashManager.stash({ db: b1, description: "stash test 1" });
376
+ chai.expect(stash1).to.exist;
377
+ chai.assert(Guid.isGuid(stash1.id));
378
+ chai.expect(stash1.description).to.equals("stash test 1");
379
+ chai.expect(stash1.briefcaseId).equals(b1.briefcaseId);
380
+ chai.expect(stash1.iModelId).to.equals(b1.iModelId);
381
+ chai.expect(stash1.timestamp).to.exist;
382
+ chai.expect(stash1.description).to.exist;
383
+ chai.expect(stash1.hash).length(64);
384
+ chai.expect(stash1.parentChangeset).to.exist;
385
+ chai.expect(stash1.idSequences.element).to.equals("0x30000000004");
386
+ chai.expect(stash1.idSequences.instance).to.equals("0x30000000000");
387
+ chai.expect(stash1.acquiredLocks).equals(4);
388
+ chai.expect(stash1.txns).to.exist;
389
+ chai.expect(stash1.txns).to.have.lengthOf(4);
390
+ chai.expect(stash1.txns[0].props.description).to.equal("first");
391
+ chai.expect(stash1.txns[1].props.description).to.equal("second");
392
+ chai.expect(stash1.txns[2].props.description).to.equal("third");
393
+ chai.expect(stash1.txns[3].props.description).to.equal("fourth");
394
+ chai.expect(stash1.txns[0].id).to.equals("0x100000000");
395
+ chai.expect(stash1.txns[1].id).to.equals("0x100000001");
396
+ chai.expect(stash1.txns[2].id).to.equals("0x100000002");
397
+ chai.expect(stash1.txns[3].id).to.equals("0x100000003");
398
+ await testIModel.insertElement(b1);
399
+ b1.saveChanges(`fifth`);
400
+ await testIModel.updateElement(b1, e1);
401
+ b1.saveChanges(`sixth`);
402
+ await testIModel.insertElement(b1);
403
+ b1.saveChanges(`seventh`);
404
+ const stash2 = await StashManager.stash({ db: b1, description: "stash test 2" });
405
+ chai.expect(stash2).to.exist;
406
+ chai.expect(stash2.description).to.equals("stash test 2");
407
+ chai.expect(stash2.hash).length(64);
408
+ chai.expect(stash2.parentChangeset).to.exist;
409
+ chai.expect(stash2.idSequences.element).to.equals("0x30000000006");
410
+ chai.expect(stash2.idSequences.instance).to.equals("0x30000000000");
411
+ chai.expect(stash2.acquiredLocks).equals(4);
412
+ chai.expect(stash2.txns).to.exist;
413
+ chai.expect(stash2.txns).to.have.lengthOf(7);
414
+ chai.expect(stash2.txns[0].props.description).to.equal("first");
415
+ chai.expect(stash2.txns[1].props.description).to.equal("second");
416
+ chai.expect(stash2.txns[2].props.description).to.equal("third");
417
+ chai.expect(stash2.txns[3].props.description).to.equal("fourth");
418
+ chai.expect(stash2.txns[4].props.description).to.equal("fifth");
419
+ chai.expect(stash2.txns[5].props.description).to.equal("sixth");
420
+ chai.expect(stash2.txns[6].props.description).to.equal("seventh");
421
+ chai.expect(stash2.txns[0].id).to.equals("0x100000000");
422
+ chai.expect(stash2.txns[1].id).to.equals("0x100000001");
423
+ chai.expect(stash2.txns[2].id).to.equals("0x100000002");
424
+ chai.expect(stash2.txns[3].id).to.equals("0x100000003");
425
+ chai.expect(stash2.txns[4].id).to.equals("0x100000004");
426
+ chai.expect(stash2.txns[5].id).to.equals("0x100000005");
427
+ chai.expect(stash2.txns[6].id).to.equals("0x100000006");
428
+ const stashes = StashManager.getStashes(b1);
429
+ chai.expect(stashes).to.have.lengthOf(2);
430
+ chai.expect(stashes[0].description).to.equals("stash test 2");
431
+ chai.expect(stashes[1].description).to.equals("stash test 1");
432
+ chai.expect(stashes[0]).to.deep.equal(stash2);
433
+ chai.expect(stashes[1]).to.deep.equal(stash1);
434
+ StashManager.dropAllStashes(b1);
435
+ chai.expect(StashManager.getStashes(b1)).to.have.lengthOf(0);
436
436
  });
437
- chai.expect(b1.txns.getMode()).to.equal("direct");
438
- b1.txns.withIndirectTxnMode(() => {
439
- chai.expect(b1.txns.getMode()).to.equal("indirect");
437
+ it("recursively calling withIndirectTxnMode()", async () => {
438
+ const b1 = await testIModel.openBriefcase();
439
+ chai.expect(b1.txns.getMode()).to.equal("direct");
440
+ b1.txns.withIndirectTxnMode(() => {
441
+ chai.expect(b1.txns.getMode()).to.equal("indirect");
442
+ });
443
+ chai.expect(b1.txns.getMode()).to.equal("direct");
440
444
  b1.txns.withIndirectTxnMode(() => {
441
445
  chai.expect(b1.txns.getMode()).to.equal("indirect");
442
446
  b1.txns.withIndirectTxnMode(() => {
443
447
  chai.expect(b1.txns.getMode()).to.equal("indirect");
444
448
  b1.txns.withIndirectTxnMode(() => {
445
449
  chai.expect(b1.txns.getMode()).to.equal("indirect");
450
+ b1.txns.withIndirectTxnMode(() => {
451
+ chai.expect(b1.txns.getMode()).to.equal("indirect");
452
+ });
453
+ chai.expect(b1.txns.getMode()).to.equal("indirect");
446
454
  });
447
455
  chai.expect(b1.txns.getMode()).to.equal("indirect");
448
456
  });
449
- chai.expect(b1.txns.getMode()).to.equal("indirect");
450
457
  });
458
+ chai.expect(b1.txns.getMode()).to.equal("direct");
459
+ chai.expect(() => b1.txns.withIndirectTxnMode(() => {
460
+ chai.expect(b1.txns.getMode()).to.equal("indirect");
461
+ throw new Error("Test error");
462
+ })).to.throw();
463
+ chai.expect(b1.txns.getMode()).to.equal("direct");
451
464
  });
452
- chai.expect(b1.txns.getMode()).to.equal("direct");
453
- chai.expect(() => b1.txns.withIndirectTxnMode(() => {
454
- chai.expect(b1.txns.getMode()).to.equal("indirect");
455
- throw new Error("Test error");
456
- })).to.throw();
457
- chai.expect(b1.txns.getMode()).to.equal("direct");
458
- });
459
- it("should fail to importSchemas() & importSchemaStrings() in indirect scope", async () => {
460
- const b1 = await testIModel.openBriefcase();
461
- const schema = `<?xml version="1.0" encoding="UTF-8"?>
465
+ it("should fail to importSchemas() & importSchemaStrings() in indirect scope", async () => {
466
+ const b1 = await testIModel.openBriefcase();
467
+ const schema = `<?xml version="1.0" encoding="UTF-8"?>
462
468
  <ECSchema schemaName="MySchema" alias="ms1" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
463
469
  <ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
464
470
  <ECEntityClass typeName="my_class">
@@ -466,68 +472,66 @@ describe("rebase changes & stashing api", function () {
466
472
  <ECProperty propertyName="prop1" typeName="string" />
467
473
  </ECEntityClass>
468
474
  </ECSchema>`;
469
- const schemaFile = path.join(KnownTestLocations.outputDir, "MySchema.01.00.00.ecschema.xml");
470
- if (existsSync(schemaFile)) {
471
- unlinkSync(schemaFile);
472
- }
473
- writeFileSync(schemaFile, schema, { encoding: "utf8" });
474
- await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
475
- await b1.importSchemas([schema]);
476
- })).to.be.rejectedWith("Cannot import schemas while in an indirect change scope");
477
- b1.abandonChanges();
478
- await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
475
+ const schemaFile = path.join(KnownTestLocations.outputDir, "MySchema.01.00.00.ecschema.xml");
476
+ if (existsSync(schemaFile)) {
477
+ unlinkSync(schemaFile);
478
+ }
479
+ writeFileSync(schemaFile, schema, { encoding: "utf8" });
480
+ await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
481
+ await b1.importSchemas([schema]);
482
+ })).to.be.rejectedWith("Cannot import schemas while in an indirect change scope");
483
+ b1.abandonChanges();
484
+ await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
485
+ await b1.importSchemaStrings([schema]);
486
+ })).to.be.rejectedWith("Cannot import schemas while in an indirect change scope");
487
+ b1.abandonChanges();
479
488
  await b1.importSchemaStrings([schema]);
480
- })).to.be.rejectedWith("Cannot import schemas while in an indirect change scope");
481
- b1.abandonChanges();
482
- await b1.importSchemaStrings([schema]);
483
- b1.saveChanges();
484
- await b1.pushChanges({ description: "import schema" });
485
- });
486
- it("should fail to saveChanges() & pushChanges() in indirect scope", async () => {
487
- const b1 = await testIModel.openBriefcase();
488
- await testIModel.insertElement(b1);
489
- await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
490
489
  b1.saveChanges();
491
- })).to.be.rejectedWith("Cannot save changes while in an indirect change scope");
492
- chai.expect(() => b1.txns.withIndirectTxnMode(() => {
490
+ await b1.pushChanges({ description: "import schema" });
491
+ });
492
+ it("should fail to saveChanges() & pushChanges() in indirect scope", async () => {
493
+ const b1 = await testIModel.openBriefcase();
494
+ await testIModel.insertElement(b1);
495
+ await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
496
+ b1.saveChanges();
497
+ })).to.be.rejectedWith("Cannot save changes while in an indirect change scope");
498
+ chai.expect(() => b1.txns.withIndirectTxnMode(() => {
499
+ b1.saveChanges();
500
+ })).to.be.throws("Cannot save changes while in an indirect change scope");
493
501
  b1.saveChanges();
494
- })).to.be.throws("Cannot save changes while in an indirect change scope");
495
- b1.saveChanges();
496
- await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
502
+ await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
503
+ await b1.pushChanges({ description: "test" });
504
+ })).to.be.rejectedWith("Cannot pull and apply changeset while in an indirect change scope");
497
505
  await b1.pushChanges({ description: "test" });
498
- })).to.be.rejectedWith("Cannot pull and apply changeset while in an indirect change scope");
499
- await b1.pushChanges({ description: "test" });
500
- });
501
- it("should fail to saveFileProperty/deleteFileProperty in indirect scope", async () => {
502
- // pull/push/saveFileProperty/deleteFileProperty should be called inside indirect change scope.
503
- const b1 = await testIModel.openBriefcase();
504
- b1.saveFileProperty({ namespace: "test", name: "test" }, "Hello, World");
505
- await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
506
- b1.saveFileProperty({ namespace: "test", name: "test" }, "This should fail 1");
507
- })).to.be.rejectedWith("Cannot save file property while in an indirect change scope");
508
- chai.expect(b1.queryFilePropertyString({ namespace: "test", name: "test" })).to.equal("Hello, World");
509
- await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
510
- b1.deleteFileProperty({ namespace: "test", name: "test" });
511
- })).to.be.rejectedWith("Cannot delete file property while in an indirect change scope");
512
- chai.expect(b1.queryFilePropertyString({ namespace: "test", name: "test" })).to.equal("Hello, World");
513
- chai.expect(() => b1.txns.withIndirectTxnMode(() => {
514
- b1.saveFileProperty({ namespace: "test", name: "test" }, "This should fail 2");
515
- })).to.be.throws("Cannot save file property while in an indirect change scope");
516
- chai.expect(b1.queryFilePropertyString({ namespace: "test", name: "test" })).to.equal("Hello, World");
517
- chai.expect(() => b1.txns.withIndirectTxnMode(() => {
518
- b1.deleteFileProperty({ namespace: "test", name: "test" });
519
- })).to.be.throws("Cannot delete file property while in an indirect change scope");
520
- b1.saveChanges();
521
- });
522
- it("recursively calling withIndirectTxnModeAsync()", async () => {
523
- const b1 = await testIModel.openBriefcase();
524
- chai.expect(b1.txns.getMode()).to.equal("direct");
525
- await b1.txns.withIndirectTxnModeAsync(async () => {
526
- chai.expect(b1.txns.getMode()).to.equal("indirect");
527
506
  });
528
- chai.expect(b1.txns.getMode()).to.equal("direct");
529
- await b1.txns.withIndirectTxnModeAsync(async () => {
530
- chai.expect(b1.txns.getMode()).to.equal("indirect");
507
+ it("should fail to saveFileProperty/deleteFileProperty in indirect scope", async () => {
508
+ // pull/push/saveFileProperty/deleteFileProperty should be called inside indirect change scope.
509
+ const b1 = await testIModel.openBriefcase();
510
+ b1.saveFileProperty({ namespace: "test", name: "test" }, "Hello, World");
511
+ await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
512
+ b1.saveFileProperty({ namespace: "test", name: "test" }, "This should fail 1");
513
+ })).to.be.rejectedWith("Cannot save file property while in an indirect change scope");
514
+ chai.expect(b1.queryFilePropertyString({ namespace: "test", name: "test" })).to.equal("Hello, World");
515
+ await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
516
+ b1.deleteFileProperty({ namespace: "test", name: "test" });
517
+ })).to.be.rejectedWith("Cannot delete file property while in an indirect change scope");
518
+ chai.expect(b1.queryFilePropertyString({ namespace: "test", name: "test" })).to.equal("Hello, World");
519
+ chai.expect(() => b1.txns.withIndirectTxnMode(() => {
520
+ b1.saveFileProperty({ namespace: "test", name: "test" }, "This should fail 2");
521
+ })).to.be.throws("Cannot save file property while in an indirect change scope");
522
+ chai.expect(b1.queryFilePropertyString({ namespace: "test", name: "test" })).to.equal("Hello, World");
523
+ chai.expect(() => b1.txns.withIndirectTxnMode(() => {
524
+ b1.deleteFileProperty({ namespace: "test", name: "test" });
525
+ })).to.be.throws("Cannot delete file property while in an indirect change scope");
526
+ b1.saveChanges();
527
+ });
528
+ it("recursively calling withIndirectTxnModeAsync()", async () => {
529
+ const b1 = await testIModel.openBriefcase();
530
+ chai.expect(b1.txns.getMode()).to.equal("direct");
531
+ await b1.txns.withIndirectTxnModeAsync(async () => {
532
+ chai.expect(b1.txns.getMode()).to.equal("indirect");
533
+ });
534
+ chai.expect(b1.txns.getMode()).to.equal("direct");
531
535
  await b1.txns.withIndirectTxnModeAsync(async () => {
532
536
  chai.expect(b1.txns.getMode()).to.equal("indirect");
533
537
  await b1.txns.withIndirectTxnModeAsync(async () => {
@@ -536,216 +540,218 @@ describe("rebase changes & stashing api", function () {
536
540
  chai.expect(b1.txns.getMode()).to.equal("indirect");
537
541
  await b1.txns.withIndirectTxnModeAsync(async () => {
538
542
  chai.expect(b1.txns.getMode()).to.equal("indirect");
543
+ await b1.txns.withIndirectTxnModeAsync(async () => {
544
+ chai.expect(b1.txns.getMode()).to.equal("indirect");
545
+ });
546
+ chai.expect(b1.txns.getMode()).to.equal("indirect");
539
547
  });
540
548
  chai.expect(b1.txns.getMode()).to.equal("indirect");
541
549
  });
550
+ });
551
+ b1.txns.withIndirectTxnMode(() => {
542
552
  chai.expect(b1.txns.getMode()).to.equal("indirect");
543
553
  });
544
554
  });
545
- b1.txns.withIndirectTxnMode(() => {
555
+ chai.expect(b1.txns.getMode()).to.equal("direct");
556
+ await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
546
557
  chai.expect(b1.txns.getMode()).to.equal("indirect");
547
- });
558
+ throw new Error("Test error");
559
+ })).rejectedWith(Error);
560
+ chai.expect(b1.txns.getMode()).to.equal("direct");
548
561
  });
549
- chai.expect(b1.txns.getMode()).to.equal("direct");
550
- await chai.expect(b1.txns.withIndirectTxnModeAsync(async () => {
551
- chai.expect(b1.txns.getMode()).to.equal("indirect");
552
- throw new Error("Test error");
553
- })).rejectedWith(Error);
554
- chai.expect(b1.txns.getMode()).to.equal("direct");
555
- });
556
- it("should restore mutually exclusive stashes", async () => {
557
- const b1 = await testIModel.openBriefcase();
558
- // stash 1
559
- const e1 = await testIModel.insertElement(b1);
560
- chai.expect(e1).to.exist;
561
- b1.saveChanges("first");
562
- const stash1 = await StashManager.stash({ db: b1, description: "stash test 1", discardLocalChanges: true, retainLocks: true });
563
- chai.expect(stash1).to.exist;
564
- chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
565
- chai.expect(b1.txns.isUndoPossible).to.be.false;
566
- chai.expect(b1.txns.isRedoPossible).to.be.false;
567
- // stash 2
568
- const e2 = await testIModel.insertElement(b1);
569
- chai.expect(e2).to.exist;
570
- b1.saveChanges("second");
571
- const stash2 = await StashManager.stash({ db: b1, description: "stash test 2", discardLocalChanges: true, retainLocks: true });
572
- chai.expect(stash2).to.exist;
573
- chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
574
- chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
575
- chai.expect(b1.txns.isUndoPossible).to.be.false;
576
- chai.expect(b1.txns.isRedoPossible).to.be.false;
577
- // stash 3
578
- const e3 = await testIModel.insertElement(b1);
579
- chai.expect(e3).to.exist;
580
- b1.saveChanges("third");
581
- const stash3 = await StashManager.stash({ db: b1, description: "stash test 3", discardLocalChanges: true, retainLocks: true });
582
- chai.expect(stash3).to.exist;
583
- chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
584
- chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
585
- chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
586
- chai.expect(b1.txns.isUndoPossible).to.be.false;
587
- chai.expect(b1.txns.isRedoPossible).to.be.false;
588
- chai.expect(e1).not.equals(e2);
589
- chai.expect(e1).not.equals(e3);
590
- chai.expect(e2).not.equals(e3);
591
- const stashes = StashManager.getStashes(b1);
592
- chai.expect(stashes).to.have.lengthOf(3);
593
- chai.expect(stashes[0].description).to.equals("stash test 3");
594
- chai.expect(stashes[1].description).to.equals("stash test 2");
595
- chai.expect(stashes[2].description).to.equals("stash test 1");
596
- // restore stash 1
597
- await StashManager.restore({ db: b1, stash: stash1 });
598
- chai.expect(b1.elements.tryGetElement(e1)).to.exist;
599
- chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
600
- chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
601
- // restore stash 2
602
- await StashManager.restore({ db: b1, stash: stash2 });
603
- chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
604
- chai.expect(b1.elements.tryGetElement(e2)).to.exist;
605
- chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
606
- // restore stash 3
607
- await StashManager.restore({ db: b1, stash: stash3 });
608
- chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
609
- chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
610
- chai.expect(b1.elements.tryGetElement(e3)).to.exist;
611
- });
612
- it("should restore stash in any order", async () => {
613
- const b1 = await testIModel.openBriefcase();
614
- // stash 1
615
- const e1 = await testIModel.insertElement(b1);
616
- chai.expect(e1).to.exist;
617
- b1.saveChanges("first");
618
- // do not discard local changes
619
- const stash1 = await StashManager.stash({ db: b1, description: "stash test 1" });
620
- chai.expect(stash1).to.exist;
621
- chai.expect(b1.elements.tryGetElement(e1)).to.exist;
622
- chai.expect(b1.txns.isUndoPossible).to.be.true;
623
- chai.expect(b1.txns.isRedoPossible).to.be.false;
624
- // stash 2
625
- const e2 = await testIModel.insertElement(b1);
626
- chai.expect(e2).to.exist;
627
- b1.saveChanges("second");
628
- // do not discard local changes
629
- const stash2 = await StashManager.stash({ db: b1, description: "stash test 2" });
630
- chai.expect(stash2).to.exist;
631
- chai.expect(b1.elements.tryGetElement(e1)).to.exist;
632
- chai.expect(b1.elements.tryGetElement(e2)).to.exist;
633
- chai.expect(b1.txns.isUndoPossible).to.be.true;
634
- chai.expect(b1.txns.isRedoPossible).to.be.false;
635
- // stash 3
636
- const e3 = await testIModel.insertElement(b1);
637
- chai.expect(e3).to.exist;
638
- b1.saveChanges("third");
639
- // do not discard local changes
640
- const stash3 = await StashManager.stash({ db: b1, description: "stash test 3" });
641
- chai.expect(stash3).to.exist;
642
- chai.expect(b1.elements.tryGetElement(e1)).to.exist;
643
- chai.expect(b1.elements.tryGetElement(e2)).to.exist;
644
- chai.expect(b1.elements.tryGetElement(e3)).to.exist;
645
- chai.expect(b1.txns.isUndoPossible).to.be.true;
646
- chai.expect(b1.txns.isRedoPossible).to.be.false;
647
- const stashes = StashManager.getStashes(b1);
648
- chai.expect(stashes).to.have.lengthOf(3);
649
- chai.expect(stashes[0].description).to.equals("stash test 3");
650
- chai.expect(stashes[1].description).to.equals("stash test 2");
651
- chai.expect(stashes[2].description).to.equals("stash test 1");
652
- await b1.discardChanges({ retainLocks: true });
653
- chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
654
- chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
655
- chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
656
- chai.expect(b1.txns.isUndoPossible).to.be.false;
657
- chai.expect(b1.txns.isRedoPossible).to.be.false;
658
- // restore stash 1
659
- await StashManager.restore({ db: b1, stash: stash1 });
660
- chai.expect(b1.elements.tryGetElement(e1)).to.exist;
661
- chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
662
- chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
663
- // restore stash 2
664
- await StashManager.restore({ db: b1, stash: stash2 });
665
- chai.expect(b1.elements.tryGetElement(e1)).to.exist;
666
- chai.expect(b1.elements.tryGetElement(e2)).to.exist;
667
- chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
668
- // restore stash 3
669
- await StashManager.restore({ db: b1, stash: stash3 });
670
- chai.expect(b1.elements.tryGetElement(e1)).to.exist;
671
- chai.expect(b1.elements.tryGetElement(e2)).to.exist;
672
- chai.expect(b1.elements.tryGetElement(e3)).to.exist;
673
- });
674
- it("should restore stash when briefcase has advanced to latest changeset", async () => {
675
- const b1 = await testIModel.openBriefcase();
676
- const b2 = await testIModel.openBriefcase();
677
- chai.expect(b1.changeset.index).to.equals(2);
678
- chai.expect(b2.changeset.index).to.equals(2);
679
- const e1 = await testIModel.insertElement(b1);
680
- chai.expect(e1).to.exist;
681
- b1.saveChanges();
682
- await b1.pushChanges({ description: `${e1} inserted` });
683
- chai.expect(b1.changeset.index).to.equals(3);
684
- const e2 = await testIModel.insertElement(b2);
685
- chai.expect(e2).to.exist;
686
- b2.saveChanges();
687
- chai.expect(b2.elements.tryGetElement(e1)).to.undefined;
688
- chai.expect(b2.elements.tryGetElement(e2)).to.exist;
689
- const b2Stash1 = await StashManager.stash({ db: b2, description: "stash test 1", discardLocalChanges: true });
690
- chai.expect(b2Stash1.parentChangeset.index).to.equals(2);
691
- chai.expect(b2.elements.tryGetElement(e1)).to.undefined;
692
- chai.expect(b2.elements.tryGetElement(e2)).to.undefined;
693
- await b2.pullChanges();
694
- chai.expect(b2.changeset.index).to.equals(3);
695
- chai.expect(b2.elements.tryGetElement(e1)).to.exist;
696
- chai.expect(b2.elements.tryGetElement(e2)).to.undefined;
697
- // stash restore should downgrade briefcase to older changeset as specified in stash
698
- await StashManager.restore({ db: b2, stash: b2Stash1 });
699
- chai.expect(b2.changeset.index).to.equals(2);
700
- chai.expect(b2.elements.tryGetElement(e1)).to.undefined;
701
- chai.expect(b2.elements.tryGetElement(e2)).to.exist;
702
- await b2.pullChanges();
703
- chai.expect(b2.changeset.index).to.equals(3);
704
- chai.expect(b2.elements.tryGetElement(e1)).to.exist;
705
- chai.expect(b2.elements.tryGetElement(e2)).to.exist;
706
- await b2.pushChanges({ description: "test" });
707
- chai.expect(b2.changeset.index).to.equals(4);
708
- });
709
- it("restore stash that has element changed by another briefcase", async () => {
710
- const b1 = await testIModel.openBriefcase();
711
- const b2 = await testIModel.openBriefcase();
712
- chai.expect(b1.changeset.index).to.equals(2);
713
- chai.expect(b2.changeset.index).to.equals(2);
714
- const e1 = await testIModel.insertElement(b1);
715
- chai.expect(e1).to.exist;
716
- b1.saveChanges();
717
- await b1.pushChanges({ description: `${e1} inserted` });
718
- chai.expect(b1.changeset.index).to.equals(3);
719
- await b2.pullChanges();
720
- chai.expect(b2.changeset.index).to.equals(3);
721
- await testIModel.updateElement(b2, e1);
722
- b2.saveChanges();
723
- chai.expect(b2.locks.holdsExclusiveLock(e1)).to.be.true;
724
- const b2Stash1 = await StashManager.stash({ db: b2, description: "stash test 1", discardLocalChanges: true });
725
- chai.expect(b2Stash1.parentChangeset.index).to.equals(3);
726
- chai.expect(b2.locks.holdsExclusiveLock(e1)).to.be.false;
727
- // stash release lock so b2 should have released lock and b1 should be able to update.
728
- await testIModel.updateElement(b1, e1);
729
- b1.saveChanges();
730
- // restore stash should fail because of lock not obtained on e1
731
- await chai.expect(StashManager.restore({ db: b2, stash: b2Stash1 })).to.be.rejectedWith("exclusive lock is already held");
732
- // push b1 changes to release lock
733
- await b1.pushChanges({ description: `${e1} inserted` });
734
- // restore stash should fail because pull is required to obtain lock
735
- await chai.expect(StashManager.restore({ db: b2, stash: b2Stash1 })).to.be.rejectedWith("pull is required to obtain lock");
736
- await b2.pullChanges();
737
- chai.expect(b2.changeset.index).to.equals(4);
738
- const elBefore = b2.elements.tryGetElementProps(e1);
739
- chai.expect(elBefore.prop1).to.equals("3");
740
- // restore stash should succeed as now it can obtain lock
741
- await StashManager.restore({ db: b2, stash: b2Stash1 });
742
- const elAfter = b2.elements.tryGetElementProps(e1);
743
- chai.expect(elAfter.prop1).to.equals("2");
744
- await b2.pushChanges({ description: `${e1} updated` });
745
- });
746
- it("schema change should not be stashed", async () => {
747
- const b1 = await testIModel.openBriefcase();
748
- const schema1 = `<?xml version="1.0" encoding="UTF-8"?>
562
+ it("should restore mutually exclusive stashes", async () => {
563
+ const b1 = await testIModel.openBriefcase();
564
+ // stash 1
565
+ const e1 = await testIModel.insertElement(b1);
566
+ chai.expect(e1).to.exist;
567
+ b1.saveChanges("first");
568
+ const stash1 = await StashManager.stash({ db: b1, description: "stash test 1", discardLocalChanges: true, retainLocks: true });
569
+ chai.expect(stash1).to.exist;
570
+ chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
571
+ chai.expect(b1.txns.isUndoPossible).to.be.false;
572
+ chai.expect(b1.txns.isRedoPossible).to.be.false;
573
+ // stash 2
574
+ const e2 = await testIModel.insertElement(b1);
575
+ chai.expect(e2).to.exist;
576
+ b1.saveChanges("second");
577
+ const stash2 = await StashManager.stash({ db: b1, description: "stash test 2", discardLocalChanges: true, retainLocks: true });
578
+ chai.expect(stash2).to.exist;
579
+ chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
580
+ chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
581
+ chai.expect(b1.txns.isUndoPossible).to.be.false;
582
+ chai.expect(b1.txns.isRedoPossible).to.be.false;
583
+ // stash 3
584
+ const e3 = await testIModel.insertElement(b1);
585
+ chai.expect(e3).to.exist;
586
+ b1.saveChanges("third");
587
+ const stash3 = await StashManager.stash({ db: b1, description: "stash test 3", discardLocalChanges: true, retainLocks: true });
588
+ chai.expect(stash3).to.exist;
589
+ chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
590
+ chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
591
+ chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
592
+ chai.expect(b1.txns.isUndoPossible).to.be.false;
593
+ chai.expect(b1.txns.isRedoPossible).to.be.false;
594
+ chai.expect(e1).not.equals(e2);
595
+ chai.expect(e1).not.equals(e3);
596
+ chai.expect(e2).not.equals(e3);
597
+ const stashes = StashManager.getStashes(b1);
598
+ chai.expect(stashes).to.have.lengthOf(3);
599
+ chai.expect(stashes[0].description).to.equals("stash test 3");
600
+ chai.expect(stashes[1].description).to.equals("stash test 2");
601
+ chai.expect(stashes[2].description).to.equals("stash test 1");
602
+ // restore stash 1
603
+ await StashManager.restore({ db: b1, stash: stash1 });
604
+ chai.expect(b1.elements.tryGetElement(e1)).to.exist;
605
+ chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
606
+ chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
607
+ // restore stash 2
608
+ await StashManager.restore({ db: b1, stash: stash2 });
609
+ chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
610
+ chai.expect(b1.elements.tryGetElement(e2)).to.exist;
611
+ chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
612
+ // restore stash 3
613
+ await StashManager.restore({ db: b1, stash: stash3 });
614
+ chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
615
+ chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
616
+ chai.expect(b1.elements.tryGetElement(e3)).to.exist;
617
+ });
618
+ it("should restore stash in any order", async () => {
619
+ const b1 = await testIModel.openBriefcase();
620
+ // stash 1
621
+ const e1 = await testIModel.insertElement(b1);
622
+ chai.expect(e1).to.exist;
623
+ b1.saveChanges("first");
624
+ // do not discard local changes
625
+ const stash1 = await StashManager.stash({ db: b1, description: "stash test 1" });
626
+ chai.expect(stash1).to.exist;
627
+ chai.expect(b1.elements.tryGetElement(e1)).to.exist;
628
+ chai.expect(b1.txns.isUndoPossible).to.be.true;
629
+ chai.expect(b1.txns.isRedoPossible).to.be.false;
630
+ // stash 2
631
+ const e2 = await testIModel.insertElement(b1);
632
+ chai.expect(e2).to.exist;
633
+ b1.saveChanges("second");
634
+ // do not discard local changes
635
+ const stash2 = await StashManager.stash({ db: b1, description: "stash test 2" });
636
+ chai.expect(stash2).to.exist;
637
+ chai.expect(b1.elements.tryGetElement(e1)).to.exist;
638
+ chai.expect(b1.elements.tryGetElement(e2)).to.exist;
639
+ chai.expect(b1.txns.isUndoPossible).to.be.true;
640
+ chai.expect(b1.txns.isRedoPossible).to.be.false;
641
+ // stash 3
642
+ const e3 = await testIModel.insertElement(b1);
643
+ chai.expect(e3).to.exist;
644
+ b1.saveChanges("third");
645
+ // do not discard local changes
646
+ const stash3 = await StashManager.stash({ db: b1, description: "stash test 3" });
647
+ chai.expect(stash3).to.exist;
648
+ chai.expect(b1.elements.tryGetElement(e1)).to.exist;
649
+ chai.expect(b1.elements.tryGetElement(e2)).to.exist;
650
+ chai.expect(b1.elements.tryGetElement(e3)).to.exist;
651
+ chai.expect(b1.txns.isUndoPossible).to.be.true;
652
+ chai.expect(b1.txns.isRedoPossible).to.be.false;
653
+ const stashes = StashManager.getStashes(b1);
654
+ chai.expect(stashes).to.have.lengthOf(3);
655
+ chai.expect(stashes[0].description).to.equals("stash test 3");
656
+ chai.expect(stashes[1].description).to.equals("stash test 2");
657
+ chai.expect(stashes[2].description).to.equals("stash test 1");
658
+ await b1.discardChanges({ retainLocks: true });
659
+ chai.expect(b1.elements.tryGetElement(e1)).to.undefined;
660
+ chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
661
+ chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
662
+ chai.expect(b1.txns.isUndoPossible).to.be.false;
663
+ chai.expect(b1.txns.isRedoPossible).to.be.false;
664
+ // restore stash 1
665
+ await StashManager.restore({ db: b1, stash: stash1 });
666
+ chai.expect(b1.elements.tryGetElement(e1)).to.exist;
667
+ chai.expect(b1.elements.tryGetElement(e2)).to.undefined;
668
+ chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
669
+ // restore stash 2
670
+ await StashManager.restore({ db: b1, stash: stash2 });
671
+ chai.expect(b1.elements.tryGetElement(e1)).to.exist;
672
+ chai.expect(b1.elements.tryGetElement(e2)).to.exist;
673
+ chai.expect(b1.elements.tryGetElement(e3)).to.undefined;
674
+ // restore stash 3
675
+ await StashManager.restore({ db: b1, stash: stash3 });
676
+ chai.expect(b1.elements.tryGetElement(e1)).to.exist;
677
+ chai.expect(b1.elements.tryGetElement(e2)).to.exist;
678
+ chai.expect(b1.elements.tryGetElement(e3)).to.exist;
679
+ });
680
+ it("should restore stash when briefcase has advanced to latest changeset", async () => {
681
+ const b1 = await testIModel.openBriefcase();
682
+ const b2 = await testIModel.openBriefcase();
683
+ chai.expect(b1.changeset.index).to.equals(2);
684
+ chai.expect(b2.changeset.index).to.equals(2);
685
+ const e1 = await testIModel.insertElement(b1);
686
+ chai.expect(e1).to.exist;
687
+ b1.saveChanges();
688
+ await b1.pushChanges({ description: `${e1} inserted` });
689
+ chai.expect(b1.changeset.index).to.equals(3);
690
+ const e2 = await testIModel.insertElement(b2);
691
+ chai.expect(e2).to.exist;
692
+ b2.saveChanges();
693
+ chai.expect(b2.elements.tryGetElement(e1)).to.undefined;
694
+ chai.expect(b2.elements.tryGetElement(e2)).to.exist;
695
+ const b2Stash1 = await StashManager.stash({ db: b2, description: "stash test 1", discardLocalChanges: true });
696
+ chai.expect(b2Stash1.parentChangeset.index).to.equals(2);
697
+ chai.expect(b2.elements.tryGetElement(e1)).to.undefined;
698
+ chai.expect(b2.elements.tryGetElement(e2)).to.undefined;
699
+ await b2.pullChanges();
700
+ chai.expect(b2.changeset.index).to.equals(3);
701
+ chai.expect(b2.elements.tryGetElement(e1)).to.exist;
702
+ chai.expect(b2.elements.tryGetElement(e2)).to.undefined;
703
+ // stash restore should downgrade briefcase to older changeset as specified in stash
704
+ await StashManager.restore({ db: b2, stash: b2Stash1 });
705
+ chai.expect(b2.changeset.index).to.equals(2);
706
+ chai.expect(b2.elements.tryGetElement(e1)).to.undefined;
707
+ chai.expect(b2.elements.tryGetElement(e2)).to.exist;
708
+ await b2.pullChanges();
709
+ chai.expect(b2.changeset.index).to.equals(3);
710
+ chai.expect(b2.elements.tryGetElement(e1)).to.exist;
711
+ chai.expect(b2.elements.tryGetElement(e2)).to.exist;
712
+ await b2.pushChanges({ description: "test" });
713
+ chai.expect(b2.changeset.index).to.equals(4);
714
+ });
715
+ it("restore stash that has element changed by another briefcase", async () => {
716
+ const b1 = await testIModel.openBriefcase();
717
+ const b2 = await testIModel.openBriefcase();
718
+ chai.expect(b1.changeset.index).to.equals(2);
719
+ chai.expect(b2.changeset.index).to.equals(2);
720
+ const e1 = await testIModel.insertElement(b1);
721
+ chai.expect(e1).to.exist;
722
+ b1.saveChanges();
723
+ await b1.pushChanges({ description: `${e1} inserted` });
724
+ chai.expect(b1.changeset.index).to.equals(3);
725
+ await b2.pullChanges();
726
+ chai.expect(b2.changeset.index).to.equals(3);
727
+ await testIModel.updateElement(b2, e1);
728
+ b2.saveChanges();
729
+ chai.expect(b2.locks.holdsExclusiveLock(e1)).to.be.true;
730
+ const b2Stash1 = await StashManager.stash({ db: b2, description: "stash test 1", discardLocalChanges: true });
731
+ chai.expect(b2Stash1.parentChangeset.index).to.equals(3);
732
+ chai.expect(b2.locks.holdsExclusiveLock(e1)).to.be.false;
733
+ // stash release lock so b2 should have released lock and b1 should be able to update.
734
+ await testIModel.updateElement(b1, e1);
735
+ b1.saveChanges();
736
+ // restore stash should fail because of lock not obtained on e1
737
+ await chai.expect(StashManager.restore({ db: b2, stash: b2Stash1 })).to.be.rejectedWith("exclusive lock is already held");
738
+ // push b1 changes to release lock
739
+ await b1.pushChanges({ description: `${e1} inserted` });
740
+ // restore stash should fail because pull is required to obtain lock
741
+ await chai.expect(StashManager.restore({ db: b2, stash: b2Stash1 })).to.be.rejectedWith("pull is required to obtain lock");
742
+ await b2.pullChanges();
743
+ chai.expect(b2.changeset.index).to.equals(4);
744
+ const elBefore = b2.elements.tryGetElementProps(e1);
745
+ chai.expect(elBefore.prop1).to.equals("3");
746
+ // restore stash should succeed as now it can obtain lock
747
+ await StashManager.restore({ db: b2, stash: b2Stash1 });
748
+ const elAfter = b2.elements.tryGetElementProps(e1);
749
+ chai.expect(elAfter.prop1).to.equals("2");
750
+ await b2.pushChanges({ description: `${e1} updated` });
751
+ });
752
+ it("schema change should not be stashed", async () => {
753
+ const b1 = await testIModel.openBriefcase();
754
+ const schema1 = `<?xml version="1.0" encoding="UTF-8"?>
749
755
  <ECSchema schemaName="TestDomain" alias="ts" version="01.00.01" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
750
756
  <ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
751
757
  <ECEntityClass typeName="a1">
@@ -767,1043 +773,1043 @@ describe("rebase changes & stashing api", function () {
767
773
  </Target>
768
774
  </ECRelationshipClass>
769
775
  </ECSchema>`;
770
- await b1.importSchemaStrings([schema1]);
771
- b1.saveChanges();
772
- await chai.expect(StashManager.stash({ db: b1, description: "stash test 1" })).to.not.rejectedWith("Bad Arg: Pending schema changeset stashing is not currently supported");
773
- });
774
- it("abort rebase", async () => {
775
- const b1 = await testIModel.openBriefcase();
776
- const b2 = await testIModel.openBriefcase();
777
- const e1 = await testIModel.insertElement(b1);
778
- b1.saveChanges();
779
- await b1.pushChanges({ description: `${e1} inserted` });
780
- const e2 = await testIModel.insertElement(b2);
781
- chai.expect(e2).to.exist;
782
- let e3 = "";
783
- b2.saveChanges();
784
- b2.txns.rebaser.setCustomHandler({
785
- shouldReinstate: (_txnProps) => {
786
- return true;
787
- },
788
- recompute: async (_txnProps) => {
789
- chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
790
- e3 = await testIModel.insertElement(b2);
791
- throw new Error("Rebase failed");
792
- },
793
- });
794
- chai.expect(b2.elements.tryGetElementProps(e1)).to.undefined;
795
- chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
796
- chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined;
797
- chai.expect(b2.changeset.index).to.equals(2);
798
- await chai.expect(b2.pullChanges()).to.be.rejectedWith("Rebase failed");
799
- chai.expect(b2.changeset.index).to.equals(3);
800
- chai.expect(e3).to.exist;
801
- chai.expect(b2.elements.tryGetElementProps(e1)).to.exist; // came from incoming changeset
802
- chai.expect(b2.elements.tryGetElementProps(e2)).to.undefined; // was local change and reversed during rebase.
803
- chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined; // was insert by reCompute() but due to exception the rebase attempt was abandoned.
804
- chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
805
- chai.expect(b2.txns.rebaser.canAbort()).is.true;
806
- await b2.txns.rebaser.abort();
807
- chai.expect(b2.changeset.index).to.equals(2);
808
- chai.expect(b2.elements.tryGetElementProps(e1)).to.undefined; // reset briefcase should move tip back to where it was before pull
809
- chai.expect(b2.elements.tryGetElementProps(e2)).to.exist; // abort should put back e2 which was only change at the time of pull
810
- chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined; // add by rebase so should not exist either
811
- chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.false;
812
- });
813
- it("calling discardChanges() from inside indirect scope is not allowed", async () => {
814
- const b1 = await testIModel.openBriefcase();
815
- await testIModel.insertElement(b1);
816
- b1.saveChanges();
817
- const p1 = b1.txns.withIndirectTxnModeAsync(async () => {
818
- await b1.discardChanges();
819
- });
820
- await chai.expect(p1).to.be.rejectedWith("Cannot discard changes when there are indirect changes");
821
- b1.saveChanges();
822
- });
823
- it("calling discardChanges() during rebasing is not allowed", async () => {
824
- const b1 = await testIModel.openBriefcase();
825
- const b2 = await testIModel.openBriefcase();
826
- await testIModel.insertElement(b1);
827
- await testIModel.insertElement(b1);
828
- b1.saveChanges();
829
- await b1.pushChanges({ description: "inserted element" });
830
- await testIModel.insertElement(b2);
831
- await testIModel.insertElement(b2);
832
- b2.saveChanges();
833
- b2.txns.rebaser.setCustomHandler({
834
- shouldReinstate: (_txnProps) => {
835
- return true;
836
- },
837
- recompute: async (_txnProps) => {
838
- chai.expect(b2.txns.rebaser.isAborting).is.false;
839
- await b2.discardChanges();
840
- },
841
- });
842
- chai.expect(b2.txns.rebaser.isAborting).is.false;
843
- const p1 = b2.pullChanges();
844
- await chai.expect(p1).to.be.rejectedWith("Cannot discard changes while a rebase is in progress");
845
- chai.expect(b2.txns.rebaser.canAbort()).is.true;
846
- await b2.txns.rebaser.abort();
847
- });
848
- it("getStash() should throw exception", async () => {
849
- const b1 = await testIModel.openBriefcase();
850
- chai.expect(() => StashManager.getStash({ db: b1, stash: "invalid_stash" })).to.throw("No stashes exist for this briefcase");
851
- chai.expect(StashManager.tryGetStash({ db: b1, stash: "invalid_stash" })).to.be.undefined;
852
- });
853
- it("edge case: a indirect update can cause FK violation", async () => {
854
- const b1 = await testIModel.openBriefcase();
855
- const b2 = await testIModel.openBriefcase();
856
- const parentId = await testIModel.insertElement(b1);
857
- const childId = await testIModel.insertElementEx(b1, { parent: { id: parentId, relClassName: "TestDomain:A1OwnsA1" } });
858
- b1.saveChanges("insert parent and child");
859
- await b1.pushChanges({ description: `inserted parent ${parentId} and child ${childId}` });
860
- await b2.pullChanges();
861
- // b1 delete childId while b1 create a child of childId as indirect change
862
- await testIModel.deleteElement(b1, childId);
863
- b1.saveChanges("delete child");
864
- // no exclusive lock required on child1
865
- const grandChildId = await testIModel.insertElementEx(b2, { parent: { id: childId, relClassName: "TestDomain:A1OwnsA1" }, markAsIndirect: true });
866
- b2.saveChanges("delete child and insert grandchild");
867
- await b1.pushChanges({ description: `deleted child ${childId}` });
868
- // should fail to pull and rebase changes.
869
- await chai.expect(b2.pushChanges({ description: `deleted child ${childId} and inserted grandchild ${grandChildId}` }))
870
- .to.be.rejectedWith("Foreign key conflicts in ChangeSet. Aborting rebase.");
871
- });
872
- it("ECSqlReader unable to read updates after saveChanges()", async () => {
873
- const b1 = await testIModel.openBriefcase();
874
- const findElement = async (id) => {
875
- const reader = b1.createQueryReader(`SELECT ECInstanceId, ec_className(ECClassId), Prop1 FROM ts.A1 WHERE ECInstanceId = ${id}`, QueryBinder.from([id]));
876
- if (await reader.step())
877
- return { id: reader.current[0], className: reader.current[1], prop1: reader.current[2] };
878
- return undefined;
879
- };
880
- const runQuery = async (query) => {
881
- const reader = b1.createQueryReader(query);
882
- let rows = 0;
883
- while (await reader.step()) {
884
- rows++;
885
- }
886
- return rows;
887
- };
888
- const runQueryParallel = async (query, times = 1) => {
889
- return Promise.all(new Array(times).fill(query).map(runQuery));
890
- };
891
- // Following query have open cached statement against BisCore.Element that will prevent
892
- // updates from being visible until the statement is finalized.
893
- await runQueryParallel(`SELECT $ FROM BisCore.Element`, 10);
894
- const e1 = await testIModel.insertElement(b1);
895
- chai.expect(await findElement(e1)).to.be.undefined;
896
- b1.saveChanges("insert element");
897
- const e1Props = await findElement(e1);
898
- chai.expect(e1Props).to.exist;
899
- await runQueryParallel(`SELECT $ FROM BisCore.Element`, 10);
900
- const e2 = await testIModel.insertElement(b1);
901
- chai.expect(await findElement(e2)).to.be.undefined;
902
- b1.saveChanges("insert second element");
903
- const e2Props = await findElement(e2);
904
- chai.expect(e2Props).to.exist;
905
- await runQueryParallel(`SELECT $ FROM BisCore.Element`, 10);
906
- const e3 = await testIModel.insertElement(b1);
907
- chai.expect(await findElement(e3)).to.be.undefined;
908
- b1.saveChanges("insert third element");
909
- const e3Props = await findElement(e3);
910
- chai.expect(e3Props).to.exist;
911
- });
912
- it("enum txn changes in recompute", async () => {
913
- const b1 = await testIModel.openBriefcase();
914
- const b2 = await testIModel.openBriefcase();
915
- const e1 = await testIModel.insertElement(b1);
916
- const e2 = await testIModel.insertElement(b1, true);
917
- b1.saveChanges();
918
- await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
919
- await b2.pullChanges();
920
- await testIModel.updateElement(b1, e1);
921
- await testIModel.updateElement(b1, e2, true);
922
- b1.saveChanges();
923
- await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
924
- await testIModel.insertElement(b2);
925
- await testIModel.insertElement(b2, true);
926
- b2.saveChanges("first change");
927
- await testIModel.insertElement(b2);
928
- await testIModel.insertElement(b2, true);
929
- b2.saveChanges("second change");
930
- await testIModel.insertElement(b2);
931
- await testIModel.insertElement(b2, true);
932
- b2.saveChanges("third change");
933
- let txnVerified = 0;
934
- b2.txns.rebaser.setCustomHandler({
935
- shouldReinstate: (_txn) => {
936
- return true;
937
- },
938
- recompute: async (txn) => {
939
- const reader = SqliteChangesetReader.openTxn({ txnId: txn.id, db: b2, disableSchemaCheck: true });
940
- const adaptor = new ChangesetECAdaptor(reader);
941
- adaptor.acceptClass("TestDomain:a1");
942
- const ids = new Set();
943
- while (adaptor.step()) {
944
- if (!adaptor.reader.isIndirect)
945
- ids.add(adaptor.inserted?.ECInstanceId || adaptor.deleted?.ECInstanceId);
946
- }
947
- adaptor.close();
948
- if (txn.props.description === "first change") {
949
- chai.expect(Array.from(ids.keys())).deep.equal(["0x40000000001"]);
950
- txnVerified++;
951
- }
952
- else if (txn.props.description === "second change") {
953
- chai.expect(Array.from(ids.keys())).deep.equal(["0x40000000003"]);
954
- txnVerified++;
955
- }
956
- else if (txn.props.description === "third change") {
957
- chai.expect(Array.from(ids.keys())).deep.equal(["0x40000000005"]);
958
- txnVerified++;
959
- }
960
- else {
961
- txnVerified++;
962
- }
963
- },
964
- });
965
- await b2.pullChanges();
966
- chai.expect(txnVerified).to.equal(3);
967
- });
968
- it("before and after rebase events", async () => {
969
- const b1 = await testIModel.openBriefcase();
970
- const b2 = await testIModel.openBriefcase();
971
- const e1 = await testIModel.insertElement(b1);
972
- const e2 = await testIModel.insertElement(b1, true);
973
- b1.saveChanges();
974
- await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
975
- await b2.pullChanges();
976
- await testIModel.updateElement(b1, e1);
977
- await testIModel.updateElement(b1, e2, true);
978
- b1.saveChanges();
979
- await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
980
- await testIModel.insertElement(b2);
981
- await testIModel.insertElement(b2, true);
982
- b2.saveChanges("first change");
983
- await testIModel.insertElement(b2);
984
- await testIModel.insertElement(b2, true);
985
- b2.saveChanges("second change");
986
- await testIModel.insertElement(b2);
987
- await testIModel.insertElement(b2, true);
988
- b2.saveChanges("third change");
989
- const events = {
990
- onRebase: {
991
- beginCount: 0,
992
- endCount: 0,
993
- beginTxns: [],
994
- },
995
- onRebaseTxn: {
996
- beginTxns: [],
997
- endTxns: [],
998
- },
999
- rebaseHandler: {
1000
- shouldReinstate: [],
1001
- recompute: [],
1002
- },
1003
- pullMerge: {
1004
- beginCount: 0,
1005
- endCount: 0,
1006
- beginChangeset: [],
1007
- endChangeset: [],
1008
- },
1009
- applyIncomingChanges: {
1010
- beginCount: 0,
1011
- endCount: 0,
1012
- beginChangesets: [],
1013
- endChangesets: [],
1014
- },
1015
- reverseLocalChanges: {
1016
- beginCount: 0,
1017
- endCount: 0,
1018
- txns: [],
1019
- },
1020
- downloadChangesets: {
1021
- beginCount: 0,
1022
- endCount: 0,
1023
- },
1024
- };
1025
- const resetEvent = () => {
1026
- events.onRebase.beginCount = 0;
1027
- events.onRebase.endCount = 0;
1028
- events.onRebase.beginTxns = [];
1029
- events.onRebaseTxn.beginTxns = [];
1030
- events.onRebaseTxn.endTxns = [];
1031
- events.rebaseHandler.shouldReinstate = [];
1032
- events.rebaseHandler.recompute = [];
1033
- events.pullMerge.beginCount = 0;
1034
- events.pullMerge.endCount = 0;
1035
- events.pullMerge.beginChangeset = [];
1036
- events.pullMerge.endChangeset = [];
1037
- events.applyIncomingChanges.beginCount = 0;
1038
- events.applyIncomingChanges.endCount = 0;
1039
- events.applyIncomingChanges.beginChangesets = [];
1040
- events.applyIncomingChanges.endChangesets = [];
1041
- events.reverseLocalChanges.beginCount = 0;
1042
- events.reverseLocalChanges.endCount = 0;
1043
- events.reverseLocalChanges.txns = [];
1044
- events.downloadChangesets.beginCount = 0;
1045
- events.downloadChangesets.endCount = 0;
1046
- };
1047
- // onPullMergeXXXX
1048
- b2.txns.rebaser.onPullMergeBegin.addListener((changeset) => {
1049
- events.pullMerge.beginCount++;
1050
- events.pullMerge.beginChangeset.push(changeset);
1051
- });
1052
- b2.txns.rebaser.onPullMergeEnd.addListener((changeset) => {
1053
- events.pullMerge.endCount++;
1054
- events.pullMerge.endChangeset.push(changeset);
1055
- });
1056
- // onApplyIncomingChangesXXXX
1057
- b2.txns.rebaser.onApplyIncomingChangesBegin.addListener((changesets) => {
1058
- events.applyIncomingChanges.beginCount++;
1059
- events.applyIncomingChanges.beginChangesets.push(...changesets);
1060
- });
1061
- b2.txns.rebaser.onApplyIncomingChangesEnd.addListener((changesets) => {
1062
- events.applyIncomingChanges.endCount++;
1063
- events.applyIncomingChanges.endChangesets.push(...changesets);
1064
- });
1065
- // onReverseLocalChangesXXXX
1066
- b2.txns.rebaser.onReverseLocalChangesBegin.addListener(() => {
1067
- events.reverseLocalChanges.beginCount++;
1068
- });
1069
- b2.txns.rebaser.onReverseLocalChangesEnd.addListener((txns) => {
1070
- events.reverseLocalChanges.endCount++;
1071
- removePropertyRecursive(txns, "timestamp"); // it changes on each run, so remove it for comparison
1072
- events.reverseLocalChanges.txns.push(...txns);
776
+ await b1.importSchemaStrings([schema1]);
777
+ b1.saveChanges();
778
+ await chai.expect(StashManager.stash({ db: b1, description: "stash test 1" })).to.not.rejectedWith("Bad Arg: Pending schema changeset stashing is not currently supported");
1073
779
  });
1074
- // onDownloadChangesetsXXXX
1075
- b2.txns.rebaser.onDownloadChangesetsBegin.addListener(() => {
1076
- events.downloadChangesets.beginCount++;
780
+ it("abort rebase", async () => {
781
+ const b1 = await testIModel.openBriefcase();
782
+ const b2 = await testIModel.openBriefcase();
783
+ const e1 = await testIModel.insertElement(b1);
784
+ b1.saveChanges();
785
+ await b1.pushChanges({ description: `${e1} inserted` });
786
+ const e2 = await testIModel.insertElement(b2);
787
+ chai.expect(e2).to.exist;
788
+ let e3 = "";
789
+ b2.saveChanges();
790
+ b2.txns.rebaser.setCustomHandler({
791
+ shouldReinstate: (_txnProps) => {
792
+ return true;
793
+ },
794
+ recompute: async (_txnProps) => {
795
+ chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
796
+ e3 = await testIModel.insertElement(b2);
797
+ throw new Error("Rebase failed");
798
+ },
799
+ });
800
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.undefined;
801
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
802
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined;
803
+ chai.expect(b2.changeset.index).to.equals(2);
804
+ await chai.expect(b2.pullChanges()).to.be.rejectedWith("Rebase failed");
805
+ chai.expect(b2.changeset.index).to.equals(3);
806
+ chai.expect(e3).to.exist;
807
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.exist; // came from incoming changeset
808
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.undefined; // was local change and reversed during rebase.
809
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined; // was insert by reCompute() but due to exception the rebase attempt was abandoned.
810
+ chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
811
+ chai.expect(b2.txns.rebaser.canAbort()).is.true;
812
+ await b2.txns.rebaser.abort();
813
+ chai.expect(b2.changeset.index).to.equals(2);
814
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.undefined; // reset briefcase should move tip back to where it was before pull
815
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.exist; // abort should put back e2 which was only change at the time of pull
816
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined; // add by rebase so should not exist either
817
+ chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.false;
1077
818
  });
1078
- b2.txns.rebaser.onDownloadChangesetsEnd.addListener(() => {
1079
- events.downloadChangesets.endCount++;
819
+ it("calling discardChanges() from inside indirect scope is not allowed", async () => {
820
+ const b1 = await testIModel.openBriefcase();
821
+ await testIModel.insertElement(b1);
822
+ b1.saveChanges();
823
+ const p1 = b1.txns.withIndirectTxnModeAsync(async () => {
824
+ await b1.discardChanges();
825
+ });
826
+ await chai.expect(p1).to.be.rejectedWith("Cannot discard changes when there are indirect changes");
827
+ b1.saveChanges();
1080
828
  });
1081
- // onRebaseXXXX
1082
- b2.txns.rebaser.onRebaseBegin.addListener((txns) => {
1083
- events.onRebase.beginCount++;
1084
- removePropertyRecursive(txns, "timestamp"); // it changes on each run, so remove it for comparison
1085
- events.onRebase.beginTxns.push(...txns);
829
+ it("calling discardChanges() during rebasing is not allowed", async () => {
830
+ const b1 = await testIModel.openBriefcase();
831
+ const b2 = await testIModel.openBriefcase();
832
+ await testIModel.insertElement(b1);
833
+ await testIModel.insertElement(b1);
834
+ b1.saveChanges();
835
+ await b1.pushChanges({ description: "inserted element" });
836
+ await testIModel.insertElement(b2);
837
+ await testIModel.insertElement(b2);
838
+ b2.saveChanges();
839
+ b2.txns.rebaser.setCustomHandler({
840
+ shouldReinstate: (_txnProps) => {
841
+ return true;
842
+ },
843
+ recompute: async (_txnProps) => {
844
+ chai.expect(b2.txns.rebaser.isAborting).is.false;
845
+ await b2.discardChanges();
846
+ },
847
+ });
848
+ chai.expect(b2.txns.rebaser.isAborting).is.false;
849
+ const p1 = b2.pullChanges();
850
+ await chai.expect(p1).to.be.rejectedWith("Cannot discard changes while a rebase is in progress");
851
+ chai.expect(b2.txns.rebaser.canAbort()).is.true;
852
+ await b2.txns.rebaser.abort();
1086
853
  });
1087
- b2.txns.rebaser.onRebaseEnd.addListener(() => {
1088
- events.onRebase.endCount++;
854
+ it("getStash() should throw exception", async () => {
855
+ const b1 = await testIModel.openBriefcase();
856
+ chai.expect(() => StashManager.getStash({ db: b1, stash: "invalid_stash" })).to.throw("No stashes exist for this briefcase");
857
+ chai.expect(StashManager.tryGetStash({ db: b1, stash: "invalid_stash" })).to.be.undefined;
1089
858
  });
1090
- // onRebaseTxnXXXX
1091
- b2.txns.rebaser.onRebaseTxnBegin.addListener((txn) => {
1092
- removePropertyRecursive(txn, "timestamp"); // it changes on each run, so remove it for comparison
1093
- events.onRebaseTxn.beginTxns.push(txn);
859
+ it("edge case: a indirect update can cause FK violation", async () => {
860
+ const b1 = await testIModel.openBriefcase();
861
+ const b2 = await testIModel.openBriefcase();
862
+ const parentId = await testIModel.insertElement(b1);
863
+ const childId = await testIModel.insertElementEx(b1, { parent: { id: parentId, relClassName: "TestDomain:A1OwnsA1" } });
864
+ b1.saveChanges("insert parent and child");
865
+ await b1.pushChanges({ description: `inserted parent ${parentId} and child ${childId}` });
866
+ await b2.pullChanges();
867
+ // b1 delete childId while b1 create a child of childId as indirect change
868
+ await testIModel.deleteElement(b1, childId);
869
+ b1.saveChanges("delete child");
870
+ // no exclusive lock required on child1
871
+ const grandChildId = await testIModel.insertElementEx(b2, { parent: { id: childId, relClassName: "TestDomain:A1OwnsA1" }, markAsIndirect: true });
872
+ b2.saveChanges("delete child and insert grandchild");
873
+ await b1.pushChanges({ description: `deleted child ${childId}` });
874
+ // should fail to pull and rebase changes.
875
+ await chai.expect(b2.pushChanges({ description: `deleted child ${childId} and inserted grandchild ${grandChildId}` }))
876
+ .to.be.rejectedWith("Foreign key conflicts in ChangeSet. Aborting rebase.");
1094
877
  });
1095
- b2.txns.rebaser.onRebaseTxnEnd.addListener((txn) => {
1096
- removePropertyRecursive(txn, "timestamp"); // it changes on each run, so remove it for comparison
1097
- events.onRebaseTxn.endTxns.push(txn);
878
+ it("ECSqlReader unable to read updates after saveChanges()", async () => {
879
+ const b1 = await testIModel.openBriefcase();
880
+ const findElement = async (id) => {
881
+ const reader = b1.createQueryReader(`SELECT ECInstanceId, ec_className(ECClassId), Prop1 FROM ts.A1 WHERE ECInstanceId = ${id}`, QueryBinder.from([id]));
882
+ if (await reader.step())
883
+ return { id: reader.current[0], className: reader.current[1], prop1: reader.current[2] };
884
+ return undefined;
885
+ };
886
+ const runQuery = async (query) => {
887
+ const reader = b1.createQueryReader(query);
888
+ let rows = 0;
889
+ while (await reader.step()) {
890
+ rows++;
891
+ }
892
+ return rows;
893
+ };
894
+ const runQueryParallel = async (query, times = 1) => {
895
+ return Promise.all(new Array(times).fill(query).map(runQuery));
896
+ };
897
+ // Following query have open cached statement against BisCore.Element that will prevent
898
+ // updates from being visible until the statement is finalized.
899
+ await runQueryParallel(`SELECT $ FROM BisCore.Element`, 10);
900
+ const e1 = await testIModel.insertElement(b1);
901
+ chai.expect(await findElement(e1)).to.be.undefined;
902
+ b1.saveChanges("insert element");
903
+ const e1Props = await findElement(e1);
904
+ chai.expect(e1Props).to.exist;
905
+ await runQueryParallel(`SELECT $ FROM BisCore.Element`, 10);
906
+ const e2 = await testIModel.insertElement(b1);
907
+ chai.expect(await findElement(e2)).to.be.undefined;
908
+ b1.saveChanges("insert second element");
909
+ const e2Props = await findElement(e2);
910
+ chai.expect(e2Props).to.exist;
911
+ await runQueryParallel(`SELECT $ FROM BisCore.Element`, 10);
912
+ const e3 = await testIModel.insertElement(b1);
913
+ chai.expect(await findElement(e3)).to.be.undefined;
914
+ b1.saveChanges("insert third element");
915
+ const e3Props = await findElement(e3);
916
+ chai.expect(e3Props).to.exist;
1098
917
  });
1099
- b2.txns.rebaser.setCustomHandler({
1100
- shouldReinstate: (_txn) => {
1101
- // shouldReinstate
1102
- removePropertyRecursive(_txn, "timestamp"); // it changes on each run, so remove it for comparison
1103
- events.rebaseHandler.shouldReinstate.push(_txn);
1104
- return true;
1105
- },
1106
- recompute: async (_txn) => {
1107
- // recompute
1108
- removePropertyRecursive(_txn, "timestamp"); // it changes on each run, so remove it for comparison
1109
- events.rebaseHandler.recompute.push(_txn);
1110
- },
918
+ it("enum txn changes in recompute", async () => {
919
+ const b1 = await testIModel.openBriefcase();
920
+ const b2 = await testIModel.openBriefcase();
921
+ const e1 = await testIModel.insertElement(b1);
922
+ const e2 = await testIModel.insertElement(b1, true);
923
+ b1.saveChanges();
924
+ await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
925
+ await b2.pullChanges();
926
+ await testIModel.updateElement(b1, e1);
927
+ await testIModel.updateElement(b1, e2, true);
928
+ b1.saveChanges();
929
+ await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
930
+ await testIModel.insertElement(b2);
931
+ await testIModel.insertElement(b2, true);
932
+ b2.saveChanges("first change");
933
+ await testIModel.insertElement(b2);
934
+ await testIModel.insertElement(b2, true);
935
+ b2.saveChanges("second change");
936
+ await testIModel.insertElement(b2);
937
+ await testIModel.insertElement(b2, true);
938
+ b2.saveChanges("third change");
939
+ let txnVerified = 0;
940
+ b2.txns.rebaser.setCustomHandler({
941
+ shouldReinstate: (_txn) => {
942
+ return true;
943
+ },
944
+ recompute: async (txn) => {
945
+ const reader = SqliteChangesetReader.openTxn({ txnId: txn.id, db: b2, disableSchemaCheck: true });
946
+ const adaptor = new ChangesetECAdaptor(reader);
947
+ adaptor.acceptClass("TestDomain:a1");
948
+ const ids = new Set();
949
+ while (adaptor.step()) {
950
+ if (!adaptor.reader.isIndirect)
951
+ ids.add(adaptor.inserted?.ECInstanceId || adaptor.deleted?.ECInstanceId);
952
+ }
953
+ adaptor.close();
954
+ if (txn.props.description === "first change") {
955
+ chai.expect(Array.from(ids.keys())).deep.equal(["0x40000000001"]);
956
+ txnVerified++;
957
+ }
958
+ else if (txn.props.description === "second change") {
959
+ chai.expect(Array.from(ids.keys())).deep.equal(["0x40000000003"]);
960
+ txnVerified++;
961
+ }
962
+ else if (txn.props.description === "third change") {
963
+ chai.expect(Array.from(ids.keys())).deep.equal(["0x40000000005"]);
964
+ txnVerified++;
965
+ }
966
+ else {
967
+ txnVerified++;
968
+ }
969
+ },
970
+ });
971
+ await b2.pullChanges();
972
+ chai.expect(txnVerified).to.equal(3);
1111
973
  });
1112
- resetEvent();
1113
- await b2.pullChanges();
1114
- // pullMerge events
1115
- chai.expect(events.pullMerge.beginCount).to.equal(1);
1116
- chai.expect(events.pullMerge.endCount).to.equal(1);
1117
- chai.expect((events.pullMerge.beginChangeset[0].index)).to.equal(3);
1118
- chai.expect((events.pullMerge.endChangeset[0].index)).to.equal(4);
1119
- // applyIncomingChanges events
1120
- chai.expect(events.applyIncomingChanges.beginCount).to.equal(1);
1121
- chai.expect(events.applyIncomingChanges.endCount).to.equal(1);
1122
- chai.expect(events.applyIncomingChanges.beginChangesets.map((cs) => cs.index)).to.deep.equal([4]);
1123
- chai.expect(events.applyIncomingChanges.endChangesets.map((cs) => cs.index)).to.deep.equal([4]);
1124
- // downloadChangesets events
1125
- chai.expect(events.downloadChangesets.beginCount).to.equal(1);
1126
- chai.expect(events.downloadChangesets.endCount).to.equal(1);
1127
- // reverseLocalChanges events
1128
- chai.expect(events.reverseLocalChanges.beginCount).to.equal(1);
1129
- chai.expect(events.reverseLocalChanges.endCount).to.equal(1);
1130
- chai.expect(events.reverseLocalChanges.txns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1131
- // rebase events
1132
- chai.expect(events.onRebase.beginCount).to.equal(1);
1133
- chai.expect(events.onRebase.endCount).to.equal(1);
1134
- chai.expect(events.onRebase.beginTxns).to.deep.equal([
1135
- {
1136
- grouped: false,
1137
- id: "0x100000000",
1138
- nextId: "0x100000001",
1139
- props: {
1140
- description: "first change"
974
+ it("before and after rebase events", async () => {
975
+ const b1 = await testIModel.openBriefcase();
976
+ const b2 = await testIModel.openBriefcase();
977
+ const e1 = await testIModel.insertElement(b1);
978
+ const e2 = await testIModel.insertElement(b1, true);
979
+ b1.saveChanges();
980
+ await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
981
+ await b2.pullChanges();
982
+ await testIModel.updateElement(b1, e1);
983
+ await testIModel.updateElement(b1, e2, true);
984
+ b1.saveChanges();
985
+ await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
986
+ await testIModel.insertElement(b2);
987
+ await testIModel.insertElement(b2, true);
988
+ b2.saveChanges("first change");
989
+ await testIModel.insertElement(b2);
990
+ await testIModel.insertElement(b2, true);
991
+ b2.saveChanges("second change");
992
+ await testIModel.insertElement(b2);
993
+ await testIModel.insertElement(b2, true);
994
+ b2.saveChanges("third change");
995
+ const events = {
996
+ onRebase: {
997
+ beginCount: 0,
998
+ endCount: 0,
999
+ beginTxns: [],
1141
1000
  },
1142
- reversed: true,
1143
- sessionId: 1,
1144
- type: "Data"
1145
- },
1146
- {
1147
- grouped: false,
1148
- id: "0x100000001",
1149
- nextId: "0x100000002",
1150
- prevId: "0x100000000",
1151
- props: {
1152
- description: "second change",
1001
+ onRebaseTxn: {
1002
+ beginTxns: [],
1003
+ endTxns: [],
1153
1004
  },
1154
- reversed: true,
1155
- sessionId: 1,
1156
- type: "Data"
1157
- },
1158
- {
1159
- grouped: false,
1160
- id: "0x100000002",
1161
- prevId: "0x100000001",
1162
- props: {
1163
- description: "third change"
1005
+ rebaseHandler: {
1006
+ shouldReinstate: [],
1007
+ recompute: [],
1164
1008
  },
1165
- reversed: true,
1166
- sessionId: 1,
1167
- type: "Data"
1168
- }
1169
- ]);
1170
- chai.expect(events.onRebaseTxn.beginTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1171
- chai.expect(events.onRebaseTxn.endTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1172
- chai.expect(events.rebaseHandler.shouldReinstate.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1173
- chai.expect(events.rebaseHandler.recompute.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1174
- await testIModel.updateElement(b1, e1);
1175
- await testIModel.updateElement(b1, e2, true);
1176
- b1.saveChanges();
1177
- await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
1178
- await testIModel.insertElement(b2);
1179
- await testIModel.insertElement(b2, true);
1180
- b2.saveChanges("fourth change");
1181
- resetEvent();
1182
- await b2.pullChanges();
1183
- chai.expect(events.onRebase.beginCount).to.equal(1);
1184
- chai.expect(events.onRebase.endCount).to.equal(1);
1185
- chai.expect(events.onRebase.beginTxns).to.deep.equal([
1186
- {
1187
- grouped: false,
1188
- id: "0x100000000",
1189
- nextId: "0x100000001",
1190
- props: {
1191
- description: "first change"
1009
+ pullMerge: {
1010
+ beginCount: 0,
1011
+ endCount: 0,
1012
+ beginChangeset: [],
1013
+ endChangeset: [],
1192
1014
  },
1193
- reversed: true,
1194
- sessionId: 1,
1195
- type: "Data"
1196
- },
1197
- {
1198
- grouped: false,
1199
- id: "0x100000001",
1200
- nextId: "0x100000002",
1201
- prevId: "0x100000000",
1202
- props: {
1203
- description: "second change"
1015
+ applyIncomingChanges: {
1016
+ beginCount: 0,
1017
+ endCount: 0,
1018
+ beginChangesets: [],
1019
+ endChangesets: [],
1204
1020
  },
1205
- reversed: true,
1206
- sessionId: 1,
1207
- type: "Data"
1208
- },
1209
- {
1210
- grouped: false,
1211
- id: "0x100000002",
1212
- nextId: "0x100000003",
1213
- prevId: "0x100000001",
1214
- props: {
1215
- description: "third change"
1021
+ reverseLocalChanges: {
1022
+ beginCount: 0,
1023
+ endCount: 0,
1024
+ txns: [],
1216
1025
  },
1217
- reversed: true,
1218
- sessionId: 1,
1219
- type: "Data"
1220
- },
1221
- {
1222
- grouped: false,
1223
- id: "0x100000003",
1224
- prevId: "0x100000002",
1225
- props: {
1226
- description: "fourth change"
1026
+ downloadChangesets: {
1027
+ beginCount: 0,
1028
+ endCount: 0,
1227
1029
  },
1228
- reversed: true,
1229
- sessionId: 1,
1230
- type: "Data"
1231
- }
1232
- ]);
1233
- chai.expect(events.onRebaseTxn.beginTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
1234
- chai.expect(events.onRebaseTxn.endTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
1235
- chai.expect(events.rebaseHandler.shouldReinstate.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
1236
- chai.expect(events.rebaseHandler.recompute.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
1237
- });
1238
- it("onModelGeometryChanged() not fired during rebase/pullMerge with no local change", async () => {
1239
- const b1 = await testIModel.openBriefcase();
1240
- const b2 = await testIModel.openBriefcase();
1241
- const pushChangeFromB2 = async () => {
1030
+ };
1031
+ const resetEvent = () => {
1032
+ events.onRebase.beginCount = 0;
1033
+ events.onRebase.endCount = 0;
1034
+ events.onRebase.beginTxns = [];
1035
+ events.onRebaseTxn.beginTxns = [];
1036
+ events.onRebaseTxn.endTxns = [];
1037
+ events.rebaseHandler.shouldReinstate = [];
1038
+ events.rebaseHandler.recompute = [];
1039
+ events.pullMerge.beginCount = 0;
1040
+ events.pullMerge.endCount = 0;
1041
+ events.pullMerge.beginChangeset = [];
1042
+ events.pullMerge.endChangeset = [];
1043
+ events.applyIncomingChanges.beginCount = 0;
1044
+ events.applyIncomingChanges.endCount = 0;
1045
+ events.applyIncomingChanges.beginChangesets = [];
1046
+ events.applyIncomingChanges.endChangesets = [];
1047
+ events.reverseLocalChanges.beginCount = 0;
1048
+ events.reverseLocalChanges.endCount = 0;
1049
+ events.reverseLocalChanges.txns = [];
1050
+ events.downloadChangesets.beginCount = 0;
1051
+ events.downloadChangesets.endCount = 0;
1052
+ };
1053
+ // onPullMergeXXXX
1054
+ b2.txns.rebaser.onPullMergeBegin.addListener((changeset) => {
1055
+ events.pullMerge.beginCount++;
1056
+ events.pullMerge.beginChangeset.push(changeset);
1057
+ });
1058
+ b2.txns.rebaser.onPullMergeEnd.addListener((changeset) => {
1059
+ events.pullMerge.endCount++;
1060
+ events.pullMerge.endChangeset.push(changeset);
1061
+ });
1062
+ // onApplyIncomingChangesXXXX
1063
+ b2.txns.rebaser.onApplyIncomingChangesBegin.addListener((changesets) => {
1064
+ events.applyIncomingChanges.beginCount++;
1065
+ events.applyIncomingChanges.beginChangesets.push(...changesets);
1066
+ });
1067
+ b2.txns.rebaser.onApplyIncomingChangesEnd.addListener((changesets) => {
1068
+ events.applyIncomingChanges.endCount++;
1069
+ events.applyIncomingChanges.endChangesets.push(...changesets);
1070
+ });
1071
+ // onReverseLocalChangesXXXX
1072
+ b2.txns.rebaser.onReverseLocalChangesBegin.addListener(() => {
1073
+ events.reverseLocalChanges.beginCount++;
1074
+ });
1075
+ b2.txns.rebaser.onReverseLocalChangesEnd.addListener((txns) => {
1076
+ events.reverseLocalChanges.endCount++;
1077
+ removePropertyRecursive(txns, "timestamp"); // it changes on each run, so remove it for comparison
1078
+ events.reverseLocalChanges.txns.push(...txns);
1079
+ });
1080
+ // onDownloadChangesetsXXXX
1081
+ b2.txns.rebaser.onDownloadChangesetsBegin.addListener(() => {
1082
+ events.downloadChangesets.beginCount++;
1083
+ });
1084
+ b2.txns.rebaser.onDownloadChangesetsEnd.addListener(() => {
1085
+ events.downloadChangesets.endCount++;
1086
+ });
1087
+ // onRebaseXXXX
1088
+ b2.txns.rebaser.onRebaseBegin.addListener((txns) => {
1089
+ events.onRebase.beginCount++;
1090
+ removePropertyRecursive(txns, "timestamp"); // it changes on each run, so remove it for comparison
1091
+ events.onRebase.beginTxns.push(...txns);
1092
+ });
1093
+ b2.txns.rebaser.onRebaseEnd.addListener(() => {
1094
+ events.onRebase.endCount++;
1095
+ });
1096
+ // onRebaseTxnXXXX
1097
+ b2.txns.rebaser.onRebaseTxnBegin.addListener((txn) => {
1098
+ removePropertyRecursive(txn, "timestamp"); // it changes on each run, so remove it for comparison
1099
+ events.onRebaseTxn.beginTxns.push(txn);
1100
+ });
1101
+ b2.txns.rebaser.onRebaseTxnEnd.addListener((txn) => {
1102
+ removePropertyRecursive(txn, "timestamp"); // it changes on each run, so remove it for comparison
1103
+ events.onRebaseTxn.endTxns.push(txn);
1104
+ });
1105
+ b2.txns.rebaser.setCustomHandler({
1106
+ shouldReinstate: (_txn) => {
1107
+ // shouldReinstate
1108
+ removePropertyRecursive(_txn, "timestamp"); // it changes on each run, so remove it for comparison
1109
+ events.rebaseHandler.shouldReinstate.push(_txn);
1110
+ return true;
1111
+ },
1112
+ recompute: async (_txn) => {
1113
+ // recompute
1114
+ removePropertyRecursive(_txn, "timestamp"); // it changes on each run, so remove it for comparison
1115
+ events.rebaseHandler.recompute.push(_txn);
1116
+ },
1117
+ });
1118
+ resetEvent();
1242
1119
  await b2.pullChanges();
1120
+ // pullMerge events
1121
+ chai.expect(events.pullMerge.beginCount).to.equal(1);
1122
+ chai.expect(events.pullMerge.endCount).to.equal(1);
1123
+ chai.expect((events.pullMerge.beginChangeset[0].index)).to.equal(3);
1124
+ chai.expect((events.pullMerge.endChangeset[0].index)).to.equal(4);
1125
+ // applyIncomingChanges events
1126
+ chai.expect(events.applyIncomingChanges.beginCount).to.equal(1);
1127
+ chai.expect(events.applyIncomingChanges.endCount).to.equal(1);
1128
+ chai.expect(events.applyIncomingChanges.beginChangesets.map((cs) => cs.index)).to.deep.equal([4]);
1129
+ chai.expect(events.applyIncomingChanges.endChangesets.map((cs) => cs.index)).to.deep.equal([4]);
1130
+ // downloadChangesets events
1131
+ chai.expect(events.downloadChangesets.beginCount).to.equal(1);
1132
+ chai.expect(events.downloadChangesets.endCount).to.equal(1);
1133
+ // reverseLocalChanges events
1134
+ chai.expect(events.reverseLocalChanges.beginCount).to.equal(1);
1135
+ chai.expect(events.reverseLocalChanges.endCount).to.equal(1);
1136
+ chai.expect(events.reverseLocalChanges.txns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1137
+ // rebase events
1138
+ chai.expect(events.onRebase.beginCount).to.equal(1);
1139
+ chai.expect(events.onRebase.endCount).to.equal(1);
1140
+ chai.expect(events.onRebase.beginTxns).to.deep.equal([
1141
+ {
1142
+ grouped: false,
1143
+ id: "0x100000000",
1144
+ nextId: "0x100000001",
1145
+ props: {
1146
+ description: "first change"
1147
+ },
1148
+ reversed: true,
1149
+ sessionId: 1,
1150
+ type: "Data"
1151
+ },
1152
+ {
1153
+ grouped: false,
1154
+ id: "0x100000001",
1155
+ nextId: "0x100000002",
1156
+ prevId: "0x100000000",
1157
+ props: {
1158
+ description: "second change",
1159
+ },
1160
+ reversed: true,
1161
+ sessionId: 1,
1162
+ type: "Data"
1163
+ },
1164
+ {
1165
+ grouped: false,
1166
+ id: "0x100000002",
1167
+ prevId: "0x100000001",
1168
+ props: {
1169
+ description: "third change"
1170
+ },
1171
+ reversed: true,
1172
+ sessionId: 1,
1173
+ type: "Data"
1174
+ }
1175
+ ]);
1176
+ chai.expect(events.onRebaseTxn.beginTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1177
+ chai.expect(events.onRebaseTxn.endTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1178
+ chai.expect(events.rebaseHandler.shouldReinstate.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1179
+ chai.expect(events.rebaseHandler.recompute.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002"]);
1180
+ await testIModel.updateElement(b1, e1);
1181
+ await testIModel.updateElement(b1, e2, true);
1182
+ b1.saveChanges();
1183
+ await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
1243
1184
  await testIModel.insertElement(b2);
1244
- b2.saveChanges();
1245
- await b2.pushChanges({ description: "insert element on b2" });
1246
- };
1247
- const events = {
1248
- modelGeometryChanged: [],
1249
- };
1250
- const getGeometryGuidFromB1 = (modelId) => {
1251
- const modelProps = b1.models.tryGetModelProps(modelId);
1252
- return modelProps?.geometryGuid;
1253
- };
1254
- const clearEvents = () => {
1255
- events.modelGeometryChanged = [];
1256
- };
1257
- b1.txns.onModelGeometryChanged.addListener((changes) => {
1258
- events.modelGeometryChanged.push(changes);
1185
+ await testIModel.insertElement(b2, true);
1186
+ b2.saveChanges("fourth change");
1187
+ resetEvent();
1188
+ await b2.pullChanges();
1189
+ chai.expect(events.onRebase.beginCount).to.equal(1);
1190
+ chai.expect(events.onRebase.endCount).to.equal(1);
1191
+ chai.expect(events.onRebase.beginTxns).to.deep.equal([
1192
+ {
1193
+ grouped: false,
1194
+ id: "0x100000000",
1195
+ nextId: "0x100000001",
1196
+ props: {
1197
+ description: "first change"
1198
+ },
1199
+ reversed: true,
1200
+ sessionId: 1,
1201
+ type: "Data"
1202
+ },
1203
+ {
1204
+ grouped: false,
1205
+ id: "0x100000001",
1206
+ nextId: "0x100000002",
1207
+ prevId: "0x100000000",
1208
+ props: {
1209
+ description: "second change"
1210
+ },
1211
+ reversed: true,
1212
+ sessionId: 1,
1213
+ type: "Data"
1214
+ },
1215
+ {
1216
+ grouped: false,
1217
+ id: "0x100000002",
1218
+ nextId: "0x100000003",
1219
+ prevId: "0x100000001",
1220
+ props: {
1221
+ description: "third change"
1222
+ },
1223
+ reversed: true,
1224
+ sessionId: 1,
1225
+ type: "Data"
1226
+ },
1227
+ {
1228
+ grouped: false,
1229
+ id: "0x100000003",
1230
+ prevId: "0x100000002",
1231
+ props: {
1232
+ description: "fourth change"
1233
+ },
1234
+ reversed: true,
1235
+ sessionId: 1,
1236
+ type: "Data"
1237
+ }
1238
+ ]);
1239
+ chai.expect(events.onRebaseTxn.beginTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
1240
+ chai.expect(events.onRebaseTxn.endTxns.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
1241
+ chai.expect(events.rebaseHandler.shouldReinstate.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
1242
+ chai.expect(events.rebaseHandler.recompute.map((txn) => txn.id)).to.deep.equal(["0x100000000", "0x100000001", "0x100000002", "0x100000003"]);
1259
1243
  });
1260
- clearEvents();
1261
- b1.txns.rebaser.setCustomHandler({
1262
- shouldReinstate: (_txn) => {
1263
- return true;
1264
- },
1265
- recompute: async (_txn) => {
1266
- },
1244
+ it("onModelGeometryChanged() not fired during rebase/pullMerge with no local change", async () => {
1245
+ const b1 = await testIModel.openBriefcase();
1246
+ const b2 = await testIModel.openBriefcase();
1247
+ const pushChangeFromB2 = async () => {
1248
+ await b2.pullChanges();
1249
+ await testIModel.insertElement(b2);
1250
+ b2.saveChanges();
1251
+ await b2.pushChanges({ description: "insert element on b2" });
1252
+ };
1253
+ const events = {
1254
+ modelGeometryChanged: [],
1255
+ };
1256
+ const getGeometryGuidFromB1 = (modelId) => {
1257
+ const modelProps = b1.models.tryGetModelProps(modelId);
1258
+ return modelProps?.geometryGuid;
1259
+ };
1260
+ const clearEvents = () => {
1261
+ events.modelGeometryChanged = [];
1262
+ };
1263
+ b1.txns.onModelGeometryChanged.addListener((changes) => {
1264
+ events.modelGeometryChanged.push(changes);
1265
+ });
1266
+ clearEvents();
1267
+ b1.txns.rebaser.setCustomHandler({
1268
+ shouldReinstate: (_txn) => {
1269
+ return true;
1270
+ },
1271
+ recompute: async (_txn) => {
1272
+ },
1273
+ });
1274
+ await pushChangeFromB2();
1275
+ clearEvents();
1276
+ const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1277
+ chai.expect(geomGuidBeforePull).is.undefined;
1278
+ await b1.pushChanges({ description: "push changes on b1" });
1279
+ const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1280
+ chai.expect(geomGuidAfterPull).is.undefined;
1281
+ chai.expect(events.modelGeometryChanged.length).to.equal(0);
1267
1282
  });
1268
- await pushChangeFromB2();
1269
- clearEvents();
1270
- const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1271
- chai.expect(geomGuidBeforePull).is.undefined;
1272
- await b1.pushChanges({ description: "push changes on b1" });
1273
- const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1274
- chai.expect(geomGuidAfterPull).is.undefined;
1275
- chai.expect(events.modelGeometryChanged.length).to.equal(0);
1276
- });
1277
- it("onModelGeometryChanged() fired during rebase with geometric local change", async () => {
1278
- const b1 = await testIModel.openBriefcase();
1279
- const b2 = await testIModel.openBriefcase();
1280
- const pushChangeFromB2 = async () => {
1281
- await b2.pullChanges();
1282
- await testIModel.insertElement(b2);
1283
- b2.saveChanges();
1284
- await b2.pushChanges({ description: "insert element on b2" });
1285
- };
1286
- const events = {
1287
- modelGeometryChanged: [],
1288
- onGeometryChanged: [],
1289
- };
1290
- const getGeometryGuidFromB1 = (modelId) => {
1291
- const modelProps = b1.models.tryGetModelProps(modelId);
1292
- return modelProps?.geometryGuid;
1293
- };
1294
- const clearEvents = () => {
1295
- events.modelGeometryChanged = [];
1296
- events.onGeometryChanged = [];
1297
- };
1298
- b1.txns.onModelGeometryChanged.addListener((changes) => {
1299
- events.modelGeometryChanged.push(changes);
1283
+ it("onModelGeometryChanged() fired during rebase with geometric local change", async () => {
1284
+ const b1 = await testIModel.openBriefcase();
1285
+ const b2 = await testIModel.openBriefcase();
1286
+ const pushChangeFromB2 = async () => {
1287
+ await b2.pullChanges();
1288
+ await testIModel.insertElement(b2);
1289
+ b2.saveChanges();
1290
+ await b2.pushChanges({ description: "insert element on b2" });
1291
+ };
1292
+ const events = {
1293
+ modelGeometryChanged: [],
1294
+ onGeometryChanged: [],
1295
+ };
1296
+ const getGeometryGuidFromB1 = (modelId) => {
1297
+ const modelProps = b1.models.tryGetModelProps(modelId);
1298
+ return modelProps?.geometryGuid;
1299
+ };
1300
+ const clearEvents = () => {
1301
+ events.modelGeometryChanged = [];
1302
+ events.onGeometryChanged = [];
1303
+ };
1304
+ b1.txns.onModelGeometryChanged.addListener((changes) => {
1305
+ events.modelGeometryChanged.push(changes);
1306
+ });
1307
+ b1.txns.onGeometryChanged.addListener((changes) => {
1308
+ events.onGeometryChanged.push(changes);
1309
+ });
1310
+ clearEvents();
1311
+ const e1 = await testIModel.insertElement(b1);
1312
+ const e2 = await testIModel.insertElement(b1, true);
1313
+ chai.expect(e1).to.exist;
1314
+ chai.expect(e2).to.exist;
1315
+ b1.saveChanges(`insert element ${e1} and ${e2}`);
1316
+ chai.expect(events.modelGeometryChanged.length).to.equal(1);
1317
+ chai.expect(events.modelGeometryChanged[0].length).to.equal(1);
1318
+ chai.expect(events.modelGeometryChanged[0][0].id).to.equal("0x20000000001");
1319
+ chai.assert(Guid.isGuid(events.modelGeometryChanged[0][0].guid));
1320
+ b1.txns.rebaser.setCustomHandler({
1321
+ shouldReinstate: (_txn) => {
1322
+ return true;
1323
+ },
1324
+ recompute: async (_txn) => {
1325
+ await testIModel.updateElement(b1, e1);
1326
+ await testIModel.updateElement(b1, e2);
1327
+ },
1328
+ });
1329
+ await pushChangeFromB2();
1330
+ clearEvents();
1331
+ const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1332
+ await b1.pushChanges({ description: "push changes on b1" });
1333
+ const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1334
+ chai.expect(geomGuidBeforePull).to.not.equal(geomGuidAfterPull);
1335
+ chai.expect(events.modelGeometryChanged.length).to.equal(4);
1300
1336
  });
1301
- b1.txns.onGeometryChanged.addListener((changes) => {
1302
- events.onGeometryChanged.push(changes);
1337
+ it("onModelGeometryChanged() fired during rebase with non-geometric local change", async () => {
1338
+ const b1 = await testIModel.openBriefcase();
1339
+ const b2 = await testIModel.openBriefcase();
1340
+ const pushChangeFromB2 = async () => {
1341
+ await b2.pullChanges();
1342
+ await testIModel.insertElement(b2);
1343
+ b2.saveChanges();
1344
+ await b2.pushChanges({ description: "insert element on b2" });
1345
+ };
1346
+ const events = {
1347
+ modelGeometryChanged: [],
1348
+ };
1349
+ const getGeometryGuidFromB1 = (modelId) => {
1350
+ const modelProps = b1.models.tryGetModelProps(modelId);
1351
+ return modelProps?.geometryGuid;
1352
+ };
1353
+ const clearEvents = () => {
1354
+ events.modelGeometryChanged = [];
1355
+ };
1356
+ b1.txns.onModelGeometryChanged.addListener((changes) => {
1357
+ events.modelGeometryChanged.push(changes);
1358
+ });
1359
+ clearEvents();
1360
+ b1.txns.rebaser.setCustomHandler({
1361
+ shouldReinstate: (_txn) => {
1362
+ return true;
1363
+ },
1364
+ recompute: async (_txn) => {
1365
+ },
1366
+ });
1367
+ await pushChangeFromB2();
1368
+ await testIModel.insertRecipe2d(b1);
1369
+ b1.saveChanges();
1370
+ clearEvents();
1371
+ const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1372
+ chai.expect(geomGuidBeforePull).is.undefined;
1373
+ await b1.pushChanges({ description: "push changes on b1" });
1374
+ const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1375
+ chai.expect(geomGuidAfterPull).to.exist;
1376
+ chai.expect(events.modelGeometryChanged.length).to.equal(1);
1303
1377
  });
1304
- clearEvents();
1305
- const e1 = await testIModel.insertElement(b1);
1306
- const e2 = await testIModel.insertElement(b1, true);
1307
- chai.expect(e1).to.exist;
1308
- chai.expect(e2).to.exist;
1309
- b1.saveChanges(`insert element ${e1} and ${e2}`);
1310
- chai.expect(events.modelGeometryChanged.length).to.equal(1);
1311
- chai.expect(events.modelGeometryChanged[0].length).to.equal(1);
1312
- chai.expect(events.modelGeometryChanged[0][0].id).to.equal("0x20000000001");
1313
- chai.assert(Guid.isGuid(events.modelGeometryChanged[0][0].guid));
1314
- b1.txns.rebaser.setCustomHandler({
1315
- shouldReinstate: (_txn) => {
1316
- return true;
1317
- },
1318
- recompute: async (_txn) => {
1319
- await testIModel.updateElement(b1, e1);
1320
- await testIModel.updateElement(b1, e2);
1321
- },
1378
+ it("onModelGeometryChanged() fired during rebase with geometric local change", async () => {
1379
+ const b1 = await testIModel.openBriefcase();
1380
+ const b2 = await testIModel.openBriefcase();
1381
+ const pushChangeFromB2 = async () => {
1382
+ await b2.pullChanges();
1383
+ await testIModel.insertRecipe2d(b2);
1384
+ b2.saveChanges();
1385
+ await b2.pushChanges({ description: "insert element on b2" });
1386
+ };
1387
+ const events = {
1388
+ modelGeometryChanged: [],
1389
+ onGeometryChanged: [],
1390
+ };
1391
+ const getGeometryGuidFromB1 = (modelId) => {
1392
+ const modelProps = b1.models.tryGetModelProps(modelId);
1393
+ return modelProps?.geometryGuid;
1394
+ };
1395
+ const clearEvents = () => {
1396
+ events.modelGeometryChanged = [];
1397
+ events.onGeometryChanged = [];
1398
+ };
1399
+ b1.txns.onModelGeometryChanged.addListener((changes) => {
1400
+ events.modelGeometryChanged.push(changes);
1401
+ });
1402
+ b1.txns.onGeometryChanged.addListener((changes) => {
1403
+ events.onGeometryChanged.push(changes);
1404
+ });
1405
+ clearEvents();
1406
+ b1.txns.rebaser.setCustomHandler({
1407
+ shouldReinstate: (_txn) => {
1408
+ return true;
1409
+ },
1410
+ recompute: async (_txn) => {
1411
+ await testIModel.insertElement(b1);
1412
+ },
1413
+ });
1414
+ await pushChangeFromB2();
1415
+ clearEvents();
1416
+ const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1417
+ chai.expect(geomGuidBeforePull).is.undefined;
1418
+ await b1.pushChanges({ description: "push changes on b1" });
1419
+ const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1420
+ chai.expect(geomGuidAfterPull).is.undefined;
1421
+ chai.expect(events.modelGeometryChanged.length).to.equal(0);
1322
1422
  });
1323
- await pushChangeFromB2();
1324
- clearEvents();
1325
- const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1326
- await b1.pushChanges({ description: "push changes on b1" });
1327
- const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1328
- chai.expect(geomGuidBeforePull).to.not.equal(geomGuidAfterPull);
1329
- chai.expect(events.modelGeometryChanged.length).to.equal(4);
1330
- });
1331
- it("onModelGeometryChanged() fired during rebase with non-geometric local change", async () => {
1332
- const b1 = await testIModel.openBriefcase();
1333
- const b2 = await testIModel.openBriefcase();
1334
- const pushChangeFromB2 = async () => {
1423
+ it("rebase multi txn", async () => {
1424
+ const b1 = await testIModel.openBriefcase();
1425
+ const b2 = await testIModel.openBriefcase();
1426
+ const e1 = await testIModel.insertElement(b1);
1427
+ const e2 = await testIModel.insertElement(b1, true);
1428
+ b1.saveChanges();
1429
+ await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
1335
1430
  await b2.pullChanges();
1336
- await testIModel.insertElement(b2);
1431
+ chai.expect(b2.txns.beginMultiTxnOperation()).to.be.equals(DbResult.BE_SQLITE_OK);
1432
+ let elId = await testIModel.insertElement(b2);
1433
+ b2.saveChanges(`insert element ${elId}`);
1434
+ elId = await testIModel.insertElement(b2);
1435
+ b2.saveChanges(`insert element ${elId}`);
1436
+ elId = await testIModel.insertElement(b2);
1437
+ b2.saveChanges(`insert element ${elId}`);
1438
+ chai.expect(b2.txns.endMultiTxnOperation()).to.be.equals(DbResult.BE_SQLITE_OK);
1337
1439
  b2.saveChanges();
1338
- await b2.pushChanges({ description: "insert element on b2" });
1339
- };
1340
- const events = {
1341
- modelGeometryChanged: [],
1342
- };
1343
- const getGeometryGuidFromB1 = (modelId) => {
1344
- const modelProps = b1.models.tryGetModelProps(modelId);
1345
- return modelProps?.geometryGuid;
1346
- };
1347
- const clearEvents = () => {
1348
- events.modelGeometryChanged = [];
1349
- };
1350
- b1.txns.onModelGeometryChanged.addListener((changes) => {
1351
- events.modelGeometryChanged.push(changes);
1440
+ elId = await testIModel.insertElement(b2);
1441
+ b2.saveChanges(`insert element ${elId}`);
1442
+ let txns = Array.from(b2.txns.queryTxns());
1443
+ chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1444
+ chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1445
+ chai.expect(txns[0].sessionId).to.be.equals(1);
1446
+ chai.expect(txns[0].grouped).to.be.equals(false);
1447
+ chai.expect(txns[0].reversed).to.be.equals(false);
1448
+ chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1449
+ chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1450
+ chai.expect(txns[1].sessionId).to.be.equals(1);
1451
+ chai.expect(txns[1].grouped).to.be.equals(true);
1452
+ chai.expect(txns[1].reversed).to.be.equals(false);
1453
+ chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1454
+ chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1455
+ chai.expect(txns[2].sessionId).to.be.equals(1);
1456
+ chai.expect(txns[2].grouped).to.be.equals(true);
1457
+ chai.expect(txns[2].reversed).to.be.equals(false);
1458
+ chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1459
+ chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1460
+ chai.expect(txns[3].type).to.be.equals("Data");
1461
+ chai.expect(txns[3].grouped).to.be.equals(false);
1462
+ chai.expect(txns[3].reversed).to.be.equals(false);
1463
+ // reverse single txn 0x100000003
1464
+ chai.expect(b2.txns.reverseSingleTxn()).to.be.equals(DbResult.BE_SQLITE_OK);
1465
+ txns = Array.from(b2.txns.queryTxns());
1466
+ chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1467
+ chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1468
+ chai.expect(txns[0].sessionId).to.be.equals(1);
1469
+ chai.expect(txns[0].grouped).to.be.equals(false);
1470
+ chai.expect(txns[0].reversed).to.be.equals(false);
1471
+ chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1472
+ chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1473
+ chai.expect(txns[1].sessionId).to.be.equals(1);
1474
+ chai.expect(txns[1].grouped).to.be.equals(true);
1475
+ chai.expect(txns[1].reversed).to.be.equals(false);
1476
+ chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1477
+ chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1478
+ chai.expect(txns[2].sessionId).to.be.equals(1);
1479
+ chai.expect(txns[2].grouped).to.be.equals(true);
1480
+ chai.expect(txns[2].reversed).to.be.equals(false);
1481
+ chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1482
+ chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1483
+ chai.expect(txns[3].type).to.be.equals("Data");
1484
+ chai.expect(txns[3].grouped).to.be.equals(false);
1485
+ chai.expect(txns[3].reversed).to.be.equals(true);
1486
+ // reverse multi txn. should reverse 0x100000000, 0x100000001 & 0x100000002
1487
+ chai.expect(b2.txns.reverseSingleTxn()).to.be.equals(DbResult.BE_SQLITE_OK);
1488
+ txns = Array.from(b2.txns.queryTxns());
1489
+ chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1490
+ chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1491
+ chai.expect(txns[0].sessionId).to.be.equals(1);
1492
+ chai.expect(txns[0].grouped).to.be.equals(false);
1493
+ chai.expect(txns[0].reversed).to.be.equals(true);
1494
+ chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1495
+ chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1496
+ chai.expect(txns[1].sessionId).to.be.equals(1);
1497
+ chai.expect(txns[1].grouped).to.be.equals(true);
1498
+ chai.expect(txns[1].reversed).to.be.equals(true);
1499
+ chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1500
+ chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1501
+ chai.expect(txns[2].sessionId).to.be.equals(1);
1502
+ chai.expect(txns[2].grouped).to.be.equals(true);
1503
+ chai.expect(txns[2].reversed).to.be.equals(true);
1504
+ chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1505
+ chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1506
+ chai.expect(txns[3].type).to.be.equals("Data");
1507
+ chai.expect(txns[3].grouped).to.be.equals(false);
1508
+ chai.expect(txns[3].reversed).to.be.equals(true);
1509
+ // reinstate the transaction
1510
+ chai.expect(b2.txns.isRedoPossible).to.be.equals(true);
1511
+ chai.expect(b2.txns.reinstateTxn()).to.be.equals(DbResult.BE_SQLITE_OK);
1512
+ txns = Array.from(b2.txns.queryTxns());
1513
+ chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1514
+ chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1515
+ chai.expect(txns[0].sessionId).to.be.equals(1);
1516
+ chai.expect(txns[0].grouped).to.be.equals(false);
1517
+ chai.expect(txns[0].reversed).to.be.equals(false);
1518
+ chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1519
+ chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1520
+ chai.expect(txns[1].sessionId).to.be.equals(1);
1521
+ chai.expect(txns[1].grouped).to.be.equals(true);
1522
+ chai.expect(txns[1].reversed).to.be.equals(false);
1523
+ chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1524
+ chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1525
+ chai.expect(txns[2].sessionId).to.be.equals(1);
1526
+ chai.expect(txns[2].grouped).to.be.equals(true);
1527
+ chai.expect(txns[2].reversed).to.be.equals(false);
1528
+ chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1529
+ chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1530
+ chai.expect(txns[3].type).to.be.equals("Data");
1531
+ chai.expect(txns[3].grouped).to.be.equals(false);
1532
+ chai.expect(txns[3].reversed).to.be.equals(true);
1533
+ // reinstate the transaction
1534
+ chai.expect(b2.txns.isRedoPossible).to.be.equals(true);
1535
+ chai.expect(b2.txns.reinstateTxn()).to.be.equals(DbResult.BE_SQLITE_OK);
1536
+ txns = Array.from(b2.txns.queryTxns());
1537
+ chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1538
+ chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1539
+ chai.expect(txns[0].sessionId).to.be.equals(1);
1540
+ chai.expect(txns[0].grouped).to.be.equals(false);
1541
+ chai.expect(txns[0].reversed).to.be.equals(false);
1542
+ chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1543
+ chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1544
+ chai.expect(txns[1].sessionId).to.be.equals(1);
1545
+ chai.expect(txns[1].grouped).to.be.equals(true);
1546
+ chai.expect(txns[1].reversed).to.be.equals(false);
1547
+ chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1548
+ chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1549
+ chai.expect(txns[2].sessionId).to.be.equals(1);
1550
+ chai.expect(txns[2].grouped).to.be.equals(true);
1551
+ chai.expect(txns[2].reversed).to.be.equals(false);
1552
+ chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1553
+ chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1554
+ chai.expect(txns[3].type).to.be.equals("Data");
1555
+ chai.expect(txns[3].grouped).to.be.equals(false);
1556
+ chai.expect(txns[3].reversed).to.be.equals(false);
1557
+ chai.expect(b2.txns.isRedoPossible).to.be.equals(false);
1558
+ await testIModel.updateElement(b1, e1);
1559
+ await testIModel.updateElement(b1, e2, true);
1560
+ b1.saveChanges();
1561
+ await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
1562
+ const recomputeTxnIds = [];
1563
+ b2.txns.rebaser.setCustomHandler({
1564
+ shouldReinstate: (_txn) => {
1565
+ return true;
1566
+ },
1567
+ recompute: async (txn) => {
1568
+ recomputeTxnIds.push(txn.id);
1569
+ },
1570
+ });
1571
+ await b2.pullChanges();
1572
+ chai.expect(recomputeTxnIds).to.deep.equals([
1573
+ "0x100000000",
1574
+ "0x100000001",
1575
+ "0x100000002",
1576
+ "0x100000003",
1577
+ ]);
1352
1578
  });
1353
- clearEvents();
1354
- b1.txns.rebaser.setCustomHandler({
1355
- shouldReinstate: (_txn) => {
1356
- return true;
1357
- },
1358
- recompute: async (_txn) => {
1359
- },
1579
+ it("abort rebase should discard in-memory changes", async () => {
1580
+ const b1 = await testIModel.openBriefcase();
1581
+ const b2 = await testIModel.openBriefcase();
1582
+ const e1 = await testIModel.insertElement(b1);
1583
+ b1.saveChanges();
1584
+ await b1.pushChanges({ description: `${e1} inserted` });
1585
+ const e2 = await testIModel.insertElement(b2);
1586
+ chai.expect(e2).to.exist;
1587
+ let e3 = "";
1588
+ b2.saveChanges();
1589
+ b2.txns.rebaser.setCustomHandler({
1590
+ shouldReinstate: (_txnProps) => {
1591
+ return true;
1592
+ },
1593
+ recompute: async (_txnProps) => {
1594
+ chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
1595
+ e3 = await testIModel.insertElement(b2);
1596
+ throw new Error("Rebase failed");
1597
+ },
1598
+ });
1599
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.undefined;
1600
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1601
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined;
1602
+ chai.expect(b2.changeset.index).to.equals(2);
1603
+ await chai.expect(b2.pullChanges()).to.be.rejectedWith("Rebase failed");
1604
+ chai.expect(b2.changeset.index).to.equals(3);
1605
+ chai.expect(e3).to.exist;
1606
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.exist; // came from incoming changeset
1607
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.undefined; // was local change and reversed during rebase.
1608
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined; // was insert by reCompute() but due to exception the rebase attempt was abandoned.
1609
+ chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
1610
+ // make temp change
1611
+ b2.saveFileProperty({ name: "test", namespace: "testNamespace" }, "testValue");
1612
+ chai.expect(b2.txns.hasUnsavedChanges).is.true;
1613
+ chai.expect(b2.txns.rebaser.canAbort()).is.true;
1614
+ // should abort with unsaved local changes
1615
+ await b2.txns.rebaser.abort();
1616
+ chai.expect(b2.changeset.index).to.equals(2);
1617
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.undefined; // reset briefcase should move tip back to where it was before pull
1618
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.exist; // abort should put back e2 which was only change at the time of pull
1619
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined; // add by rebase so should not exist either
1620
+ chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.false;
1360
1621
  });
1361
- await pushChangeFromB2();
1362
- await testIModel.insertRecipe2d(b1);
1363
- b1.saveChanges();
1364
- clearEvents();
1365
- const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1366
- chai.expect(geomGuidBeforePull).is.undefined;
1367
- await b1.pushChanges({ description: "push changes on b1" });
1368
- const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1369
- chai.expect(geomGuidAfterPull).to.exist;
1370
- chai.expect(events.modelGeometryChanged.length).to.equal(1);
1371
- });
1372
- it("onModelGeometryChanged() fired during rebase with geometric local change", async () => {
1373
- const b1 = await testIModel.openBriefcase();
1374
- const b2 = await testIModel.openBriefcase();
1375
- const pushChangeFromB2 = async () => {
1622
+ it("two users insert same ElementGroupsMembers instance", async () => {
1623
+ const b1 = await testIModel.openBriefcase();
1624
+ const b2 = await testIModel.openBriefcase();
1625
+ const e1 = await testIModel.insertElement(b1);
1626
+ const e2 = await testIModel.insertElement(b1);
1627
+ const e3 = await testIModel.insertElement(b1);
1628
+ const e4 = await testIModel.insertElement(b1);
1629
+ chai.expect(e1).to.exist;
1630
+ chai.expect(e2).to.exist;
1631
+ chai.expect(e3).to.exist;
1632
+ chai.expect(e4).to.exist;
1633
+ b1.saveChanges();
1634
+ await b1.pushChanges({ description: `inserted elements` });
1376
1635
  await b2.pullChanges();
1377
- await testIModel.insertRecipe2d(b2);
1636
+ const r1 = b1.relationships.insertInstance(ElementGroupsMembers.create(b1, e1, e2, 10).toJSON());
1637
+ const r2 = b1.relationships.insertInstance(ElementGroupsMembers.create(b1, e3, e4, 20).toJSON());
1638
+ chai.expect(r1).to.exist;
1639
+ chai.expect(r2).to.exist;
1640
+ b1.saveChanges();
1641
+ await b1.pushChanges({ description: `inserted relationship` });
1642
+ const r3 = b2.relationships.insertInstance(ElementGroupsMembers.create(b2, e1, e2, 10).toJSON());
1643
+ const r4 = b2.relationships.insertInstance(ElementGroupsMembers.create(b2, e3, e4, 20).toJSON());
1644
+ chai.expect(r3).to.exist;
1645
+ chai.expect(r4).to.exist;
1378
1646
  b2.saveChanges();
1379
- await b2.pushChanges({ description: "insert element on b2" });
1380
- };
1381
- const events = {
1382
- modelGeometryChanged: [],
1383
- onGeometryChanged: [],
1384
- };
1385
- const getGeometryGuidFromB1 = (modelId) => {
1386
- const modelProps = b1.models.tryGetModelProps(modelId);
1387
- return modelProps?.geometryGuid;
1388
- };
1389
- const clearEvents = () => {
1390
- events.modelGeometryChanged = [];
1391
- events.onGeometryChanged = [];
1392
- };
1393
- b1.txns.onModelGeometryChanged.addListener((changes) => {
1394
- events.modelGeometryChanged.push(changes);
1395
- });
1396
- b1.txns.onGeometryChanged.addListener((changes) => {
1397
- events.onGeometryChanged.push(changes);
1398
- });
1399
- clearEvents();
1400
- b1.txns.rebaser.setCustomHandler({
1401
- shouldReinstate: (_txn) => {
1402
- return true;
1403
- },
1404
- recompute: async (_txn) => {
1405
- await testIModel.insertElement(b1);
1406
- },
1407
- });
1408
- await pushChangeFromB2();
1409
- clearEvents();
1410
- const geomGuidBeforePull = getGeometryGuidFromB1("0x20000000001");
1411
- chai.expect(geomGuidBeforePull).is.undefined;
1412
- await b1.pushChanges({ description: "push changes on b1" });
1413
- const geomGuidAfterPull = getGeometryGuidFromB1("0x20000000001");
1414
- chai.expect(geomGuidAfterPull).is.undefined;
1415
- chai.expect(events.modelGeometryChanged.length).to.equal(0);
1416
- });
1417
- it("rebase multi txn", async () => {
1418
- const b1 = await testIModel.openBriefcase();
1419
- const b2 = await testIModel.openBriefcase();
1420
- const e1 = await testIModel.insertElement(b1);
1421
- const e2 = await testIModel.insertElement(b1, true);
1422
- b1.saveChanges();
1423
- await b1.pushChanges({ description: "insert element 1 direct and 1 indirect" });
1424
- await b2.pullChanges();
1425
- chai.expect(b2.txns.beginMultiTxnOperation()).to.be.equals(DbResult.BE_SQLITE_OK);
1426
- let elId = await testIModel.insertElement(b2);
1427
- b2.saveChanges(`insert element ${elId}`);
1428
- elId = await testIModel.insertElement(b2);
1429
- b2.saveChanges(`insert element ${elId}`);
1430
- elId = await testIModel.insertElement(b2);
1431
- b2.saveChanges(`insert element ${elId}`);
1432
- chai.expect(b2.txns.endMultiTxnOperation()).to.be.equals(DbResult.BE_SQLITE_OK);
1433
- b2.saveChanges();
1434
- elId = await testIModel.insertElement(b2);
1435
- b2.saveChanges(`insert element ${elId}`);
1436
- let txns = Array.from(b2.txns.queryTxns());
1437
- chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1438
- chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1439
- chai.expect(txns[0].sessionId).to.be.equals(1);
1440
- chai.expect(txns[0].grouped).to.be.equals(false);
1441
- chai.expect(txns[0].reversed).to.be.equals(false);
1442
- chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1443
- chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1444
- chai.expect(txns[1].sessionId).to.be.equals(1);
1445
- chai.expect(txns[1].grouped).to.be.equals(true);
1446
- chai.expect(txns[1].reversed).to.be.equals(false);
1447
- chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1448
- chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1449
- chai.expect(txns[2].sessionId).to.be.equals(1);
1450
- chai.expect(txns[2].grouped).to.be.equals(true);
1451
- chai.expect(txns[2].reversed).to.be.equals(false);
1452
- chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1453
- chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1454
- chai.expect(txns[3].type).to.be.equals("Data");
1455
- chai.expect(txns[3].grouped).to.be.equals(false);
1456
- chai.expect(txns[3].reversed).to.be.equals(false);
1457
- // reverse single txn 0x100000003
1458
- chai.expect(b2.txns.reverseSingleTxn()).to.be.equals(DbResult.BE_SQLITE_OK);
1459
- txns = Array.from(b2.txns.queryTxns());
1460
- chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1461
- chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1462
- chai.expect(txns[0].sessionId).to.be.equals(1);
1463
- chai.expect(txns[0].grouped).to.be.equals(false);
1464
- chai.expect(txns[0].reversed).to.be.equals(false);
1465
- chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1466
- chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1467
- chai.expect(txns[1].sessionId).to.be.equals(1);
1468
- chai.expect(txns[1].grouped).to.be.equals(true);
1469
- chai.expect(txns[1].reversed).to.be.equals(false);
1470
- chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1471
- chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1472
- chai.expect(txns[2].sessionId).to.be.equals(1);
1473
- chai.expect(txns[2].grouped).to.be.equals(true);
1474
- chai.expect(txns[2].reversed).to.be.equals(false);
1475
- chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1476
- chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1477
- chai.expect(txns[3].type).to.be.equals("Data");
1478
- chai.expect(txns[3].grouped).to.be.equals(false);
1479
- chai.expect(txns[3].reversed).to.be.equals(true);
1480
- // reverse multi txn. should reverse 0x100000000, 0x100000001 & 0x100000002
1481
- chai.expect(b2.txns.reverseSingleTxn()).to.be.equals(DbResult.BE_SQLITE_OK);
1482
- txns = Array.from(b2.txns.queryTxns());
1483
- chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1484
- chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1485
- chai.expect(txns[0].sessionId).to.be.equals(1);
1486
- chai.expect(txns[0].grouped).to.be.equals(false);
1487
- chai.expect(txns[0].reversed).to.be.equals(true);
1488
- chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1489
- chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1490
- chai.expect(txns[1].sessionId).to.be.equals(1);
1491
- chai.expect(txns[1].grouped).to.be.equals(true);
1492
- chai.expect(txns[1].reversed).to.be.equals(true);
1493
- chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1494
- chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1495
- chai.expect(txns[2].sessionId).to.be.equals(1);
1496
- chai.expect(txns[2].grouped).to.be.equals(true);
1497
- chai.expect(txns[2].reversed).to.be.equals(true);
1498
- chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1499
- chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1500
- chai.expect(txns[3].type).to.be.equals("Data");
1501
- chai.expect(txns[3].grouped).to.be.equals(false);
1502
- chai.expect(txns[3].reversed).to.be.equals(true);
1503
- // reinstate the transaction
1504
- chai.expect(b2.txns.isRedoPossible).to.be.equals(true);
1505
- chai.expect(b2.txns.reinstateTxn()).to.be.equals(DbResult.BE_SQLITE_OK);
1506
- txns = Array.from(b2.txns.queryTxns());
1507
- chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1508
- chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1509
- chai.expect(txns[0].sessionId).to.be.equals(1);
1510
- chai.expect(txns[0].grouped).to.be.equals(false);
1511
- chai.expect(txns[0].reversed).to.be.equals(false);
1512
- chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1513
- chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1514
- chai.expect(txns[1].sessionId).to.be.equals(1);
1515
- chai.expect(txns[1].grouped).to.be.equals(true);
1516
- chai.expect(txns[1].reversed).to.be.equals(false);
1517
- chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1518
- chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1519
- chai.expect(txns[2].sessionId).to.be.equals(1);
1520
- chai.expect(txns[2].grouped).to.be.equals(true);
1521
- chai.expect(txns[2].reversed).to.be.equals(false);
1522
- chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1523
- chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1524
- chai.expect(txns[3].type).to.be.equals("Data");
1525
- chai.expect(txns[3].grouped).to.be.equals(false);
1526
- chai.expect(txns[3].reversed).to.be.equals(true);
1527
- // reinstate the transaction
1528
- chai.expect(b2.txns.isRedoPossible).to.be.equals(true);
1529
- chai.expect(b2.txns.reinstateTxn()).to.be.equals(DbResult.BE_SQLITE_OK);
1530
- txns = Array.from(b2.txns.queryTxns());
1531
- chai.expect(txns[0].id).to.be.equals("0x100000000"); // 1st after beginMultiTxnOperation()
1532
- chai.expect(txns[0].props.description).to.be.equals("insert element 0x40000000001");
1533
- chai.expect(txns[0].sessionId).to.be.equals(1);
1534
- chai.expect(txns[0].grouped).to.be.equals(false);
1535
- chai.expect(txns[0].reversed).to.be.equals(false);
1536
- chai.expect(txns[1].id).to.be.equals("0x100000001"); // 2nd after beginMultiTxnOperation()
1537
- chai.expect(txns[1].props.description).to.be.equals("insert element 0x40000000002");
1538
- chai.expect(txns[1].sessionId).to.be.equals(1);
1539
- chai.expect(txns[1].grouped).to.be.equals(true);
1540
- chai.expect(txns[1].reversed).to.be.equals(false);
1541
- chai.expect(txns[2].id).to.be.equals("0x100000002"); // 3rd after beginMultiTxnOperation() & before endMultiTxnOperation()
1542
- chai.expect(txns[2].props.description).to.be.equals("insert element 0x40000000003");
1543
- chai.expect(txns[2].sessionId).to.be.equals(1);
1544
- chai.expect(txns[2].grouped).to.be.equals(true);
1545
- chai.expect(txns[2].reversed).to.be.equals(false);
1546
- chai.expect(txns[3].id).to.be.equals("0x100000003"); // 4th after endMultiTxnOperation()
1547
- chai.expect(txns[3].props.description).to.be.equals("insert element 0x40000000004");
1548
- chai.expect(txns[3].type).to.be.equals("Data");
1549
- chai.expect(txns[3].grouped).to.be.equals(false);
1550
- chai.expect(txns[3].reversed).to.be.equals(false);
1551
- chai.expect(b2.txns.isRedoPossible).to.be.equals(false);
1552
- await testIModel.updateElement(b1, e1);
1553
- await testIModel.updateElement(b1, e2, true);
1554
- b1.saveChanges();
1555
- await b1.pushChanges({ description: "update element 1 direct and 1 indirect" });
1556
- const recomputeTxnIds = [];
1557
- b2.txns.rebaser.setCustomHandler({
1558
- shouldReinstate: (_txn) => {
1559
- return true;
1560
- },
1561
- recompute: async (txn) => {
1562
- recomputeTxnIds.push(txn.id);
1563
- },
1564
- });
1565
- await b2.pullChanges();
1566
- chai.expect(recomputeTxnIds).to.deep.equals([
1567
- "0x100000000",
1568
- "0x100000001",
1569
- "0x100000002",
1570
- "0x100000003",
1571
- ]);
1572
- });
1573
- it("abort rebase should discard in-memory changes", async () => {
1574
- const b1 = await testIModel.openBriefcase();
1575
- const b2 = await testIModel.openBriefcase();
1576
- const e1 = await testIModel.insertElement(b1);
1577
- b1.saveChanges();
1578
- await b1.pushChanges({ description: `${e1} inserted` });
1579
- const e2 = await testIModel.insertElement(b2);
1580
- chai.expect(e2).to.exist;
1581
- let e3 = "";
1582
- b2.saveChanges();
1583
- b2.txns.rebaser.setCustomHandler({
1584
- shouldReinstate: (_txnProps) => {
1585
- return true;
1586
- },
1587
- recompute: async (_txnProps) => {
1588
- chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
1589
- e3 = await testIModel.insertElement(b2);
1590
- throw new Error("Rebase failed");
1591
- },
1592
- });
1593
- chai.expect(b2.elements.tryGetElementProps(e1)).to.undefined;
1594
- chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1595
- chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined;
1596
- chai.expect(b2.changeset.index).to.equals(2);
1597
- await chai.expect(b2.pullChanges()).to.be.rejectedWith("Rebase failed");
1598
- chai.expect(b2.changeset.index).to.equals(3);
1599
- chai.expect(e3).to.exist;
1600
- chai.expect(b2.elements.tryGetElementProps(e1)).to.exist; // came from incoming changeset
1601
- chai.expect(b2.elements.tryGetElementProps(e2)).to.undefined; // was local change and reversed during rebase.
1602
- chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined; // was insert by reCompute() but due to exception the rebase attempt was abandoned.
1603
- chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
1604
- // make temp change
1605
- b2.saveFileProperty({ name: "test", namespace: "testNamespace" }, "testValue");
1606
- chai.expect(b2.txns.hasUnsavedChanges).is.true;
1607
- chai.expect(b2.txns.rebaser.canAbort()).is.true;
1608
- // should abort with unsaved local changes
1609
- await b2.txns.rebaser.abort();
1610
- chai.expect(b2.changeset.index).to.equals(2);
1611
- chai.expect(b2.elements.tryGetElementProps(e1)).to.undefined; // reset briefcase should move tip back to where it was before pull
1612
- chai.expect(b2.elements.tryGetElementProps(e2)).to.exist; // abort should put back e2 which was only change at the time of pull
1613
- chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined; // add by rebase so should not exist either
1614
- chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.false;
1615
- });
1616
- it("two users insert same ElementGroupsMembers instance", async () => {
1617
- const b1 = await testIModel.openBriefcase();
1618
- const b2 = await testIModel.openBriefcase();
1619
- const e1 = await testIModel.insertElement(b1);
1620
- const e2 = await testIModel.insertElement(b1);
1621
- const e3 = await testIModel.insertElement(b1);
1622
- const e4 = await testIModel.insertElement(b1);
1623
- chai.expect(e1).to.exist;
1624
- chai.expect(e2).to.exist;
1625
- chai.expect(e3).to.exist;
1626
- chai.expect(e4).to.exist;
1627
- b1.saveChanges();
1628
- await b1.pushChanges({ description: `inserted elements` });
1629
- await b2.pullChanges();
1630
- const r1 = b1.relationships.insertInstance(ElementGroupsMembers.create(b1, e1, e2, 10).toJSON());
1631
- const r2 = b1.relationships.insertInstance(ElementGroupsMembers.create(b1, e3, e4, 20).toJSON());
1632
- chai.expect(r1).to.exist;
1633
- chai.expect(r2).to.exist;
1634
- b1.saveChanges();
1635
- await b1.pushChanges({ description: `inserted relationship` });
1636
- const r3 = b2.relationships.insertInstance(ElementGroupsMembers.create(b2, e1, e2, 10).toJSON());
1637
- const r4 = b2.relationships.insertInstance(ElementGroupsMembers.create(b2, e3, e4, 20).toJSON());
1638
- chai.expect(r3).to.exist;
1639
- chai.expect(r4).to.exist;
1640
- b2.saveChanges();
1641
- await b2.pushChanges({ description: `inserted relationship` });
1642
- await b2.pullChanges();
1643
- chai.expect(b2.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r1)).to.exist;
1644
- chai.expect(b2.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r2)).to.exist;
1645
- chai.expect(b2.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r3)).to.be.undefined;
1646
- chai.expect(b2.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r4)).to.be.undefined;
1647
- chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r1)).to.exist;
1648
- chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r2)).to.exist;
1649
- chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r3)).to.be.undefined;
1650
- chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r4)).to.be.undefined;
1651
- });
1652
- it("one user update and other delete the link table relationships", async () => {
1653
- const b1 = await testIModel.openBriefcase();
1654
- const b2 = await testIModel.openBriefcase();
1655
- const e1 = await testIModel.insertElement(b1);
1656
- const e2 = await testIModel.insertElement(b1);
1657
- const r1 = b1.relationships.insertInstance(ElementGroupsMembers.create(b1, e1, e2, 10).toJSON());
1658
- const r2 = b1.relationships.insertInstance(ElementGroupsMembers.create(b1, e1, e2, 20).toJSON());
1659
- chai.expect(e1).to.exist;
1660
- chai.expect(e2).to.exist;
1661
- chai.expect(r1).to.exist;
1662
- chai.expect(r2).to.exist;
1663
- b1.saveChanges();
1664
- await b1.pushChanges({ description: `inserted elements and relationship` });
1665
- await b2.pullChanges();
1666
- // intentionally change memberPriority to 10 for which there is another relationship already exists.
1667
- chai.expect(() => b2.relationships.updateInstance({
1668
- id: r1,
1669
- classFullName: ElementGroupsMembers.classFullName,
1670
- sourceId: e1,
1671
- targetId: e2,
1672
- memberPriority: 20
1673
- })).to.throws("error updating relationship");
1674
- b2.relationships.updateInstance({
1675
- id: r1,
1676
- classFullName: ElementGroupsMembers.classFullName,
1677
- sourceId: e1,
1678
- targetId: e2,
1679
- memberPriority: 60
1680
- });
1681
- b1.relationships.deleteInstance({
1682
- id: r1,
1683
- classFullName: ElementGroupsMembers.classFullName,
1684
- sourceId: e1,
1685
- targetId: e2
1647
+ await b2.pushChanges({ description: `inserted relationship` });
1648
+ await b2.pullChanges();
1649
+ chai.expect(b2.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r1)).to.exist;
1650
+ chai.expect(b2.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r2)).to.exist;
1651
+ chai.expect(b2.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r3)).to.be.undefined;
1652
+ chai.expect(b2.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r4)).to.be.undefined;
1653
+ chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r1)).to.exist;
1654
+ chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r2)).to.exist;
1655
+ chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r3)).to.be.undefined;
1656
+ chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r4)).to.be.undefined;
1686
1657
  });
1687
- b1.saveChanges();
1688
- await b1.pushChanges({ description: `deleted relationship` });
1689
- b2.saveChanges();
1690
- await b2.pushChanges({ description: `updated relationship` });
1691
- await b2.pullChanges();
1692
- await b1.pullChanges();
1693
- chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r1)).to.be.undefined;
1694
- chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r2)).to.exist;
1695
- });
1696
- it("aborting rebaser in middle of rebase session where at least one txn is successfully rebased (used to cause crash)", async () => {
1697
- const b1 = await testIModel.openBriefcase();
1698
- const b2 = await testIModel.openBriefcase();
1699
- const createTxn = async (b) => {
1700
- const id = await testIModel.insertElement(b);
1701
- chai.expect(id).is.exist;
1702
- b.saveChanges(`created element ${id}`);
1703
- return id;
1704
- };
1705
- const e1 = await createTxn(b1);
1706
- await b1.pushChanges({ description: `${e1} inserted` });
1707
- const e2 = await createTxn(b2);
1708
- const e3 = await createTxn(b2);
1709
- const e4 = await createTxn(b2);
1710
- let e5 = "";
1711
- b2.txns.rebaser.setCustomHandler({
1712
- shouldReinstate: (_txnProps) => {
1713
- return true;
1714
- },
1715
- recompute: async (txnProps) => {
1716
- chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
1717
- if (txnProps.id === "0x100000001") {
1718
- e5 = await testIModel.insertElement(b2);
1719
- throw new Error("Rebase failed");
1720
- }
1721
- },
1658
+ it("one user update and other delete the link table relationships", async () => {
1659
+ const b1 = await testIModel.openBriefcase();
1660
+ const b2 = await testIModel.openBriefcase();
1661
+ const e1 = await testIModel.insertElement(b1);
1662
+ const e2 = await testIModel.insertElement(b1);
1663
+ const r1 = b1.relationships.insertInstance(ElementGroupsMembers.create(b1, e1, e2, 10).toJSON());
1664
+ const r2 = b1.relationships.insertInstance(ElementGroupsMembers.create(b1, e1, e2, 20).toJSON());
1665
+ chai.expect(e1).to.exist;
1666
+ chai.expect(e2).to.exist;
1667
+ chai.expect(r1).to.exist;
1668
+ chai.expect(r2).to.exist;
1669
+ b1.saveChanges();
1670
+ await b1.pushChanges({ description: `inserted elements and relationship` });
1671
+ await b2.pullChanges();
1672
+ // intentionally change memberPriority to 10 for which there is another relationship already exists.
1673
+ chai.expect(() => b2.relationships.updateInstance({
1674
+ id: r1,
1675
+ classFullName: ElementGroupsMembers.classFullName,
1676
+ sourceId: e1,
1677
+ targetId: e2,
1678
+ memberPriority: 20
1679
+ })).to.throws("error updating relationship");
1680
+ b2.relationships.updateInstance({
1681
+ id: r1,
1682
+ classFullName: ElementGroupsMembers.classFullName,
1683
+ sourceId: e1,
1684
+ targetId: e2,
1685
+ memberPriority: 60
1686
+ });
1687
+ b1.relationships.deleteInstance({
1688
+ id: r1,
1689
+ classFullName: ElementGroupsMembers.classFullName,
1690
+ sourceId: e1,
1691
+ targetId: e2
1692
+ });
1693
+ b1.saveChanges();
1694
+ await b1.pushChanges({ description: `deleted relationship` });
1695
+ b2.saveChanges();
1696
+ await b2.pushChanges({ description: `updated relationship` });
1697
+ await b2.pullChanges();
1698
+ await b1.pullChanges();
1699
+ chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r1)).to.be.undefined;
1700
+ chai.expect(b1.relationships.tryGetInstanceProps(ElementGroupsMembers.classFullName, r2)).to.exist;
1722
1701
  });
1723
- chai.expect(b2.elements.tryGetElementProps(e1)).to.be.undefined;
1724
- chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1725
- chai.expect(b2.elements.tryGetElementProps(e3)).to.exist;
1726
- chai.expect(b2.elements.tryGetElementProps(e4)).to.exist;
1727
- chai.expect(b2.elements.tryGetElementProps(e5)).to.be.undefined;
1728
- chai.expect(b2.changeset.index).to.equals(2);
1729
- await chai.expect(b2.pullChanges()).to.be.rejectedWith("Rebase failed");
1730
- await chai.expect(createTxn(b2)).to.be.rejectedWith(`Could not save changes (created element 0x40000000004)`);
1731
- chai.expect(b2.changeset.index).to.equals(3);
1732
- chai.expect(e3).to.exist;
1733
- chai.expect(b2.elements.tryGetElementProps(e1)).to.exist;
1734
- chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1735
- chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined;
1736
- chai.expect(b2.elements.tryGetElementProps(e4)).to.undefined;
1737
- chai.expect(b2.elements.tryGetElementProps(e5)).to.exist;
1738
- chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
1739
- // make temp change
1740
- b2.saveFileProperty({ name: "test", namespace: "testNamespace" }, "testValue");
1741
- chai.expect(b2.txns.hasUnsavedChanges).is.true;
1742
- chai.expect(b2.txns.rebaser.canAbort()).is.true;
1743
- // should abort with unsaved local changes
1744
- await b2.txns.rebaser.abort();
1745
- chai.expect(b2.changeset.index).to.equals(2);
1746
- chai.expect(b2.elements.tryGetElementProps(e1)).to.be.undefined;
1747
- chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1748
- chai.expect(b2.elements.tryGetElementProps(e3)).to.exist;
1749
- chai.expect(b2.elements.tryGetElementProps(e4)).to.exist;
1750
- chai.expect(b2.elements.tryGetElementProps(e5)).to.be.undefined;
1751
- chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.false;
1752
- b2.txns.rebaser.setCustomHandler({
1753
- shouldReinstate: (_txnProps) => {
1754
- return true;
1755
- },
1756
- recompute: async (_txnProps) => { },
1702
+ it("aborting rebaser in middle of rebase session where at least one txn is successfully rebased (used to cause crash)", async () => {
1703
+ const b1 = await testIModel.openBriefcase();
1704
+ const b2 = await testIModel.openBriefcase();
1705
+ const createTxn = async (b) => {
1706
+ const id = await testIModel.insertElement(b);
1707
+ chai.expect(id).is.exist;
1708
+ b.saveChanges(`created element ${id}`);
1709
+ return id;
1710
+ };
1711
+ const e1 = await createTxn(b1);
1712
+ await b1.pushChanges({ description: `${e1} inserted` });
1713
+ const e2 = await createTxn(b2);
1714
+ const e3 = await createTxn(b2);
1715
+ const e4 = await createTxn(b2);
1716
+ let e5 = "";
1717
+ b2.txns.rebaser.setCustomHandler({
1718
+ shouldReinstate: (_txnProps) => {
1719
+ return true;
1720
+ },
1721
+ recompute: async (txnProps) => {
1722
+ chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
1723
+ if (txnProps.id === "0x100000001") {
1724
+ e5 = await testIModel.insertElement(b2);
1725
+ throw new Error("Rebase failed");
1726
+ }
1727
+ },
1728
+ });
1729
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.be.undefined;
1730
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1731
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.exist;
1732
+ chai.expect(b2.elements.tryGetElementProps(e4)).to.exist;
1733
+ chai.expect(b2.elements.tryGetElementProps(e5)).to.be.undefined;
1734
+ chai.expect(b2.changeset.index).to.equals(2);
1735
+ await chai.expect(b2.pullChanges()).to.be.rejectedWith("Rebase failed");
1736
+ await chai.expect(createTxn(b2)).to.be.rejectedWith(`Could not save changes (created element 0x40000000004)`);
1737
+ chai.expect(b2.changeset.index).to.equals(3);
1738
+ chai.expect(e3).to.exist;
1739
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.exist;
1740
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1741
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.undefined;
1742
+ chai.expect(b2.elements.tryGetElementProps(e4)).to.undefined;
1743
+ chai.expect(b2.elements.tryGetElementProps(e5)).to.exist;
1744
+ chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.true;
1745
+ // make temp change
1746
+ b2.saveFileProperty({ name: "test", namespace: "testNamespace" }, "testValue");
1747
+ chai.expect(b2.txns.hasUnsavedChanges).is.true;
1748
+ chai.expect(b2.txns.rebaser.canAbort()).is.true;
1749
+ // should abort with unsaved local changes
1750
+ await b2.txns.rebaser.abort();
1751
+ chai.expect(b2.changeset.index).to.equals(2);
1752
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.be.undefined;
1753
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1754
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.exist;
1755
+ chai.expect(b2.elements.tryGetElementProps(e4)).to.exist;
1756
+ chai.expect(b2.elements.tryGetElementProps(e5)).to.be.undefined;
1757
+ chai.expect(BriefcaseManager.containsRestorePoint(b2, BriefcaseManager.PULL_MERGE_RESTORE_POINT_NAME)).is.false;
1758
+ b2.txns.rebaser.setCustomHandler({
1759
+ shouldReinstate: (_txnProps) => {
1760
+ return true;
1761
+ },
1762
+ recompute: async (_txnProps) => { },
1763
+ });
1764
+ const e6 = await createTxn(b2);
1765
+ b2.saveChanges(`created element ${e6}`);
1766
+ chai.expect(b2.txns.getCurrentTxnId()).to.equal("0x100000004");
1767
+ chai.expect(b2.txns.getLastSavedTxnProps()?.id).to.equal(`0x100000003`);
1768
+ await b2.pullChanges();
1769
+ chai.expect(b2.elements.tryGetElementProps(e1)).to.exist;
1770
+ chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1771
+ chai.expect(b2.elements.tryGetElementProps(e3)).to.exist;
1772
+ chai.expect(b2.elements.tryGetElementProps(e4)).to.exist;
1773
+ const e7 = await createTxn(b2);
1774
+ b2.saveChanges(`created element ${e7}`);
1775
+ chai.expect(b2.txns.getCurrentTxnId()).to.equal("0x100000005");
1776
+ chai.expect(b2.txns.getLastSavedTxnProps()?.id).to.equal(`0x100000004`);
1777
+ await b2.pushChanges({ description: "pushed after rebase aborted" });
1778
+ await b1.pullChanges();
1779
+ chai.expect(b1.elements.tryGetElementProps(e1)).to.exist;
1780
+ chai.expect(b1.elements.tryGetElementProps(e2)).to.exist;
1781
+ chai.expect(b1.elements.tryGetElementProps(e3)).to.exist;
1782
+ chai.expect(b1.elements.tryGetElementProps(e4)).to.exist;
1783
+ chai.expect(b1.elements.tryGetElementProps(e7)).to.exist;
1757
1784
  });
1758
- const e6 = await createTxn(b2);
1759
- b2.saveChanges(`created element ${e6}`);
1760
- chai.expect(b2.txns.getCurrentTxnId()).to.equal("0x100000004");
1761
- chai.expect(b2.txns.getLastSavedTxnProps()?.id).to.equal(`0x100000003`);
1762
- await b2.pullChanges();
1763
- chai.expect(b2.elements.tryGetElementProps(e1)).to.exist;
1764
- chai.expect(b2.elements.tryGetElementProps(e2)).to.exist;
1765
- chai.expect(b2.elements.tryGetElementProps(e3)).to.exist;
1766
- chai.expect(b2.elements.tryGetElementProps(e4)).to.exist;
1767
- const e7 = await createTxn(b2);
1768
- b2.saveChanges(`created element ${e7}`);
1769
- chai.expect(b2.txns.getCurrentTxnId()).to.equal("0x100000005");
1770
- chai.expect(b2.txns.getLastSavedTxnProps()?.id).to.equal(`0x100000004`);
1771
- await b2.pushChanges({ description: "pushed after rebase aborted" });
1772
- await b1.pullChanges();
1773
- chai.expect(b1.elements.tryGetElementProps(e1)).to.exist;
1774
- chai.expect(b1.elements.tryGetElementProps(e2)).to.exist;
1775
- chai.expect(b1.elements.tryGetElementProps(e3)).to.exist;
1776
- chai.expect(b1.elements.tryGetElementProps(e4)).to.exist;
1777
- chai.expect(b1.elements.tryGetElementProps(e7)).to.exist;
1778
- });
1779
- it("changeset DDL error are ignored and ec_* tables are used to reconstruct the sqlite tables", async () => {
1780
- const b1 = await testIModel.openBriefcase();
1781
- const b2 = await testIModel.openBriefcase();
1782
- const iModelId = testIModel.iModelId;
1783
- const targetDir = path.join(KnownTestLocations.outputDir, iModelId, "changesets");
1784
- let ver = 0;
1785
- let props = 0;
1786
- const tblGeom2d = "bis_GeometricElement2d";
1787
- const geom2dBaseColumnList = [
1788
- "ElementId",
1789
- "ECClassId",
1790
- "CategoryId",
1791
- "Origin_X",
1792
- "Origin_Y",
1793
- "Rotation",
1794
- "BBoxLow_X",
1795
- "BBoxLow_Y",
1796
- "BBoxHigh_X",
1797
- "BBoxHigh_Y",
1798
- "GeometryStream",
1799
- "TypeDefinitionId",
1800
- "TypeDefinitionRelECClassId",
1801
- "js1",
1802
- "js2",
1803
- ];
1804
- const generateSchema = (noOfNewPropsToAdd) => {
1805
- props += noOfNewPropsToAdd;
1806
- return `<?xml version="1.0" encoding="UTF-8"?>
1785
+ it("changeset DDL error are ignored and ec_* tables are used to reconstruct the sqlite tables", async () => {
1786
+ const b1 = await testIModel.openBriefcase();
1787
+ const b2 = await testIModel.openBriefcase();
1788
+ const iModelId = testIModel.iModelId;
1789
+ const targetDir = path.join(KnownTestLocations.outputDir, iModelId, "changesets");
1790
+ let ver = 0;
1791
+ let props = 0;
1792
+ const tblGeom2d = "bis_GeometricElement2d";
1793
+ const geom2dBaseColumnList = [
1794
+ "ElementId",
1795
+ "ECClassId",
1796
+ "CategoryId",
1797
+ "Origin_X",
1798
+ "Origin_Y",
1799
+ "Rotation",
1800
+ "BBoxLow_X",
1801
+ "BBoxLow_Y",
1802
+ "BBoxHigh_X",
1803
+ "BBoxHigh_Y",
1804
+ "GeometryStream",
1805
+ "TypeDefinitionId",
1806
+ "TypeDefinitionRelECClassId",
1807
+ "js1",
1808
+ "js2",
1809
+ ];
1810
+ const generateSchema = (noOfNewPropsToAdd) => {
1811
+ props += noOfNewPropsToAdd;
1812
+ return `<?xml version="1.0" encoding="UTF-8"?>
1807
1813
  <ECSchema schemaName="TestDomain1" alias="ts1" version="01.00.${ver++}" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
1808
1814
  <ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
1809
1815
  <ECEntityClass typeName="test">
@@ -1812,66 +1818,67 @@ describe("rebase changes & stashing api", function () {
1812
1818
  ${Array.from({ length: props - 1 }, (_, i) => `<ECProperty propertyName="prop${i + 2}" typeName="string" />`).join("\n ")}
1813
1819
  </ECEntityClass>
1814
1820
  </ECSchema>`;
1815
- };
1816
- const getColumnNames = (b, tableName) => {
1817
- return b.withSqliteStatement(`PRAGMA table_info(${tableName})`, (stmt) => {
1818
- const columnNames = [];
1819
- while (stmt.step() === DbResult.BE_SQLITE_ROW) {
1820
- columnNames.push(stmt.getValue(1).getString());
1821
+ };
1822
+ const getColumnNames = (b, tableName) => {
1823
+ return b.withSqliteStatement(`PRAGMA table_info(${tableName})`, (stmt) => {
1824
+ const columnNames = [];
1825
+ while (stmt.step() === DbResult.BE_SQLITE_ROW) {
1826
+ columnNames.push(stmt.getValue(1).getString());
1827
+ }
1828
+ return columnNames;
1829
+ });
1830
+ };
1831
+ const withLatestChangeset = async (cb) => {
1832
+ const csInfo = await HubMock.getLatestChangeset({ iModelId });
1833
+ const info = await HubMock.downloadChangeset({
1834
+ iModelId,
1835
+ changeset: { id: csInfo.id },
1836
+ targetDir,
1837
+ });
1838
+ const reader = SqliteChangesetReader.openFile({ db: b1, fileName: info.pathname });
1839
+ try {
1840
+ await cb(reader);
1841
+ }
1842
+ finally {
1843
+ reader.close();
1821
1844
  }
1822
- return columnNames;
1845
+ };
1846
+ // Verify initial columns
1847
+ chai.expect(getColumnNames(b1, tblGeom2d)).deep.equals(geom2dBaseColumnList);
1848
+ chai.expect(getColumnNames(b2, tblGeom2d)).deep.equals(geom2dBaseColumnList);
1849
+ // Import schema that add 5 new properties that should add 3 new shared columns
1850
+ await b1.importSchemaStrings([generateSchema(5)]);
1851
+ await b1.pushChanges({ description: `imported schema version 1.0.${ver - 1}` });
1852
+ // Verify columns after schema import
1853
+ chai.expect(getColumnNames(b1, tblGeom2d)).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5"]);
1854
+ //verify changeset has schema changes
1855
+ await withLatestChangeset(async (reader) => {
1856
+ const schemaChanges = reader.getDdlChanges()?.split(";");
1857
+ chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js3] BLOB");
1858
+ chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js4] BLOB");
1859
+ chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js5] BLOB");
1823
1860
  });
1824
- };
1825
- const withLatestChangeset = async (cb) => {
1826
- const csInfo = await HubMock.getLatestChangeset({ iModelId });
1827
- const info = await HubMock.downloadChangeset({
1828
- iModelId,
1829
- changeset: { id: csInfo.id },
1830
- targetDir,
1861
+ await b2.pullChanges();
1862
+ chai.expect(getColumnNames(b2, "bis_GeometricElement2d")).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5"]);
1863
+ // Import schema that add 5 new properties that should add 3 new shared columns
1864
+ await b1.importSchemaStrings([generateSchema(1)]);
1865
+ await b1.pushChanges({ description: `imported schema version 1.0.${ver - 1}` });
1866
+ // Verify columns after schema import
1867
+ chai.expect(getColumnNames(b1, tblGeom2d)).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5", "js6"]);
1868
+ //verify changeset has schema changes
1869
+ await withLatestChangeset(async (reader) => {
1870
+ const schemaChanges = reader.getDdlChanges()?.split(";");
1871
+ chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js6] BLOB");
1831
1872
  });
1832
- const reader = SqliteChangesetReader.openFile({ db: b1, fileName: info.pathname });
1833
- try {
1834
- await cb(reader);
1835
- }
1836
- finally {
1837
- reader.close();
1838
- }
1839
- };
1840
- // Verify initial columns
1841
- chai.expect(getColumnNames(b1, tblGeom2d)).deep.equals(geom2dBaseColumnList);
1842
- chai.expect(getColumnNames(b2, tblGeom2d)).deep.equals(geom2dBaseColumnList);
1843
- // Import schema that add 5 new properties that should add 3 new shared columns
1844
- await b1.importSchemaStrings([generateSchema(5)]);
1845
- await b1.pushChanges({ description: `imported schema version 1.0.${ver - 1}` });
1846
- // Verify columns after schema import
1847
- chai.expect(getColumnNames(b1, tblGeom2d)).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5"]);
1848
- //verify changeset has schema changes
1849
- await withLatestChangeset(async (reader) => {
1850
- const schemaChanges = reader.getDdlChanges()?.split(";");
1851
- chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js3] BLOB");
1852
- chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js4] BLOB");
1853
- chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js5] BLOB");
1854
- });
1855
- await b2.pullChanges();
1856
- chai.expect(getColumnNames(b2, "bis_GeometricElement2d")).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5"]);
1857
- // Import schema that add 5 new properties that should add 3 new shared columns
1858
- await b1.importSchemaStrings([generateSchema(1)]);
1859
- await b1.pushChanges({ description: `imported schema version 1.0.${ver - 1}` });
1860
- // Verify columns after schema import
1861
- chai.expect(getColumnNames(b1, tblGeom2d)).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5", "js6"]);
1862
- //verify changeset has schema changes
1863
- await withLatestChangeset(async (reader) => {
1864
- const schemaChanges = reader.getDdlChanges()?.split(";");
1865
- chai.expect(schemaChanges).to.include("ALTER TABLE [bis_GeometricElement2d] ADD COLUMN [js6] BLOB");
1873
+ // delete the table so DDL apply should fail
1874
+ b2[_nativeDb].executeSql(`DROP TABLE ${tblGeom2d}`);
1875
+ // this would fail before this PR but should succeed as DDL error are ignored and table reconstruction is attempted using ec_* tables
1876
+ await b2.pullChanges();
1877
+ // Verify columns after schema import
1878
+ chai.expect(getColumnNames(b2, tblGeom2d)).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5", "js6"]);
1879
+ b1.close();
1880
+ b2.close();
1866
1881
  });
1867
- // delete the table so DDL apply should fail
1868
- b2[_nativeDb].executeSql(`DROP TABLE ${tblGeom2d}`);
1869
- // this would fail before this PR but should succeed as DDL error are ignored and table reconstruction is attempted using ec_* tables
1870
- await b2.pullChanges();
1871
- // Verify columns after schema import
1872
- chai.expect(getColumnNames(b2, tblGeom2d)).deep.equals([...geom2dBaseColumnList, "js3", "js4", "js5", "js6"]);
1873
- b1.close();
1874
- b2.close();
1875
1882
  });
1876
- });
1883
+ }
1877
1884
  //# sourceMappingURL=Rebase.test.js.map