Skip to content

Commit 0cbccaf

Browse files
committed
Add more test. Runtime error handling fix
1 parent 225ddd8 commit 0cbccaf

File tree

2 files changed

+98
-24
lines changed

2 files changed

+98
-24
lines changed

lambda-runtime/src/commonMain/kotlin/io/github/trueangle/knative/lambda/runtime/LambdaRuntime.kt

+24-21
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import kotlinx.serialization.json.Json
3434
object LambdaRuntime {
3535
@OptIn(ExperimentalSerializationApi::class)
3636
internal val json = Json { explicitNulls = false }
37+
3738
@PublishedApi
3839
internal val curlHttpClient = createHttpClient(Curl.create())
3940

@@ -85,35 +86,37 @@ internal class Runner(
8586
try {
8687
log.info("Runtime is ready for a new event")
8788

88-
val (event, context) = client.retrieveNextEvent<I>(inputTypeInfo)
89+
try {
90+
val (event, context) = client.retrieveNextEvent<I>(inputTypeInfo)
8991

90-
with(log) {
91-
setContext(context)
92+
with(log) {
93+
setContext(context)
9294

93-
debug(event)
94-
debug(context)
95-
info("$handlerName invocation started")
96-
}
95+
debug(event)
96+
debug(context)
97+
info("$handlerName invocation started")
98+
}
9799

98-
if (handler is LambdaStreamHandler<I, *>) {
99-
val response = streamingResponse { handler.handleRequest(event, it, context) }
100+
if (handler is LambdaStreamHandler<I, *>) {
101+
val response = streamingResponse { handler.handleRequest(event, it, context) }
100102

101-
log.info("$handlerName started response streaming")
103+
log.info("$handlerName started response streaming")
102104

103-
client.streamResponse(context, response)
104-
} else {
105-
handler as LambdaBufferedHandler<I, O>
106-
val response = bufferedResponse(context) { handler.handleRequest(event, context) }
105+
client.streamResponse(context, response)
106+
} else {
107+
handler as LambdaBufferedHandler<I, O>
108+
val response = bufferedResponse(context) { handler.handleRequest(event, context) }
107109

108-
log.info("$handlerName invocation completed")
109-
log.debug(response)
110+
log.info("$handlerName invocation completed")
111+
log.debug(response)
110112

111-
client.sendResponse(context, response, outputTypeInfo)
112-
}
113-
} catch (e: LambdaRuntimeException) {
114-
log.error(e)
113+
client.sendResponse(context, response, outputTypeInfo)
114+
}
115+
} catch (e: LambdaRuntimeException) {
116+
log.error(e)
115117

116-
client.reportError(e)
118+
client.reportError(e)
119+
}
117120
} catch (e: LambdaEnvironmentException) {
118121
when (e) {
119122
is NonRecoverableStateException -> {

lambda-runtime/src/nativeTest/kotlin/io/github/trueangle/knative/lambda/runtime/LambdaRuntimeTest.kt

+74-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import dev.mokkery.spy
99
import dev.mokkery.verify
1010
import dev.mokkery.verify.VerifyMode.Companion.exactly
1111
import dev.mokkery.verify.VerifyMode.Companion.not
12+
import dev.mokkery.verify.VerifyMode.Companion.order
1213
import dev.mokkery.verifySuspend
14+
import io.github.trueangle.knative.lambda.runtime.LambdaEnvironmentException.*
1315
import io.github.trueangle.knative.lambda.runtime.LambdaRuntimeException.Invocation.EventBodyParseException
1416
import io.github.trueangle.knative.lambda.runtime.LambdaRuntimeException.Invocation.HandlerException
1517
import io.github.trueangle.knative.lambda.runtime.ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_FUNCTION_NAME
@@ -191,7 +193,7 @@ class LambdaRuntimeTest {
191193
}
192194

193195
@Test
194-
fun `GIVEN api error WHEN retrieveNextEvent THEN terminate`() = runTest {
196+
fun `GIVEN env api error WHEN retrieveNextEvent THEN terminate`() = runTest {
195197
val lambdaRunner = createRunner(MockEngine { request ->
196198
val path = request.url.encodedPath
197199
when {
@@ -213,6 +215,75 @@ class LambdaRuntimeTest {
213215
verify { log.log(FATAL, any<Any>(), any()) }
214216
}
215217

218+
@Test
219+
fun `GIVEN bad request from env api WHEN sendResponse THEN skip event`() = runTest {
220+
val lambdaRunner = createRunner(MockEngine { request ->
221+
val path = request.url.encodedPath
222+
when {
223+
path.contains("invocation/next") -> respondNextEventSuccess("")
224+
path.contains("${context.awsRequestId}/response") -> respondError(HttpStatusCode.BadGateway)
225+
else -> respondBadRequest()
226+
}
227+
})
228+
val client = lambdaRunner.client
229+
val handler = object : LambdaBufferedHandler<String, String> {
230+
override suspend fun handleRequest(input: String, context: Context) = throw RuntimeException()
231+
}
232+
233+
lambdaRunner.run(singleEventMode = true) { handler }
234+
235+
verifySuspend { client.reportError(any<HandlerException>()) }
236+
verify(order) { log.log(ERROR, any<HandlerException>(), any()) }
237+
verify(order) { log.log(ERROR, any<BadRequestException>(), any()) }
238+
verify(not) { log.log(FATAL, any<Any>(), any()) }
239+
verify(not) { lambdaRunner.env.terminate() }
240+
}
241+
242+
@Test
243+
fun `GIVEN unknown http error from env api WHEN reportError THEN skip event`() = runTest {
244+
val lambdaRunner = createRunner(MockEngine { request ->
245+
val path = request.url.encodedPath
246+
when {
247+
path.contains("invocation/next") -> respondNextEventSuccess("")
248+
else -> respondBadRequest()
249+
}
250+
})
251+
val client = lambdaRunner.client
252+
val handler = object : LambdaBufferedHandler<String, String> {
253+
override suspend fun handleRequest(input: String, context: Context) = throw RuntimeException()
254+
}
255+
256+
lambdaRunner.run(singleEventMode = true) { handler }
257+
258+
verifySuspend { client.reportError(any<HandlerException>()) }
259+
verify(order) { log.log(ERROR, any<HandlerException>(), any()) }
260+
verify(order) { log.log(ERROR, any<BadRequestException>(), any()) }
261+
verify(not) { log.log(FATAL, any<Any>(), any()) }
262+
verify(not) { lambdaRunner.env.terminate() }
263+
}
264+
265+
@Test
266+
fun `GIVEN internal server error from env api WHEN reportError THEN terminate`() = runTest {
267+
val lambdaRunner = createRunner(MockEngine { request ->
268+
val path = request.url.encodedPath
269+
when {
270+
path.contains("invocation/next") -> respondNextEventSuccess("")
271+
path.contains("${context.awsRequestId}/error") -> respondError(HttpStatusCode.InternalServerError)
272+
else -> respondBadRequest()
273+
}
274+
})
275+
val client = lambdaRunner.client
276+
val handler = object : LambdaBufferedHandler<String, String> {
277+
override suspend fun handleRequest(input: String, context: Context) = throw RuntimeException()
278+
}
279+
280+
assertFailsWith<TerminateException> { lambdaRunner.run(singleEventMode = true) { handler } }
281+
verifySuspend { client.reportError(any<HandlerException>()) }
282+
verify { log.log(ERROR, any<HandlerException>(), any()) }
283+
verify { log.log(FATAL, any<NonRecoverableStateException>(), any()) }
284+
verify { lambdaRunner.env.terminate() }
285+
}
286+
216287
@Test
217288
fun `GIVEN EventBodyParseException WHEN retrieveNextEvent THEN report error AND skip event`() = runTest {
218289
val event = NonSerialObject("")
@@ -238,7 +309,7 @@ class LambdaRuntimeTest {
238309
}
239310

240311
@Test
241-
fun `GIVEN Handler exception WHEN handleRequest THEN report error AND skip event`() = runTest {
312+
fun `GIVEN Handler exception WHEN invoke handler THEN report error AND skip event`() = runTest {
242313
val lambdaRunner = createRunner(MockEngine { request ->
243314
val path = request.url.encodedPath
244315
when {
@@ -261,7 +332,7 @@ class LambdaRuntimeTest {
261332
}
262333

263334
@Test
264-
fun `GIVEN Handler exception WHEN streamingResponse THEN consume error`() = runTest {
335+
fun `GIVEN Handler exception WHEN invoke streaming handler THEN consume error`() = runTest {
265336
val lambdaRunner = createRunner(MockEngine { request ->
266337
val path = request.url.encodedPath
267338
when {

0 commit comments

Comments
 (0)