@itwin/core-backend 5.7.0-dev.9 → 5.8.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.
Files changed (104) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/lib/cjs/BriefcaseManager.d.ts +138 -1
  3. package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
  4. package/lib/cjs/BriefcaseManager.js +336 -1
  5. package/lib/cjs/BriefcaseManager.js.map +1 -1
  6. package/lib/cjs/CloudSqlite.js +1 -1
  7. package/lib/cjs/CloudSqlite.js.map +1 -1
  8. package/lib/cjs/IModelDb.d.ts +71 -0
  9. package/lib/cjs/IModelDb.d.ts.map +1 -1
  10. package/lib/cjs/IModelDb.js +122 -6
  11. package/lib/cjs/IModelDb.js.map +1 -1
  12. package/lib/cjs/IModelHost.d.ts +15 -0
  13. package/lib/cjs/IModelHost.d.ts.map +1 -1
  14. package/lib/cjs/IModelHost.js +12 -0
  15. package/lib/cjs/IModelHost.js.map +1 -1
  16. package/lib/cjs/IModelJsFs.d.ts +2 -0
  17. package/lib/cjs/IModelJsFs.d.ts.map +1 -1
  18. package/lib/cjs/IModelJsFs.js +2 -0
  19. package/lib/cjs/IModelJsFs.js.map +1 -1
  20. package/lib/cjs/TileStorage.js +1 -1
  21. package/lib/cjs/TileStorage.js.map +1 -1
  22. package/lib/cjs/TxnManager.d.ts +39 -0
  23. package/lib/cjs/TxnManager.d.ts.map +1 -1
  24. package/lib/cjs/TxnManager.js +149 -0
  25. package/lib/cjs/TxnManager.js.map +1 -1
  26. package/lib/cjs/ViewDefinition.d.ts.map +1 -1
  27. package/lib/cjs/ViewDefinition.js +2 -0
  28. package/lib/cjs/ViewDefinition.js.map +1 -1
  29. package/lib/cjs/internal/IModelDbFontsImpl.js +1 -1
  30. package/lib/cjs/internal/IModelDbFontsImpl.js.map +1 -1
  31. package/lib/cjs/internal/IntegrityCheck.d.ts +240 -0
  32. package/lib/cjs/internal/IntegrityCheck.d.ts.map +1 -0
  33. package/lib/cjs/internal/IntegrityCheck.js +193 -0
  34. package/lib/cjs/internal/IntegrityCheck.js.map +1 -0
  35. package/lib/cjs/internal/annotations/fields.js +2 -2
  36. package/lib/cjs/internal/annotations/fields.js.map +1 -1
  37. package/lib/cjs/rpc/tracing.js +2 -2
  38. package/lib/cjs/rpc/tracing.js.map +1 -1
  39. package/lib/cjs/workspace/Workspace.js +1 -1
  40. package/lib/cjs/workspace/Workspace.js.map +1 -1
  41. package/lib/esm/BriefcaseManager.d.ts +138 -1
  42. package/lib/esm/BriefcaseManager.d.ts.map +1 -1
  43. package/lib/esm/BriefcaseManager.js +336 -1
  44. package/lib/esm/BriefcaseManager.js.map +1 -1
  45. package/lib/esm/CloudSqlite.js +1 -1
  46. package/lib/esm/CloudSqlite.js.map +1 -1
  47. package/lib/esm/IModelDb.d.ts +71 -0
  48. package/lib/esm/IModelDb.d.ts.map +1 -1
  49. package/lib/esm/IModelDb.js +122 -6
  50. package/lib/esm/IModelDb.js.map +1 -1
  51. package/lib/esm/IModelHost.d.ts +15 -0
  52. package/lib/esm/IModelHost.d.ts.map +1 -1
  53. package/lib/esm/IModelHost.js +12 -0
  54. package/lib/esm/IModelHost.js.map +1 -1
  55. package/lib/esm/IModelJsFs.d.ts +2 -0
  56. package/lib/esm/IModelJsFs.d.ts.map +1 -1
  57. package/lib/esm/IModelJsFs.js +2 -0
  58. package/lib/esm/IModelJsFs.js.map +1 -1
  59. package/lib/esm/TileStorage.js +1 -1
  60. package/lib/esm/TileStorage.js.map +1 -1
  61. package/lib/esm/TxnManager.d.ts +39 -0
  62. package/lib/esm/TxnManager.d.ts.map +1 -1
  63. package/lib/esm/TxnManager.js +150 -1
  64. package/lib/esm/TxnManager.js.map +1 -1
  65. package/lib/esm/ViewDefinition.d.ts.map +1 -1
  66. package/lib/esm/ViewDefinition.js +2 -0
  67. package/lib/esm/ViewDefinition.js.map +1 -1
  68. package/lib/esm/internal/IModelDbFontsImpl.js +1 -1
  69. package/lib/esm/internal/IModelDbFontsImpl.js.map +1 -1
  70. package/lib/esm/internal/IntegrityCheck.d.ts +240 -0
  71. package/lib/esm/internal/IntegrityCheck.d.ts.map +1 -0
  72. package/lib/esm/internal/IntegrityCheck.js +187 -0
  73. package/lib/esm/internal/IntegrityCheck.js.map +1 -0
  74. package/lib/esm/internal/annotations/fields.js +2 -2
  75. package/lib/esm/internal/annotations/fields.js.map +1 -1
  76. package/lib/esm/rpc/tracing.js +2 -2
  77. package/lib/esm/rpc/tracing.js.map +1 -1
  78. package/lib/esm/test/SquashSchemaAndDataChanges.test.d.ts +2 -0
  79. package/lib/esm/test/SquashSchemaAndDataChanges.test.d.ts.map +1 -0
  80. package/lib/esm/test/SquashSchemaAndDataChanges.test.js +241 -0
  81. package/lib/esm/test/SquashSchemaAndDataChanges.test.js.map +1 -0
  82. package/lib/esm/test/ecdb/ECSqlQuery.test.js +13 -2
  83. package/lib/esm/test/ecdb/ECSqlQuery.test.js.map +1 -1
  84. package/lib/esm/test/hubaccess/Rebase.test.js +1575 -1568
  85. package/lib/esm/test/hubaccess/Rebase.test.js.map +1 -1
  86. package/lib/esm/test/hubaccess/SemanticRebase.test.d.ts +2 -0
  87. package/lib/esm/test/hubaccess/SemanticRebase.test.d.ts.map +1 -0
  88. package/lib/esm/test/hubaccess/SemanticRebase.test.js +1206 -0
  89. package/lib/esm/test/hubaccess/SemanticRebase.test.js.map +1 -0
  90. package/lib/esm/test/imodel/IModel.test.js +1 -1
  91. package/lib/esm/test/imodel/IModel.test.js.map +1 -1
  92. package/lib/esm/test/standalone/ChangesetReader.test.js +173 -2
  93. package/lib/esm/test/standalone/ChangesetReader.test.js.map +1 -1
  94. package/lib/esm/test/standalone/IntegrityCheck.test.d.ts +2 -0
  95. package/lib/esm/test/standalone/IntegrityCheck.test.d.ts.map +1 -0
  96. package/lib/esm/test/standalone/IntegrityCheck.test.js +385 -0
  97. package/lib/esm/test/standalone/IntegrityCheck.test.js.map +1 -0
  98. package/lib/esm/test/standalone/ViewDefinition.test.js +14 -2
  99. package/lib/esm/test/standalone/ViewDefinition.test.js.map +1 -1
  100. package/lib/esm/test/standalone/Workspace.test.js +5 -0
  101. package/lib/esm/test/standalone/Workspace.test.js.map +1 -1
  102. package/lib/esm/workspace/Workspace.js +1 -1
  103. package/lib/esm/workspace/Workspace.js.map +1 -1
  104. package/package.json +14 -14
@@ -0,0 +1,1206 @@
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 { Id64 } from "@itwin/core-bentley";
6
+ import { Code, IModel, SubCategoryAppearance } from "@itwin/core-common";
7
+ import * as chai from "chai";
8
+ import { HubWrappers, IModelTestUtils, KnownTestLocations } from "..";
9
+ import { BriefcaseManager, ChannelControl, DrawingCategory, IModelJsFs } from "../../core-backend";
10
+ import { HubMock } from "../../internal/HubMock";
11
+ import { EntityClass } from "@itwin/ecschema-metadata";
12
+ import { TestUtils } from "../TestUtils";
13
+ /**
14
+ * Test infrastructure for rebase tests in this file.
15
+ * Manages two briefcases (far and local)
16
+ */
17
+ class TestIModel {
18
+ iModelId = "";
19
+ drawingModelId = "";
20
+ drawingCategoryId = "";
21
+ far;
22
+ local;
23
+ constructor(iModelId, drawingModelId, drawingCategoryId, far, local) {
24
+ this.iModelId = iModelId;
25
+ this.drawingModelId = drawingModelId;
26
+ this.drawingCategoryId = drawingCategoryId;
27
+ this.far = far;
28
+ this.local = local;
29
+ }
30
+ /** Reusable schema definitions for testing rebase with schema transformations */
31
+ static schemas = {
32
+ /** Base schema v01.00.00 with classes A, C, D */
33
+ v01x00x00: `<?xml version="1.0" encoding="UTF-8"?>
34
+ <ECSchema schemaName="TestDomain" alias="td" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
35
+ <ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
36
+ <ECEntityClass typeName="A">
37
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
38
+ <ECProperty propertyName="PropA" typeName="string"/>
39
+ </ECEntityClass>
40
+ <ECEntityClass typeName="C">
41
+ <BaseClass>A</BaseClass>
42
+ <ECProperty propertyName="PropC" typeName="string"/>
43
+ </ECEntityClass>
44
+ <ECEntityClass typeName="D">
45
+ <BaseClass>A</BaseClass>
46
+ <ECProperty propertyName="PropD" typeName="string"/>
47
+ </ECEntityClass>
48
+ </ECSchema>`,
49
+ /** v01.00.01 - Adds PropC2 to class C (trivial additive change) */
50
+ v01x00x01AddPropC2: `<?xml version="1.0" encoding="UTF-8"?>
51
+ <ECSchema schemaName="TestDomain" alias="td" version="01.00.01" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
52
+ <ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
53
+ <ECEntityClass typeName="A">
54
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
55
+ <ECProperty propertyName="PropA" typeName="string"/>
56
+ </ECEntityClass>
57
+ <ECEntityClass typeName="C">
58
+ <BaseClass>A</BaseClass>
59
+ <ECProperty propertyName="PropC" typeName="string"/>
60
+ <ECProperty propertyName="PropC2" typeName="string"/>
61
+ </ECEntityClass>
62
+ <ECEntityClass typeName="D">
63
+ <BaseClass>A</BaseClass>
64
+ <ECProperty propertyName="PropD" typeName="string"/>
65
+ </ECEntityClass>
66
+ </ECSchema>`,
67
+ /** v01.00.02 - Adds PropD2 to class D (trivial additive change) */
68
+ v01x00x02AddPropD2: `<?xml version="1.0" encoding="UTF-8"?>
69
+ <ECSchema schemaName="TestDomain" alias="td" version="01.00.02" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
70
+ <ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
71
+ <ECEntityClass typeName="A">
72
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
73
+ <ECProperty propertyName="PropA" typeName="string"/>
74
+ </ECEntityClass>
75
+ <ECEntityClass typeName="C">
76
+ <BaseClass>A</BaseClass>
77
+ <ECProperty propertyName="PropC" typeName="string"/>
78
+ <ECProperty propertyName="PropC2" typeName="string"/>
79
+ </ECEntityClass>
80
+ <ECEntityClass typeName="D">
81
+ <BaseClass>A</BaseClass>
82
+ <ECProperty propertyName="PropD" typeName="string"/>
83
+ <ECProperty propertyName="PropD2" typeName="string"/>
84
+ </ECEntityClass>
85
+ </ECSchema>`,
86
+ /** v01.00.02 - Moves PropC from C to A (requires data transformation) on top of v01.00.01 */
87
+ v01x00x02MovePropCToA: `<?xml version="1.0" encoding="UTF-8"?>
88
+ <ECSchema schemaName="TestDomain" alias="td" version="01.00.02" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
89
+ <ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
90
+ <ECEntityClass typeName="A">
91
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
92
+ <ECProperty propertyName="PropA" typeName="string"/>
93
+ <ECProperty propertyName="PropC" typeName="string"/>
94
+ </ECEntityClass>
95
+ <ECEntityClass typeName="C">
96
+ <BaseClass>A</BaseClass>
97
+ <ECProperty propertyName="PropC2" typeName="string"/>
98
+ </ECEntityClass>
99
+ <ECEntityClass typeName="D">
100
+ <BaseClass>A</BaseClass>
101
+ <ECProperty propertyName="PropD" typeName="string"/>
102
+ </ECEntityClass>
103
+ </ECSchema>`,
104
+ /** v01.00.03 - Builds on top of v01.00.02 and in addition moves PropD to base, so we can have incoming and local transforming changes */
105
+ v01x00x03MovePropCAndD: `<?xml version="1.0" encoding="UTF-8"?>
106
+ <ECSchema schemaName="TestDomain" alias="td" version="01.00.03" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
107
+ <ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
108
+ <ECEntityClass typeName="A">
109
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
110
+ <ECProperty propertyName="PropA" typeName="string"/>
111
+ <ECProperty propertyName="PropC" typeName="string"/>
112
+ <ECProperty propertyName="PropD" typeName="string"/>
113
+ </ECEntityClass>
114
+ <ECEntityClass typeName="C">
115
+ <BaseClass>A</BaseClass>
116
+ <ECProperty propertyName="PropC2" typeName="string"/>
117
+ </ECEntityClass>
118
+ <ECEntityClass typeName="D">
119
+ <BaseClass>A</BaseClass>
120
+ <ECProperty propertyName="PropD2" typeName="string"/>
121
+ </ECEntityClass>
122
+ </ECSchema>`,
123
+ /** v01.00.01 (incompatible variant) - Adds PropC3 instead of PropC2 to class C (same version) */
124
+ v01x00x01AddPropC3Incompatible: `<?xml version="1.0" encoding="UTF-8"?>
125
+ <ECSchema schemaName="TestDomain" alias="td" version="01.00.01" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
126
+ <ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
127
+ <ECEntityClass typeName="A">
128
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
129
+ <ECProperty propertyName="PropA" typeName="string"/>
130
+ </ECEntityClass>
131
+ <ECEntityClass typeName="C">
132
+ <BaseClass>A</BaseClass>
133
+ <ECProperty propertyName="PropC" typeName="string"/>
134
+ <ECProperty propertyName="PropC3" typeName="string"/>
135
+ </ECEntityClass>
136
+ <ECEntityClass typeName="D">
137
+ <BaseClass>A</BaseClass>
138
+ <ECProperty propertyName="PropD" typeName="string"/>
139
+ </ECEntityClass>
140
+ </ECSchema>`,
141
+ /** v01.00.02 (incompatible variant) - Adds PropC3 instead of PropC2 to class C (higher version) */
142
+ v01x00x02AddPropC3Incompatible: `<?xml version="1.0" encoding="UTF-8"?>
143
+ <ECSchema schemaName="TestDomain" alias="td" version="01.00.02" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
144
+ <ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
145
+ <ECEntityClass typeName="A">
146
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
147
+ <ECProperty propertyName="PropA" typeName="string"/>
148
+ </ECEntityClass>
149
+ <ECEntityClass typeName="C">
150
+ <BaseClass>A</BaseClass>
151
+ <ECProperty propertyName="PropC" typeName="string"/>
152
+ <ECProperty propertyName="PropC3" typeName="string"/>
153
+ </ECEntityClass>
154
+ <ECEntityClass typeName="D">
155
+ <BaseClass>A</BaseClass>
156
+ <ECProperty propertyName="PropD" typeName="string"/>
157
+ </ECEntityClass>
158
+ </ECSchema>`,
159
+ /** v01.00.02 (incompatible variant) - Adds PropC2 (higher version, different type) */
160
+ v01x00x02AddPropC2Incompatible: `<?xml version="1.0" encoding="UTF-8"?>
161
+ <ECSchema schemaName="TestDomain" alias="td" version="01.00.02" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
162
+ <ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
163
+ <ECEntityClass typeName="A">
164
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
165
+ <ECProperty propertyName="PropA" typeName="string"/>
166
+ </ECEntityClass>
167
+ <ECEntityClass typeName="C">
168
+ <BaseClass>A</BaseClass>
169
+ <ECProperty propertyName="PropC" typeName="string"/>
170
+ <ECProperty propertyName="PropC2" typeName="int"/>
171
+ </ECEntityClass>
172
+ <ECEntityClass typeName="D">
173
+ <BaseClass>A</BaseClass>
174
+ <ECProperty propertyName="PropD" typeName="string"/>
175
+ </ECEntityClass>
176
+ </ECSchema>`,
177
+ /** v01.00.03 - Adds PropC2 as string (used to test incompatibility when reinstated on top of v01.00.02 with PropC2:int) */
178
+ v01x00x03AddPropC2: `<?xml version="1.0" encoding="UTF-8"?>
179
+ <ECSchema schemaName="TestDomain" alias="td" version="01.00.03" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
180
+ <ECSchemaReference name="BisCore" version="01.00.23" alias="bis"/>
181
+ <ECEntityClass typeName="A">
182
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
183
+ <ECProperty propertyName="PropA" typeName="string"/>
184
+ </ECEntityClass>
185
+ <ECEntityClass typeName="C">
186
+ <BaseClass>A</BaseClass>
187
+ <ECProperty propertyName="PropC" typeName="string"/>
188
+ <ECProperty propertyName="PropC2" typeName="string"/>
189
+ </ECEntityClass>
190
+ <ECEntityClass typeName="D">
191
+ <BaseClass>A</BaseClass>
192
+ <ECProperty propertyName="PropD" typeName="string"/>
193
+ </ECEntityClass>
194
+ </ECSchema>`,
195
+ };
196
+ /**
197
+ * Create and initialize a new test iModel with far and local briefcases.
198
+ * @param testName Unique name for this test (passed to HubMock.startup)
199
+ * @returns Fully initialized TestIModel with both briefcases open
200
+ */
201
+ static async initialize(testName) {
202
+ HubMock.startup(testName, KnownTestLocations.outputDir);
203
+ const iModelId = await HubMock.createNewIModel({
204
+ iTwinId: HubMock.iTwinId,
205
+ iModelName: testName,
206
+ description: `Rebase schema update with data transform tests: ${testName}`,
207
+ });
208
+ // Open far briefcase and use it for initialization
209
+ const far = await HubWrappers.downloadAndOpenBriefcase({
210
+ iTwinId: HubMock.iTwinId,
211
+ iModelId,
212
+ accessToken: "far-user",
213
+ });
214
+ far.channels.addAllowedChannel(ChannelControl.sharedChannelName);
215
+ far.saveChanges();
216
+ // Initialize with base schema
217
+ await far.importSchemaStrings([TestIModel.schemas.v01x00x00]);
218
+ far.saveChanges("import base schema");
219
+ await far.pushChanges({ description: "import base schema" });
220
+ // Create model and category
221
+ const modelCode = IModelTestUtils.getUniqueModelCode(far, "DrawingModel");
222
+ await far.locks.acquireLocks({ shared: IModel.dictionaryId });
223
+ const [, drawingModelId] = IModelTestUtils.createAndInsertDrawingPartitionAndModel(far, modelCode);
224
+ const drawingCategoryId = DrawingCategory.insert(far, IModel.dictionaryId, "DrawingCategory", new SubCategoryAppearance());
225
+ far.saveChanges();
226
+ await far.pushChanges({ description: "create model and category" });
227
+ // Open local briefcase
228
+ const local = await HubWrappers.downloadAndOpenBriefcase({
229
+ iTwinId: HubMock.iTwinId,
230
+ iModelId,
231
+ accessToken: "local-user",
232
+ });
233
+ local.channels.addAllowedChannel(ChannelControl.sharedChannelName);
234
+ local.saveChanges();
235
+ return new TestIModel(iModelId, drawingModelId, drawingCategoryId, far, local);
236
+ }
237
+ insertElement(briefcase, className, properties) {
238
+ const elementProps = {
239
+ classFullName: className,
240
+ model: this.drawingModelId,
241
+ category: this.drawingCategoryId,
242
+ code: Code.createEmpty(),
243
+ ...properties,
244
+ };
245
+ const element = briefcase.elements.createElement(elementProps);
246
+ return briefcase.elements.insertElement(element.toJSON());
247
+ }
248
+ updateElement(briefcase, elementId, updates) {
249
+ const element = briefcase.elements.getElement(elementId);
250
+ Object.assign(element, updates);
251
+ briefcase.elements.updateElement(element.toJSON());
252
+ }
253
+ getElement(briefcase, elementId) {
254
+ return briefcase.elements.getElement(elementId);
255
+ }
256
+ checkIfFolderExists(briefcase, txnId, isSchemaFolder) {
257
+ if (isSchemaFolder)
258
+ return BriefcaseManager.semanticRebaseSchemaFolderExists(briefcase, txnId);
259
+ return BriefcaseManager.semanticRebaseDataFolderExists(briefcase, txnId);
260
+ }
261
+ checkifRebaseFolderExists(briefcase) {
262
+ const folderPath = BriefcaseManager.getBasePathForSemanticRebaseLocalFiles(briefcase);
263
+ return IModelJsFs.existsSync(folderPath);
264
+ }
265
+ shutdown() {
266
+ this.far.close();
267
+ this.local.close();
268
+ HubMock.shutdown();
269
+ }
270
+ }
271
+ /**
272
+ * Test suite for rebase logic with schema changes that require data transformations.
273
+ */
274
+ describe("Semantic Rebase", function () {
275
+ this.timeout(60000); // operations can be slow
276
+ let t;
277
+ before(async () => {
278
+ await TestUtils.shutdownBackend(); // Automatically TestUtils.startBackend() is called before every test suite starts we need to shut tht down and startup our new TestUtils with semantic rebase on
279
+ await TestUtils.startBackend({ useSemanticRebase: true });
280
+ });
281
+ afterEach(() => {
282
+ if (t) {
283
+ t.shutdown();
284
+ t = undefined;
285
+ }
286
+ });
287
+ after(async () => {
288
+ await TestUtils.shutdownBackend();
289
+ await TestUtils.startBackend(); // restart normal backend so subsequent test suites aren't left without IModelHost
290
+ });
291
+ it("local data changes onto incoming trivial schema change", async () => {
292
+ t = await TestIModel.initialize("TrivialSchemaIncoming");
293
+ // Local creates an element
294
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
295
+ const elementId = t.insertElement(t.local, "TestDomain:C", {
296
+ propA: "value_a",
297
+ propC: "value_c",
298
+ });
299
+ t.local.saveChanges("create element");
300
+ await t.local.pushChanges({ description: "create test element" });
301
+ // Far imports updated schema with new property PropC2
302
+ await t.far.pullChanges();
303
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
304
+ // Verify that we're holding a shared lock (not exclusive) for semantic rebase
305
+ chai.expect(t.far.locks.holdsSharedLock(IModel.repositoryModelId)).to.be.true;
306
+ chai.expect(t.far.holdsSchemaLock).to.be.false;
307
+ const txnProps = t.far.txns.getLastSavedTxnProps();
308
+ chai.expect(txnProps).to.not.be.undefined;
309
+ chai.expect(txnProps.type).to.equal("Schema");
310
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.true;
311
+ t.far.saveChanges("add PropC2 to schema");
312
+ await t.far.pushChanges({ description: "add PropC2 to class C" });
313
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.false; // after push the folder should not be there
314
+ // Local makes local changes to the element
315
+ await t.local.locks.acquireLocks({ exclusive: elementId });
316
+ t.updateElement(t.local, elementId, { propA: "local_update_a" });
317
+ t.local.saveChanges("local update to propA");
318
+ // Local pulls and rebases local changes onto incoming schema change
319
+ await t.local.pullChanges();
320
+ // Verify: local changes preserved, schema updated
321
+ const element = t.getElement(t.local, elementId);
322
+ chai.expect(element.propA).to.equal("local_update_a", "Local property update should be preserved");
323
+ chai.expect(element.propC).to.equal("value_c", "Original propC should be preserved");
324
+ const schema = t.local.getSchemaProps("TestDomain");
325
+ chai.expect(schema.version).to.equal("01.00.01", "Schema should be updated to v01.00.01");
326
+ });
327
+ it("local trivial schema change onto incoming data changes", async () => {
328
+ t = await TestIModel.initialize("TrivialSchemaLocal");
329
+ // Local creates an element
330
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
331
+ const elementId = t.insertElement(t.local, "TestDomain:C", {
332
+ propA: "value_a",
333
+ propC: "value_c",
334
+ });
335
+ t.local.saveChanges("create element");
336
+ await t.local.pushChanges({ description: "create test element" });
337
+ // Local imports updated schema locally
338
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
339
+ // Verify that we're holding a shared lock (not exclusive) for semantic rebase
340
+ chai.expect(t.local.locks.holdsSharedLock(IModel.repositoryModelId)).to.be.true;
341
+ chai.expect(t.local.holdsSchemaLock).to.be.false;
342
+ const txnProps = t.local.txns.getLastSavedTxnProps();
343
+ chai.expect(txnProps).to.not.be.undefined;
344
+ chai.expect(txnProps.type).to.equal("Schema");
345
+ chai.expect(t.checkIfFolderExists(t.local, txnProps.id, true)).to.be.true;
346
+ t.local.saveChanges("local schema update");
347
+ // Far pulls element, then updates it
348
+ await t.far.pullChanges();
349
+ await t.far.locks.acquireLocks({ exclusive: elementId });
350
+ t.updateElement(t.far, elementId, { propA: "far_update_a" });
351
+ t.far.saveChanges("far update to propA");
352
+ await t.far.pushChanges({ description: "update element propA" });
353
+ // Local pulls and rebases local schema change onto incoming data changes
354
+ await t.local.pullChanges();
355
+ chai.expect(t.checkIfFolderExists(t.local, txnProps.id, true)).to.be.true; // after rebase the folder should be there until push is called
356
+ // Verify: incoming data changes applied, local schema preserved
357
+ const element = t.getElement(t.local, elementId);
358
+ chai.expect(element.propA).to.equal("far_update_a", "Incoming property update should be applied");
359
+ chai.expect(element.propC).to.equal("value_c", "Original propC should be preserved");
360
+ const schema = t.local.getSchemaProps("TestDomain");
361
+ chai.expect(schema.version).to.equal("01.00.01", "Local schema update should be preserved");
362
+ });
363
+ it("local data changes onto incoming data changes", async () => {
364
+ t = await TestIModel.initialize("DataOntoData");
365
+ // Local creates two elements
366
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
367
+ const elementId1 = t.insertElement(t.local, "TestDomain:C", {
368
+ propA: "value_a1",
369
+ propC: "value_c1",
370
+ });
371
+ const elementId2 = t.insertElement(t.local, "TestDomain:C", {
372
+ propA: "value_a2",
373
+ propC: "value_c2",
374
+ });
375
+ t.local.saveChanges("create elements");
376
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // its data changes on both sides semantic rebase is not used
377
+ await t.local.pushChanges({ description: "create test elements" });
378
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // its data changes on both sides semantic rebase is not used
379
+ // Far updates first element
380
+ await t.far.pullChanges();
381
+ await t.far.locks.acquireLocks({ exclusive: elementId1 });
382
+ t.updateElement(t.far, elementId1, { propC: "far_update_c" });
383
+ t.far.saveChanges("far update to propC");
384
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // its data changes on both sides semantic rebase is not used
385
+ await t.far.pushChanges({ description: "update element propC" });
386
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // its data changes on both sides semantic rebase is not used
387
+ // Local makes local changes to second element
388
+ await t.local.locks.acquireLocks({ exclusive: elementId2 });
389
+ t.updateElement(t.local, elementId2, { propA: "local_update_a" });
390
+ t.local.saveChanges("local update to propA");
391
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // its data changes on both sides semantic rebase is not used
392
+ // Local pulls and rebases
393
+ await t.local.pullChanges();
394
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // its data changes on both sides semantic rebase is not used
395
+ // Verify: both changes applied to their respective elements
396
+ const element1 = t.getElement(t.local, elementId1);
397
+ chai.expect(element1.propA).to.equal("value_a1", "Element 1 propA should be unchanged");
398
+ chai.expect(element1.propC).to.equal("far_update_c", "Element 1 incoming update should be applied");
399
+ const element2 = t.getElement(t.local, elementId2);
400
+ chai.expect(element2.propA).to.equal("local_update_a", "Element 2 local update should be preserved");
401
+ chai.expect(element2.propC).to.equal("value_c2", "Element 2 propC should be unchanged");
402
+ });
403
+ it("local trivial schema changes onto incoming trivial schema changes (local newer)", async () => {
404
+ t = await TestIModel.initialize("TrivialSchemaLocalNewer");
405
+ // Far imports v01.00.01 (adds PropC2)
406
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
407
+ const txnProps = t.far.txns.getLastSavedTxnProps();
408
+ chai.expect(txnProps).to.not.be.undefined;
409
+ chai.expect(txnProps.type).to.equal("Schema");
410
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.true;
411
+ t.far.saveChanges("add PropC2 to schema");
412
+ await t.far.pushChanges({ description: "add PropC2 to class C" });
413
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.false; // after push the folder should not be there
414
+ // Local imports v01.00.02 (adds PropC2 and PropD2 - newer)
415
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x02AddPropD2]);
416
+ const txnPropsLocal = t.local.txns.getLastSavedTxnProps();
417
+ chai.expect(txnPropsLocal).to.not.be.undefined;
418
+ chai.expect(txnPropsLocal.type).to.equal("Schema");
419
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.true;
420
+ t.local.saveChanges("local schema update to v01.00.02");
421
+ // Local pulls and rebases
422
+ await t.local.pullChanges();
423
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.true; // after rebase the folder should be there because local is newer until push is called
424
+ // Verify: local schema preserved (newer version)
425
+ const schema = t.local.getSchemaProps("TestDomain");
426
+ chai.expect(schema.version).to.equal("01.00.02", "Local schema (newer) should be preserved");
427
+ });
428
+ it("local trivial schema changes onto incoming trivial schema changes (incoming newer)", async () => {
429
+ t = await TestIModel.initialize("TrivialSchemaIncomingNewer");
430
+ // Far imports v01.00.02 (adds PropC2 and PropD2 - newer)
431
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x02AddPropD2]);
432
+ const txnProps = t.far.txns.getLastSavedTxnProps();
433
+ chai.expect(txnProps).to.not.be.undefined;
434
+ chai.expect(txnProps.type).to.equal("Schema");
435
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.true;
436
+ t.far.saveChanges("add PropC2 and PropD2 to schema");
437
+ await t.far.pushChanges({ description: "update schema to v01.00.02" });
438
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.false; // after push the folder should not be there
439
+ // Local imports v01.00.01 (adds only PropC2 - older)
440
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
441
+ const txnPropsLocal = t.local.txns.getLastSavedTxnProps();
442
+ chai.expect(txnPropsLocal).to.not.be.undefined;
443
+ chai.expect(txnPropsLocal.type).to.equal("Schema");
444
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.true;
445
+ t.local.saveChanges("local schema update to v01.00.01");
446
+ // Local pulls and rebases
447
+ await t.local.pullChanges();
448
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.false; // after rebase the folder should not be there because incoming is newer so while rebasing it should be a no op
449
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // there should not be a rebase folder because the rebase folder is deleted after rebase if it contains nothing
450
+ // Verify: incoming schema preserved (newer version, local should not override)
451
+ const schema = t.local.getSchemaProps("TestDomain");
452
+ chai.expect(schema.version).to.equal("01.00.02", "Incoming schema (newer) should win, local should not override");
453
+ });
454
+ it("local trivial schema changes onto incoming identical schema changes", async () => {
455
+ t = await TestIModel.initialize("TrivialSchemaIdentical");
456
+ // Far imports v01.00.01 (adds PropC2)
457
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
458
+ // Verify that we're holding a shared lock (not exclusive) for semantic rebase
459
+ chai.expect(t.far.locks.holdsSharedLock(IModel.repositoryModelId)).to.be.true;
460
+ chai.expect(t.far.holdsSchemaLock).to.be.false;
461
+ const txnProps = t.far.txns.getLastSavedTxnProps();
462
+ chai.expect(txnProps).to.not.be.undefined;
463
+ chai.expect(txnProps.type).to.equal("Schema");
464
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.true;
465
+ t.far.saveChanges("add PropC2 to schema");
466
+ await t.far.pushChanges({ description: "add PropC2 to class C" });
467
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.false; // after push the folder should not be there
468
+ // Local imports the same v01.00.01 (adds PropC2)
469
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
470
+ const txnPropsLocal = t.local.txns.getLastSavedTxnProps();
471
+ chai.expect(txnPropsLocal).to.not.be.undefined;
472
+ chai.expect(txnPropsLocal.type).to.equal("Schema");
473
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.true;
474
+ t.local.saveChanges("local schema update to v01.00.01");
475
+ // Local pulls and rebases
476
+ await t.local.pullChanges();
477
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.false; // after rebase the folder should not be there as both are identical
478
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // there should not be a rebase folder because the rebase folder is deleted if it contains nothing after rebase
479
+ // Verify: schema preserved (both sides identical)
480
+ const schema = t.local.getSchemaProps("TestDomain");
481
+ chai.expect(schema.version).to.equal("01.00.01", "Schema should be v01.00.01");
482
+ });
483
+ it("local trivial schema changes onto incoming identical schema changes with data changes on both sides", async () => {
484
+ t = await TestIModel.initialize("TrivialSchemaIdenticalWithData");
485
+ // Far imports v01.00.01 (adds PropC2) and creates an element
486
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
487
+ const txnProps = t.far.txns.getLastSavedTxnProps();
488
+ chai.expect(txnProps).to.not.be.undefined;
489
+ chai.expect(txnProps.type).to.equal("Schema");
490
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.true;
491
+ t.far.saveChanges("add PropC2 to schema");
492
+ await t.far.pushChanges({ description: "add PropC2 to class C" });
493
+ chai.expect(t.checkIfFolderExists(t.far, txnProps.id, true)).to.be.false; // after push the folder should not be there
494
+ await t.far.locks.acquireLocks({ shared: t.drawingModelId });
495
+ const farElementId = t.insertElement(t.far, "TestDomain:C", {
496
+ propA: "far_value_a",
497
+ propC: "far_value_c",
498
+ propC2: "far_value_c2",
499
+ });
500
+ t.far.saveChanges("far creates element with new property");
501
+ await t.far.pushChanges({ description: "far creates element" });
502
+ // Local imports the same v01.00.01 (adds PropC2) and creates an element
503
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
504
+ const txnPropsLocal = t.local.txns.getLastSavedTxnProps();
505
+ chai.expect(txnPropsLocal).to.not.be.undefined;
506
+ chai.expect(txnPropsLocal.type).to.equal("Schema");
507
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.true;
508
+ t.local.saveChanges("local schema update to v01.00.01");
509
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
510
+ const localElementId = t.insertElement(t.local, "TestDomain:C", {
511
+ propA: "local_value_a",
512
+ propC: "local_value_c",
513
+ propC2: "local_value_c2",
514
+ });
515
+ t.local.saveChanges("local creates element with new property");
516
+ // Local pulls and rebases
517
+ await t.local.pullChanges();
518
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.false; // after rebase the folder should not be there as both are identical
519
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // there should not be a rebase folder because the rebase folder is deleted after rebase if it contains nothing
520
+ // Verify: schema preserved (both sides identical)
521
+ const schema = t.local.getSchemaProps("TestDomain");
522
+ chai.expect(schema.version).to.equal("01.00.01", "Schema should be v01.00.01");
523
+ // Verify: both elements exist with their original properties
524
+ const farElement = t.getElement(t.local, farElementId);
525
+ chai.expect(farElement.propA).to.equal("far_value_a", "Far element propA should be preserved");
526
+ chai.expect(farElement.propC).to.equal("far_value_c", "Far element propC should be preserved");
527
+ chai.expect(farElement.propC2).to.equal("far_value_c2", "Far element propC2 should be preserved");
528
+ const localElement = t.getElement(t.local, localElementId);
529
+ chai.expect(localElement.propA).to.equal("local_value_a", "Local element propA should be preserved");
530
+ chai.expect(localElement.propC).to.equal("local_value_c", "Local element propC should be preserved");
531
+ chai.expect(localElement.propC2).to.equal("local_value_c2", "Local element propC2 should be preserved");
532
+ });
533
+ it("both add different properties, increment to same version number", async () => {
534
+ t = await TestIModel.initialize("TrivialSchemaIncompatible");
535
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
536
+ t.far.saveChanges("add PropC2 to schema");
537
+ await t.far.pushChanges({ description: "add PropC2 to class C" });
538
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC3Incompatible]);
539
+ t.local.saveChanges("local schema update to v01.00.01 with PropC3");
540
+ await t.local.pullChanges(); // TODO: this currently passes, because same version number means no upgrade is attempted
541
+ //TODO: this should probably fail instead as both sides made incompatible changes to the same version, but this is unrelated to semantic rebase itself
542
+ const schema = t.local.getSchemaProps("TestDomain");
543
+ chai.expect(schema.version).to.equal("01.00.01", "Schema should be v01.00.01");
544
+ });
545
+ it("both add compatible properties, local version number higher", async () => {
546
+ t = await TestIModel.initialize("CompatibleSchemaLocalHigher");
547
+ // Far imports v01.00.01 (adds PropC2)
548
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
549
+ t.far.saveChanges("add PropC2 to schema");
550
+ await t.far.pushChanges({ description: "add PropC2 to class C" });
551
+ // Local imports v01.00.02 (adds PropC2 and PropD2 - compatible higher version)
552
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x02AddPropD2]);
553
+ t.local.saveChanges("local schema update to v01.00.02 with PropD2");
554
+ // Local pulls and rebases
555
+ await t.local.pullChanges();
556
+ // Verify: Local schema wins (higher version)
557
+ const schema = t.local.getSchemaProps("TestDomain");
558
+ chai.expect(schema.version).to.equal("01.00.02", "Schema should be v01.00.02 (higher version wins)");
559
+ const classC = await t.local.schemaContext.getSchemaItem("TestDomain", "C", EntityClass);
560
+ chai.expect(classC).to.not.be.undefined;
561
+ chai.expect(await classC.getProperty("PropC2")).to.exist;
562
+ const classD = await t.local.schemaContext.getSchemaItem("TestDomain", "D", EntityClass);
563
+ chai.expect(classD).to.not.be.undefined;
564
+ chai.expect(await classD.getProperty("PropD2")).to.exist;
565
+ });
566
+ it("both add compatible properties, incoming version number higher", async () => {
567
+ t = await TestIModel.initialize("CompatibleSchemaIncomingHigher");
568
+ // Far imports v01.00.02 (adds PropC2 and PropD2 - higher version)
569
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x02AddPropD2]);
570
+ t.far.saveChanges("add PropC2 and PropD2 to schema");
571
+ await t.far.pushChanges({ description: "update schema to v01.00.02" });
572
+ // Local imports v01.00.01 (adds only PropC2 - compatible lower version)
573
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
574
+ t.local.saveChanges("local schema update to v01.00.01 with PropC2");
575
+ // Local pulls and rebases
576
+ await t.local.pullChanges();
577
+ // Verify: Incoming schema wins (higher version)
578
+ const schema = t.local.getSchemaProps("TestDomain");
579
+ chai.expect(schema.version).to.equal("01.00.02", "Schema should be v01.00.02 (higher version wins)");
580
+ const classC = await t.local.schemaContext.getSchemaItem("TestDomain", "C", EntityClass);
581
+ chai.expect(classC).to.not.be.undefined;
582
+ chai.expect(await classC.getProperty("PropC2")).to.exist;
583
+ const classD = await t.local.schemaContext.getSchemaItem("TestDomain", "D", EntityClass);
584
+ chai.expect(classD).to.not.be.undefined;
585
+ chai.expect(await classD.getProperty("PropD2")).to.exist;
586
+ });
587
+ it("both add same but incompatible property, local version number higher", async () => {
588
+ t = await TestIModel.initialize("TrivialSchemaIncompatible");
589
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
590
+ t.far.saveChanges("add PropC2 to schema");
591
+ await t.far.pushChanges({ description: "add PropC2 to class C" });
592
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x02AddPropC2Incompatible]);
593
+ t.local.saveChanges("local schema update to v01.00.02 with PropC2");
594
+ // Local pulls and rebases - this should detect the incompatibility and fail
595
+ await chai.expect(t.local.pullChanges()).to.be.rejectedWith("ECSchema Upgrade failed");
596
+ });
597
+ it("both add same but incompatible property, incoming version number higher", async () => {
598
+ t = await TestIModel.initialize("TrivialSchemaIncompatible");
599
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x02AddPropC2Incompatible]);
600
+ t.far.saveChanges("add PropC2 to schema");
601
+ await t.far.pushChanges({ description: "import v01.00.02 with PropC2 as int" });
602
+ // Local uses v01.00.03 with PropC2:string — higher version ensures the upgrade is attempted
603
+ // during reinstatement, which detects the type mismatch (string vs int)
604
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x03AddPropC2]);
605
+ t.local.saveChanges("local schema update to v01.00.03 with PropC2 as string");
606
+ // Local pulls and rebases - this should detect the incompatibility and fail
607
+ await chai.expect(t.local.pullChanges()).to.be.rejectedWith("ECSchema Upgrade failed");
608
+ });
609
+ it("local transforming schema change onto incoming trivial schema change", async () => {
610
+ t = await TestIModel.initialize("LocalTransformIncomingTrivial");
611
+ // Far: Insert Element and import trivial schema change
612
+ await t.far.locks.acquireLocks({ shared: t.drawingModelId });
613
+ const farElementId = t.insertElement(t.far, "TestDomain:C", {
614
+ propA: "far_value_a",
615
+ propC: "far_value_c",
616
+ });
617
+ t.far.saveChanges("far create element");
618
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
619
+ const txnPropsFar = t.far.txns.getLastSavedTxnProps();
620
+ chai.expect(txnPropsFar).to.not.be.undefined;
621
+ chai.expect(txnPropsFar.type).to.equal("Schema");
622
+ chai.expect(t.checkIfFolderExists(t.far, txnPropsFar.id, true)).to.be.true; // schema folder should exist
623
+ t.far.saveChanges("far trivial schema update");
624
+ await t.far.pushChanges({ description: "far add PropC2" });
625
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // after push the folder should not be there
626
+ // Local: Insert Element and import transforming schema change
627
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
628
+ const localElementId = t.insertElement(t.local, "TestDomain:C", {
629
+ propA: "local_value_a",
630
+ propC: "local_value_c",
631
+ });
632
+ t.local.saveChanges("local create element");
633
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
634
+ const txnPropsLocal = t.local.txns.getLastSavedTxnProps();
635
+ chai.expect(txnPropsLocal).to.not.be.undefined;
636
+ chai.expect(txnPropsLocal.type).to.equal("Schema");
637
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.true; // schema folder should exist
638
+ t.local.saveChanges("local transforming schema update");
639
+ // Local pulls and rebases transforming change onto incoming trivial change
640
+ await t.local.pushChanges({ description: "local move PropC to A" });
641
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // after push the folder should not be there
642
+ // Verify: both elements have PropC intact, schema transformed locally
643
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
644
+ const farElement = t.getElement(t.local, farElementId);
645
+ chai.expect(farElement.propA).to.equal("far_value_a", "Far element propA should be preserved");
646
+ chai.expect(farElement.propC).to.equal("far_value_c", "Far element propC should be preserved after transform");
647
+ const localElement = t.getElement(t.local, localElementId);
648
+ chai.expect(localElement.propA).to.equal("local_value_a", "Local element propA should be preserved");
649
+ chai.expect(localElement.propC).to.equal("local_value_c", "Local element propC should be preserved after transform");
650
+ });
651
+ it("local trivial schema change onto incoming transforming schema change", async () => {
652
+ t = await TestIModel.initialize("LocalTrivialIncomingTransform");
653
+ // Far: Insert Element and import transforming schema change
654
+ await t.far.locks.acquireLocks({ shared: t.drawingModelId });
655
+ const farElementId = t.insertElement(t.far, "TestDomain:C", {
656
+ propA: "far_value_a",
657
+ propC: "far_value_c",
658
+ });
659
+ t.far.saveChanges("far create element");
660
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
661
+ t.far.saveChanges("far transforming schema update");
662
+ await t.far.pushChanges({ description: "far move PropC to A" });
663
+ // Local: Insert Element and import trivial schema change
664
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
665
+ const localElementId = t.insertElement(t.local, "TestDomain:C", {
666
+ propA: "local_value_a",
667
+ propC: "local_value_c",
668
+ });
669
+ t.local.saveChanges("local create element");
670
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
671
+ const txnPropsLocal = t.local.txns.getLastSavedTxnProps();
672
+ chai.expect(txnPropsLocal).to.not.be.undefined;
673
+ chai.expect(txnPropsLocal.type).to.equal("Schema");
674
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.true; // schema folder should exist
675
+ t.local.saveChanges("local trivial schema update");
676
+ // Local pulls and rebases trivial change onto incoming transforming change
677
+ await t.local.pushChanges({ description: "local add PropC2" });
678
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // after push the folder should not be there
679
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
680
+ // Verify: both elements have PropC intact after incoming transform
681
+ const farElement = t.getElement(t.local, farElementId);
682
+ chai.expect(farElement.propA).to.equal("far_value_a", "Far element propA should be preserved");
683
+ chai.expect(farElement.propC).to.equal("far_value_c", "Far element propC should be preserved after incoming transform");
684
+ const localElement = t.getElement(t.local, localElementId);
685
+ chai.expect(localElement.propA).to.equal("local_value_a", "Local element propA should be preserved");
686
+ chai.expect(localElement.propC).to.equal("local_value_c", "Local element propC should be preserved after incoming transform");
687
+ });
688
+ it("local transforming schema change onto incoming transforming schema change", async () => {
689
+ t = await TestIModel.initialize("BothTransforming");
690
+ // Far: Create elements with PropC and PropD, import transforming schema (moves PropC to A)
691
+ await t.far.locks.acquireLocks({ shared: t.drawingModelId });
692
+ const farElementC = t.insertElement(t.far, "TestDomain:C", {
693
+ propA: "far_value_a_c",
694
+ propC: "far_value_c",
695
+ });
696
+ const farElementD = t.insertElement(t.far, "TestDomain:D", {
697
+ propA: "far_value_a_d",
698
+ propD: "far_value_d",
699
+ });
700
+ t.far.saveChanges("far create elements");
701
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
702
+ const txnPropsFar = t.far.txns.getLastSavedTxnProps();
703
+ chai.expect(txnPropsFar).to.not.be.undefined;
704
+ chai.expect(txnPropsFar.type).to.equal("Schema");
705
+ chai.expect(t.checkIfFolderExists(t.far, txnPropsFar.id, true)).to.be.true; // schema folder should exist
706
+ t.far.saveChanges("far move PropC to A");
707
+ await t.far.pushChanges({ description: "far transform PropC" });
708
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // after push the folder should not be there
709
+ // Local: Create elements with PropC and PropD, import transforming schema (moves both PropC and PropD to A)
710
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
711
+ const localElementC = t.insertElement(t.local, "TestDomain:C", {
712
+ propA: "local_value_a_c",
713
+ propC: "local_value_c",
714
+ });
715
+ const localElementD = t.insertElement(t.local, "TestDomain:D", {
716
+ propA: "local_value_a_d",
717
+ propD: "local_value_d",
718
+ });
719
+ t.local.saveChanges("local create elements");
720
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x03MovePropCAndD]);
721
+ const txnPropsLocal = t.local.txns.getLastSavedTxnProps();
722
+ chai.expect(txnPropsLocal).to.not.be.undefined;
723
+ chai.expect(txnPropsLocal.type).to.equal("Schema");
724
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.true; // schema folder should exist
725
+ t.local.saveChanges("local move PropC and PropD to A");
726
+ // Local pulls and rebases both transforming changes
727
+ await t.local.pushChanges({ description: "local transform PropC and PropD" });
728
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // after push the folder should not be there
729
+ t.far.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
730
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
731
+ // Verify: all elements have both PropC and PropD intact
732
+ const farElemC = t.getElement(t.local, farElementC);
733
+ chai.expect(farElemC.propA).to.equal("far_value_a_c", "Far element C propA should be preserved");
734
+ chai.expect(farElemC.propC).to.equal("far_value_c", "Far element C propC should be preserved after both transforms");
735
+ const farElemD = t.getElement(t.local, farElementD);
736
+ chai.expect(farElemD.propA).to.equal("far_value_a_d", "Far element D propA should be preserved");
737
+ chai.expect(farElemD.propD).to.equal("far_value_d", "Far element D propD should be preserved after both transforms");
738
+ const localElemC = t.getElement(t.local, localElementC);
739
+ chai.expect(localElemC.propA).to.equal("local_value_a_c", "Local element C propA should be preserved");
740
+ chai.expect(localElemC.propC).to.equal("local_value_c", "Local element C propC should be preserved after both transforms");
741
+ const localElemD = t.getElement(t.local, localElementD);
742
+ chai.expect(localElemD.propA).to.equal("local_value_a_d", "Local element D propA should be preserved");
743
+ chai.expect(localElemD.propD).to.equal("local_value_d", "Local element D propD should be preserved after both transforms");
744
+ });
745
+ it("local data update onto incoming transforming schema change", async () => {
746
+ t = await TestIModel.initialize("LocalDataIncomingTransform");
747
+ // Insert one instance and populate to both far and local
748
+ await t.far.locks.acquireLocks({ shared: t.drawingModelId });
749
+ const elementId = t.insertElement(t.far, "TestDomain:C", {
750
+ propA: "initial_value_a",
751
+ propC: "initial_value_c",
752
+ });
753
+ t.far.saveChanges("far create element");
754
+ await t.far.pushChanges({ description: "create shared element" });
755
+ // Local pulls to get the element
756
+ await t.local.pullChanges();
757
+ // Far imports transforming schema (moves PropC from C to A)
758
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
759
+ const txnPropsFar = t.far.txns.getLastSavedTxnProps();
760
+ chai.expect(txnPropsFar).to.not.be.undefined;
761
+ chai.expect(txnPropsFar.type).to.equal("Schema");
762
+ chai.expect(t.checkIfFolderExists(t.far, txnPropsFar.id, true)).to.be.true; // schema folder should exist
763
+ t.far.saveChanges("far transforming schema update");
764
+ await t.far.pushChanges({ description: "far move PropC to A" });
765
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // after push the folder should not be there
766
+ // Local updates PropC on the element
767
+ await t.local.locks.acquireLocks({ exclusive: elementId });
768
+ t.updateElement(t.local, elementId, { propC: "local_modified_c" });
769
+ t.local.saveChanges("local update propC");
770
+ let element = t.getElement(t.local, elementId);
771
+ chai.expect(element.propA).to.equal("initial_value_a", "PropA should be unchanged");
772
+ chai.expect(element.propC).to.equal("local_modified_c", "PropC should have the local modified value before incoming transform");
773
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // no schema change yet on local so no rebase folder
774
+ // Local pulls and rebases data change onto incoming transforming schema change
775
+ await t.local.pullChanges();
776
+ // after rebase the folder should not be there because data change folder is created on the fly and removed once rebased and rebase folder is also removed if it contains nothing
777
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false;
778
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
779
+ // Verify: PropC has the modified local value after the transform
780
+ element = t.getElement(t.local, elementId);
781
+ chai.expect(element.propA).to.equal("initial_value_a", "PropA should be unchanged");
782
+ chai.expect(element.propC).to.equal("local_modified_c", "PropC should have the local modified value after incoming transform");
783
+ const schema = t.local.getSchemaProps("TestDomain");
784
+ chai.expect(schema.version).to.equal("01.00.02", "Schema should be transformed to v01.00.02");
785
+ });
786
+ it("Incoming data update onto local transforming schema change", async () => {
787
+ t = await TestIModel.initialize("IncomingDataLocalTransform");
788
+ // Insert one instance and populate to both far and local
789
+ await t.far.locks.acquireLocks({ shared: t.drawingModelId });
790
+ const elementIdFar = t.insertElement(t.far, "TestDomain:C", {
791
+ propA: "far_value_a",
792
+ propC: "far_value_c",
793
+ });
794
+ t.far.saveChanges("far create element");
795
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // no schema change yet on far so no rebase folder
796
+ await t.far.pushChanges({ description: "create shared element" });
797
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // after push the folder should not be there
798
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
799
+ const elementIdLocal = t.insertElement(t.local, "TestDomain:C", {
800
+ propA: "local_value_a",
801
+ propC: "local_value_c",
802
+ });
803
+ t.local.saveChanges("local create element");
804
+ // Far imports transforming schema (moves PropC from C to A)
805
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
806
+ const txnPropsLocal = t.local.txns.getLastSavedTxnProps();
807
+ chai.expect(txnPropsLocal).to.not.be.undefined;
808
+ chai.expect(txnPropsLocal.type).to.equal("Schema");
809
+ chai.expect(t.checkIfFolderExists(t.local, txnPropsLocal.id, true)).to.be.true; // schema folder should exist
810
+ t.local.saveChanges("local transforming schema update");
811
+ // local pulls and rebases and then pushes
812
+ await t.local.pushChanges({ description: "far move PropC to A" });
813
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // after push the folder should not be there
814
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
815
+ const elementFar = t.getElement(t.local, elementIdFar);
816
+ chai.expect(elementFar.propA).to.equal("far_value_a", "PropA should be unchanged");
817
+ chai.expect(elementFar.propC).to.equal("far_value_c", "PropC should be unchanged");
818
+ const elementLocal = t.getElement(t.local, elementIdLocal);
819
+ chai.expect(elementLocal.propA).to.equal("local_value_a", "PropA should be unchanged");
820
+ chai.expect(elementLocal.propC).to.equal("local_value_c", "PropC should be unchanged");
821
+ const schema = t.local.getSchemaProps("TestDomain");
822
+ chai.expect(schema.version).to.equal("01.00.02", "Schema should be transformed to v01.00.02");
823
+ });
824
+ it("Check if associated rebase folders get deleted when a briefcase is deleted or not", async () => {
825
+ t = await TestIModel.initialize("IncomingDataLocalTransform");
826
+ // Must close briefcases before deleting their files - on Windows, open files are locked by the OS.
827
+ // Save paths before closing since pathName getter throws on closed dbs.
828
+ const localPath = t.local.pathName;
829
+ const localRebasePath = BriefcaseManager.getBasePathForSemanticRebaseLocalFiles(t.local);
830
+ const farPath = t.far.pathName;
831
+ const farRebasePath = BriefcaseManager.getBasePathForSemanticRebaseLocalFiles(t.far);
832
+ t.local.close();
833
+ await BriefcaseManager.deleteBriefcaseFiles(localPath);
834
+ chai.expect(IModelJsFs.existsSync(localRebasePath)).to.be.false; // after briefcase deletion the rebase folder should also be deleted
835
+ t.far.close();
836
+ await BriefcaseManager.deleteBriefcaseFiles(farPath);
837
+ chai.expect(IModelJsFs.existsSync(farRebasePath)).to.be.false; // after briefcase deletion the rebase folder should also be deleted
838
+ });
839
+ it("local multiple data transactions onto incoming transforming schema change", async () => {
840
+ t = await TestIModel.initialize("LocalMultipleDataIncomingTransform");
841
+ // Insert initial element and push
842
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
843
+ const elementId1 = t.insertElement(t.local, "TestDomain:C", {
844
+ propA: "initial_a",
845
+ propC: "initial_c",
846
+ });
847
+ t.local.saveChanges("create first element");
848
+ await t.local.pushChanges({ description: "create initial element" });
849
+ // Far imports transforming schema (moves PropC from C to A)
850
+ await t.far.pullChanges();
851
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
852
+ t.far.saveChanges("far transforming schema update");
853
+ await t.far.pushChanges({ description: "far move PropC to A" });
854
+ // Local makes first data change
855
+ await t.local.locks.acquireLocks({ exclusive: elementId1 });
856
+ t.updateElement(t.local, elementId1, { propA: "first_update_a" });
857
+ t.local.saveChanges("first data change");
858
+ // Local makes second data change - create new element
859
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
860
+ const elementId2 = t.insertElement(t.local, "TestDomain:C", {
861
+ propA: "second_element_a",
862
+ propC: "second_element_c",
863
+ });
864
+ t.local.saveChanges("second data change - new element");
865
+ // Local pulls and rebases both transactions onto incoming transforming schema
866
+ await t.local.pullChanges();
867
+ t.local.clearCaches();
868
+ // Verify: both local data changes preserved after incoming transform
869
+ const element1 = t.getElement(t.local, elementId1);
870
+ chai.expect(element1.propA).to.equal("first_update_a", "First element propA update should be preserved");
871
+ chai.expect(element1.propC).to.equal("initial_c", "First element propC should be preserved after transform");
872
+ const element2 = t.getElement(t.local, elementId2);
873
+ chai.expect(element2.propA).to.equal("second_element_a", "Second element propA should be preserved");
874
+ chai.expect(element2.propC).to.equal("second_element_c", "Second element propC should be preserved after transform");
875
+ const schema = t.local.getSchemaProps("TestDomain");
876
+ chai.expect(schema.version).to.equal("01.00.02", "Schema should be transformed to v01.00.02");
877
+ });
878
+ it("local transforming schema change onto incoming multiple data transactions", async () => {
879
+ t = await TestIModel.initialize("LocalTransformIncomingMultipleData");
880
+ // Create initial element
881
+ await t.far.locks.acquireLocks({ shared: t.drawingModelId });
882
+ const elementId1 = t.insertElement(t.far, "TestDomain:C", {
883
+ propA: "initial_a",
884
+ propC: "initial_c",
885
+ });
886
+ t.far.saveChanges("create first element");
887
+ await t.far.pushChanges({ description: "create initial element" });
888
+ // Local pulls and imports transforming schema
889
+ await t.local.pullChanges();
890
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
891
+ t.local.saveChanges("local transforming schema update");
892
+ // Far makes first data change
893
+ await t.far.locks.acquireLocks({ exclusive: elementId1 });
894
+ t.updateElement(t.far, elementId1, { propA: "far_first_update_a" });
895
+ t.far.saveChanges("far first data change");
896
+ // Far makes second data change - create new element
897
+ await t.far.locks.acquireLocks({ shared: t.drawingModelId });
898
+ const elementId2 = t.insertElement(t.far, "TestDomain:C", {
899
+ propA: "far_second_element_a",
900
+ propC: "far_second_element_c",
901
+ });
902
+ t.far.saveChanges("far second data change - new element");
903
+ await t.far.pushChanges({ description: "far multiple data changes" });
904
+ // Local pulls and rebases local transforming schema onto incoming data changes
905
+ await t.local.pullChanges();
906
+ t.local.clearCaches();
907
+ // Verify: both incoming data changes applied, local schema transformation preserved
908
+ const element1 = t.getElement(t.local, elementId1);
909
+ chai.expect(element1.propA).to.equal("far_first_update_a", "First element incoming update should be applied");
910
+ chai.expect(element1.propC).to.equal("initial_c", "First element propC should be preserved after transform");
911
+ const element2 = t.getElement(t.local, elementId2);
912
+ chai.expect(element2.propA).to.equal("far_second_element_a", "Second element should exist with correct propA");
913
+ chai.expect(element2.propC).to.equal("far_second_element_c", "Second element propC should be preserved after transform");
914
+ const schema = t.local.getSchemaProps("TestDomain");
915
+ chai.expect(schema.version).to.equal("01.00.02", "Local schema transformation should be preserved");
916
+ });
917
+ it("should fail when importing schema with unsaved data changes", async () => {
918
+ t = await TestIModel.initialize("UnsavedDataChangesSchemaImport");
919
+ // Create element but DO NOT save changes
920
+ await t.local.locks.acquireLocks({ shared: t.drawingModelId });
921
+ t.insertElement(t.local, "TestDomain:C", {
922
+ propA: "unsaved_a",
923
+ propC: "unsaved_c",
924
+ });
925
+ // Intentionally NOT calling t.local.saveChanges()
926
+ // Try to import schema - this should fail
927
+ await chai.expect(t.local.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2])).to.be.rejectedWith("Cannot import schemas with unsaved changes when useSemanticRebase flag is on");
928
+ // Verify: element was not saved, schema was not imported
929
+ const schema = t.local.getSchemaProps("TestDomain");
930
+ chai.expect(schema.version).to.equal("01.00.00", "Schema should remain at v01.00.00");
931
+ });
932
+ });
933
+ /**
934
+ * Test suite for tests related to rebase logic with schema changes (for indirect changes) that require data transformations.
935
+ */
936
+ describe("Semantic Rebase with indirect changes", function () {
937
+ this.timeout(60000); // operations can be slow
938
+ let t;
939
+ before(async () => {
940
+ await TestUtils.shutdownBackend(); // Automatically TestUtils.startBackend() is called before every test suite starts we need to shut tht down and startup our new TestUtils with semantic rebase on
941
+ await TestUtils.startBackend({ useSemanticRebase: true });
942
+ });
943
+ afterEach(() => {
944
+ if (t) {
945
+ t.shutdown();
946
+ t = undefined;
947
+ }
948
+ });
949
+ after(async () => {
950
+ await TestUtils.shutdownBackend();
951
+ await TestUtils.startBackend(); // restart normal backend so subsequent test suites aren't left without IModelHost
952
+ });
953
+ it("Incoming data update onto local data change", async () => {
954
+ t = await TestIModel.initialize("IncomingDataLocalDataChange");
955
+ let elementIdFar = "";
956
+ await t.far.txns.withIndirectTxnModeAsync(async () => {
957
+ // Insert one instance and populate to both far and local
958
+ elementIdFar = t.insertElement(t.far, "TestDomain:C", {
959
+ propA: "far_value_a",
960
+ propC: "far_value_c",
961
+ });
962
+ });
963
+ t.far.saveChanges("far create indirect element");
964
+ await t.far.pushChanges({ description: "create indirect element" });
965
+ let elementIdLocal = "";
966
+ await t.local.txns.withIndirectTxnModeAsync(async () => {
967
+ // Insert one instance and populate to both far and local
968
+ elementIdLocal = t.insertElement(t.local, "TestDomain:C", {
969
+ propA: "local_value_a",
970
+ propC: "local_value_c",
971
+ });
972
+ });
973
+ t.local.saveChanges("local create indirect element");
974
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // there should not be a rebase folder because no schema change on either side
975
+ await t.local.pushChanges({ description: "local pulls andcreate indirect element" });
976
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // there should not be a rebase folder because no schema change on either side
977
+ chai.expect(Id64.isValidId64(elementIdFar) && Id64.isValidId64(elementIdLocal)).to.be.true;
978
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
979
+ const elementFar = t.getElement(t.local, elementIdFar);
980
+ chai.expect(elementFar.propA).to.equal("far_value_a", "PropA should be unchanged");
981
+ chai.expect(elementFar.propC).to.equal("far_value_c", "PropC should be unchanged");
982
+ const elementLocal = t.getElement(t.local, elementIdLocal);
983
+ chai.expect(elementLocal.propA).to.equal("local_value_a", "PropA should be unchanged");
984
+ chai.expect(elementLocal.propC).to.equal("local_value_c", "PropC should be unchanged");
985
+ });
986
+ it("Incoming data and schema update onto local data change", async () => {
987
+ t = await TestIModel.initialize("IncomingDataAndSchemaLocalDataChange");
988
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
989
+ const farTxnProps = t.far.txns.getLastSavedTxnProps();
990
+ chai.expect(farTxnProps).to.not.be.undefined;
991
+ chai.expect(farTxnProps.type).to.equal("Schema");
992
+ chai.expect(t.checkIfFolderExists(t.far, farTxnProps.id, true)).to.be.true; // schema folder should exist
993
+ let elementIdFar = "";
994
+ await t.far.txns.withIndirectTxnModeAsync(async () => {
995
+ // Insert one instance and populate to both far and local
996
+ elementIdFar = t.insertElement(t.far, "TestDomain:C", {
997
+ propA: "far_value_a",
998
+ propC: "far_value_c",
999
+ });
1000
+ });
1001
+ t.far.saveChanges("far create indirect element");
1002
+ await t.far.pushChanges({ description: "create indirect element" });
1003
+ chai.expect(t.checkIfFolderExists(t.far, farTxnProps.id, true)).to.be.false; // after push the schema folder should not be there
1004
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // after push the folder should not be there
1005
+ let elementIdLocal = "";
1006
+ await t.local.txns.withIndirectTxnModeAsync(async () => {
1007
+ // Insert one instance and populate to both far and local
1008
+ elementIdLocal = t.insertElement(t.local, "TestDomain:C", {
1009
+ propA: "local_value_a",
1010
+ propC: "local_value_c",
1011
+ });
1012
+ });
1013
+ t.local.saveChanges("local create indirect element");
1014
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // there should not be a rebase folder because no schema change on local side
1015
+ await t.local.pushChanges({ description: "local pulls andcreate indirect element" });
1016
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false;
1017
+ chai.expect(Id64.isValidId64(elementIdFar) && Id64.isValidId64(elementIdLocal)).to.be.true;
1018
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
1019
+ const elementFar = t.getElement(t.local, elementIdFar);
1020
+ chai.expect(elementFar.propA).to.equal("far_value_a", "PropA should be unchanged");
1021
+ chai.expect(elementFar.propC).to.equal("far_value_c", "PropC should be unchanged");
1022
+ const elementLocal = t.getElement(t.local, elementIdLocal);
1023
+ chai.expect(elementLocal.propA).to.equal("local_value_a", "PropA should be unchanged");
1024
+ chai.expect(elementLocal.propC).to.equal("local_value_c", "PropC should be unchanged");
1025
+ const schema = t.local.getSchemaProps("TestDomain");
1026
+ chai.expect(schema.version).to.equal("01.00.01", "Schema should be transformed to v01.00.02");
1027
+ });
1028
+ it("Incoming data and schema update onto local data and schema change", async () => {
1029
+ t = await TestIModel.initialize("IncomingDataLocalDataAndSchemaChange");
1030
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x01AddPropC2]);
1031
+ const farTxnProps = t.far.txns.getLastSavedTxnProps();
1032
+ chai.expect(farTxnProps).to.not.be.undefined;
1033
+ chai.expect(farTxnProps.type).to.equal("Schema");
1034
+ chai.expect(t.checkIfFolderExists(t.far, farTxnProps.id, true)).to.be.true; // schema folder should exist
1035
+ let elementIdFar = "";
1036
+ await t.far.txns.withIndirectTxnModeAsync(async () => {
1037
+ // Insert one instance and populate to both far and local
1038
+ elementIdFar = t.insertElement(t.far, "TestDomain:C", {
1039
+ propA: "far_value_a",
1040
+ propC: "far_value_c",
1041
+ });
1042
+ });
1043
+ t.far.saveChanges("far create indirect element");
1044
+ await t.far.pushChanges({ description: "create indirect element" });
1045
+ chai.expect(t.checkIfFolderExists(t.far, farTxnProps.id, true)).to.be.false; // after push the schema folder should not be there
1046
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // after push the folder should not be there
1047
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x02AddPropD2]);
1048
+ const localTxnProps = t.local.txns.getLastSavedTxnProps();
1049
+ chai.expect(localTxnProps).to.not.be.undefined;
1050
+ chai.expect(localTxnProps.type).to.equal("Schema");
1051
+ chai.expect(t.checkIfFolderExists(t.local, localTxnProps.id, true)).to.be.true; // schema folder should exist
1052
+ let elementIdLocal = "";
1053
+ await t.local.txns.withIndirectTxnModeAsync(async () => {
1054
+ // Insert one instance and populate to both far and local
1055
+ elementIdLocal = t.insertElement(t.local, "TestDomain:C", {
1056
+ propA: "local_value_a",
1057
+ propC: "local_value_c",
1058
+ });
1059
+ });
1060
+ t.local.saveChanges("local create indirect element");
1061
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.true; // there should be a rebase folder because schema change on local side
1062
+ await t.local.pushChanges({ description: "local pulls andcreate indirect element" });
1063
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // after push the folder should not be there
1064
+ chai.expect(Id64.isValidId64(elementIdFar) && Id64.isValidId64(elementIdLocal)).to.be.true;
1065
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
1066
+ const elementFar = t.getElement(t.local, elementIdFar);
1067
+ chai.expect(elementFar.propA).to.equal("far_value_a", "PropA should be unchanged");
1068
+ chai.expect(elementFar.propC).to.equal("far_value_c", "PropC should be unchanged");
1069
+ const elementLocal = t.getElement(t.local, elementIdLocal);
1070
+ chai.expect(elementLocal.propA).to.equal("local_value_a", "PropA should be unchanged");
1071
+ chai.expect(elementLocal.propC).to.equal("local_value_c", "PropC should be unchanged");
1072
+ const schema = t.local.getSchemaProps("TestDomain");
1073
+ chai.expect(schema.version).to.equal("01.00.02", "Schema should be transformed to v01.00.02");
1074
+ });
1075
+ it("Incoming data and Transforming schema update onto local data change", async () => {
1076
+ t = await TestIModel.initialize("IncomingDataAndTransformingSchemaLocalDataChange");
1077
+ let elementIdFar = "";
1078
+ await t.far.txns.withIndirectTxnModeAsync(async () => {
1079
+ // Insert one instance and populate to both far and local
1080
+ elementIdFar = t.insertElement(t.far, "TestDomain:C", {
1081
+ propA: "far_value_a",
1082
+ propC: "far_value_c",
1083
+ });
1084
+ });
1085
+ t.far.saveChanges("far create indirect element");
1086
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
1087
+ const farTxnProps = t.far.txns.getLastSavedTxnProps();
1088
+ chai.expect(farTxnProps).to.not.be.undefined;
1089
+ chai.expect(farTxnProps.type).to.equal("Schema");
1090
+ chai.expect(t.checkIfFolderExists(t.far, farTxnProps.id, true)).to.be.true; // schema folder should exist
1091
+ await t.far.pushChanges({ description: "create indirect element" });
1092
+ chai.expect(t.checkIfFolderExists(t.far, farTxnProps.id, true)).to.be.false; // after push the schema folder should not be there
1093
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // after push the folder should not be there
1094
+ let elementIdLocal = "";
1095
+ await t.local.txns.withIndirectTxnModeAsync(async () => {
1096
+ // Insert one instance and populate to both far and local
1097
+ elementIdLocal = t.insertElement(t.local, "TestDomain:C", {
1098
+ propA: "local_value_a",
1099
+ propC: "local_value_c",
1100
+ });
1101
+ });
1102
+ t.local.saveChanges("local create indirect element");
1103
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // there should not be a rebase folder because no schema change on local side
1104
+ await t.local.pushChanges({ description: "local pulls andcreate indirect element" });
1105
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false;
1106
+ chai.expect(Id64.isValidId64(elementIdFar) && Id64.isValidId64(elementIdLocal)).to.be.true;
1107
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
1108
+ const elementFar = t.getElement(t.local, elementIdFar);
1109
+ chai.expect(elementFar.propA).to.equal("far_value_a", "PropA should be unchanged");
1110
+ chai.expect(elementFar.propC).to.equal("far_value_c", "PropC should be unchanged");
1111
+ const elementLocal = t.getElement(t.local, elementIdLocal);
1112
+ chai.expect(elementLocal.propA).to.equal("local_value_a", "PropA should be unchanged");
1113
+ chai.expect(elementLocal.propC).to.equal("local_value_c", "PropC should be unchanged");
1114
+ const schema = t.local.getSchemaProps("TestDomain");
1115
+ chai.expect(schema.version).to.equal("01.00.02", "Schema should be transformed to v01.00.02");
1116
+ });
1117
+ it("Incoming data and transforming schema update onto local data and transforming schema change", async () => {
1118
+ t = await TestIModel.initialize("IncomingDataAndTransformingSchemaLocalDataAndTransformingSchemaChange");
1119
+ await t.far.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
1120
+ const farTxnProps = t.far.txns.getLastSavedTxnProps();
1121
+ chai.expect(farTxnProps).to.not.be.undefined;
1122
+ chai.expect(farTxnProps.type).to.equal("Schema");
1123
+ chai.expect(t.checkIfFolderExists(t.far, farTxnProps.id, true)).to.be.true; // schema folder should exist
1124
+ let elementIdFar = "";
1125
+ await t.far.txns.withIndirectTxnModeAsync(async () => {
1126
+ // Insert one instance and populate to both far and local
1127
+ elementIdFar = t.insertElement(t.far, "TestDomain:C", {
1128
+ propA: "far_value_a",
1129
+ propC: "far_value_c",
1130
+ });
1131
+ });
1132
+ t.far.saveChanges("far create indirect element");
1133
+ await t.far.pushChanges({ description: "create indirect element" });
1134
+ chai.expect(t.checkIfFolderExists(t.far, farTxnProps.id, true)).to.be.false; // after push the schema folder should not be there
1135
+ chai.expect(t.checkifRebaseFolderExists(t.far)).to.be.false; // after push the folder should not be there
1136
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
1137
+ const localTxnProps = t.local.txns.getLastSavedTxnProps();
1138
+ chai.expect(localTxnProps).to.not.be.undefined;
1139
+ chai.expect(localTxnProps.type).to.equal("Schema");
1140
+ chai.expect(t.checkIfFolderExists(t.local, localTxnProps.id, true)).to.be.true; // schema folder should exist
1141
+ let elementIdLocal = "";
1142
+ await t.local.txns.withIndirectTxnModeAsync(async () => {
1143
+ // Insert one instance and populate to both far and local
1144
+ elementIdLocal = t.insertElement(t.local, "TestDomain:C", {
1145
+ propA: "local_value_a",
1146
+ propC: "local_value_c",
1147
+ });
1148
+ });
1149
+ t.local.saveChanges("local create indirect element");
1150
+ await t.local.pullChanges();
1151
+ chai.expect(t.checkIfFolderExists(t.local, localTxnProps.id, true)).to.be.false; // because it is a no op change we are importing similar schema
1152
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.false; // schema change is no op and data changes are generated on the fly and removed once rebased so rebase folder should not be there
1153
+ chai.expect(Id64.isValidId64(elementIdFar) && Id64.isValidId64(elementIdLocal)).to.be.true;
1154
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
1155
+ const elementFar = t.getElement(t.local, elementIdFar);
1156
+ chai.expect(elementFar.propA).to.equal("far_value_a", "PropA should be unchanged");
1157
+ chai.expect(elementFar.propC).to.equal("far_value_c", "PropC should be unchanged");
1158
+ const elementLocal = t.getElement(t.local, elementIdLocal);
1159
+ chai.expect(elementLocal.propA).to.equal("local_value_a", "PropA should be unchanged");
1160
+ chai.expect(elementLocal.propC).to.equal("local_value_c", "PropC should be unchanged");
1161
+ const schema = t.local.getSchemaProps("TestDomain");
1162
+ chai.expect(schema.version).to.equal("01.00.02", "Schema should be transformed to v01.00.02");
1163
+ });
1164
+ it("Incoming data update onto local data and transforming schema change", async () => {
1165
+ // This test fails but should not fail actually - needs investigation
1166
+ t = await TestIModel.initialize("IncomingDataAndSchemaLocalDataChange");
1167
+ let elementIdFar = "";
1168
+ await t.far.txns.withIndirectTxnModeAsync(async () => {
1169
+ // Insert one instance and populate to both far and local
1170
+ elementIdFar = t.insertElement(t.far, "TestDomain:C", {
1171
+ propA: "far_value_a",
1172
+ propC: "far_value_c",
1173
+ });
1174
+ });
1175
+ t.far.saveChanges("far create indirect element");
1176
+ await t.far.pushChanges({ description: "create indirect element" });
1177
+ await t.local.importSchemaStrings([TestIModel.schemas.v01x00x02MovePropCToA]);
1178
+ const localTxnProps = t.local.txns.getLastSavedTxnProps();
1179
+ chai.expect(localTxnProps).to.not.be.undefined;
1180
+ chai.expect(localTxnProps.type).to.equal("Schema");
1181
+ chai.expect(t.checkIfFolderExists(t.local, localTxnProps.id, true)).to.be.true; // schema folder should exist
1182
+ let elementIdLocal = "";
1183
+ await t.local.txns.withIndirectTxnModeAsync(async () => {
1184
+ // Insert one instance and populate to both far and local
1185
+ elementIdLocal = t.insertElement(t.local, "TestDomain:C", {
1186
+ propA: "local_value_a",
1187
+ propC: "local_value_c",
1188
+ });
1189
+ });
1190
+ t.local.saveChanges("local create indirect element");
1191
+ chai.expect(t.checkifRebaseFolderExists(t.local)).to.be.true; // there should be a rebase folder because schema change on local side
1192
+ await t.local.pushChanges({ description: "local pulls andcreate indirect element" });
1193
+ chai.expect(t.checkIfFolderExists(t.local, localTxnProps.id, true)).to.be.false; // after push the schema folder should not be there
1194
+ chai.expect(Id64.isValidId64(elementIdFar) && Id64.isValidId64(elementIdLocal)).to.be.true;
1195
+ t.local.clearCaches(); // Clear caches to ensure we read transformed properties from iModel
1196
+ const elementFar = t.getElement(t.local, elementIdFar);
1197
+ chai.expect(elementFar.propA).to.equal("far_value_a", "PropA should be unchanged");
1198
+ chai.expect(elementFar.propC).to.equal("far_value_c", "PropC should be unchanged");
1199
+ const elementLocal = t.getElement(t.local, elementIdLocal);
1200
+ chai.expect(elementLocal.propA).to.equal("local_value_a", "PropA should be unchanged");
1201
+ chai.expect(elementLocal.propC).to.equal("local_value_c", "PropC should be unchanged");
1202
+ const schema = t.local.getSchemaProps("TestDomain");
1203
+ chai.expect(schema.version).to.equal("01.00.02", "Schema should be transformed to v01.00.02");
1204
+ });
1205
+ });
1206
+ //# sourceMappingURL=SemanticRebase.test.js.map