@itwin/core-backend 5.7.0-dev.13 → 5.7.0-dev.14
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.
- package/lib/cjs/BriefcaseManager.d.ts +138 -1
- package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
- package/lib/cjs/BriefcaseManager.js +336 -1
- package/lib/cjs/BriefcaseManager.js.map +1 -1
- package/lib/cjs/CloudSqlite.js +1 -1
- package/lib/cjs/CloudSqlite.js.map +1 -1
- package/lib/cjs/IModelDb.d.ts +14 -0
- package/lib/cjs/IModelDb.d.ts.map +1 -1
- package/lib/cjs/IModelDb.js +71 -6
- package/lib/cjs/IModelDb.js.map +1 -1
- package/lib/cjs/IModelHost.d.ts +15 -0
- package/lib/cjs/IModelHost.d.ts.map +1 -1
- package/lib/cjs/IModelHost.js +12 -0
- package/lib/cjs/IModelHost.js.map +1 -1
- package/lib/cjs/IModelJsFs.d.ts +2 -0
- package/lib/cjs/IModelJsFs.d.ts.map +1 -1
- package/lib/cjs/IModelJsFs.js +2 -0
- package/lib/cjs/IModelJsFs.js.map +1 -1
- package/lib/cjs/TxnManager.d.ts +39 -0
- package/lib/cjs/TxnManager.d.ts.map +1 -1
- package/lib/cjs/TxnManager.js +149 -0
- package/lib/cjs/TxnManager.js.map +1 -1
- package/lib/esm/BriefcaseManager.d.ts +138 -1
- package/lib/esm/BriefcaseManager.d.ts.map +1 -1
- package/lib/esm/BriefcaseManager.js +336 -1
- package/lib/esm/BriefcaseManager.js.map +1 -1
- package/lib/esm/CloudSqlite.js +1 -1
- package/lib/esm/CloudSqlite.js.map +1 -1
- package/lib/esm/IModelDb.d.ts +14 -0
- package/lib/esm/IModelDb.d.ts.map +1 -1
- package/lib/esm/IModelDb.js +71 -6
- package/lib/esm/IModelDb.js.map +1 -1
- package/lib/esm/IModelHost.d.ts +15 -0
- package/lib/esm/IModelHost.d.ts.map +1 -1
- package/lib/esm/IModelHost.js +12 -0
- package/lib/esm/IModelHost.js.map +1 -1
- package/lib/esm/IModelJsFs.d.ts +2 -0
- package/lib/esm/IModelJsFs.d.ts.map +1 -1
- package/lib/esm/IModelJsFs.js +2 -0
- package/lib/esm/IModelJsFs.js.map +1 -1
- package/lib/esm/TxnManager.d.ts +39 -0
- package/lib/esm/TxnManager.d.ts.map +1 -1
- package/lib/esm/TxnManager.js +150 -1
- package/lib/esm/TxnManager.js.map +1 -1
- package/lib/esm/test/SquashSchemaAndDataChanges.test.d.ts +2 -0
- package/lib/esm/test/SquashSchemaAndDataChanges.test.d.ts.map +1 -0
- package/lib/esm/test/SquashSchemaAndDataChanges.test.js +241 -0
- package/lib/esm/test/SquashSchemaAndDataChanges.test.js.map +1 -0
- package/lib/esm/test/hubaccess/Rebase.test.js +1575 -1568
- package/lib/esm/test/hubaccess/Rebase.test.js.map +1 -1
- package/lib/esm/test/hubaccess/SemanticRebase.test.d.ts +2 -0
- package/lib/esm/test/hubaccess/SemanticRebase.test.d.ts.map +1 -0
- package/lib/esm/test/hubaccess/SemanticRebase.test.js +1206 -0
- package/lib/esm/test/hubaccess/SemanticRebase.test.js.map +1 -0
- package/lib/esm/test/standalone/Workspace.test.js +5 -0
- package/lib/esm/test/standalone/Workspace.test.js.map +1 -1
- 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,
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
await
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
chai.
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
chai.expect(b1.txns.getMode()).to.equal("
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
492
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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.
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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
|
-
|
|
1079
|
-
|
|
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
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
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
|
-
|
|
1088
|
-
|
|
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
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
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
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
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
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
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
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
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
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
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
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
-
|
|
1302
|
-
|
|
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
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
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
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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
|
-
|
|
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
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
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
|
-
|
|
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:
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
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
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
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
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
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
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
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
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
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
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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
|