Skip to content

Commit 07d8f1e

Browse files
committed
fix(bulk-model-sync): handle nodes without originalId in ModelSynchronizer
The test cases can not be easily generalized. It is fine to test this only for the ModelSynchronizer because bulk-model-sync-gradle-test already covers these cases for the ModelImporter.
1 parent 24cd782 commit 07d8f1e

File tree

3 files changed

+81
-9
lines changed

3 files changed

+81
-9
lines changed

bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/ModelSynchronizer.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import org.modelix.model.api.INodeReference
2323
import org.modelix.model.api.IReferenceLink
2424
import org.modelix.model.api.IReplaceableNode
2525
import org.modelix.model.api.IRole
26+
import org.modelix.model.api.PNodeAdapter
2627
import org.modelix.model.api.isChildRoleOrdered
2728
import org.modelix.model.api.remove
2829
import org.modelix.model.data.NodeData
@@ -121,8 +122,6 @@ class ModelSynchronizer(
121122

122123
val allExpectedNodesDoNotExist by lazy {
123124
sourceNodes.all { sourceNode ->
124-
val originalId = sourceNode.originalId()
125-
checkNotNull(originalId) { "Specified node '$sourceNode' has no ID." }
126125
nodeAssociation.resolveTarget(sourceNode) == null
127126
}
128127
}
@@ -132,8 +131,6 @@ class ModelSynchronizer(
132131
targetParent.addNewChildren(role, -1, sourceNodes.map { it.getConceptReference() })
133132
.zip(sourceNodes)
134133
.forEach { (newChild, sourceChild) ->
135-
val expectedId = sourceChild.originalId()
136-
checkNotNull(expectedId) { "Specified node '$sourceChild' has no ID." }
137134
nodeAssociation.associate(sourceChild, newChild)
138135
synchronizeNode(sourceChild, newChild)
139136
}
@@ -153,7 +150,7 @@ class ModelSynchronizer(
153150

154151
sourceNodes.forEachIndexed { indexInImport, expected ->
155152
val existingChildren = targetParent.getChildren(role).toList()
156-
val expectedId = checkNotNull(expected.originalId()) { "Specified node '$expected' has no id" }
153+
val expectedId = checkNotNull(expected.originalIdOrFallback()) { "Specified node '$expected' has no id" }
157154
// newIndex is the index on which to import the expected child.
158155
// It might be -1 if the child does not exist and should be added at the end.
159156
val newIndex = if (isOrdered) {
@@ -165,7 +162,7 @@ class ModelSynchronizer(
165162
// Reusable indexing would be possible if we switch from
166163
// a depth-first import to a breadth-first import.)
167164
existingChildren
168-
.indexOfFirst { existingChild -> existingChild.originalId() == expected.originalId() }
165+
.indexOfFirst { existingChild -> existingChild.originalId() == expectedId }
169166
}
170167
// existingChildren.getOrNull handles `-1` as needed by returning `null`.
171168
val nodeAtIndex = existingChildren.getOrNull(newIndex)
@@ -288,3 +285,11 @@ class ModelSynchronizer(
288285
fun needsSynchronization(node: INode): Boolean
289286
}
290287
}
288+
289+
private fun INode.originalIdOrFallback(): String? {
290+
val originalRef = getOriginalReference()
291+
if (originalRef != null) return originalRef
292+
293+
if (this is PNodeAdapter) return reference.serialize()
294+
return null
295+
}

bulk-model-sync-lib/src/commonTest/kotlin/org/modelix/model/sync/bulk/ModelSynchronizerTest.kt

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,94 @@
1616

1717
package org.modelix.model.sync.bulk
1818

19+
import org.modelix.model.ModelFacade
1920
import org.modelix.model.api.IBranch
2021
import org.modelix.model.api.INode
2122
import org.modelix.model.api.IProperty
2223
import org.modelix.model.api.PBranch
2324
import org.modelix.model.api.PNodeAdapter
25+
import org.modelix.model.api.addNewChild
2426
import org.modelix.model.api.getDescendants
2527
import org.modelix.model.api.getRootNode
2628
import org.modelix.model.client.IdGenerator
2729
import org.modelix.model.data.ModelData
2830
import org.modelix.model.data.NodeData.Companion.ID_PROPERTY_KEY
2931
import org.modelix.model.lazy.CLTree
3032
import org.modelix.model.lazy.ObjectStoreCache
33+
import org.modelix.model.operations.AddNewChildOp
34+
import org.modelix.model.operations.AddNewChildrenOp
3135
import org.modelix.model.operations.OTBranch
3236
import org.modelix.model.persistent.MapBasedStore
3337
import org.modelix.model.test.RandomModelChangeGenerator
38+
import kotlin.js.JsName
3439
import kotlin.random.Random
40+
import kotlin.test.Test
41+
import kotlin.test.assertEquals
3542
import kotlin.test.assertTrue
3643

37-
class ModelSynchronizerTest : AbstractModelSyncTest() {
44+
open class ModelSynchronizerTest : AbstractModelSyncTest() {
45+
46+
@Test
47+
@JsName("can_handle_added_child_without_original_id_without_existing_sibling")
48+
fun `can handle added child without original id (without existing sibling)`() {
49+
val sourceBranch = createLocalBranch().apply {
50+
runWrite {
51+
getRootNode().apply {
52+
setPropertyValue(ID_PROPERTY_KEY, "root")
53+
addNewChild("test")
54+
}
55+
}
56+
}.toOTBranch()
57+
58+
val targetBranch = createLocalBranch().apply {
59+
runWrite {
60+
getRootNode().setPropertyValue(ID_PROPERTY_KEY, "root")
61+
}
62+
}.toOTBranch()
63+
64+
runTest(sourceBranch, targetBranch) {
65+
assertEquals(1, targetBranch.getRootNode().allChildren.count())
66+
assertEquals(1, targetBranch.getNumOfUsedOperationsByType()[AddNewChildrenOp::class])
67+
}
68+
}
69+
70+
@Test
71+
@JsName("can_handle_added_child_without_original_id_with_existing_sibling")
72+
fun `can handle added child without original id (with existing sibling)`() {
73+
val sourceBranch = createLocalBranch().apply {
74+
runWrite {
75+
getRootNode().apply {
76+
setPropertyValue(ID_PROPERTY_KEY, "root")
77+
addNewChild("test").setPropertyValue(ID_PROPERTY_KEY, "sibling")
78+
addNewChild("test")
79+
}
80+
}
81+
}.toOTBranch()
82+
83+
val targetBranch = createLocalBranch().apply {
84+
runWrite {
85+
getRootNode().apply {
86+
setPropertyValue(ID_PROPERTY_KEY, "root")
87+
addNewChild("test").setPropertyValue(ID_PROPERTY_KEY, "sibling")
88+
}
89+
}
90+
}.toOTBranch()
91+
92+
runTest(sourceBranch, targetBranch) {
93+
assertEquals(2, targetBranch.getRootNode().allChildren.count())
94+
assertEquals(1, targetBranch.getNumOfUsedOperationsByType()[AddNewChildOp::class])
95+
}
96+
}
97+
98+
private fun createLocalBranch() = ModelFacade.toLocalBranch(ModelFacade.newLocalTree())
3899

39100
override fun runTest(initialData: ModelData, expectedData: ModelData, assertions: OTBranch.() -> Unit) {
40101
val sourceBranch = createOTBranchFromModel(expectedData)
41102
val targetBranch = createOTBranchFromModel(initialData)
103+
runTest(sourceBranch, targetBranch, assertions)
104+
}
42105

106+
private fun runTest(sourceBranch: OTBranch, targetBranch: OTBranch, assertions: OTBranch.() -> Unit) {
43107
sourceBranch.runRead {
44108
val sourceRoot = sourceBranch.getRootNode()
45109

@@ -138,3 +202,7 @@ class ModelSynchronizerTest : AbstractModelSyncTest() {
138202
}
139203
}
140204
}
205+
206+
private fun IBranch.toOTBranch(): OTBranch {
207+
return OTBranch(this, IdGenerator.getInstance(1), ObjectStoreCache(MapBasedStore()))
208+
}

bulk-model-sync-lib/src/jvmTest/kotlin/org/modelix/model/sync/bulk/ModelSynchronizerWithInvalidationTreeTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,11 @@ import org.modelix.model.lazy.CLTree
2727
import org.modelix.model.lazy.ObjectStoreCache
2828
import org.modelix.model.operations.OTBranch
2929
import org.modelix.model.persistent.MapBasedStore
30-
import org.modelix.model.sync.bulk.ModelSynchronizerTest.BasicAssociation
3130
import org.modelix.model.test.RandomModelChangeGenerator
3231
import kotlin.random.Random
3332
import kotlin.test.assertTrue
3433

35-
class ModelSynchronizerWithInvalidationTreeTest : AbstractModelSyncTest() {
34+
class ModelSynchronizerWithInvalidationTreeTest : ModelSynchronizerTest() {
3635

3736
override fun runTest(initialData: ModelData, expectedData: ModelData, assertions: OTBranch.() -> Unit) {
3837
val sourceBranch = createOTBranchFromModel(expectedData)

0 commit comments

Comments
 (0)