-
Notifications
You must be signed in to change notification settings - Fork 172
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
feat(retrofit): add getErrorBodyAs() method to SpinnakerServerException #1027
feat(retrofit): add getErrorBodyAs() method to SpinnakerServerException #1027
Conversation
c171653
to
cbfa0ed
Compare
...t/src/main/java/com/netflix/spinnaker/kork/retrofit/exceptions/SpinnakerServerException.java
Outdated
Show resolved
Hide resolved
...t/src/main/java/com/netflix/spinnaker/kork/retrofit/exceptions/SpinnakerServerException.java
Outdated
Show resolved
Hide resolved
...t/src/main/java/com/netflix/spinnaker/kork/retrofit/exceptions/SpinnakerServerException.java
Outdated
Show resolved
Hide resolved
...t/src/main/java/com/netflix/spinnaker/kork/retrofit/exceptions/SpinnakerServerException.java
Outdated
Show resolved
Hide resolved
e497f0b
to
f610a6b
Compare
… response error payload as Map
…pinnakerServerException
…o support retrofit2 changes
f610a6b
to
da6cc53
Compare
...t/src/main/java/com/netflix/spinnaker/kork/retrofit/exceptions/SpinnakerServerException.java
Show resolved
Hide resolved
91cb58a
to
9b8d7e0
Compare
...t/src/main/java/com/netflix/spinnaker/kork/retrofit/exceptions/SpinnakerServerException.java
Outdated
Show resolved
Hide resolved
...t/src/main/java/com/netflix/spinnaker/kork/retrofit/exceptions/SpinnakerServerException.java
Outdated
Show resolved
Hide resolved
...t/src/main/java/com/netflix/spinnaker/kork/retrofit/exceptions/SpinnakerServerException.java
Outdated
Show resolved
Hide resolved
…tead of multiple calls
9b8d7e0
to
554f290
Compare
@@ -69,8 +69,8 @@ public Response getResponse() { | |||
* @throws RuntimeException wrapping the underlying IOException if unable to convert the body to | |||
* the specified {@code type}. | |||
*/ | |||
public <T> T getErrorBodyAs(Class<T> type) { | |||
if (response == null || response.errorBody() == null) { |
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 still need the null check, yes? Do we have a test that covers this?
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.
ya, SpinnakerRetrofit2ErrorHandleTest covers that scenario
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.
Can you link to the specific test please? I don't see anything in SpinnakerRetrofit2ErrorHandleTest that calls getErrorBodyAs. It's hard to tell which test constructs a SpinnakerServerException with a RetrofitException built with a successful Response (i.e. one where response.errorBody is null here).
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.
I suspect this is easier to test without all the rest endpoint stuff, like with a new RetrofitExceptionTest class.
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.
Added test cases to RetrofitException and tried for Response.errorBody as null, but it is impossible to set response without error body, testcases helps to show those scenarios if we create response success response with otherthan success code it will throw IllegalArgumentException...
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.
5f047fe shows that it is possible.
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.
Perhaps a better solution is to make the constructor private? And then assert that Response.errorBody() is not null in the constructor?
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.
Done in f0d5abe
@@ -69,10 +69,11 @@ public Response getResponse() { | |||
* @throws RuntimeException wrapping the underlying IOException if unable to convert the body to | |||
* the specified {@code type}. | |||
*/ | |||
public <T> T getErrorBodyAs(Class<T> type) { |
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.
This is a comment about an earlier line in this file, not modified (yet) in this PR. As I stare at this code some more, I noticed that Response is a generic type...So, instead of
private final Response response;
above, it's more correct to have
private final Response<T> response;
and then add T as a type parameter to RetrofitException and the other places Response is used.
We'd need a different letter here if we use T for that. Perhaps let's leave this for a followup PR.
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.
Or maybe Response<?>
is more appropriate?...
so construction only happens via the RetrofitException's factory methods
…on.getResponseBody
7fc44db
to
0018064
Compare
SpinnakerServerException serverException = new SpinnakerServerException(retrofitException); | ||
|
||
Map<String, Object> responseBody = serverException.getResponseBody(); | ||
assertEquals(responseBody, null); |
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.
assertEquals(responseBody, null); | |
assertNull(responseBody); |
SpinnakerServerException serverException = new SpinnakerServerException(retrofitException); | ||
|
||
Map<String, Object> responseBody = serverException.getResponseBody(); | ||
assertEquals(responseBody, null); |
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.
assertEquals(responseBody, null); | |
assertNull(responseBody); |
"{\"name\"=\"test\"}")), | ||
retrofit2Service); | ||
|
||
assertThrows(RuntimeException.class, () -> retrofitException.getBodyAs(HashMap.class)); |
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.
I could use some comments here, or perhaps a more descriptive method name. Why does this test expect a RuntimeException?
.addConverterFactory(JacksonConverterFactory.create()) | ||
.build(); | ||
|
||
RetrofitException retrofitException = |
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.
I'd like to see some more test coverage here, and potentially in RetrofitExceptionTest as well -- of http responses with non-200 errors codes that name non-json response bodies, as well as a json list.
…on and RetrofitException
import retrofit2.Converter; | ||
import retrofit2.Retrofit; | ||
|
||
public class ByteArrayConverterFactoryTestUtil extends Converter.Factory { |
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.
javadoc please
@@ -78,6 +89,8 @@ public void testHttpConversionFail() { | |||
"{\"name\"=\"test\"}")), | |||
retrofit2Service); | |||
|
|||
// Gives MalformedJsonException or JsonEOFException or IllegalStateException, or | |||
// some other exception for json parsing, which is a type of RuntimeException. |
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.
I still don't understand why to expect these exceptions. Isn't the response body valid json?
ResponseBody errorBody = response.errorBody(); | ||
assertNull(errorBody); | ||
assertThrows( | ||
NullPointerException.class, () -> RetrofitException.httpError(response, retrofit2Service)); |
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.
Could use some comments here explaining why to expect a NullPointerException.
String body = response.body(); | ||
assertNotNull(body); | ||
|
||
// Assert the response body |
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.
It's clear from the code that this is what's happening. What's not clear is why.
} | ||
|
||
@Test | ||
public void test2xxSuccessResponseWithWildcard() { |
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.
What additional coverage does this test give beyond test2xxSuccessResponseWithType?
this.testApiServiceForJsonResponse(expectedContentBodyMap, jsonContentString, null); | ||
} | ||
|
||
private void testApiServiceForJsonResponse( |
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.
javadoc please
assertNotNull(jsonContentString); | ||
|
||
String invalidContentJsonBodyString = (String) jsonContentString; | ||
// validate contents cannot be null, otherwise it gives NullPointerException |
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.
remove duplicate comment?
Response<?> actualResponse = Response.error(HttpStatus.UNAUTHORIZED.value(), responseBody); | ||
assertNotNull(actualResponse); | ||
|
||
// assert response body is null |
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.
I'm not sure we need to worry about actualResponse.body() since RetrofitException doesn't use it.
// Use default as GsonConverterFactory if not specified, for json list with lenient option to | ||
// allow invalid json strings also | ||
if (converterFactoryList == null || converterFactoryList.size() == 0) { | ||
com.google.gson.Gson gson = new GsonBuilder().setLenient().create(); |
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.
Create this once and store in a variable outside this function?
Retrofit.Builder customRetrofitBuilder = | ||
new Retrofit.Builder().baseUrl("http://localhost:8987/"); | ||
|
||
// Use default as GsonConverterFactory if not specified, for json list with lenient option to |
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.
// Use default as GsonConverterFactory if not specified, for json list with lenient option to | |
// Use GsonConverterFactory if not specified, for json list with lenient option to |
Please also explain the choice of GsonConverterFactory (it may be arbitrary). Is it the only one with a lenient option?
for (Object key : expectedContentBodyMap.keySet()) { | ||
assertEquals(expectedContentBodyMap.get(key), actualResponseBodyContentMap.get(key)); | ||
} | ||
} catch (RuntimeException exception) { |
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.
Not sure what this catch-and-rethrow adds.
assertEquals(type, "UNKOWN_IOEXCEPTION_WHILE_SERVER_COMMUNICATION"); | ||
|
||
Map<String, String> responseBodyMap = retrofitException.getBodyAs(HashMap.class); | ||
assertNull(responseBodyMap); |
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.
Why expect null here?
MediaType.parse("application/json" + "; charset=utf-8"), "{\"name\":\"test\"}"); | ||
Response<?> actualResponse = Response.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), body); | ||
assertNotNull(actualResponse); | ||
networkException.initCause(new retrofit2.HttpException(actualResponse)); |
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.
Why is it important to do this?
} | ||
|
||
@Test | ||
public void testOrderOfConverterFactory() throws JsonProcessingException { |
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.
Not sure we need to test retrofit2 internals. What would we do if this test starts failing?
String cause = exception.getCause().getMessage(); | ||
assertNotNull(cause); | ||
assertNull(exception.getRetryable()); | ||
assertTrue(cause.contains("timeout")); |
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.
What is this assertion telling us?
// create a simple invalid json string | ||
// "{\"name\":\"te";// incomplete json gives com.fasterxml.jackson.core.io.JsonEOFException | ||
String responseBodyString = | ||
"{\"name\"=\"test\"}"; // com.fasterxml.jackson.core.JsonParseException |
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.
Doesn't match the comments.
} | ||
|
||
@Test | ||
public void test200ResponseWithInvalidJson() throws Exception { |
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.
Please also add a test of non-2xx (e.g. 500) response with invalid json.
…on and RetrofitException
add the getErrorBodyAs() method to SpinnakerServerException which returns the error response body as Map to handle the exception.