@itwin/core-backend 5.13.0-dev.9 → 5.14.0-dev.2
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/CHANGELOG.md +33 -1
- package/lib/cjs/Category.d.ts +4 -4
- package/lib/cjs/Category.js.map +1 -1
- package/lib/cjs/ChangesetECAdaptor.d.ts +5 -0
- package/lib/cjs/ChangesetECAdaptor.d.ts.map +1 -1
- package/lib/cjs/ChangesetECAdaptor.js +13 -3
- package/lib/cjs/ChangesetECAdaptor.js.map +1 -1
- package/lib/cjs/CodeSpecs.d.ts +3 -3
- package/lib/cjs/CodeSpecs.js.map +1 -1
- package/lib/cjs/DisplayStyle.d.ts +2 -2
- package/lib/cjs/DisplayStyle.js.map +1 -1
- package/lib/cjs/EditTxn.d.ts.map +1 -1
- package/lib/cjs/EditTxn.js +1 -0
- package/lib/cjs/EditTxn.js.map +1 -1
- package/lib/cjs/Element.d.ts +48 -12
- package/lib/cjs/Element.d.ts.map +1 -1
- package/lib/cjs/Element.js +120 -24
- package/lib/cjs/Element.js.map +1 -1
- package/lib/cjs/IModelDb.d.ts +30 -15
- package/lib/cjs/IModelDb.d.ts.map +1 -1
- package/lib/cjs/IModelDb.js +51 -15
- package/lib/cjs/IModelDb.js.map +1 -1
- package/lib/cjs/Model.d.ts +5 -5
- package/lib/cjs/Model.d.ts.map +1 -1
- package/lib/cjs/Model.js.map +1 -1
- package/lib/cjs/PropertyStore.js +1 -1
- package/lib/cjs/PropertyStore.js.map +1 -1
- package/lib/cjs/SchemaSync.d.ts +37 -9
- package/lib/cjs/SchemaSync.d.ts.map +1 -1
- package/lib/cjs/SchemaSync.js +125 -6
- package/lib/cjs/SchemaSync.js.map +1 -1
- package/lib/cjs/SynchronousChannel.d.ts +62 -0
- package/lib/cjs/SynchronousChannel.d.ts.map +1 -0
- package/lib/cjs/SynchronousChannel.js +11 -0
- package/lib/cjs/SynchronousChannel.js.map +1 -0
- package/lib/cjs/Texture.d.ts +1 -1
- package/lib/cjs/Texture.js.map +1 -1
- package/lib/cjs/TxnManager.d.ts +44 -4
- package/lib/cjs/TxnManager.d.ts.map +1 -1
- package/lib/cjs/TxnManager.js +72 -14
- package/lib/cjs/TxnManager.js.map +1 -1
- package/lib/cjs/ViewDefinition.d.ts +6 -6
- package/lib/cjs/ViewDefinition.d.ts.map +1 -1
- package/lib/cjs/ViewDefinition.js.map +1 -1
- package/lib/cjs/annotations/LeaderGeometry.d.ts +22 -7
- package/lib/cjs/annotations/LeaderGeometry.d.ts.map +1 -1
- package/lib/cjs/annotations/LeaderGeometry.js +104 -36
- package/lib/cjs/annotations/LeaderGeometry.js.map +1 -1
- package/lib/cjs/annotations/TextAnnotationElement.d.ts +1 -1
- package/lib/cjs/annotations/TextAnnotationElement.d.ts.map +1 -1
- package/lib/cjs/annotations/TextAnnotationElement.js +25 -10
- package/lib/cjs/annotations/TextAnnotationElement.js.map +1 -1
- package/lib/cjs/core-backend.d.ts +1 -0
- package/lib/cjs/core-backend.d.ts.map +1 -1
- package/lib/cjs/core-backend.js +1 -0
- package/lib/cjs/core-backend.js.map +1 -1
- package/lib/cjs/internal/IntegrityCheck.d.ts +22 -0
- package/lib/cjs/internal/IntegrityCheck.d.ts.map +1 -1
- package/lib/cjs/internal/IntegrityCheck.js +13 -0
- package/lib/cjs/internal/IntegrityCheck.js.map +1 -1
- package/lib/cjs/internal/NoReservations.d.ts +6 -0
- package/lib/cjs/internal/NoReservations.d.ts.map +1 -0
- package/lib/cjs/internal/NoReservations.js +21 -0
- package/lib/cjs/internal/NoReservations.js.map +1 -0
- package/lib/cjs/internal/SchemaSyncReservations.d.ts +4 -0
- package/lib/cjs/internal/SchemaSyncReservations.d.ts.map +1 -0
- package/lib/cjs/internal/SchemaSyncReservations.js +118 -0
- package/lib/cjs/internal/SchemaSyncReservations.js.map +1 -0
- package/lib/cjs/internal/Symbols.d.ts +1 -0
- package/lib/cjs/internal/Symbols.d.ts.map +1 -1
- package/lib/cjs/internal/Symbols.js +2 -1
- package/lib/cjs/internal/Symbols.js.map +1 -1
- package/lib/esm/Category.d.ts +4 -4
- package/lib/esm/Category.js.map +1 -1
- package/lib/esm/ChangesetECAdaptor.d.ts +5 -0
- package/lib/esm/ChangesetECAdaptor.d.ts.map +1 -1
- package/lib/esm/ChangesetECAdaptor.js +13 -3
- package/lib/esm/ChangesetECAdaptor.js.map +1 -1
- package/lib/esm/CodeSpecs.d.ts +3 -3
- package/lib/esm/CodeSpecs.js.map +1 -1
- package/lib/esm/DisplayStyle.d.ts +2 -2
- package/lib/esm/DisplayStyle.js.map +1 -1
- package/lib/esm/EditTxn.d.ts.map +1 -1
- package/lib/esm/EditTxn.js +1 -0
- package/lib/esm/EditTxn.js.map +1 -1
- package/lib/esm/Element.d.ts +48 -12
- package/lib/esm/Element.d.ts.map +1 -1
- package/lib/esm/Element.js +121 -25
- package/lib/esm/Element.js.map +1 -1
- package/lib/esm/IModelDb.d.ts +30 -15
- package/lib/esm/IModelDb.d.ts.map +1 -1
- package/lib/esm/IModelDb.js +52 -16
- package/lib/esm/IModelDb.js.map +1 -1
- package/lib/esm/Model.d.ts +5 -5
- package/lib/esm/Model.d.ts.map +1 -1
- package/lib/esm/Model.js.map +1 -1
- package/lib/esm/PropertyStore.js +1 -1
- package/lib/esm/PropertyStore.js.map +1 -1
- package/lib/esm/SchemaSync.d.ts +37 -9
- package/lib/esm/SchemaSync.d.ts.map +1 -1
- package/lib/esm/SchemaSync.js +127 -8
- package/lib/esm/SchemaSync.js.map +1 -1
- package/lib/esm/SynchronousChannel.d.ts +62 -0
- package/lib/esm/SynchronousChannel.d.ts.map +1 -0
- package/lib/esm/SynchronousChannel.js +9 -0
- package/lib/esm/SynchronousChannel.js.map +1 -0
- package/lib/esm/Texture.d.ts +1 -1
- package/lib/esm/Texture.js.map +1 -1
- package/lib/esm/TxnManager.d.ts +44 -4
- package/lib/esm/TxnManager.d.ts.map +1 -1
- package/lib/esm/TxnManager.js +72 -14
- package/lib/esm/TxnManager.js.map +1 -1
- package/lib/esm/ViewDefinition.d.ts +6 -6
- package/lib/esm/ViewDefinition.d.ts.map +1 -1
- package/lib/esm/ViewDefinition.js.map +1 -1
- package/lib/esm/annotations/LeaderGeometry.d.ts +22 -7
- package/lib/esm/annotations/LeaderGeometry.d.ts.map +1 -1
- package/lib/esm/annotations/LeaderGeometry.js +104 -36
- package/lib/esm/annotations/LeaderGeometry.js.map +1 -1
- package/lib/esm/annotations/TextAnnotationElement.d.ts +1 -1
- package/lib/esm/annotations/TextAnnotationElement.d.ts.map +1 -1
- package/lib/esm/annotations/TextAnnotationElement.js +25 -10
- package/lib/esm/annotations/TextAnnotationElement.js.map +1 -1
- package/lib/esm/core-backend.d.ts +1 -0
- package/lib/esm/core-backend.d.ts.map +1 -1
- package/lib/esm/core-backend.js +1 -0
- package/lib/esm/core-backend.js.map +1 -1
- package/lib/esm/internal/IntegrityCheck.d.ts +22 -0
- package/lib/esm/internal/IntegrityCheck.d.ts.map +1 -1
- package/lib/esm/internal/IntegrityCheck.js +13 -0
- package/lib/esm/internal/IntegrityCheck.js.map +1 -1
- package/lib/esm/internal/NoReservations.d.ts +6 -0
- package/lib/esm/internal/NoReservations.d.ts.map +1 -0
- package/lib/esm/internal/NoReservations.js +18 -0
- package/lib/esm/internal/NoReservations.js.map +1 -0
- package/lib/esm/internal/SchemaSyncReservations.d.ts +4 -0
- package/lib/esm/internal/SchemaSyncReservations.d.ts.map +1 -0
- package/lib/esm/internal/SchemaSyncReservations.js +115 -0
- package/lib/esm/internal/SchemaSyncReservations.js.map +1 -0
- package/lib/esm/internal/Symbols.d.ts +1 -0
- package/lib/esm/internal/Symbols.d.ts.map +1 -1
- package/lib/esm/internal/Symbols.js +1 -0
- package/lib/esm/internal/Symbols.js.map +1 -1
- package/lib/esm/test/IModelTestUtils.d.ts +1 -1
- package/lib/esm/test/IModelTestUtils.d.ts.map +1 -1
- package/lib/esm/test/IModelTestUtils.js +8 -3
- package/lib/esm/test/IModelTestUtils.js.map +1 -1
- package/lib/esm/test/annotations/LeaderGeometry.test.js +178 -2
- package/lib/esm/test/annotations/LeaderGeometry.test.js.map +1 -1
- package/lib/esm/test/annotations/TextAnnotation.test.js +48 -18
- package/lib/esm/test/annotations/TextAnnotation.test.js.map +1 -1
- package/lib/esm/test/ecdb/ECSqlStatement.test.js +177 -0
- package/lib/esm/test/ecdb/ECSqlStatement.test.js.map +1 -1
- package/lib/esm/test/ecdb/QueryReaders.test.js +2 -16
- package/lib/esm/test/ecdb/QueryReaders.test.js.map +1 -1
- package/lib/esm/test/ecsql/src/ECSqlTestRunner.test.js +2 -2
- package/lib/esm/test/ecsql/src/ECSqlTestRunner.test.js.map +1 -1
- package/lib/esm/test/element/ElementRoundTrip.test.js +80 -2
- package/lib/esm/test/element/ElementRoundTrip.test.js.map +1 -1
- package/lib/esm/test/imodel/IModelECSql.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelECSql.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelECSql.test.js +367 -0
- package/lib/esm/test/imodel/IModelECSql.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelElements.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelElements.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelElements.test.js +969 -0
- package/lib/esm/test/imodel/IModelElements.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelGeolocation.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelGeolocation.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelGeolocation.test.js +539 -0
- package/lib/esm/test/imodel/IModelGeolocation.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelLifecycle.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelLifecycle.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelLifecycle.test.js +499 -0
- package/lib/esm/test/imodel/IModelLifecycle.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelMetadata.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelMetadata.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelMetadata.test.js +274 -0
- package/lib/esm/test/imodel/IModelMetadata.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelModels.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelModels.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelModels.test.js +357 -0
- package/lib/esm/test/imodel/IModelModels.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelRelationships.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelRelationships.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelRelationships.test.js +407 -0
- package/lib/esm/test/imodel/IModelRelationships.test.js.map +1 -0
- package/lib/esm/test/imodel/IModelTestFixtures.d.ts +53 -0
- package/lib/esm/test/imodel/IModelTestFixtures.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelTestFixtures.js +107 -0
- package/lib/esm/test/imodel/IModelTestFixtures.js.map +1 -0
- package/lib/esm/test/imodel/IModelViews.test.d.ts +2 -0
- package/lib/esm/test/imodel/IModelViews.test.d.ts.map +1 -0
- package/lib/esm/test/imodel/IModelViews.test.js +199 -0
- package/lib/esm/test/imodel/IModelViews.test.js.map +1 -0
- package/lib/esm/test/index.d.ts +0 -1
- package/lib/esm/test/index.d.ts.map +1 -1
- package/lib/esm/test/index.js +0 -1
- package/lib/esm/test/index.js.map +1 -1
- package/lib/esm/test/standalone/ChangesetReader.test.js +8 -26
- package/lib/esm/test/standalone/ChangesetReader.test.js.map +1 -1
- package/lib/esm/test/standalone/DgnDomainMergeConflict.test.d.ts +2 -0
- package/lib/esm/test/standalone/DgnDomainMergeConflict.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/DgnDomainMergeConflict.test.js +263 -0
- package/lib/esm/test/standalone/DgnDomainMergeConflict.test.js.map +1 -0
- package/lib/esm/test/standalone/GeometryStream.test.js +1 -3
- package/lib/esm/test/standalone/GeometryStream.test.js.map +1 -1
- package/lib/esm/test/standalone/IModelWrite.test.d.ts.map +1 -1
- package/lib/esm/test/standalone/IModelWrite.test.js +78 -1
- package/lib/esm/test/standalone/IModelWrite.test.js.map +1 -1
- package/lib/esm/test/standalone/IntegrityCheck.test.js +29 -10
- package/lib/esm/test/standalone/IntegrityCheck.test.js.map +1 -1
- package/lib/esm/test/standalone/SQliteChangesetReaderAndChangesetECAdaptor.test.js +23 -0
- package/lib/esm/test/standalone/SQliteChangesetReaderAndChangesetECAdaptor.test.js.map +1 -1
- package/lib/esm/test/standalone/SchemaSyncDb.test.d.ts +2 -0
- package/lib/esm/test/standalone/SchemaSyncDb.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/SchemaSyncDb.test.js +221 -0
- package/lib/esm/test/standalone/SchemaSyncDb.test.js.map +1 -0
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.d.ts +2 -0
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.d.ts.map +1 -0
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.js +459 -0
- package/lib/esm/test/standalone/SchemaSyncElementReservation.test.js.map +1 -0
- package/lib/esm/test/standalone/SnapshotDb.test.js +1 -0
- package/lib/esm/test/standalone/SnapshotDb.test.js.map +1 -1
- package/lib/esm/test/standalone/TxnManager.test.js +108 -8
- package/lib/esm/test/standalone/TxnManager.test.js.map +1 -1
- package/package.json +14 -17
- package/lib/esm/test/RevisionUtility.d.ts +0 -33
- package/lib/esm/test/RevisionUtility.d.ts.map +0 -1
- package/lib/esm/test/RevisionUtility.js +0 -52
- package/lib/esm/test/RevisionUtility.js.map +0 -1
- package/lib/esm/test/imodel/IModel.test.d.ts +0 -2
- package/lib/esm/test/imodel/IModel.test.d.ts.map +0 -1
- package/lib/esm/test/imodel/IModel.test.js +0 -3277
- package/lib/esm/test/imodel/IModel.test.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DgnDomainMergeConflict.test.d.ts","sourceRoot":"","sources":["../../../../src/test/standalone/DgnDomainMergeConflict.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
3
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import { DbConflictCause, DbConflictResolution, DbOpcode, DbResult } from "@itwin/core-bentley";
|
|
7
|
+
import { assert, expect } from "chai";
|
|
8
|
+
import * as chai from "chai";
|
|
9
|
+
import * as chaiAsPromised from "chai-as-promised";
|
|
10
|
+
import * as sinon from "sinon";
|
|
11
|
+
import { HubWrappers, KnownTestLocations } from "../";
|
|
12
|
+
import { HubMock } from "../../internal/HubMock";
|
|
13
|
+
import { BriefcaseDb, ChannelControl, FunctionalSchema } from "../../core-backend";
|
|
14
|
+
import { IModelTestUtils, TestUserType } from "../IModelTestUtils";
|
|
15
|
+
import { TestUtils } from "../TestUtils";
|
|
16
|
+
import { withEditTxn } from "../../EditTxn";
|
|
17
|
+
import { _nativeDb } from "../../internal/Symbols";
|
|
18
|
+
import { IModelNative } from "../../internal/NativePlatform";
|
|
19
|
+
chai.use(chaiAsPromised);
|
|
20
|
+
/**
|
|
21
|
+
* `dgn_Domain` holds one bookkeeping row per BIS domain present in the briefcase, and the row is
|
|
22
|
+
* created automatically as soon as the domain's schema is imported. Merging a changeset that
|
|
23
|
+
* registers a domain therefore conflicts with the row that was just created locally. The conflict
|
|
24
|
+
* is benign - both rows describe the same domain - so it must never fail the pull.
|
|
25
|
+
*
|
|
26
|
+
* The tests build a timeline where two changesets each insert the same `dgn_Domain` row:
|
|
27
|
+
*
|
|
28
|
+
* cs1 (b1) - "Domain schema upgrade" : INSERT dgn_Domain('Functional')
|
|
29
|
+
* cs2 (b3) - duplicate domain rows : INSERT dgn_Domain('Functional') again
|
|
30
|
+
*
|
|
31
|
+
* b1 and b3 both start at version 0 and perform the same domain upgrade, and cs2 is appended to
|
|
32
|
+
* the timeline without rebasing. A normal `pushChanges()` cannot produce this shape because the
|
|
33
|
+
* rebase drops the duplicate insert first, but it is observed in the field from connectors.
|
|
34
|
+
*
|
|
35
|
+
* Merging both changesets in one pull used to abort, while pulling them one at a time succeeded,
|
|
36
|
+
* leaving the cached briefcase permanently stuck. All of these must now succeed alike.
|
|
37
|
+
*/
|
|
38
|
+
describe("dgn_Domain merge conflict", () => {
|
|
39
|
+
let iTwinId;
|
|
40
|
+
before(async () => {
|
|
41
|
+
await TestUtils.startBackend();
|
|
42
|
+
HubMock.startup("DgnDomainMergeConflict", KnownTestLocations.outputDir);
|
|
43
|
+
iTwinId = HubMock.iTwinId;
|
|
44
|
+
});
|
|
45
|
+
after(() => HubMock.shutdown());
|
|
46
|
+
/** The domain names present in the briefcase's `dgn_Domain` table. */
|
|
47
|
+
function queryDomains(db) {
|
|
48
|
+
const names = [];
|
|
49
|
+
db.withPreparedSqliteStatement("SELECT Name FROM dgn_Domain ORDER BY Name", (stmt) => {
|
|
50
|
+
while (stmt.step() === DbResult.BE_SQLITE_ROW)
|
|
51
|
+
names.push(stmt.getValueString(0));
|
|
52
|
+
});
|
|
53
|
+
return names;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Creates an iModel whose timeline holds two changesets that each INSERT the same
|
|
57
|
+
* `dgn_Domain` row, and leaves both authoring briefcases closed.
|
|
58
|
+
*/
|
|
59
|
+
async function createTimelineWithDuplicateDomainRows(iModelName) {
|
|
60
|
+
const accessToken1 = await HubWrappers.getAccessToken(TestUserType.SuperManager);
|
|
61
|
+
const accessToken3 = await HubWrappers.getAccessToken(TestUserType.Super);
|
|
62
|
+
const iModelId = await HubMock.createNewIModel({ accessToken: accessToken1, iTwinId, iModelName, description: iModelName, noLocks: true });
|
|
63
|
+
assert.isNotEmpty(iModelId);
|
|
64
|
+
const b1 = await HubWrappers.downloadAndOpenBriefcase({ accessToken: accessToken1, iTwinId, iModelId, noLock: true });
|
|
65
|
+
b1.channels.addAllowedChannel(ChannelControl.sharedChannelName);
|
|
66
|
+
// b3 is downloaded at version 0 as well, so it never learns about cs1.
|
|
67
|
+
const b3 = await HubWrappers.downloadAndOpenBriefcase({ accessToken: accessToken3, iTwinId, iModelId, noLock: true });
|
|
68
|
+
b3.channels.addAllowedChannel(ChannelControl.sharedChannelName);
|
|
69
|
+
// cs1: the "Domain schema upgrade" changeset. Importing the Functional *domain* adds a
|
|
70
|
+
// `dgn_Domain` row for "Functional", and that insert is part of the pushed changeset.
|
|
71
|
+
await withEditTxn(b1, "Domain schema upgrade", async () => {
|
|
72
|
+
await FunctionalSchema.importSchema(b1);
|
|
73
|
+
});
|
|
74
|
+
await b1.pushChanges({ accessToken: accessToken1, description: "Domain schema upgrade" });
|
|
75
|
+
expect(queryDomains(b1)).to.include("Functional");
|
|
76
|
+
// cs2: b3 performs the same domain upgrade while unaware of cs1, so its changeset carries a
|
|
77
|
+
// second INSERT for the same `dgn_Domain` row. It is appended to the timeline without a
|
|
78
|
+
// rebase (a rebase would rewrite the duplicate insert away, which is exactly why this shape
|
|
79
|
+
// of changeset cannot be produced by `pushChanges` but is observed in the field).
|
|
80
|
+
await withEditTxn(b3, "Domain schema upgrade", async () => {
|
|
81
|
+
await FunctionalSchema.importSchema(b3);
|
|
82
|
+
});
|
|
83
|
+
// extra content so cs2 does not hash to the same changeset id as cs1
|
|
84
|
+
withEditTxn(b3, "connector data", (txn) => {
|
|
85
|
+
IModelTestUtils.createAndInsertPhysicalPartitionAndModel(txn, IModelTestUtils.getUniqueModelCode(b3, "ConnectorModel"), true);
|
|
86
|
+
});
|
|
87
|
+
const csProps = b3[_nativeDb].startCreateChangeset();
|
|
88
|
+
csProps.briefcaseId = b3.briefcaseId;
|
|
89
|
+
csProps.description = "Duplicate dgn_Domain rows";
|
|
90
|
+
csProps.size = fs.statSync(csProps.pathname).size;
|
|
91
|
+
csProps.parentId = b1.changeset.id;
|
|
92
|
+
csProps.id = IModelNative.platform.DgnDb.computeChangesetId(csProps);
|
|
93
|
+
await HubMock.pushChangeset({ accessToken: accessToken3, iModelId, changesetProps: csProps });
|
|
94
|
+
b3[_nativeDb].abandonCreateChangeset();
|
|
95
|
+
b1.close();
|
|
96
|
+
b3.close();
|
|
97
|
+
return { iModelId, tipIndex: 2 };
|
|
98
|
+
}
|
|
99
|
+
it("resolves the duplicate dgn_Domain insert when the changesets are pulled one at a time", async () => {
|
|
100
|
+
const { iModelId } = await createTimelineWithDuplicateDomainRows("DgnDomainOneAtATime");
|
|
101
|
+
const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);
|
|
102
|
+
// A briefcase parked at version 0, i.e. behind the domain upgrade.
|
|
103
|
+
const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });
|
|
104
|
+
try {
|
|
105
|
+
expect(b2.txns.hasPendingTxns).to.be.false;
|
|
106
|
+
// Each pull is its own pull-merge session, so no local change is pending when the
|
|
107
|
+
// conflicting insert is applied.
|
|
108
|
+
await b2.pullChanges({ accessToken, toIndex: 1 });
|
|
109
|
+
await b2.pullChanges({ accessToken });
|
|
110
|
+
expect(b2.changeset.index).to.equal(2);
|
|
111
|
+
expect(queryDomains(b2).filter((name) => name === "Functional")).to.have.lengthOf(1);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
b2.close();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
it("resolves the duplicate dgn_Domain insert when the changesets are merged by a single pullChanges", async () => {
|
|
118
|
+
const { iModelId } = await createTimelineWithDuplicateDomainRows("DgnDomainSinglePull");
|
|
119
|
+
const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);
|
|
120
|
+
const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });
|
|
121
|
+
try {
|
|
122
|
+
expect(b2.txns.hasPendingTxns).to.be.false;
|
|
123
|
+
// Both changesets are merged inside one pull-merge session, so merging cs1 leaves a pending
|
|
124
|
+
// change behind while cs2 is merged. That used to turn the benign `dgn_Domain` conflict into
|
|
125
|
+
// an abort and leave the briefcase stuck mid-merge. The conflict is now always resolved by
|
|
126
|
+
// keeping the incoming row, so batching no longer changes the outcome.
|
|
127
|
+
await b2.pullChanges({ accessToken });
|
|
128
|
+
expect(b2.changeset.index).to.equal(2);
|
|
129
|
+
expect(queryDomains(b2).filter((name) => name === "Functional")).to.have.lengthOf(1);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
b2.close();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
it("succeeds for a briefcase downloaded at the tip", async () => {
|
|
136
|
+
const { iModelId } = await createTimelineWithDuplicateDomainRows("DgnDomainFreshBriefcase");
|
|
137
|
+
const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);
|
|
138
|
+
const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, noLock: true });
|
|
139
|
+
try {
|
|
140
|
+
expect(b2.changeset.index).to.equal(2);
|
|
141
|
+
expect(queryDomains(b2).filter((name) => name === "Functional")).to.have.lengthOf(1);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
b2.close();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
it("skips the duplicate local dgn_Domain insert while rebasing local txns", async () => {
|
|
148
|
+
const { iModelId } = await createTimelineWithDuplicateDomainRows("DgnDomainRebase");
|
|
149
|
+
const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);
|
|
150
|
+
const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });
|
|
151
|
+
try {
|
|
152
|
+
b2.channels.addAllowedChannel(ChannelControl.sharedChannelName);
|
|
153
|
+
// b2 imports the same domain locally and keeps the txn pending, so pulling has to merge the
|
|
154
|
+
// two incoming inserts and then replay a local txn that inserts the very same
|
|
155
|
+
// `dgn_Domain` row over the already merged one.
|
|
156
|
+
await withEditTxn(b2, "Domain schema upgrade", async () => {
|
|
157
|
+
await FunctionalSchema.importSchema(b2);
|
|
158
|
+
});
|
|
159
|
+
expect(b2.txns.hasPendingTxns).to.be.true;
|
|
160
|
+
await b2.pullChanges({ accessToken });
|
|
161
|
+
expect(b2.changeset.index).to.equal(2);
|
|
162
|
+
expect(queryDomains(b2).filter((name) => name === "Functional")).to.have.lengthOf(1);
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
b2.close();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
describe("conflict handler guards", () => {
|
|
169
|
+
let b2;
|
|
170
|
+
before(async () => {
|
|
171
|
+
const { iModelId } = await createTimelineWithDuplicateDomainRows("DgnDomainHandlerGuards");
|
|
172
|
+
const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);
|
|
173
|
+
b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });
|
|
174
|
+
});
|
|
175
|
+
after(() => b2?.close());
|
|
176
|
+
/** A conflict as native code would report it, with just enough behavior for the handlers. */
|
|
177
|
+
function makeConflictArgs(overrides) {
|
|
178
|
+
return {
|
|
179
|
+
indirect: false,
|
|
180
|
+
columnCount: 3,
|
|
181
|
+
changesetFile: "test.changeset",
|
|
182
|
+
txn: { id: "0x1", type: "Schema", descr: "Domain schema upgrade" },
|
|
183
|
+
getForeignKeyConflicts: () => 0,
|
|
184
|
+
dump: () => { },
|
|
185
|
+
setLastError: () => { },
|
|
186
|
+
getPrimaryKeyColumns: () => [1, 0, 0],
|
|
187
|
+
getColumnNames: () => ["Name", "Version", "Description"],
|
|
188
|
+
getValueType: () => undefined,
|
|
189
|
+
getValueBinary: () => undefined,
|
|
190
|
+
getValueId: () => undefined,
|
|
191
|
+
getValueText: () => undefined,
|
|
192
|
+
getValueInteger: () => undefined,
|
|
193
|
+
getValueDouble: () => undefined,
|
|
194
|
+
isValueNull: () => undefined,
|
|
195
|
+
...overrides,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const onMergeConflict = (args) => b2.onChangesetConflict(args);
|
|
199
|
+
const onRebaseConflict = (args) => b2.txns._onRebaseLocalTxnConflict(args);
|
|
200
|
+
it("keeps the incoming row for a duplicate dgn_Domain insert while merging", () => {
|
|
201
|
+
const args = makeConflictArgs({ tableName: "dgn_Domain", cause: DbConflictCause.Conflict, opcode: DbOpcode.Insert });
|
|
202
|
+
expect(onMergeConflict(args)).to.equal(DbConflictResolution.Replace);
|
|
203
|
+
});
|
|
204
|
+
it("skips a duplicate dgn_Domain insert while rebasing", () => {
|
|
205
|
+
const args = makeConflictArgs({ tableName: "dgn_Domain", cause: DbConflictCause.Conflict, opcode: DbOpcode.Insert });
|
|
206
|
+
expect(onRebaseConflict(args)).to.equal(DbConflictResolution.Skip);
|
|
207
|
+
});
|
|
208
|
+
// A `Data` conflict means an UPDATE or DELETE whose "before" values do not match the row in
|
|
209
|
+
// the briefcase, i.e. the two rows really do differ. Resolving those automatically would
|
|
210
|
+
// silently discard a genuine change, so they must keep the pre-existing behavior.
|
|
211
|
+
for (const opcode of [DbOpcode.Update, DbOpcode.Delete]) {
|
|
212
|
+
it(`does not swallow a dgn_Domain ${DbOpcode[opcode]} whose before values differ while merging`, () => {
|
|
213
|
+
let lastError;
|
|
214
|
+
const args = makeConflictArgs({ tableName: "dgn_Domain", cause: DbConflictCause.Data, opcode });
|
|
215
|
+
args.setLastError = (message) => lastError = message;
|
|
216
|
+
// With a local change pending, a mismatched UPDATE/DELETE must still abort the merge
|
|
217
|
+
// rather than be resolved as a benign duplicate domain registration.
|
|
218
|
+
const pending = sinon.stub(b2.txns, "hasPendingTxns").get(() => true);
|
|
219
|
+
try {
|
|
220
|
+
expect(onMergeConflict(args)).to.equal(DbConflictResolution.Abort);
|
|
221
|
+
expect(lastError).to.not.be.undefined;
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
pending.restore();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
it(`does not swallow a dgn_Domain ${DbOpcode[opcode]} whose before values differ while rebasing`, () => {
|
|
228
|
+
const args = makeConflictArgs({ tableName: "dgn_Domain", cause: DbConflictCause.Data, opcode });
|
|
229
|
+
// The local change wins, as it does for every other non-`ec_` table.
|
|
230
|
+
expect(onRebaseConflict(args)).to.equal(DbConflictResolution.Replace);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
it("does not resolve conflicts on other tables", () => {
|
|
234
|
+
const args = makeConflictArgs({ tableName: "bis_Element", cause: DbConflictCause.Conflict, opcode: DbOpcode.Insert });
|
|
235
|
+
expect(onRebaseConflict(args)).to.equal(DbConflictResolution.Abort);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
// TODO: unskip once a `@bentley/imodeljs-native` version containing iTwin/imodel-native#1548 is
|
|
239
|
+
// pinned in core/backend/package.json. The default handler lives in the addon, so this case
|
|
240
|
+
// cannot pass against 5.13.9.
|
|
241
|
+
it.skip("resolves the conflict when the TypeScript handler defers to the default handler", async () => {
|
|
242
|
+
const { iModelId } = await createTimelineWithDuplicateDomainRows("DgnDomainNativeHandler");
|
|
243
|
+
const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);
|
|
244
|
+
// `IModelDb.onChangesetConflict` is consulted first and normally resolves this conflict.
|
|
245
|
+
// Returning `undefined` hands the decision to the default handler in the addon, which is the
|
|
246
|
+
// path taken by hosts that install no JavaScript handler, such as checkpoint creation and
|
|
247
|
+
// change history. Both handlers must agree, otherwise the same changeset would apply in an
|
|
248
|
+
// iTwin.js backend but be rejected by those services.
|
|
249
|
+
const stub = sinon.stub(BriefcaseDb.prototype, "onChangesetConflict").returns(undefined);
|
|
250
|
+
const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });
|
|
251
|
+
try {
|
|
252
|
+
await b2.pullChanges({ accessToken });
|
|
253
|
+
expect(stub.called, "the default conflict handler should have been reached via the stub").to.be.true;
|
|
254
|
+
expect(b2.changeset.index).to.equal(2);
|
|
255
|
+
expect(queryDomains(b2).filter((name) => name === "Functional")).to.have.lengthOf(1);
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
stub.restore();
|
|
259
|
+
b2.close();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
//# sourceMappingURL=DgnDomainMergeConflict.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DgnDomainMergeConflict.test.js","sourceRoot":"","sources":["../../../../src/test/standalone/DgnDomainMergeConflict.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\nimport * as fs from \"fs\";\r\nimport { DbConflictCause, DbConflictResolution, DbOpcode, DbResult, GuidString } from \"@itwin/core-bentley\";\r\nimport { ChangesetFileProps } from \"@itwin/core-common\";\r\nimport { assert, expect } from \"chai\";\r\nimport * as chai from \"chai\";\r\nimport * as chaiAsPromised from \"chai-as-promised\";\r\nimport * as sinon from \"sinon\";\r\nimport { HubWrappers, KnownTestLocations } from \"../\";\r\nimport { HubMock } from \"../../internal/HubMock\";\r\nimport { BriefcaseDb, ChannelControl, FunctionalSchema } from \"../../core-backend\";\r\nimport { IModelTestUtils, TestUserType } from \"../IModelTestUtils\";\r\nimport { TestUtils } from \"../TestUtils\";\r\nimport { withEditTxn } from \"../../EditTxn\";\r\nimport { _nativeDb } from \"../../internal/Symbols\";\r\nimport { IModelNative } from \"../../internal/NativePlatform\";\r\n\r\nchai.use(chaiAsPromised);\r\n\r\n/**\r\n * `dgn_Domain` holds one bookkeeping row per BIS domain present in the briefcase, and the row is\r\n * created automatically as soon as the domain's schema is imported. Merging a changeset that\r\n * registers a domain therefore conflicts with the row that was just created locally. The conflict\r\n * is benign - both rows describe the same domain - so it must never fail the pull.\r\n *\r\n * The tests build a timeline where two changesets each insert the same `dgn_Domain` row:\r\n *\r\n * cs1 (b1) - \"Domain schema upgrade\" : INSERT dgn_Domain('Functional')\r\n * cs2 (b3) - duplicate domain rows : INSERT dgn_Domain('Functional') again\r\n *\r\n * b1 and b3 both start at version 0 and perform the same domain upgrade, and cs2 is appended to\r\n * the timeline without rebasing. A normal `pushChanges()` cannot produce this shape because the\r\n * rebase drops the duplicate insert first, but it is observed in the field from connectors.\r\n *\r\n * Merging both changesets in one pull used to abort, while pulling them one at a time succeeded,\r\n * leaving the cached briefcase permanently stuck. All of these must now succeed alike.\r\n */\r\ndescribe(\"dgn_Domain merge conflict\", () => {\r\n let iTwinId: GuidString;\r\n\r\n before(async () => {\r\n await TestUtils.startBackend();\r\n HubMock.startup(\"DgnDomainMergeConflict\", KnownTestLocations.outputDir);\r\n iTwinId = HubMock.iTwinId;\r\n });\r\n after(() => HubMock.shutdown());\r\n\r\n /** The domain names present in the briefcase's `dgn_Domain` table. */\r\n function queryDomains(db: BriefcaseDb): string[] {\r\n const names: string[] = [];\r\n db.withPreparedSqliteStatement(\"SELECT Name FROM dgn_Domain ORDER BY Name\", (stmt) => {\r\n while (stmt.step() === DbResult.BE_SQLITE_ROW)\r\n names.push(stmt.getValueString(0));\r\n });\r\n return names;\r\n }\r\n\r\n /**\r\n * Creates an iModel whose timeline holds two changesets that each INSERT the same\r\n * `dgn_Domain` row, and leaves both authoring briefcases closed.\r\n */\r\n async function createTimelineWithDuplicateDomainRows(iModelName: string) {\r\n const accessToken1 = await HubWrappers.getAccessToken(TestUserType.SuperManager);\r\n const accessToken3 = await HubWrappers.getAccessToken(TestUserType.Super);\r\n\r\n const iModelId = await HubMock.createNewIModel({ accessToken: accessToken1, iTwinId, iModelName, description: iModelName, noLocks: true });\r\n assert.isNotEmpty(iModelId);\r\n\r\n const b1 = await HubWrappers.downloadAndOpenBriefcase({ accessToken: accessToken1, iTwinId, iModelId, noLock: true });\r\n b1.channels.addAllowedChannel(ChannelControl.sharedChannelName);\r\n // b3 is downloaded at version 0 as well, so it never learns about cs1.\r\n const b3 = await HubWrappers.downloadAndOpenBriefcase({ accessToken: accessToken3, iTwinId, iModelId, noLock: true });\r\n b3.channels.addAllowedChannel(ChannelControl.sharedChannelName);\r\n\r\n // cs1: the \"Domain schema upgrade\" changeset. Importing the Functional *domain* adds a\r\n // `dgn_Domain` row for \"Functional\", and that insert is part of the pushed changeset.\r\n await withEditTxn(b1, \"Domain schema upgrade\", async () => {\r\n await FunctionalSchema.importSchema(b1);\r\n });\r\n await b1.pushChanges({ accessToken: accessToken1, description: \"Domain schema upgrade\" });\r\n expect(queryDomains(b1)).to.include(\"Functional\");\r\n\r\n // cs2: b3 performs the same domain upgrade while unaware of cs1, so its changeset carries a\r\n // second INSERT for the same `dgn_Domain` row. It is appended to the timeline without a\r\n // rebase (a rebase would rewrite the duplicate insert away, which is exactly why this shape\r\n // of changeset cannot be produced by `pushChanges` but is observed in the field).\r\n await withEditTxn(b3, \"Domain schema upgrade\", async () => {\r\n await FunctionalSchema.importSchema(b3);\r\n });\r\n // extra content so cs2 does not hash to the same changeset id as cs1\r\n withEditTxn(b3, \"connector data\", (txn) => {\r\n IModelTestUtils.createAndInsertPhysicalPartitionAndModel(txn, IModelTestUtils.getUniqueModelCode(b3, \"ConnectorModel\"), true);\r\n });\r\n\r\n const csProps = b3[_nativeDb].startCreateChangeset() as ChangesetFileProps;\r\n csProps.briefcaseId = b3.briefcaseId;\r\n csProps.description = \"Duplicate dgn_Domain rows\";\r\n csProps.size = fs.statSync(csProps.pathname).size;\r\n csProps.parentId = b1.changeset.id;\r\n csProps.id = IModelNative.platform.DgnDb.computeChangesetId(csProps);\r\n await HubMock.pushChangeset({ accessToken: accessToken3, iModelId, changesetProps: csProps });\r\n b3[_nativeDb].abandonCreateChangeset();\r\n\r\n b1.close();\r\n b3.close();\r\n return { iModelId, tipIndex: 2 };\r\n }\r\n\r\n it(\"resolves the duplicate dgn_Domain insert when the changesets are pulled one at a time\", async () => {\r\n const { iModelId } = await createTimelineWithDuplicateDomainRows(\"DgnDomainOneAtATime\");\r\n const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);\r\n\r\n // A briefcase parked at version 0, i.e. behind the domain upgrade.\r\n const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });\r\n try {\r\n expect(b2.txns.hasPendingTxns).to.be.false;\r\n\r\n // Each pull is its own pull-merge session, so no local change is pending when the\r\n // conflicting insert is applied.\r\n await b2.pullChanges({ accessToken, toIndex: 1 });\r\n await b2.pullChanges({ accessToken });\r\n\r\n expect(b2.changeset.index).to.equal(2);\r\n expect(queryDomains(b2).filter((name) => name === \"Functional\")).to.have.lengthOf(1);\r\n } finally {\r\n b2.close();\r\n }\r\n });\r\n\r\n it(\"resolves the duplicate dgn_Domain insert when the changesets are merged by a single pullChanges\", async () => {\r\n const { iModelId } = await createTimelineWithDuplicateDomainRows(\"DgnDomainSinglePull\");\r\n const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);\r\n\r\n const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });\r\n try {\r\n expect(b2.txns.hasPendingTxns).to.be.false;\r\n\r\n // Both changesets are merged inside one pull-merge session, so merging cs1 leaves a pending\r\n // change behind while cs2 is merged. That used to turn the benign `dgn_Domain` conflict into\r\n // an abort and leave the briefcase stuck mid-merge. The conflict is now always resolved by\r\n // keeping the incoming row, so batching no longer changes the outcome.\r\n await b2.pullChanges({ accessToken });\r\n\r\n expect(b2.changeset.index).to.equal(2);\r\n expect(queryDomains(b2).filter((name) => name === \"Functional\")).to.have.lengthOf(1);\r\n } finally {\r\n b2.close();\r\n }\r\n });\r\n\r\n it(\"succeeds for a briefcase downloaded at the tip\", async () => {\r\n const { iModelId } = await createTimelineWithDuplicateDomainRows(\"DgnDomainFreshBriefcase\");\r\n const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);\r\n\r\n const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, noLock: true });\r\n try {\r\n expect(b2.changeset.index).to.equal(2);\r\n expect(queryDomains(b2).filter((name) => name === \"Functional\")).to.have.lengthOf(1);\r\n } finally {\r\n b2.close();\r\n }\r\n });\r\n\r\n it(\"skips the duplicate local dgn_Domain insert while rebasing local txns\", async () => {\r\n const { iModelId } = await createTimelineWithDuplicateDomainRows(\"DgnDomainRebase\");\r\n const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);\r\n\r\n const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });\r\n try {\r\n b2.channels.addAllowedChannel(ChannelControl.sharedChannelName);\r\n\r\n // b2 imports the same domain locally and keeps the txn pending, so pulling has to merge the\r\n // two incoming inserts and then replay a local txn that inserts the very same\r\n // `dgn_Domain` row over the already merged one.\r\n await withEditTxn(b2, \"Domain schema upgrade\", async () => {\r\n await FunctionalSchema.importSchema(b2);\r\n });\r\n expect(b2.txns.hasPendingTxns).to.be.true;\r\n\r\n await b2.pullChanges({ accessToken });\r\n\r\n expect(b2.changeset.index).to.equal(2);\r\n expect(queryDomains(b2).filter((name) => name === \"Functional\")).to.have.lengthOf(1);\r\n } finally {\r\n b2.close();\r\n }\r\n });\r\n\r\n describe(\"conflict handler guards\", () => {\r\n let b2: BriefcaseDb;\r\n\r\n before(async () => {\r\n const { iModelId } = await createTimelineWithDuplicateDomainRows(\"DgnDomainHandlerGuards\");\r\n const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);\r\n b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });\r\n });\r\n after(() => b2?.close());\r\n\r\n /** A conflict as native code would report it, with just enough behavior for the handlers. */\r\n function makeConflictArgs(overrides: { tableName: string, cause: DbConflictCause, opcode: DbOpcode, indirect?: boolean }) {\r\n return {\r\n indirect: false,\r\n columnCount: 3,\r\n changesetFile: \"test.changeset\",\r\n txn: { id: \"0x1\", type: \"Schema\", descr: \"Domain schema upgrade\" },\r\n getForeignKeyConflicts: () => 0,\r\n dump: () => { },\r\n setLastError: () => { },\r\n getPrimaryKeyColumns: () => [1, 0, 0],\r\n getColumnNames: () => [\"Name\", \"Version\", \"Description\"],\r\n getValueType: () => undefined,\r\n getValueBinary: () => undefined,\r\n getValueId: () => undefined,\r\n getValueText: () => undefined,\r\n getValueInteger: () => undefined,\r\n getValueDouble: () => undefined,\r\n isValueNull: () => undefined,\r\n ...overrides,\r\n } as any;\r\n }\r\n\r\n const onMergeConflict = (args: any): DbConflictResolution | undefined => (b2 as any).onChangesetConflict(args);\r\n const onRebaseConflict = (args: any): DbConflictResolution => (b2.txns as any)._onRebaseLocalTxnConflict(args);\r\n\r\n it(\"keeps the incoming row for a duplicate dgn_Domain insert while merging\", () => {\r\n const args = makeConflictArgs({ tableName: \"dgn_Domain\", cause: DbConflictCause.Conflict, opcode: DbOpcode.Insert });\r\n expect(onMergeConflict(args)).to.equal(DbConflictResolution.Replace);\r\n });\r\n\r\n it(\"skips a duplicate dgn_Domain insert while rebasing\", () => {\r\n const args = makeConflictArgs({ tableName: \"dgn_Domain\", cause: DbConflictCause.Conflict, opcode: DbOpcode.Insert });\r\n expect(onRebaseConflict(args)).to.equal(DbConflictResolution.Skip);\r\n });\r\n\r\n // A `Data` conflict means an UPDATE or DELETE whose \"before\" values do not match the row in\r\n // the briefcase, i.e. the two rows really do differ. Resolving those automatically would\r\n // silently discard a genuine change, so they must keep the pre-existing behavior.\r\n for (const opcode of [DbOpcode.Update, DbOpcode.Delete]) {\r\n it(`does not swallow a dgn_Domain ${DbOpcode[opcode]} whose before values differ while merging`, () => {\r\n let lastError: string | undefined;\r\n const args = makeConflictArgs({ tableName: \"dgn_Domain\", cause: DbConflictCause.Data, opcode });\r\n args.setLastError = (message: string) => lastError = message;\r\n\r\n // With a local change pending, a mismatched UPDATE/DELETE must still abort the merge\r\n // rather than be resolved as a benign duplicate domain registration.\r\n const pending = sinon.stub(b2.txns, \"hasPendingTxns\").get(() => true);\r\n try {\r\n expect(onMergeConflict(args)).to.equal(DbConflictResolution.Abort);\r\n expect(lastError).to.not.be.undefined;\r\n } finally {\r\n pending.restore();\r\n }\r\n });\r\n\r\n it(`does not swallow a dgn_Domain ${DbOpcode[opcode]} whose before values differ while rebasing`, () => {\r\n const args = makeConflictArgs({ tableName: \"dgn_Domain\", cause: DbConflictCause.Data, opcode });\r\n // The local change wins, as it does for every other non-`ec_` table.\r\n expect(onRebaseConflict(args)).to.equal(DbConflictResolution.Replace);\r\n });\r\n }\r\n\r\n it(\"does not resolve conflicts on other tables\", () => {\r\n const args = makeConflictArgs({ tableName: \"bis_Element\", cause: DbConflictCause.Conflict, opcode: DbOpcode.Insert });\r\n expect(onRebaseConflict(args)).to.equal(DbConflictResolution.Abort);\r\n });\r\n });\r\n\r\n // TODO: unskip once a `@bentley/imodeljs-native` version containing iTwin/imodel-native#1548 is\r\n // pinned in core/backend/package.json. The default handler lives in the addon, so this case\r\n // cannot pass against 5.13.9.\r\n it.skip(\"resolves the conflict when the TypeScript handler defers to the default handler\", async () => {\r\n const { iModelId } = await createTimelineWithDuplicateDomainRows(\"DgnDomainNativeHandler\");\r\n const accessToken = await HubWrappers.getAccessToken(TestUserType.Regular);\r\n\r\n // `IModelDb.onChangesetConflict` is consulted first and normally resolves this conflict.\r\n // Returning `undefined` hands the decision to the default handler in the addon, which is the\r\n // path taken by hosts that install no JavaScript handler, such as checkpoint creation and\r\n // change history. Both handlers must agree, otherwise the same changeset would apply in an\r\n // iTwin.js backend but be rejected by those services.\r\n const stub = sinon.stub(BriefcaseDb.prototype, \"onChangesetConflict\" as any).returns(undefined);\r\n const b2 = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId, iModelId, asOf: { first: true }, noLock: true });\r\n try {\r\n await b2.pullChanges({ accessToken });\r\n\r\n expect(stub.called, \"the default conflict handler should have been reached via the stub\").to.be.true;\r\n expect(b2.changeset.index).to.equal(2);\r\n expect(queryDomains(b2).filter((name) => name === \"Functional\")).to.have.lengthOf(1);\r\n } finally {\r\n stub.restore();\r\n b2.close();\r\n }\r\n });\r\n});\r\n"]}
|
|
@@ -2082,9 +2082,7 @@ describe("ElementGeometry", () => {
|
|
|
2082
2082
|
saveTestTxn(imodel, true);
|
|
2083
2083
|
const updateElemProps = imodel.elements.getElementProps({ id: newId, wantGeometry: true });
|
|
2084
2084
|
assert.isUndefined(updateElemProps.geom);
|
|
2085
|
-
assert.
|
|
2086
|
-
const updatedPlacement = Placement3d.fromJSON(updateElemProps.placement);
|
|
2087
|
-
assert.isTrue(updatedPlacement.bbox.isNull);
|
|
2085
|
+
assert.isUndefined(updateElemProps.placement);
|
|
2088
2086
|
});
|
|
2089
2087
|
});
|
|
2090
2088
|
describe("BRepGeometry", () => {
|