Skip to content

Commit f427ac9

Browse files
committed
More tests
1 parent 6079c7d commit f427ac9

File tree

4 files changed

+99
-29
lines changed

4 files changed

+99
-29
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import io.github.trueangle.knative.lambda.runtime.LambdaEnvironmentException.Non
88
import io.github.trueangle.knative.lambda.runtime.LambdaRuntimeException
99
import io.github.trueangle.knative.lambda.runtime.LambdaRuntimeException.Invocation.EventBodyParseException
1010
import io.github.trueangle.knative.lambda.runtime.api.dto.toDto
11-
import io.github.trueangle.knative.lambda.runtime.log.Log
1211
import io.ktor.client.HttpClient
1312
import io.ktor.client.call.body
13+
import io.ktor.client.plugins.retry
1414
import io.ktor.client.plugins.timeout
1515
import io.ktor.client.request.get
1616
import io.ktor.client.request.headers
@@ -47,6 +47,11 @@ internal class LambdaClientImpl(private val httpClient: HttpClient): LambdaClien
4747
url("${invokeUrl}/invocation/next")
4848

4949
timeout { requestTimeoutMillis = requestTimeout }
50+
51+
retry {
52+
retryOnExceptionOrServerErrors(3)
53+
exponentialDelay()
54+
}
5055
}
5156
val context = contextFromResponseHeaders(response)
5257
val body = try {

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,4 @@ class JsonLogFormatterTest {
100100

101101
assertEquals(expected, actual)
102102
}
103-
104-
private data class NonSerialObject(val hello: String)
105103
}

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

Lines changed: 91 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import com.goncalossilva.resources.Resource
44
import dev.mokkery.answering.throws
55
import dev.mokkery.every
66
import dev.mokkery.matcher.any
7-
import dev.mokkery.matcher.eq
87
import dev.mokkery.mock
98
import dev.mokkery.spy
109
import dev.mokkery.verify
1110
import dev.mokkery.verify.VerifyMode.Companion.exactly
1211
import dev.mokkery.verify.VerifyMode.Companion.not
1312
import 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
1415
import io.github.trueangle.knative.lambda.runtime.ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_FUNCTION_NAME
1516
import io.github.trueangle.knative.lambda.runtime.ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_FUNCTION_VERSION
1617
import 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
2425
import io.github.trueangle.knative.lambda.runtime.log.LogLevel.FATAL
2526
import io.ktor.client.engine.HttpClientEngine
2627
import io.ktor.client.engine.mock.MockEngine
28+
import io.ktor.client.engine.mock.MockRequestHandleScope
2729
import io.ktor.client.engine.mock.respond
2830
import io.ktor.client.engine.mock.respondBadRequest
31+
import io.ktor.client.engine.mock.respondError
2932
import io.ktor.http.HttpHeaders
3033
import io.ktor.http.HttpStatusCode
3134
import io.ktor.http.headers
@@ -41,8 +44,9 @@ import platform.posix.setenv
4144
import kotlin.test.BeforeTest
4245
import kotlin.test.Test
4346
import 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

4751
class 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()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ internal fun mockContext(awsRequestId: String = "awsRequestId") = Context(
1616
)
1717

1818
@Serializable
19-
internal data class SampleObject(val hello: String)
19+
internal data class SampleObject(val hello: String)
20+
internal data class NonSerialObject(val hello: String)

0 commit comments

Comments
 (0)