@@ -4,13 +4,14 @@ import com.goncalossilva.resources.Resource
44import dev.mokkery.answering.throws
55import dev.mokkery.every
66import dev.mokkery.matcher.any
7- import dev.mokkery.matcher.eq
87import dev.mokkery.mock
98import dev.mokkery.spy
109import dev.mokkery.verify
1110import dev.mokkery.verify.VerifyMode.Companion.exactly
1211import dev.mokkery.verify.VerifyMode.Companion.not
1312import dev.mokkery.verifySuspend
13+ import io.github.trueangle.knative.lambda.runtime.LambdaRuntimeException.Invocation.EventBodyParseException
14+ import io.github.trueangle.knative.lambda.runtime.LambdaRuntimeException.Invocation.HandlerException
1415import io.github.trueangle.knative.lambda.runtime.ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_FUNCTION_NAME
1516import io.github.trueangle.knative.lambda.runtime.ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_FUNCTION_VERSION
1617import io.github.trueangle.knative.lambda.runtime.ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_API
@@ -24,8 +25,10 @@ import io.github.trueangle.knative.lambda.runtime.log.LogLevel.ERROR
2425import io.github.trueangle.knative.lambda.runtime.log.LogLevel.FATAL
2526import io.ktor.client.engine.HttpClientEngine
2627import io.ktor.client.engine.mock.MockEngine
28+ import io.ktor.client.engine.mock.MockRequestHandleScope
2729import io.ktor.client.engine.mock.respond
2830import io.ktor.client.engine.mock.respondBadRequest
31+ import io.ktor.client.engine.mock.respondError
2932import io.ktor.http.HttpHeaders
3033import io.ktor.http.HttpStatusCode
3134import io.ktor.http.headers
@@ -41,8 +44,9 @@ import platform.posix.setenv
4144import kotlin.test.BeforeTest
4245import kotlin.test.Test
4346import kotlin.test.assertFailsWith
47+ import kotlin.test.assertTrue
4448
45- const val RESOURCES_PATH = " src/nativeTest/resources"
49+ internal const val RESOURCES_PATH = " src/nativeTest/resources"
4650
4751class LambdaRuntimeTest {
4852 private val log = mock<LambdaLogger >()
@@ -71,18 +75,7 @@ class LambdaRuntimeTest {
7175 val lambdaRunner = createRunner(MockEngine { request ->
7276 val path = request.url.encodedPath
7377 when {
74- path.contains(" invocation/next" ) -> respond(
75- content = ByteReadChannel (lambdaEvent),
76- status = HttpStatusCode .OK ,
77- headers = headers {
78- append(HttpHeaders .ContentType , " application/json" )
79- append(" Lambda-Runtime-Aws-Request-Id" , context.awsRequestId)
80- append(" Lambda-Runtime-Deadline-Ms" , context.deadlineTimeInMs.toString())
81- append(" Lambda-Runtime-Invoked-Function-Arn" , context.invokedFunctionArn)
82-
83- }
84- )
85-
78+ path.contains(" invocation/next" ) -> respondNextEventSuccess(lambdaEvent)
8679 path.contains(" /invocation/${context.awsRequestId} /response" ) -> respond(
8780 content = ByteReadChannel (" Ok" ),
8881 status = HttpStatusCode .Accepted ,
@@ -105,6 +98,7 @@ class LambdaRuntimeTest {
10598 verifySuspend(exactly(1 )) { client.sendResponse(context, handlerResponse, typeInfo<String >()) }
10699 verify(not ) { log.log(ERROR , any<Any >(), any()) }
107100 verify(not ) { log.log(FATAL , any<Any >(), any()) }
101+ verify(not ) { lambdaRunner.env.terminate() }
108102 }
109103
110104 @Test
@@ -116,17 +110,7 @@ class LambdaRuntimeTest {
116110 val lambdaRunner = createRunner(MockEngine { request ->
117111 val path = request.url.encodedPath
118112 when {
119- path.contains(" invocation/next" ) -> respond(
120- content = ByteReadChannel (lambdaEventJson),
121- status = HttpStatusCode .OK ,
122- headers = headers {
123- append(HttpHeaders .ContentType , " application/json" )
124- append(" Lambda-Runtime-Aws-Request-Id" , context.awsRequestId)
125- append(" Lambda-Runtime-Deadline-Ms" , context.deadlineTimeInMs.toString())
126- append(" Lambda-Runtime-Invoked-Function-Arn" , context.invokedFunctionArn)
127- }
128- )
129-
113+ path.contains(" invocation/next" ) -> respondNextEventSuccess(lambdaEventJson)
130114 path.contains(" /invocation/${context.awsRequestId} /response" ) -> respond(
131115 content = ByteReadChannel (" Ok" ),
132116 status = HttpStatusCode .Accepted ,
@@ -148,6 +132,7 @@ class LambdaRuntimeTest {
148132 verifySuspend(exactly(1 )) { client.sendResponse(context, handlerResponse, typeInfo<SampleObject >()) }
149133 verify(not ) { log.log(ERROR , any<Any >(), any()) }
150134 verify(not ) { log.log(FATAL , any<Any >(), any()) }
135+ verify(not ) { lambdaRunner.env.terminate() }
151136 }
152137
153138 @Test
@@ -194,6 +179,76 @@ class LambdaRuntimeTest {
194179 verify { log.log(FATAL , any<Any >(), any()) }
195180 }
196181
182+ @Test
183+ fun `GIVEN api error WHEN retrieveNextEvent THEN terminate` () = runTest {
184+ val lambdaRunner = createRunner(MockEngine { request ->
185+ val path = request.url.encodedPath
186+ when {
187+ path.contains(" /invocation/next" ) -> {
188+ respondError(HttpStatusCode .InternalServerError )
189+ }
190+
191+ else -> respondError(HttpStatusCode .Forbidden )
192+ }
193+ })
194+ val handler = object : LambdaBufferedHandler <String , String > {
195+ override suspend fun handleRequest (input : String , context : Context ) = " "
196+ }
197+
198+ assertFailsWith<TerminateException > {
199+ lambdaRunner.run (singleEventMode = true ) { handler }
200+ }
201+ verify(not ) { log.log(ERROR , any<Any >(), any()) }
202+ verify { log.log(FATAL , any<Any >(), any()) }
203+ }
204+
205+ @Test
206+ fun `GIVEN EventBodyParseException WHEN retrieveNextEvent THEN report error AND continue working` () = runTest {
207+ val event = NonSerialObject (" " )
208+ val lambdaRunner = createRunner(MockEngine { request ->
209+ val path = request.url.encodedPath
210+ when {
211+ path.contains(" invocation/next" ) -> respondNextEventSuccess(event.toString())
212+ path.contains(" ${context.awsRequestId} /error" ) -> respond(" " , HttpStatusCode .Accepted )
213+ else -> respondBadRequest()
214+ }
215+ })
216+ val client = lambdaRunner.client
217+ val handler = object : LambdaBufferedHandler <NonSerialObject , String > {
218+ override suspend fun handleRequest (input : NonSerialObject , context : Context ) = " "
219+ }
220+
221+ lambdaRunner.run (singleEventMode = true ) { handler }
222+
223+ verifySuspend { client.reportError(any<EventBodyParseException >()) }
224+ verify { log.log(ERROR , any<Any >(), any()) }
225+ verify(not ) { log.log(FATAL , any<Any >(), any()) }
226+ verify(not ) { lambdaRunner.env.terminate() }
227+ }
228+
229+ @Test
230+ fun `GIVEN Handler exception WHEN handleRequest THEN report error AND continue working` () = runTest {
231+ val lambdaRunner = createRunner(MockEngine { request ->
232+ val path = request.url.encodedPath
233+ when {
234+ path.contains(" invocation/next" ) -> respondNextEventSuccess(" " )
235+ path.contains(" ${context.awsRequestId} /error" ) -> respond(" " , HttpStatusCode .Accepted )
236+ else -> respondBadRequest()
237+ }
238+ })
239+ val client = lambdaRunner.client
240+ val handler = object : LambdaBufferedHandler <String , String > {
241+ override suspend fun handleRequest (input : String , context : Context ) = throw RuntimeException ()
242+ }
243+
244+ lambdaRunner.run (singleEventMode = true ) { handler }
245+
246+ verifySuspend { client.reportError(any<HandlerException >()) }
247+ verify { log.log(ERROR , any<HandlerException >(), any()) }
248+ verify(not ) { log.log(FATAL , any<Any >(), any()) }
249+ verify(not ) { lambdaRunner.env.terminate() }
250+ }
251+
197252 @OptIn(ExperimentalForeignApi ::class )
198253 private fun mockEnvironment () {
199254 if (getenv(AWS_LAMBDA_FUNCTION_NAME )?.toKString().isNullOrEmpty()) {
@@ -218,6 +273,17 @@ class LambdaRuntimeTest {
218273 return Runner (lambdaClient, log, env)
219274 }
220275
276+ private fun MockRequestHandleScope.respondNextEventSuccess (lambdaEvent : String ) = respond(
277+ content = ByteReadChannel (lambdaEvent),
278+ status = HttpStatusCode .OK ,
279+ headers = headers {
280+ append(HttpHeaders .ContentType , " application/json" )
281+ append(" Lambda-Runtime-Aws-Request-Id" , context.awsRequestId)
282+ append(" Lambda-Runtime-Deadline-Ms" , context.deadlineTimeInMs.toString())
283+ append(" Lambda-Runtime-Invoked-Function-Arn" , context.invokedFunctionArn)
284+ }
285+ )
286+
221287 private class InitErrorHandler : LambdaBufferedHandler <String , String > {
222288 init {
223289 throw RuntimeException ()
0 commit comments