15
15
16
16
package org.modelix.model.server.handlers
17
17
18
+ import io.ktor.client.HttpClient
19
+ import io.ktor.client.engine.cio.CIO
20
+ import io.ktor.client.plugins.HttpTimeout
21
+ import io.ktor.client.plugins.defaultRequest
18
22
import io.ktor.client.request.get
19
23
import io.ktor.http.HttpStatusCode
20
24
import io.ktor.http.appendPathSegments
21
25
import io.ktor.http.takeFrom
26
+ import io.ktor.server.application.Application
27
+ import io.ktor.server.netty.NettyApplicationEngine
22
28
import io.ktor.server.testing.ApplicationTestBuilder
23
29
import io.ktor.server.testing.testApplication
24
30
import kotlinx.coroutines.CoroutineScope
25
31
import kotlinx.coroutines.coroutineScope
32
+ import kotlinx.coroutines.flow.channelFlow
33
+ import kotlinx.coroutines.flow.emitAll
34
+ import kotlinx.coroutines.flow.flow
35
+ import kotlinx.coroutines.test.runTest
36
+ import kotlinx.coroutines.withTimeout
26
37
import org.modelix.authorization.installAuthentication
27
38
import org.modelix.model.InMemoryModels
28
39
import org.modelix.model.api.IConceptReference
@@ -32,11 +43,13 @@ import org.modelix.model.client2.runWrite
32
43
import org.modelix.model.client2.useVersionStreamFormat
33
44
import org.modelix.model.lazy.RepositoryId
34
45
import org.modelix.model.server.installDefaultServerPlugins
46
+ import org.modelix.model.server.runWithNettyServer
35
47
import org.modelix.model.server.store.InMemoryStoreClient
36
48
import org.modelix.model.server.store.LocalModelClient
37
49
import kotlin.test.Test
38
50
import kotlin.test.assertEquals
39
51
import kotlin.test.fail
52
+ import kotlin.time.Duration.Companion.seconds
40
53
41
54
class ModelReplicationServerTest {
42
55
@@ -47,7 +60,7 @@ class ModelReplicationServerTest {
47
60
return ModelReplicationServer (repositoriesManager, modelClient, InMemoryModels ())
48
61
}
49
62
50
- private fun runTest (
63
+ private fun runWithTestModelServer (
51
64
modelReplicationServer : ModelReplicationServer = getDefaultModelReplicationServer(),
52
65
block : suspend ApplicationTestBuilder .(scope: CoroutineScope ) -> Unit ,
53
66
) = testApplication {
@@ -63,7 +76,7 @@ class ModelReplicationServerTest {
63
76
}
64
77
65
78
@Test
66
- fun `pulling delta does not return objects twice` () = runTest {
79
+ fun `pulling delta does not return objects twice` () = runWithTestModelServer {
67
80
// Arrange
68
81
val url = " http://localhost/v2"
69
82
val modelClient = ModelClientV2 .builder().url(url).client(client).build().also { it.init () }
@@ -117,7 +130,7 @@ class ModelReplicationServerTest {
117
130
val repositoryId = RepositoryId (" repo1" )
118
131
val branchRef = repositoryId.getBranchReference()
119
132
120
- runTest (modelReplicationServer) {
133
+ runWithTestModelServer (modelReplicationServer) {
121
134
repositoriesManager.createRepository(repositoryId, null )
122
135
123
136
// Act
@@ -133,4 +146,63 @@ class ModelReplicationServerTest {
133
146
assertEquals(HttpStatusCode .InternalServerError , response.status)
134
147
}
135
148
}
149
+
150
+ @Test
151
+ fun `server closes connection when failing to compute delta after starting to respond` () = runTest {
152
+ // Arrange
153
+ val repositoryId = RepositoryId (" repo1" )
154
+ val branchRef = repositoryId.getBranchReference()
155
+ val storeClient = InMemoryStoreClient ()
156
+ val modelClient = LocalModelClient (storeClient)
157
+ val repositoriesManager = RepositoriesManager (modelClient)
158
+ val faultyRepositoriesManager = object :
159
+ IRepositoriesManager by repositoriesManager {
160
+ override suspend fun computeDelta (versionHash : String , baseVersionHash : String? ): ObjectData {
161
+ val originalFlow = repositoriesManager.computeDelta(versionHash, baseVersionHash).asFlow()
162
+ val brokenFlow = channelFlow<Pair <String , String >> {
163
+ error(" Unexpected error." )
164
+ }
165
+ return ObjectDataFlow (
166
+ flow {
167
+ emitAll(originalFlow)
168
+ emitAll(brokenFlow)
169
+ },
170
+ )
171
+ }
172
+ }
173
+ repositoriesManager.createRepository(repositoryId, null )
174
+
175
+ suspend fun createClient (server : NettyApplicationEngine ): HttpClient {
176
+ val port = server.resolvedConnectors().first().port
177
+ return HttpClient (CIO ) {
178
+ defaultRequest {
179
+ url(" http://localhost:$port " )
180
+ }
181
+ install(HttpTimeout ) {
182
+ requestTimeoutMillis = 5_000
183
+ }
184
+ }
185
+ }
186
+
187
+ val modelReplicationServer = ModelReplicationServer (faultyRepositoriesManager, modelClient, InMemoryModels ())
188
+ val setupBlock = { application: Application -> modelReplicationServer.init (application) }
189
+ val testBlock: suspend (server: NettyApplicationEngine ) -> Unit = { server ->
190
+ withTimeout(10 .seconds) {
191
+ val client = createClient(server)
192
+ // Act
193
+ val response = client.get {
194
+ url {
195
+ takeFrom(url)
196
+ appendPathSegments(" v2" , " repositories" , repositoryId.id, " branches" , branchRef.branchName)
197
+ }
198
+ useVersionStreamFormat()
199
+ }
200
+
201
+ // Assert
202
+ // The response should be delivered even if an exception is thrown inside the flow.
203
+ assertEquals(HttpStatusCode .OK , response.status)
204
+ }
205
+ }
206
+ runWithNettyServer(setupBlock, testBlock)
207
+ }
136
208
}
0 commit comments