Skip to content

Commit 8e04371

Browse files
authored
Merge pull request #660 from modelix/fix/content-explorer-null-references
MODELIX-846 Null-References lead to exception in data explorer
2 parents 8e82ab7 + 1533d6e commit 8e04371

File tree

7 files changed

+112
-91
lines changed

7 files changed

+112
-91
lines changed

model-server/src/main/kotlin/org/modelix/model/server/handlers/ContentExplorer.kt

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import kotlinx.html.small
2929
import kotlinx.html.stream.appendHTML
3030
import kotlinx.html.style
3131
import kotlinx.html.table
32+
import kotlinx.html.tbody
3233
import kotlinx.html.td
3334
import kotlinx.html.th
3435
import kotlinx.html.thead
@@ -250,10 +251,12 @@ class ContentExplorer(private val client: IModelClient, private val repoManager:
250251
th { +"Value" }
251252
}
252253
}
253-
for (propertyRole in node.getPropertyRoles()) {
254-
tr {
255-
td { +propertyRole }
256-
td { +"${node.getPropertyValue(propertyRole)}" }
254+
tbody {
255+
for (propertyRole in node.getPropertyRoles()) {
256+
tr {
257+
td { +propertyRole }
258+
td { +"${node.getPropertyValue(propertyRole)}" }
259+
}
257260
}
258261
}
259262
}
@@ -265,19 +268,21 @@ class ContentExplorer(private val client: IModelClient, private val repoManager:
265268
thead {
266269
tr {
267270
th { +"ReferenceRole" }
268-
th { +"NodeId" }
269-
th { +"Value" }
271+
th { +"Target NodeId" }
272+
th { +"Target Reference" }
270273
}
271274
}
272-
INodeResolutionScope.runWithAdditionalScope(node.getArea()) {
273-
for (referenceRole in node.getReferenceRoles()) {
274-
tr {
275-
td { +referenceRole }
276-
td {
277-
+"${(node.getReferenceTarget(referenceRole) as PNodeAdapter).nodeId}"
278-
}
279-
td {
280-
+"${node.getReferenceTarget(referenceRole)}"
275+
tbody {
276+
INodeResolutionScope.runWithAdditionalScope(node.getArea()) {
277+
for (referenceRole in node.getReferenceRoles()) {
278+
tr {
279+
td { +referenceRole }
280+
td {
281+
+"${(node.getReferenceTarget(referenceRole) as? PNodeAdapter)?.nodeId}"
282+
}
283+
td {
284+
+"${node.getReferenceTargetRef(referenceRole)?.serialize()}"
285+
}
281286
}
282287
}
283288
}

model-server/src/test/kotlin/org/modelix/model/server/ModelClientV2Test.kt

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,8 @@
1515

1616
package org.modelix.model.server
1717

18-
import io.ktor.serialization.kotlinx.json.json
19-
import io.ktor.server.application.install
20-
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
21-
import io.ktor.server.resources.Resources
22-
import io.ktor.server.routing.IgnoreTrailingSlash
2318
import io.ktor.server.testing.ApplicationTestBuilder
2419
import io.ktor.server.testing.testApplication
25-
import io.ktor.server.websocket.WebSockets
2620
import org.modelix.authorization.installAuthentication
2721
import org.modelix.model.api.IConceptReference
2822
import org.modelix.model.api.ITree
@@ -50,23 +44,12 @@ class ModelClientV2Test {
5044
private fun runTest(block: suspend ApplicationTestBuilder.() -> Unit) = testApplication {
5145
application {
5246
installAuthentication(unitTestMode = true)
53-
install(ContentNegotiation) {
54-
json()
55-
}
56-
install(WebSockets)
57-
install(Resources)
58-
install(IgnoreTrailingSlash)
47+
installDefaultServerPlugins()
5948
ModelReplicationServer(InMemoryStoreClient()).init(this)
6049
}
6150
block()
6251
}
6352

64-
private suspend fun ApplicationTestBuilder.createModelClient(): ModelClientV2 {
65-
val url = "http://localhost/v2"
66-
val modelClient = ModelClientV2.builder().url(url).client(client).build().also { it.init() }
67-
return modelClient
68-
}
69-
7053
@Test
7154
fun test_t1() = runTest {
7255
val client = createModelClient()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2024.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.modelix.model.server
18+
19+
import io.ktor.serialization.kotlinx.json.json
20+
import io.ktor.server.application.Application
21+
import io.ktor.server.application.install
22+
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
23+
import io.ktor.server.resources.Resources
24+
import io.ktor.server.routing.IgnoreTrailingSlash
25+
import io.ktor.server.testing.ApplicationTestBuilder
26+
import io.ktor.server.websocket.WebSockets
27+
import org.modelix.model.client2.ModelClientV2
28+
29+
suspend fun ApplicationTestBuilder.createModelClient(): ModelClientV2 {
30+
val url = "http://localhost/v2"
31+
return ModelClientV2.builder().url(url).client(client).build().also { it.init() }
32+
}
33+
34+
fun Application.installDefaultServerPlugins() {
35+
install(WebSockets)
36+
install(ContentNegotiation) { json() }
37+
install(Resources)
38+
install(IgnoreTrailingSlash)
39+
}

model-server/src/test/kotlin/org/modelix/model/server/PullPerformanceTest.kt

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,8 @@
1616

1717
package org.modelix.model.server
1818

19-
import io.ktor.serialization.kotlinx.json.json
20-
import io.ktor.server.application.install
21-
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
22-
import io.ktor.server.resources.Resources
23-
import io.ktor.server.routing.IgnoreTrailingSlash
2419
import io.ktor.server.testing.ApplicationTestBuilder
2520
import io.ktor.server.testing.testApplication
26-
import io.ktor.server.websocket.WebSockets
2721
import kotlinx.coroutines.coroutineScope
2822
import org.modelix.authorization.installAuthentication
2923
import org.modelix.model.api.IChildLink
@@ -48,12 +42,7 @@ class PullPerformanceTest {
4842
val repositoriesManager = RepositoriesManager(LocalModelClient(storeClientWithStatistics))
4943
application {
5044
installAuthentication(unitTestMode = true)
51-
install(ContentNegotiation) {
52-
json()
53-
}
54-
install(WebSockets)
55-
install(Resources)
56-
install(IgnoreTrailingSlash)
45+
installDefaultServerPlugins()
5746
ModelReplicationServer(repositoriesManager).init(this)
5847
KeyValueLikeModelServer(repositoriesManager).init(this)
5948
}

model-server/src/test/kotlin/org/modelix/model/server/ReplicatedRepositoryTest.kt

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,8 @@
1515

1616
package org.modelix.model.server
1717

18-
import io.ktor.serialization.kotlinx.json.json
19-
import io.ktor.server.application.install
20-
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
21-
import io.ktor.server.resources.Resources
22-
import io.ktor.server.routing.IgnoreTrailingSlash
2318
import io.ktor.server.testing.ApplicationTestBuilder
2419
import io.ktor.server.testing.testApplication
25-
import io.ktor.server.websocket.WebSockets
2620
import kotlinx.coroutines.CoroutineScope
2721
import kotlinx.coroutines.coroutineScope
2822
import kotlinx.coroutines.delay
@@ -73,12 +67,7 @@ class ReplicatedRepositoryTest {
7367
private fun runTest(block: suspend ApplicationTestBuilder.(scope: CoroutineScope) -> Unit) = testApplication {
7468
application {
7569
installAuthentication(unitTestMode = true)
76-
install(ContentNegotiation) {
77-
json()
78-
}
79-
install(WebSockets)
80-
install(Resources)
81-
install(IgnoreTrailingSlash)
70+
installDefaultServerPlugins()
8271
val repositoriesManager = RepositoriesManager(LocalModelClient(InMemoryStoreClient()))
8372
ModelReplicationServer(repositoriesManager).init(this)
8473
KeyValueLikeModelServer(repositoriesManager).init(this)

model-server/src/test/kotlin/org/modelix/model/server/handlers/ContentExplorerTest.kt

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,25 @@ import io.ktor.client.statement.bodyAsText
2424
import io.ktor.http.ContentType
2525
import io.ktor.http.contentType
2626
import io.ktor.serialization.kotlinx.json.json
27-
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
28-
import io.ktor.server.resources.Resources
29-
import io.ktor.server.routing.IgnoreTrailingSlash
3027
import io.ktor.server.testing.ApplicationTestBuilder
3128
import io.ktor.server.testing.testApplication
32-
import io.ktor.server.websocket.WebSockets
3329
import org.jsoup.Jsoup
3430
import org.jsoup.nodes.Element
3531
import org.jsoup.select.Evaluator
32+
import org.modelix.model.api.IReferenceLink
33+
import org.modelix.model.api.ITree
34+
import org.modelix.model.api.NodeReferenceById
3635
import org.modelix.model.client.successful
36+
import org.modelix.model.client2.runWrite
3737
import org.modelix.model.lazy.CLVersion
38+
import org.modelix.model.lazy.RepositoryId
3839
import org.modelix.model.server.api.v2.VersionDelta
40+
import org.modelix.model.server.createModelClient
41+
import org.modelix.model.server.installDefaultServerPlugins
3942
import org.modelix.model.server.store.InMemoryStoreClient
4043
import org.modelix.model.server.store.LocalModelClient
4144
import kotlin.test.Test
45+
import kotlin.test.assertEquals
4246
import kotlin.test.assertNotNull
4347
import kotlin.test.assertTrue
4448
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation
@@ -48,26 +52,22 @@ class ContentExplorerTest {
4852
private val modelClient = LocalModelClient(InMemoryStoreClient())
4953
private val repoManager = RepositoriesManager(modelClient)
5054

51-
private fun runTest(body: suspend (ApplicationTestBuilder.() -> Unit)) {
52-
testApplication {
53-
install(WebSockets)
54-
install(ContentNegotiation) { json() }
55-
install(Resources)
56-
install(IgnoreTrailingSlash)
57-
application {
58-
ModelReplicationServer(repoManager).init(this)
59-
ContentExplorer(modelClient, repoManager).init(this)
60-
}
61-
62-
body()
55+
private fun runTest(body: suspend (ApplicationTestBuilder.() -> Unit)) = testApplication {
56+
application {
57+
installDefaultServerPlugins()
58+
ModelReplicationServer(repoManager).init(this)
59+
ContentExplorer(modelClient, repoManager).init(this)
6360
}
61+
body()
62+
}
63+
64+
private fun ApplicationTestBuilder.createHttpClient() = createClient {
65+
install(ClientContentNegotiation) { json() }
6466
}
6567

6668
@Test
6769
fun `node inspector finds root node`() = runTest {
68-
val client = createClient {
69-
install(ClientContentNegotiation) { json() }
70-
}
70+
val client = createHttpClient()
7171

7272
val delta: VersionDelta = client.post("/v2/repositories/node-inspector/init").body()
7373

@@ -80,11 +80,37 @@ class ContentExplorerTest {
8080
}
8181

8282
@Test
83-
fun `nodes can be expanded`() = runTest {
84-
val client = createClient {
85-
install(ClientContentNegotiation) { json() }
83+
fun `node inspector can handle unresolvable references`() = runTest {
84+
val modelClient = createModelClient()
85+
val repoId = RepositoryId("node-inspector-null-ref")
86+
val branchRef = repoId.getBranchReference("master")
87+
val refLinkName = "myUnresolvableRef"
88+
val refLinkTargetRef = NodeReferenceById("notAResolvableId")
89+
90+
modelClient.initRepository(repoId)
91+
92+
modelClient.runWrite(branchRef) { root ->
93+
root.setReferenceTarget(IReferenceLink.fromName(refLinkName), refLinkTargetRef)
8694
}
8795

96+
val versionHash = modelClient.pullHash(branchRef)
97+
98+
val response = client.get("/content/$versionHash/${ITree.ROOT_ID}/")
99+
val html = Jsoup.parse(response.bodyAsText())
100+
val nameCell = html.selectXpath("""//td[text()="$refLinkName"]""").first() ?: error("table cell not found")
101+
val row = checkNotNull(nameCell.parent()) { "table row not found" }
102+
val targetNodeIdCell = row.allElements[2] // index 0 is the row itself and 1 the nameCell
103+
val targetRefCell = row.allElements[3]
104+
105+
assertTrue(response.successful)
106+
assertEquals("null", targetNodeIdCell.text())
107+
assertEquals(refLinkTargetRef.serialize(), targetRefCell.text())
108+
}
109+
110+
@Test
111+
fun `nodes can be expanded`() = runTest {
112+
val client = createHttpClient()
113+
88114
val delta: VersionDelta = client.post("/v2/repositories/node-expansion/init").body()
89115

90116
val versionHash = delta.versionHash

model-server/src/test/kotlin/org/modelix/model/server/handlers/ModelReplicationServerTest.kt

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,8 @@ package org.modelix.model.server.handlers
1818
import io.ktor.client.request.get
1919
import io.ktor.http.appendPathSegments
2020
import io.ktor.http.takeFrom
21-
import io.ktor.serialization.kotlinx.json.json
22-
import io.ktor.server.application.install
23-
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
24-
import io.ktor.server.resources.Resources
25-
import io.ktor.server.routing.IgnoreTrailingSlash
2621
import io.ktor.server.testing.ApplicationTestBuilder
2722
import io.ktor.server.testing.testApplication
28-
import io.ktor.server.websocket.WebSockets
2923
import kotlinx.coroutines.CoroutineScope
3024
import kotlinx.coroutines.coroutineScope
3125
import org.modelix.authorization.installAuthentication
@@ -35,6 +29,7 @@ import org.modelix.model.client2.readVersionDelta
3529
import org.modelix.model.client2.runWrite
3630
import org.modelix.model.client2.useVersionStreamFormat
3731
import org.modelix.model.lazy.RepositoryId
32+
import org.modelix.model.server.installDefaultServerPlugins
3833
import org.modelix.model.server.store.InMemoryStoreClient
3934
import org.modelix.model.server.store.LocalModelClient
4035
import kotlin.test.Test
@@ -45,12 +40,7 @@ class ModelReplicationServerTest {
4540
private fun runTest(block: suspend ApplicationTestBuilder.(scope: CoroutineScope) -> Unit) = testApplication {
4641
application {
4742
installAuthentication(unitTestMode = true)
48-
install(ContentNegotiation) {
49-
json()
50-
}
51-
install(WebSockets)
52-
install(Resources)
53-
install(IgnoreTrailingSlash)
43+
installDefaultServerPlugins()
5444
val repositoriesManager = RepositoriesManager(LocalModelClient(InMemoryStoreClient()))
5545
ModelReplicationServer(repositoriesManager).init(this)
5646
KeyValueLikeModelServer(repositoriesManager).init(this)

0 commit comments

Comments
 (0)