Skip to content

Commit

Permalink
Test the HTTP Endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
slinkydeveloper committed Aug 30, 2022
1 parent fc092e1 commit 4aae4a2
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 12 deletions.
2 changes: 1 addition & 1 deletion contracts/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protobuf {
it.generateDescriptorSet = true
it.descriptorSetOptions.includeImports = true
it.descriptorSetOptions.path =
"${rootProject.projectDir}/.restate/descriptors/${it.sourceSet.name}.descriptor"
"${rootProject.projectDir}/.restate/descriptors/${project.name}.descriptor"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package dev.restate.e2e.utils

@Target(AnnotationTarget.VALUE_PARAMETER) annotation class InjectHttpEndpointURL
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,24 @@ private constructor(
private val additionalContainers: Map<String, GenericContainer<*>>,
private val additionalConfig: Map<String, Any>,
private val runtimeContainerName: String,
private val descriptorFile: String?,
) {

companion object {
private const val RESTATE_RUNTIME_CONTAINER_ENV = "RESTATE_RUNTIME_CONTAINER"
private const val DEFAULT_RUNTIME_CONTAINER =
"restatedev.jfrog.io/restatedev-docker-local/runtime"
private const val RUNTIME_GRPC_ENDPOINT = 8090

private const val IMAGE_PULL_POLICY = "E2E_IMAGE_PULL_POLICY"
private const val CONTAINER_LOGS_DIR_ENV = "CONTAINER_LOGS_DIR"

private const val IMAGE_PULL_POLICY_ENV = "E2E_IMAGE_PULL_POLICY"
private const val ALWAYS_PULL = "always"

private const val DESCRIPTORS_FILE_ENV = "DESCRIPTORS_FILE"

private const val RUNTIME_GRPC_ENDPOINT = 8090
private const val RUNTIME_HTTP_ENDPOINT = 8091

private val logger = LogManager.getLogger(RestateDeployer::class.java)

@JvmStatic
Expand All @@ -58,6 +65,7 @@ private constructor(
private var additionalConfig: MutableMap<String, Any> = mutableMapOf(),
private var runtimeContainer: String =
System.getenv(RESTATE_RUNTIME_CONTAINER_ENV) ?: DEFAULT_RUNTIME_CONTAINER,
private var descriptorDirectory: String? = System.getenv(DESCRIPTORS_FILE_ENV),
) {

fun withFunction(functionSpec: FunctionSpec) = apply { this.functions.add(functionSpec) }
Expand Down Expand Up @@ -96,7 +104,12 @@ private constructor(

fun build() =
RestateDeployer(
runtimeDeployments, functions, additionalContainers, additionalConfig, runtimeContainer)
runtimeDeployments,
functions,
additionalContainers,
additionalConfig,
runtimeContainer,
descriptorDirectory)
}

fun deploy(testClass: Class<*>) {
Expand Down Expand Up @@ -141,6 +154,10 @@ private constructor(
config["consensus"] = mapOf("storage_type" to "Memory")
config["grpc_port"] = RUNTIME_GRPC_ENDPOINT
config["services"] = functionContainers.values.flatMap { it.first.toManifests(mapper) }
if (descriptorFile != null) {
config["http_endpoint"] =
mapOf("port" to RUNTIME_HTTP_ENDPOINT, "descriptors_path" to "/contracts.descriptor")
}
config.putAll(additionalConfig)

mapper.writeValue(configFile, config)
Expand All @@ -163,9 +180,15 @@ private constructor(
MountableFile.forHostPath(configFile.toPath()), "/restate.yaml")
.withCommand("--id 1 --configuration-file /restate.yaml")

if (System.getenv(IMAGE_PULL_POLICY) == ALWAYS_PULL) {
if (System.getenv(IMAGE_PULL_POLICY_ENV) == ALWAYS_PULL) {
runtimeContainer!!.withImagePullPolicy(PullPolicy.alwaysPull())
}
if (descriptorFile != null) {
runtimeContainer!!
.withCopyFileToContainer(
MountableFile.forHostPath(descriptorFile), "/contracts.descriptor")
.addExposedPort(RUNTIME_HTTP_ENDPOINT)
}

runtimeContainer!!.start()

Expand All @@ -187,6 +210,18 @@ private constructor(
"Runtime is not configured, as RestateDeployer::deploy has not been invoked")
}

fun getRuntimeHttpEndpointUrl(): URL {
checkNotNull(descriptorFile) {
"No descriptor directory is configured to start the HTTP Endpoint. " +
"Make sure when running tests you have the $DESCRIPTORS_FILE_ENV environment variable correctly configured"
}
return runtimeContainer?.getMappedPort(RUNTIME_HTTP_ENDPOINT)?.let {
URL("http", "127.0.0.1", it, "/")
}
?: throw java.lang.IllegalStateException(
"Runtime is not configured, as RestateDeployer::deploy has not been invoked")
}

fun getAdditionalContainerExposedPort(hostName: String, port: Int): String {
return additionalContainers[hostName]?.let { "${it.host}:${it.getMappedPort(port)}" }
?: throw java.lang.IllegalStateException(
Expand All @@ -196,7 +231,7 @@ private constructor(
private fun computeContainerTestLogsDir(testClass: Class<*>): Path {
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")
return Path.of(
System.getenv("CONTAINER_LOGS_DIR")!!,
System.getenv(CONTAINER_LOGS_DIR_ENV)!!,
"${testClass.canonicalName}_${LocalDateTime.now().format(formatter)}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.grpc.ManagedChannel
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder
import io.grpc.stub.AbstractBlockingStub
import io.grpc.stub.AbstractStub
import java.net.URL
import java.util.concurrent.TimeUnit
import org.junit.jupiter.api.extension.*
import org.junit.jupiter.api.extension.ExtensionContext.Namespace
Expand All @@ -15,7 +16,7 @@ class RestateDeployerExtension(private val deployer: RestateDeployer) :

companion object {
private val NAMESPACE = Namespace.create(RestateDeployerExtension::class.java)
private val MANAGED_CHANNEL_KEY = "ManagedChannelKey"
private const val MANAGED_CHANNEL_KEY = "ManagedChannelKey"
}

override fun beforeAll(context: ExtensionContext) {
Expand Down Expand Up @@ -45,7 +46,9 @@ class RestateDeployerExtension(private val deployer: RestateDeployer) :
return (parameterContext.isAnnotated(InjectBlockingStub::class.java) &&
AbstractBlockingStub::class.java.isAssignableFrom(parameterContext.parameter.type)) ||
(parameterContext.isAnnotated(InjectContainerAddress::class.java) &&
String::class.java.isAssignableFrom(parameterContext.parameter.type))
String::class.java.isAssignableFrom(parameterContext.parameter.type)) ||
(parameterContext.isAnnotated(InjectHttpEndpointURL::class.java) &&
URL::class.java.isAssignableFrom(parameterContext.parameter.type))
}

override fun resolveParameter(
Expand All @@ -56,6 +59,8 @@ class RestateDeployerExtension(private val deployer: RestateDeployer) :
resolveBlockingStub(parameterContext, extensionContext)
} else if (parameterContext.isAnnotated(InjectContainerAddress::class.java)) {
resolveContainerAddress(parameterContext)
} else if (parameterContext.isAnnotated(InjectHttpEndpointURL::class.java)) {
resolveHttpEndpointURL()
} else {
null
}
Expand All @@ -75,9 +80,7 @@ class RestateDeployerExtension(private val deployer: RestateDeployer) :
.getStore(NAMESPACE)
.get(MANAGED_CHANNEL_KEY, ManagedChannelResource::class.java)

var stub: T = stubFactoryMethod.invoke(null, channelResource.channel) as T

return stub
return stubFactoryMethod.invoke(null, channelResource.channel) as T
}

private fun resolveContainerAddress(parameterContext: ParameterContext): Any {
Expand All @@ -86,6 +89,10 @@ class RestateDeployerExtension(private val deployer: RestateDeployer) :
return deployer.getAdditionalContainerExposedPort(annotation.hostName, annotation.port)
}

private fun resolveHttpEndpointURL(): Any {
return deployer.getRuntimeHttpEndpointUrl()
}

private class ManagedChannelResource(val channel: ManagedChannel) : Store.CloseableResource {
override fun close() {
// Shutdown channel
Expand Down
4 changes: 3 additions & 1 deletion tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {

testImplementation(platform(libs.jackson.bom))
testImplementation(libs.jackson.core)
testImplementation(libs.jackson.databind)
testImplementation(libs.jackson.yaml)

testImplementation(platform(libs.cloudevents.bom))
Expand All @@ -42,5 +43,6 @@ tasks.withType<Test> {
mapOf(
"CONTAINER_LOGS_DIR" to "$buildDir/test-results/container-logs",
"RESTATE_RUNTIME_CONTAINER" to "ghcr.io/restatedev/runtime:main",
)
"DESCRIPTORS_FILE" to
"${rootProject.projectDir}/.restate/descriptors/contracts.descriptor")
}
61 changes: 61 additions & 0 deletions tests/src/test/kotlin/dev/restate/e2e/HttpEndpointTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package dev.restate.e2e

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import dev.restate.e2e.functions.counter.CounterGrpc
import dev.restate.e2e.utils.InjectHttpEndpointURL
import dev.restate.e2e.utils.RestateDeployer
import dev.restate.e2e.utils.RestateDeployerExtension
import java.net.URI
import java.net.URL
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.nio.charset.StandardCharsets
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension

class HttpEndpointTest {

companion object {
@RegisterExtension
val deployerExt: RestateDeployerExtension =
RestateDeployerExtension(
RestateDeployer.Builder().withFunction(Containers.COUNTER_FUNCTION_SPEC).build())

private val objMapper = ObjectMapper()

private val jacksonBodySubscriber: HttpResponse.BodySubscriber<JsonNode> =
HttpResponse.BodySubscribers.mapping(
HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8), objMapper::readTree)

private val jacksonBodyHandler: HttpResponse.BodyHandler<JsonNode> =
HttpResponse.BodyHandler { jacksonBodySubscriber }

private fun jacksonBodyPublisher(value: Any): HttpRequest.BodyPublisher {
return HttpRequest.BodyPublishers.ofString(objMapper.writeValueAsString(value))
}
}

@Test
fun getAndAdd(@InjectHttpEndpointURL httpEndpointURL: URL) {
val client = HttpClient.newHttpClient()

val req =
HttpRequest.newBuilder(
URI.create("$httpEndpointURL${CounterGrpc.getGetAndAddMethod().fullMethodName}"))
.POST(jacksonBodyPublisher(mapOf("counter_name" to "my-counter", "value" to 1)))
.headers("Content-Type", "application/json")
.build()

val response = client.send(req, jacksonBodyHandler)

assertThat(response.statusCode()).isEqualTo(200)
assertThat(response.headers().firstValue("content-type"))
.get()
.asString()
.contains("application/json")
assertThat(response.body().get("newValue").asInt()).isEqualTo(1)
}
}

0 comments on commit 4aae4a2

Please sign in to comment.