Skip to content

Commit 7594a4d

Browse files
authored
misc: make TestConnection request assertions more configurable (#943)
1 parent 762d583 commit 7594a4d

File tree

3 files changed

+112
-28
lines changed

3 files changed

+112
-28
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.httptest
6+
7+
import aws.smithy.kotlin.runtime.http.readAll
8+
import aws.smithy.kotlin.runtime.http.request.HttpRequest
9+
import kotlin.test.assertEquals
10+
import kotlin.test.assertTrue
11+
12+
/**
13+
* Asserts equality between two [HttpRequest] instances. Implementations of this interface are free to choose criteria
14+
* for the equality assertion.
15+
*/
16+
public interface CallAsserter {
17+
/**
18+
* Verify that [expected] and [actual] are equal according to this asserter's criteria. If not, an [AssertionError]
19+
* is thrown.
20+
* @param msgPrefix The prefix to include in the message if an [AssertionError] is thrown
21+
* @param expected The expected request
22+
* @param actual The actual request
23+
*/
24+
public suspend fun assertEquals(msgPrefix: String, expected: HttpRequest, actual: HttpRequest)
25+
26+
public companion object {
27+
/**
28+
* Asserter that verifies every part of the requests match
29+
*/
30+
public val FullyMatching: CallAsserter = List(
31+
MatchingMethods,
32+
MatchingUrls,
33+
MatchingHeaders.All,
34+
MatchingBodies,
35+
)
36+
}
37+
38+
/**
39+
* Asserter that delegates to a collection of sub-asserters
40+
*/
41+
public class List(private vararg val asserters: CallAsserter) : CallAsserter {
42+
override suspend fun assertEquals(msgPrefix: String, expected: HttpRequest, actual: HttpRequest) {
43+
asserters.forEach { it.assertEquals(msgPrefix, expected, actual) }
44+
}
45+
}
46+
47+
/**
48+
* Asserter that verifies the methods of the requests match
49+
*/
50+
public object MatchingMethods : CallAsserter {
51+
override suspend fun assertEquals(msgPrefix: String, expected: HttpRequest, actual: HttpRequest) {
52+
assertEquals(expected.method, actual.method, "$msgPrefix: Method mismatch")
53+
}
54+
}
55+
56+
/**
57+
* Asserter that verifies the URLs of the requests match
58+
*/
59+
public object MatchingUrls : CallAsserter {
60+
override suspend fun assertEquals(msgPrefix: String, expected: HttpRequest, actual: HttpRequest) {
61+
assertEquals(expected.url.toString(), actual.url.toString(), "$msgPrefix: URL mismatch")
62+
}
63+
}
64+
65+
/**
66+
* Asserter that verifies headers of the requests match
67+
* @param shouldVerifyHeader A predicate which indicates whether a header with the given key should be verified
68+
*/
69+
public class MatchingHeaders(private val shouldVerifyHeader: (String) -> Boolean) : CallAsserter {
70+
override suspend fun assertEquals(msgPrefix: String, expected: HttpRequest, actual: HttpRequest) {
71+
expected.headers.forEach { name, values ->
72+
if (shouldVerifyHeader(name)) {
73+
values.forEach {
74+
assertTrue(actual.headers.contains(name, it), "$msgPrefix: header `$name` missing value `$it`")
75+
}
76+
}
77+
}
78+
}
79+
80+
public companion object {
81+
/**
82+
* Asserter that verifies every header of the requests match
83+
*/
84+
public val All: MatchingHeaders = MatchingHeaders { true }
85+
}
86+
}
87+
88+
/**
89+
* Asserter that verifies the bodies of the requests match
90+
*/
91+
public object MatchingBodies : CallAsserter {
92+
override suspend fun assertEquals(msgPrefix: String, expected: HttpRequest, actual: HttpRequest) {
93+
val expectedBody = expected.body.readAll()?.decodeToString()
94+
val actualBody = actual.body.readAll()?.decodeToString()
95+
assertEquals(expectedBody, actualBody, "$msgPrefix: body mismatch")
96+
}
97+
}
98+
}

runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/TestConnection.kt

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ import aws.smithy.kotlin.runtime.http.HttpStatusCode
1212
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineBase
1313
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfig
1414
import aws.smithy.kotlin.runtime.http.engine.callContext
15-
import aws.smithy.kotlin.runtime.http.readAll
1615
import aws.smithy.kotlin.runtime.http.request.HttpRequest
1716
import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder
1817
import aws.smithy.kotlin.runtime.http.response.HttpResponse
1918
import aws.smithy.kotlin.runtime.operation.ExecutionContext
2019
import aws.smithy.kotlin.runtime.time.Instant
2120
import kotlin.test.assertEquals
22-
import kotlin.test.assertTrue
2321

2422
/**
2523
* An expected HttpRequest with the response that should be returned by the engine
@@ -31,23 +29,13 @@ public data class MockRoundTrip(public val expected: HttpRequest?, public val re
3129
/**
3230
* Actual and expected [HttpRequest] pair
3331
*/
34-
public data class CallAssertion(public val expected: HttpRequest?, public val actual: HttpRequest) {
32+
public data class RequestComparands(public val expected: HttpRequest?, public val actual: HttpRequest) {
3533
/**
36-
* Assert that all of the components set on [expected] are also the same on [actual]. The actual request
37-
* may have additional headers, only the ones set in [expected] are compared.
34+
* Assert that [expected] matches [actual] according to [asserter].
35+
* @param msgPrefix The prefix to include in the message if an [AssertionError] is thrown
3836
*/
39-
internal suspend fun assertRequest(idx: Int) {
40-
if (expected == null) return
41-
assertEquals(expected.url.toString(), actual.url.toString(), "[request#$idx]: URL mismatch")
42-
expected.headers.forEach { name, values ->
43-
values.forEach {
44-
assertTrue(actual.headers.contains(name, it), "[request#$idx]: header `$name` missing value `$it`")
45-
}
46-
}
47-
48-
val expectedBody = expected.body.readAll()?.decodeToString()
49-
val actualBody = actual.body.readAll()?.decodeToString()
50-
assertEquals(expectedBody, actualBody, "[request#$idx]: body mismatch")
37+
internal suspend fun assertRequest(msgPrefix: String, asserter: CallAsserter) {
38+
expected?.let { asserter.assertEquals(msgPrefix, it, actual) }
5139
}
5240
}
5341

@@ -67,7 +55,7 @@ public class TestConnection(private val expected: List<MockRoundTrip> = emptyLis
6755

6856
// expected is mutated in-flight, store original size
6957
private val iter = expected.iterator()
70-
private var calls = mutableListOf<CallAssertion>()
58+
private var requests = mutableListOf<RequestComparands>()
7159

7260
override val config: HttpClientEngineConfig = HttpClientEngineConfig.Default
7361

@@ -121,7 +109,7 @@ public class TestConnection(private val expected: List<MockRoundTrip> = emptyLis
121109
override suspend fun roundTrip(context: ExecutionContext, request: HttpRequest): HttpCall {
122110
check(iter.hasNext()) { "TestConnection has no remaining expected requests" }
123111
val next = iter.next()
124-
calls.add(CallAssertion(next.expected, request))
112+
requests.add(RequestComparands(next.expected, request))
125113

126114
val response = next.respondWith ?: HttpResponse(HttpStatusCode.OK, Headers.Empty, HttpBody.Empty)
127115
val now = Instant.now()
@@ -131,15 +119,15 @@ public class TestConnection(private val expected: List<MockRoundTrip> = emptyLis
131119
/**
132120
* Get the list of captured HTTP requests so far
133121
*/
134-
public fun requests(): List<CallAssertion> = calls
122+
public fun requests(): List<RequestComparands> = requests
135123

136124
/**
137125
* Assert that each captured request matches the expected
138126
*/
139-
public suspend fun assertRequests() {
140-
assertEquals(expected.size, calls.size)
141-
calls.forEachIndexed { idx, captured ->
142-
captured.assertRequest(idx)
127+
public suspend fun assertRequests(asserter: CallAsserter = CallAsserter.FullyMatching) {
128+
assertEquals(expected.size, requests.size)
129+
requests.forEachIndexed { idx, captured ->
130+
captured.assertRequest("[request#$idx]", asserter)
143131
}
144132
}
145133
}

runtime/protocol/http-test/common/test/aws/smithy/kotlin/runtime/httptest/TestConnectionTest.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55

66
package aws.smithy.kotlin.runtime.httptest
77

8-
import aws.smithy.kotlin.runtime.http.HttpStatusCode
9-
import aws.smithy.kotlin.runtime.http.SdkHttpClient
10-
import aws.smithy.kotlin.runtime.http.complete
8+
import aws.smithy.kotlin.runtime.http.*
119
import aws.smithy.kotlin.runtime.http.content.ByteArrayContent
12-
import aws.smithy.kotlin.runtime.http.readAll
1310
import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder
1411
import aws.smithy.kotlin.runtime.net.Host
1512
import io.kotest.matchers.string.shouldContain
@@ -204,6 +201,7 @@ class TestConnectionTest {
204201
val client = SdkHttpClient(engine)
205202

206203
val req = HttpRequestBuilder().apply {
204+
method = HttpMethod.POST
207205
url.host = Host.Domain("test.aws.com")
208206
url.path = "/turtles-all-the-way-down"
209207
url.parameters.append("q1", "v1")

0 commit comments

Comments
 (0)