-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add some additional E2E tests for client #147
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,13 +95,33 @@ internal class ConnectInterceptor( | |
val responseHeaders = | ||
response.headers.filter { entry -> !entry.key.startsWith("trailer-") } | ||
val compressionPool = clientConfig.compressionPool(responseHeaders[CONTENT_ENCODING]?.first()) | ||
val responseBody = try { | ||
compressionPool?.decompress(response.message.buffer) ?: response.message.buffer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly we weren't handling exceptions properly when decompressing data. |
||
} catch (e: Exception) { | ||
return@UnaryFunction HTTPResponse( | ||
code = Code.INTERNAL_ERROR, | ||
message = Buffer(), | ||
headers = responseHeaders, | ||
trailers = trailers, | ||
cause = ConnectException( | ||
code = Code.INTERNAL_ERROR, | ||
errorDetailParser = serializationStrategy.errorDetailParser(), | ||
message = e.message, | ||
exception = e, | ||
), | ||
tracingInfo = response.tracingInfo, | ||
) | ||
} | ||
val message: Buffer | ||
val (code, exception) = if (response.code != Code.OK) { | ||
val error = parseConnectUnaryException(code = response.code, response.headers, response.message.buffer) | ||
val error = parseConnectUnaryException(code = response.code, response.headers, responseBody) | ||
// We've already read the response body to parse an error - don't read again. | ||
message = Buffer() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @erawhctim - This fixes the issue before where we'd both read a JSON error response and then read again for the message. |
||
error.code to error | ||
} else { | ||
message = responseBody | ||
response.code to null | ||
} | ||
val message = compressionPool?.decompress(response.message.buffer) ?: response.message.buffer | ||
HTTPResponse( | ||
code = code, | ||
message = message, | ||
|
@@ -122,18 +142,12 @@ internal class ConnectInterceptor( | |
mutableMapOf(CONNECT_PROTOCOL_VERSION_KEY to listOf(CONNECT_PROTOCOL_VERSION_VALUE)) | ||
requestHeaders.putAll(request.headers) | ||
if (requestCompression != null) { | ||
requestHeaders.put( | ||
CONNECT_STREAMING_CONTENT_ENCODING, | ||
listOf(requestCompression.compressionPool.name()), | ||
) | ||
requestHeaders[CONNECT_STREAMING_CONTENT_ENCODING] = listOf(requestCompression.compressionPool.name()) | ||
} | ||
if (requestHeaders.keys.none { it.equals(USER_AGENT, ignoreCase = true) }) { | ||
requestHeaders[USER_AGENT] = listOf("connect-kotlin/${ConnectConstants.VERSION}") | ||
} | ||
requestHeaders.put( | ||
CONNECT_STREAMING_ACCEPT_ENCODING, | ||
clientConfig.compressionPools().map { entry -> entry.name() }, | ||
) | ||
requestHeaders[CONNECT_STREAMING_ACCEPT_ENCODING] = clientConfig.compressionPools().map { entry -> entry.name() } | ||
request.clone( | ||
url = request.url, | ||
contentType = request.contentType, | ||
|
@@ -246,7 +260,7 @@ internal class ConnectInterceptor( | |
serializationStrategy.errorDetailParser(), | ||
errorJSON, | ||
) | ||
} catch (e: Throwable) { | ||
} catch (e: Exception) { | ||
return ConnectException(code, serializationStrategy.errorDetailParser(), errorJSON) | ||
} | ||
val errorDetails = parseErrorDetails(errorPayloadJSON) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,9 +17,11 @@ package com.connectrpc.okhttp | |
import com.connectrpc.Code | ||
import com.connectrpc.ProtocolClientConfig | ||
import com.connectrpc.RequestCompression | ||
import com.connectrpc.SerializationStrategy | ||
import com.connectrpc.compression.GzipCompressionPool | ||
import com.connectrpc.eliza.v1.ElizaServiceClient | ||
import com.connectrpc.eliza.v1.sayRequest | ||
import com.connectrpc.extensions.GoogleJavaJSONStrategy | ||
import com.connectrpc.extensions.GoogleJavaProtobufStrategy | ||
import com.connectrpc.impl.ProtocolClient | ||
import com.connectrpc.protocols.NetworkProtocol | ||
|
@@ -31,12 +33,16 @@ import org.assertj.core.api.Assertions.assertThat | |
import org.junit.Rule | ||
import org.junit.Test | ||
|
||
/** | ||
* Tests to exercise end to end failure cases not easily verified with conformance tests. | ||
* Over time these may be moved to conformance tests. | ||
*/ | ||
class MockWebServerTests { | ||
|
||
@get:Rule val mockWebServerRule = MockWebServerRule() | ||
|
||
@Test | ||
fun `compressed empty failure response is parsed correctly`() = runTest { | ||
fun `invalid compressed failure response is handled correctly`() = runTest { | ||
mockWebServerRule.server.enqueue( | ||
MockResponse().apply { | ||
addHeader("accept-encoding", "gzip") | ||
|
@@ -45,9 +51,66 @@ class MockWebServerTests { | |
setResponseCode(401) | ||
}, | ||
) | ||
val response = createClient().say(sayRequest { sentence = "hello" }) | ||
mockWebServerRule.server.takeRequest().apply { | ||
assertThat(path).isEqualTo("/connectrpc.eliza.v1.ElizaService/Say") | ||
} | ||
assertThat(response.code).isEqualTo(Code.INTERNAL_ERROR) | ||
} | ||
|
||
val host = mockWebServerRule.server.url("/") | ||
@Test | ||
fun `invalid compressed response data is handled correctly`() = runTest { | ||
mockWebServerRule.server.enqueue( | ||
MockResponse().apply { | ||
addHeader("accept-encoding", "gzip") | ||
addHeader("content-encoding", "gzip") | ||
setBody("this isn't gzipped") | ||
setResponseCode(200) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests http status = 200, invalid compressed data. |
||
}, | ||
) | ||
val response = createClient().say(sayRequest { sentence = "hello" }) | ||
mockWebServerRule.server.takeRequest().apply { | ||
assertThat(path).isEqualTo("/connectrpc.eliza.v1.ElizaService/Say") | ||
} | ||
assertThat(response.code).isEqualTo(Code.INTERNAL_ERROR) | ||
} | ||
|
||
@Test | ||
fun `invalid protobuf response data is handled correctly`() = runTest { | ||
mockWebServerRule.server.enqueue( | ||
MockResponse().apply { | ||
addHeader("accept-encoding", "gzip") | ||
addHeader("content-type", "application/proto") | ||
setBody("this isn't valid protobuf") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests http status = 200, invalid proto message. |
||
setResponseCode(200) | ||
}, | ||
) | ||
val response = createClient().say(sayRequest { sentence = "hello" }) | ||
mockWebServerRule.server.takeRequest().apply { | ||
assertThat(path).isEqualTo("/connectrpc.eliza.v1.ElizaService/Say") | ||
} | ||
assertThat(response.code).isEqualTo(Code.INTERNAL_ERROR) | ||
} | ||
|
||
@Test | ||
fun `invalid json response data is handled correctly`() = runTest { | ||
mockWebServerRule.server.enqueue( | ||
MockResponse().apply { | ||
addHeader("accept-encoding", "gzip") | ||
addHeader("content-type", "application/json") | ||
setBody("{ invalid json") | ||
setResponseCode(200) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests http status = 200, invalid json message. |
||
}, | ||
) | ||
val response = createClient(serializationStrategy = GoogleJavaJSONStrategy()).say(sayRequest { sentence = "hello" }) | ||
mockWebServerRule.server.takeRequest().apply { | ||
assertThat(path).isEqualTo("/connectrpc.eliza.v1.ElizaService/Say") | ||
} | ||
assertThat(response.code).isEqualTo(Code.INTERNAL_ERROR) | ||
} | ||
|
||
private fun createClient(serializationStrategy: SerializationStrategy = GoogleJavaProtobufStrategy()): ElizaServiceClient { | ||
val host = mockWebServerRule.server.url("/") | ||
val protocolClient = ProtocolClient( | ||
ConnectOkHttpClient( | ||
OkHttpClient.Builder() | ||
|
@@ -56,19 +119,11 @@ class MockWebServerTests { | |
), | ||
ProtocolClientConfig( | ||
host = host.toString(), | ||
serializationStrategy = GoogleJavaProtobufStrategy(), | ||
serializationStrategy = serializationStrategy, | ||
networkProtocol = NetworkProtocol.CONNECT, | ||
requestCompression = RequestCompression(0, GzipCompressionPool), | ||
compressionPools = listOf(GzipCompressionPool), | ||
), | ||
) | ||
|
||
val response = ElizaServiceClient(protocolClient).say(sayRequest { sentence = "hello" }) | ||
|
||
mockWebServerRule.server.takeRequest().apply { | ||
assertThat(path).isEqualTo("/connectrpc.eliza.v1.ElizaService/Say") | ||
} | ||
|
||
assertThat(response.code).isEqualTo(Code.UNKNOWN) | ||
return ElizaServiceClient(protocolClient) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were missing exception handling around deserializing using the Codec, which lead to not notifying callers of a failure.