Skip to content

Commit b56764e

Browse files
authored
(http): StoveHttpResponse returns body as function (#301)
* (http): getResponse returns body as function * (http): also implement postAndExpectBody and putAndExpectBody * (http): improve logic
1 parent 31d5249 commit b56764e

File tree

3 files changed

+244
-142
lines changed

3 files changed

+244
-142
lines changed

lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystem.kt

Lines changed: 166 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,17 @@
22

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

5-
import arrow.core.None
6-
import arrow.core.Option
7-
import arrow.core.getOrElse
5+
import arrow.core.*
86
import com.fasterxml.jackson.databind.ObjectMapper
97
import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper
10-
import com.trendyol.stove.testing.e2e.system.TestSystem
11-
import com.trendyol.stove.testing.e2e.system.ValidationDsl
12-
import com.trendyol.stove.testing.e2e.system.WithDsl
13-
import com.trendyol.stove.testing.e2e.system.abstractions.PluggedSystem
14-
import com.trendyol.stove.testing.e2e.system.abstractions.SystemNotRegisteredException
15-
import com.trendyol.stove.testing.e2e.system.abstractions.SystemOptions
8+
import com.trendyol.stove.testing.e2e.system.*
9+
import com.trendyol.stove.testing.e2e.system.abstractions.*
1610
import kotlinx.coroutines.future.await
17-
import java.net.URI
18-
import java.net.URLEncoder
19-
import java.net.http.HttpClient
11+
import java.net.*
12+
import java.net.http.*
2013
import java.net.http.HttpClient.Redirect.ALWAYS
2114
import java.net.http.HttpClient.Version.HTTP_2
22-
import java.net.http.HttpRequest
2315
import java.net.http.HttpRequest.BodyPublishers
24-
import java.net.http.HttpResponse
2516
import java.net.http.HttpResponse.BodyHandlers
2617
import java.time.Duration
2718
import kotlin.reflect.KClass
@@ -50,142 +41,158 @@ class HttpSystem(
5041
@PublishedApi
5142
internal val httpClient: HttpClient = httpClient()
5243

53-
@Suppress("unused")
5444
suspend fun getResponse(
5545
uri: String,
5646
queryParams: Map<String, String> = mapOf(),
5747
headers: Map<String, String> = mapOf(),
5848
token: Option<String> = None,
5949
expect: suspend (StoveHttpResponse) -> Unit
60-
): HttpSystem =
61-
httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
62-
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
63-
request
64-
}.let {
65-
expect(StoveHttpResponse(it.statusCode(), it.headers().map()))
66-
this
67-
}
68-
69-
suspend fun postAndExpectBodilessResponse(
70-
uri: String,
71-
body: Option<Any>,
72-
token: Option<String> = None,
73-
headers: Map<String, String> = mapOf(),
74-
expect: suspend (StoveHttpResponse) -> Unit
75-
): HttpSystem =
76-
httpClient.send(uri, headers = headers) { request ->
77-
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
78-
body.fold(
79-
ifEmpty = { request.POST(BodyPublishers.noBody()) },
80-
ifSome = { request.POST(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
50+
): HttpSystem = httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
51+
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
52+
request
53+
}.let {
54+
expect(
55+
StoveHttpResponse.Bodiless(
56+
it.statusCode(),
57+
it.headers().map()
8158
)
82-
}.let {
83-
expect(StoveHttpResponse(it.statusCode(), it.headers().map()))
84-
this
85-
}
59+
)
60+
this
61+
}
8662

87-
suspend fun putAndExpectBodilessResponse(
63+
suspend inline fun <reified T : Any> getResponse(
8864
uri: String,
89-
body: Option<Any>,
90-
token: Option<String> = None,
65+
queryParams: Map<String, String> = mapOf(),
9166
headers: Map<String, String> = mapOf(),
92-
expect: suspend (StoveHttpResponse) -> Unit
93-
): HttpSystem =
94-
httpClient.send(uri, headers = headers) { request ->
95-
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
96-
body.fold(
97-
ifEmpty = { request.PUT(BodyPublishers.noBody()) },
98-
ifSome = { request.PUT(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
99-
)
100-
}.let {
101-
expect(StoveHttpResponse(it.statusCode(), it.headers().map()))
102-
this
103-
}
104-
105-
suspend fun deleteAndExpectBodilessResponse(
106-
uri: String,
10767
token: Option<String> = None,
108-
headers: Map<String, String> = mapOf(),
109-
expect: suspend (StoveHttpResponse) -> Unit
110-
): HttpSystem =
111-
httpClient.send(uri, headers = headers) { request ->
112-
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
113-
request.DELETE()
114-
}.let {
115-
expect(StoveHttpResponse(it.statusCode(), it.headers().map()))
116-
this
117-
}
68+
expect: (StoveHttpResponse.WithBody<T>) -> Unit
69+
): HttpSystem = httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
70+
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
71+
request
72+
}.let {
73+
expect(
74+
StoveHttpResponse.WithBody(
75+
it.statusCode(),
76+
it.headers().map()
77+
) { deserialize(it, T::class) }
78+
)
79+
this
80+
}
11881

11982
suspend inline fun <reified TExpected : Any> get(
12083
uri: String,
12184
queryParams: Map<String, String> = mapOf(),
12285
headers: Map<String, String> = mapOf(),
12386
token: Option<String> = None,
12487
expect: (TExpected) -> Unit
125-
): HttpSystem =
126-
httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
127-
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
128-
request.GET()
129-
}.let {
130-
expect(deserialize(it, TExpected::class))
131-
this
132-
}
88+
): HttpSystem = httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
89+
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
90+
request.GET()
91+
}.let {
92+
expect(deserialize(it, TExpected::class))
93+
this
94+
}
13395

13496
suspend inline fun <reified TExpected : Any> getMany(
13597
uri: String,
13698
queryParams: Map<String, String> = mapOf(),
13799
headers: Map<String, String> = mapOf(),
138100
token: Option<String> = None,
139101
expect: (List<TExpected>) -> Unit
140-
): HttpSystem =
141-
httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
142-
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
143-
request.GET()
144-
}.let {
145-
expect(
146-
objectMapper.readValue(
147-
it.body(),
148-
objectMapper.typeFactory.constructCollectionType(List::class.java, TExpected::class.javaObjectType)
149-
)
102+
): HttpSystem = httpClient.send(uri, headers = headers, queryParams = queryParams) { request ->
103+
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
104+
request.GET()
105+
}.let {
106+
expect(
107+
objectMapper.readValue(
108+
it.body(),
109+
objectMapper.typeFactory.constructCollectionType(List::class.java, TExpected::class.javaObjectType)
150110
)
151-
this
152-
}
111+
)
112+
this
113+
}
114+
115+
suspend fun postAndExpectBodilessResponse(
116+
uri: String,
117+
body: Option<Any>,
118+
token: Option<String> = None,
119+
headers: Map<String, String> = mapOf(),
120+
expect: suspend (StoveHttpResponse) -> Unit
121+
): HttpSystem = doPostReq(uri, headers, token, body).let {
122+
expect(StoveHttpResponse.Bodiless(it.statusCode(), it.headers().map()))
123+
this
124+
}
153125

154126
suspend inline fun <reified TExpected : Any> postAndExpectJson(
155127
uri: String,
156128
body: Option<Any> = None,
157129
headers: Map<String, String> = mapOf(),
158130
token: Option<String> = None,
159131
expect: (actual: TExpected) -> Unit
160-
): HttpSystem =
161-
httpClient.send(uri, headers = headers) { request ->
162-
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
163-
body.fold(
164-
ifEmpty = { request.POST(BodyPublishers.noBody()) },
165-
ifSome = { request.POST(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
166-
)
167-
}.let {
168-
expect(deserialize(it, TExpected::class))
169-
this
170-
}
132+
): HttpSystem = doPostReq(uri, headers, token, body).let {
133+
expect(deserialize(it, TExpected::class))
134+
this
135+
}
136+
137+
/**
138+
* Posts the given [body] to the given [uri] and expects the response to have a body.
139+
*/
140+
suspend inline fun <reified TExpected : Any> postAndExpectBody(
141+
uri: String,
142+
body: Option<Any> = None,
143+
headers: Map<String, String> = mapOf(),
144+
token: Option<String> = None,
145+
expect: (actual: StoveHttpResponse.WithBody<TExpected>) -> Unit
146+
): HttpSystem = doPostReq(uri, headers, token, body).let {
147+
expect(StoveHttpResponse.WithBody(it.statusCode(), it.headers().map()) { deserialize(it, TExpected::class) })
148+
this
149+
}
150+
151+
suspend fun putAndExpectBodilessResponse(
152+
uri: String,
153+
body: Option<Any>,
154+
token: Option<String> = None,
155+
headers: Map<String, String> = mapOf(),
156+
expect: suspend (StoveHttpResponse) -> Unit
157+
): HttpSystem = doPUTReq(uri, headers, token, body).let {
158+
expect(StoveHttpResponse.Bodiless(it.statusCode(), it.headers().map()))
159+
this
160+
}
171161

172162
suspend inline fun <reified TExpected : Any> putAndExpectJson(
173163
uri: String,
174164
body: Option<Any> = None,
175165
headers: Map<String, String> = mapOf(),
176166
token: Option<String> = None,
177167
expect: (actual: TExpected) -> Unit
178-
): HttpSystem =
179-
httpClient.send(uri, headers = headers) { request ->
180-
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
181-
body.fold(
182-
ifEmpty = { request.PUT(BodyPublishers.noBody()) },
183-
ifSome = { request.PUT(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
184-
)
185-
}.let {
186-
expect(deserialize(it, TExpected::class))
187-
this
188-
}
168+
): HttpSystem = doPUTReq(uri, headers, token, body).let {
169+
expect(deserialize(it, TExpected::class))
170+
this
171+
}
172+
173+
suspend inline fun <reified TExpected : Any> putAndExpectBody(
174+
uri: String,
175+
body: Option<Any> = None,
176+
headers: Map<String, String> = mapOf(),
177+
token: Option<String> = None,
178+
expect: (actual: StoveHttpResponse.WithBody<TExpected>) -> Unit
179+
): HttpSystem = doPUTReq(uri, headers, token, body).let {
180+
expect(StoveHttpResponse.WithBody(it.statusCode(), it.headers().map()) { deserialize(it, TExpected::class) })
181+
this
182+
}
183+
184+
suspend fun deleteAndExpectBodilessResponse(
185+
uri: String,
186+
token: Option<String> = None,
187+
headers: Map<String, String> = mapOf(),
188+
expect: suspend (StoveHttpResponse) -> Unit
189+
): HttpSystem = httpClient.send(uri, headers = headers) { request ->
190+
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
191+
request.DELETE()
192+
}.let {
193+
expect(StoveHttpResponse.Bodiless(it.statusCode(), it.headers().map()))
194+
this
195+
}
189196

190197
override fun then(): TestSystem = testSystem
191198

@@ -196,32 +203,58 @@ class HttpSystem(
196203
queryParams: Map<String, String> = mapOf(),
197204
configureRequest: (request: HttpRequest.Builder) -> HttpRequest.Builder
198205
): HttpResponse<ByteArray> {
199-
val requestBuilder =
200-
HttpRequest.newBuilder()
201-
.uri(relative(uri, queryParams))
202-
.addHeaders(headers)
206+
val requestBuilder = HttpRequest.newBuilder()
207+
.uri(relative(uri, queryParams))
208+
.addHeaders(headers)
203209
return sendAsync(configureRequest(requestBuilder).build(), BodyHandlers.ofByteArray()).await()
204210
}
205211

206-
private fun HttpRequest.Builder.addHeaders(headers: Map<String, String>): HttpRequest.Builder =
207-
headers
208-
.toMutableMap()
209-
.apply { this[Headers.CONTENT_TYPE] = MediaType.APPLICATION_JSON }
210-
.forEach { (key, value) -> setHeader(key, value) }
211-
.let { this }
212+
@PublishedApi
213+
internal suspend fun doPUTReq(
214+
uri: String,
215+
headers: Map<String, String>,
216+
token: Option<String>,
217+
body: Option<Any>
218+
): HttpResponse<ByteArray> = httpClient.send(uri, headers = headers) { request ->
219+
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
220+
body.fold(
221+
ifEmpty = { request.PUT(BodyPublishers.noBody()) },
222+
ifSome = { request.PUT(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
223+
)
224+
}
225+
226+
@PublishedApi
227+
internal suspend fun doPostReq(
228+
uri: String,
229+
headers: Map<String, String>,
230+
token: Option<String>,
231+
body: Option<Any>
232+
): HttpResponse<ByteArray> = httpClient.send(uri, headers = headers) { request ->
233+
token.map { request.setHeader(Headers.AUTHORIZATION, Headers.bearer(it)) }
234+
body.fold(
235+
ifEmpty = { request.POST(BodyPublishers.noBody()) },
236+
ifSome = { request.POST(BodyPublishers.ofString(objectMapper.writeValueAsString(it))) }
237+
)
238+
}
239+
240+
private fun HttpRequest.Builder.addHeaders(
241+
headers: Map<String, String>
242+
): HttpRequest.Builder = headers
243+
.toMutableMap()
244+
.apply { this[Headers.CONTENT_TYPE] = MediaType.APPLICATION_JSON }
245+
.forEach { (key, value) -> setHeader(key, value) }
246+
.let { this }
212247

213248
private fun relative(
214249
uri: String,
215250
queryParams: Map<String, String> = mapOf()
216-
): URI =
217-
URI.create(testSystem.baseUrl)
218-
.resolve(uri + queryParams.toParamsString())
219-
220-
private fun Map<String, String>.toParamsString(): String =
221-
when {
222-
this.any() -> "?${this.map { "${it.key}=${URLEncoder.encode(it.value, Charsets.UTF_8)}" }.joinToString("&")}"
223-
else -> ""
224-
}
251+
): URI = URI.create(testSystem.baseUrl)
252+
.resolve(uri + queryParams.toParamsString())
253+
254+
private fun Map<String, String>.toParamsString(): String = when {
255+
this.any() -> "?${this.map { "${it.key}=${URLEncoder.encode(it.value, Charsets.UTF_8)}" }.joinToString("&")}"
256+
else -> ""
257+
}
225258

226259
private fun httpClient(): HttpClient {
227260
val builder = HttpClient.newBuilder()
@@ -235,11 +268,10 @@ class HttpSystem(
235268
internal fun <TExpected : Any> deserialize(
236269
it: HttpResponse<ByteArray>,
237270
clazz: KClass<TExpected>
238-
): TExpected =
239-
when {
240-
clazz.java.isAssignableFrom(String::class.java) -> String(it.body()) as TExpected
241-
else -> objectMapper.readValue(it.body(), clazz.java)
242-
}
271+
): TExpected = when {
272+
clazz.java.isAssignableFrom(String::class.java) -> String(it.body()) as TExpected
273+
else -> objectMapper.readValue(it.body(), clazz.java)
274+
}
243275

244276
override fun close() {}
245277

0 commit comments

Comments
 (0)