Skip to content

Commit

Permalink
(http): StoveHttpResponse returns body as function (#301)
Browse files Browse the repository at this point in the history
* (http): getResponse returns body as function

* (http): also implement postAndExpectBody and putAndExpectBody

* (http): improve logic
  • Loading branch information
osoykan authored Jan 23, 2024
1 parent 31d5249 commit b56764e
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,17 @@

package com.trendyol.stove.testing.e2e.http

import arrow.core.None
import arrow.core.Option
import arrow.core.getOrElse
import arrow.core.*
import com.fasterxml.jackson.databind.ObjectMapper
import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper
import com.trendyol.stove.testing.e2e.system.TestSystem
import com.trendyol.stove.testing.e2e.system.ValidationDsl
import com.trendyol.stove.testing.e2e.system.WithDsl
import com.trendyol.stove.testing.e2e.system.abstractions.PluggedSystem
import com.trendyol.stove.testing.e2e.system.abstractions.SystemNotRegisteredException
import com.trendyol.stove.testing.e2e.system.abstractions.SystemOptions
import com.trendyol.stove.testing.e2e.system.*
import com.trendyol.stove.testing.e2e.system.abstractions.*
import kotlinx.coroutines.future.await
import java.net.URI
import java.net.URLEncoder
import java.net.http.HttpClient
import java.net.*
import java.net.http.*
import java.net.http.HttpClient.Redirect.ALWAYS
import java.net.http.HttpClient.Version.HTTP_2
import java.net.http.HttpRequest
import java.net.http.HttpRequest.BodyPublishers
import java.net.http.HttpResponse
import java.net.http.HttpResponse.BodyHandlers
import java.time.Duration
import kotlin.reflect.KClass
Expand Down Expand Up @@ -50,142 +41,158 @@ class HttpSystem(
@PublishedApi
internal val httpClient: HttpClient = httpClient()

@Suppress("unused")
suspend fun getResponse(
uri: String,
queryParams: Map<String, String> = mapOf(),
headers: Map<String, String> = mapOf(),
token: Option<String> = None,
expect: suspend (StoveHttpResponse) -> Unit
): HttpSystem =
httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
request
}.let {
expect(StoveHttpResponse(it.statusCode(), it.headers().map()))
this
}

suspend fun postAndExpectBodilessResponse(
uri: String,
body: Option<Any>,
token: Option<String> = None,
headers: Map<String, String> = mapOf(),
expect: suspend (StoveHttpResponse) -> Unit
): HttpSystem =
httpClient.send(uri, headers = headers) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
body.fold(
ifEmpty = { request.POST(BodyPublishers.noBody()) },
ifSome = { request.POST(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
): HttpSystem = httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
request
}.let {
expect(
StoveHttpResponse.Bodiless(
it.statusCode(),
it.headers().map()
)
}.let {
expect(StoveHttpResponse(it.statusCode(), it.headers().map()))
this
}
)
this
}

suspend fun putAndExpectBodilessResponse(
suspend inline fun <reified T : Any> getResponse(
uri: String,
body: Option<Any>,
token: Option<String> = None,
queryParams: Map<String, String> = mapOf(),
headers: Map<String, String> = mapOf(),
expect: suspend (StoveHttpResponse) -> Unit
): HttpSystem =
httpClient.send(uri, headers = headers) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
body.fold(
ifEmpty = { request.PUT(BodyPublishers.noBody()) },
ifSome = { request.PUT(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
)
}.let {
expect(StoveHttpResponse(it.statusCode(), it.headers().map()))
this
}

suspend fun deleteAndExpectBodilessResponse(
uri: String,
token: Option<String> = None,
headers: Map<String, String> = mapOf(),
expect: suspend (StoveHttpResponse) -> Unit
): HttpSystem =
httpClient.send(uri, headers = headers) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
request.DELETE()
}.let {
expect(StoveHttpResponse(it.statusCode(), it.headers().map()))
this
}
expect: (StoveHttpResponse.WithBody<T>) -> Unit
): HttpSystem = httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
request
}.let {
expect(
StoveHttpResponse.WithBody(
it.statusCode(),
it.headers().map()
) { deserialize(it, T::class) }
)
this
}

suspend inline fun <reified TExpected : Any> get(
uri: String,
queryParams: Map<String, String> = mapOf(),
headers: Map<String, String> = mapOf(),
token: Option<String> = None,
expect: (TExpected) -> Unit
): HttpSystem =
httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
request.GET()
}.let {
expect(deserialize(it, TExpected::class))
this
}
): HttpSystem = httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
request.GET()
}.let {
expect(deserialize(it, TExpected::class))
this
}

suspend inline fun <reified TExpected : Any> getMany(
uri: String,
queryParams: Map<String, String> = mapOf(),
headers: Map<String, String> = mapOf(),
token: Option<String> = None,
expect: (List<TExpected>) -> Unit
): HttpSystem =
httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
request.GET()
}.let {
expect(
objectMapper.readValue(
it.body(),
objectMapper.typeFactory.constructCollectionType(List::class.java, TExpected::class.javaObjectType)
)
): HttpSystem = httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
request.GET()
}.let {
expect(
objectMapper.readValue(
it.body(),
objectMapper.typeFactory.constructCollectionType(List::class.java, TExpected::class.javaObjectType)
)
this
}
)
this
}

suspend fun postAndExpectBodilessResponse(
uri: String,
body: Option<Any>,
token: Option<String> = None,
headers: Map<String, String> = mapOf(),
expect: suspend (StoveHttpResponse) -> Unit
): HttpSystem = doPostReq(uri, headers, token, body).let {
expect(StoveHttpResponse.Bodiless(it.statusCode(), it.headers().map()))
this
}

suspend inline fun <reified TExpected : Any> postAndExpectJson(
uri: String,
body: Option<Any> = None,
headers: Map<String, String> = mapOf(),
token: Option<String> = None,
expect: (actual: TExpected) -> Unit
): HttpSystem =
httpClient.send(uri, headers = headers) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
body.fold(
ifEmpty = { request.POST(BodyPublishers.noBody()) },
ifSome = { request.POST(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
)
}.let {
expect(deserialize(it, TExpected::class))
this
}
): HttpSystem = doPostReq(uri, headers, token, body).let {
expect(deserialize(it, TExpected::class))
this
}

/**
* Posts the given [body] to the given [uri] and expects the response to have a body.
*/
suspend inline fun <reified TExpected : Any> postAndExpectBody(
uri: String,
body: Option<Any> = None,
headers: Map<String, String> = mapOf(),
token: Option<String> = None,
expect: (actual: StoveHttpResponse.WithBody<TExpected>) -> Unit
): HttpSystem = doPostReq(uri, headers, token, body).let {
expect(StoveHttpResponse.WithBody(it.statusCode(), it.headers().map()) { deserialize(it, TExpected::class) })
this
}

suspend fun putAndExpectBodilessResponse(
uri: String,
body: Option<Any>,
token: Option<String> = None,
headers: Map<String, String> = mapOf(),
expect: suspend (StoveHttpResponse) -> Unit
): HttpSystem = doPUTReq(uri, headers, token, body).let {
expect(StoveHttpResponse.Bodiless(it.statusCode(), it.headers().map()))
this
}

suspend inline fun <reified TExpected : Any> putAndExpectJson(
uri: String,
body: Option<Any> = None,
headers: Map<String, String> = mapOf(),
token: Option<String> = None,
expect: (actual: TExpected) -> Unit
): HttpSystem =
httpClient.send(uri, headers = headers) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
body.fold(
ifEmpty = { request.PUT(BodyPublishers.noBody()) },
ifSome = { request.PUT(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
)
}.let {
expect(deserialize(it, TExpected::class))
this
}
): HttpSystem = doPUTReq(uri, headers, token, body).let {
expect(deserialize(it, TExpected::class))
this
}

suspend inline fun <reified TExpected : Any> putAndExpectBody(
uri: String,
body: Option<Any> = None,
headers: Map<String, String> = mapOf(),
token: Option<String> = None,
expect: (actual: StoveHttpResponse.WithBody<TExpected>) -> Unit
): HttpSystem = doPUTReq(uri, headers, token, body).let {
expect(StoveHttpResponse.WithBody(it.statusCode(), it.headers().map()) { deserialize(it, TExpected::class) })
this
}

suspend fun deleteAndExpectBodilessResponse(
uri: String,
token: Option<String> = None,
headers: Map<String, String> = mapOf(),
expect: suspend (StoveHttpResponse) -> Unit
): HttpSystem = httpClient.send(uri, headers = headers) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
request.DELETE()
}.let {
expect(StoveHttpResponse.Bodiless(it.statusCode(), it.headers().map()))
this
}

override fun then(): TestSystem = testSystem

Expand All @@ -196,32 +203,58 @@ class HttpSystem(
queryParams: Map<String, String> = mapOf(),
configureRequest: (request: HttpRequest.Builder) -> HttpRequest.Builder
): HttpResponse<ByteArray> {
val requestBuilder =
HttpRequest.newBuilder()
.uri(relative(uri, queryParams))
.addHeaders(headers)
val requestBuilder = HttpRequest.newBuilder()
.uri(relative(uri, queryParams))
.addHeaders(headers)
return sendAsync(configureRequest(requestBuilder).build(), BodyHandlers.ofByteArray()).await()
}

private fun HttpRequest.Builder.addHeaders(headers: Map<String, String>): HttpRequest.Builder =
headers
.toMutableMap()
.apply { this[Headers.CONTENT_TYPE] = MediaType.APPLICATION_JSON }
.forEach { (key, value) -> setHeader(key, value) }
.let { this }
@PublishedApi
internal suspend fun doPUTReq(
uri: String,
headers: Map<String, String>,
token: Option<String>,
body: Option<Any>
): HttpResponse<ByteArray> = httpClient.send(uri, headers = headers) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
body.fold(
ifEmpty = { request.PUT(BodyPublishers.noBody()) },
ifSome = { request.PUT(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
)
}

@PublishedApi
internal suspend fun doPostReq(
uri: String,
headers: Map<String, String>,
token: Option<String>,
body: Option<Any>
): HttpResponse<ByteArray> = httpClient.send(uri, headers = headers) { request ->
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
body.fold(
ifEmpty = { request.POST(BodyPublishers.noBody()) },
ifSome = { request.POST(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
)
}

private fun HttpRequest.Builder.addHeaders(
headers: Map<String, String>
): HttpRequest.Builder = headers
.toMutableMap()
.apply { this[Headers.CONTENT_TYPE] = MediaType.APPLICATION_JSON }
.forEach { (key, value) -> setHeader(key, value) }
.let { this }

private fun relative(
uri: String,
queryParams: Map<String, String> = mapOf()
): URI =
URI.create(testSystem.baseUrl)
.resolve(uri + queryParams.toParamsString())

private fun Map<String, String>.toParamsString(): String =
when {
this.any() -> "?${this.map { "${it.key}=${URLEncoder.encode(it.value, Charsets.UTF_8)}" }.joinToString("&")}"
else -> ""
}
): URI = URI.create(testSystem.baseUrl)
.resolve(uri + queryParams.toParamsString())

private fun Map<String, String>.toParamsString(): String = when {
this.any() -> "?${this.map { "${it.key}=${URLEncoder.encode(it.value, Charsets.UTF_8)}" }.joinToString("&")}"
else -> ""
}

private fun httpClient(): HttpClient {
val builder = HttpClient.newBuilder()
Expand All @@ -235,11 +268,10 @@ class HttpSystem(
internal fun <TExpected : Any> deserialize(
it: HttpResponse<ByteArray>,
clazz: KClass<TExpected>
): TExpected =
when {
clazz.java.isAssignableFrom(String::class.java) -> String(it.body()) as TExpected
else -> objectMapper.readValue(it.body(), clazz.java)
}
): TExpected = when {
clazz.java.isAssignableFrom(String::class.java) -> String(it.body()) as TExpected
else -> objectMapper.readValue(it.body(), clazz.java)
}

override fun close() {}

Expand Down
Loading

0 comments on commit b56764e

Please sign in to comment.