Skip to content

Commit dbe16db

Browse files
committed
test(showcase): address code review feedback using custom TypeRegistry manual JSON parsing workaround
1 parent 3d2c286 commit dbe16db

1 file changed

Lines changed: 70 additions & 17 deletions

File tree

java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITErrorDetails.java

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@
1919
import static com.google.common.truth.Truth.assertThat;
2020
import static org.junit.jupiter.api.Assertions.assertThrows;
2121

22+
import com.google.api.client.http.HttpResponseException;
2223
import com.google.api.gax.rpc.ApiException;
2324
import com.google.api.gax.rpc.ErrorDetails;
2425
import com.google.api.gax.rpc.StatusCode;
26+
import com.google.gson.JsonObject;
27+
import com.google.gson.JsonParser;
28+
import com.google.protobuf.TypeRegistry;
29+
import com.google.protobuf.util.JsonFormat;
2530
import com.google.rpc.BadRequest;
2631
import com.google.rpc.DebugInfo;
2732
import com.google.rpc.ErrorInfo;
@@ -32,6 +37,7 @@
3237
import com.google.rpc.RequestInfo;
3338
import com.google.rpc.ResourceInfo;
3439
import com.google.rpc.RetryInfo;
40+
import com.google.rpc.Status;
3541
import com.google.showcase.v1beta1.EchoClient;
3642
import com.google.showcase.v1beta1.EchoResponse;
3743
import com.google.showcase.v1beta1.FailEchoWithDetailsRequest;
@@ -42,6 +48,10 @@
4248
import org.junit.jupiter.api.BeforeAll;
4349
import org.junit.jupiter.api.Test;
4450

51+
/**
52+
* Integration tests for verifying that client libraries correctly propagate and deserialize
53+
* standard and custom error details from {@link ApiException} over gRPC and HTTP/JSON transports.
54+
*/
4555
class ITErrorDetails {
4656

4757
private static EchoClient grpcClient;
@@ -63,12 +73,11 @@ static void destroyClients() throws InterruptedException {
6373
TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
6474
}
6575

66-
private void verifyErrorDetailsGrpc(ApiException exception) {
67-
assertThat(exception.getStatusCode().getCode()).isEqualTo(StatusCode.Code.ABORTED);
68-
69-
ErrorDetails errorDetails = exception.getErrorDetails();
70-
assertThat(errorDetails).isNotNull();
71-
76+
/**
77+
* Helper method to verify the content of error details. Transport-neutral validation of standard
78+
* and custom error packets.
79+
*/
80+
private void verifyErrorDetailsContent(ErrorDetails errorDetails, String expectedPoem) {
7281
// Verify standard error details are present and populated
7382
// We are assuming that the mock server's hardcoded return values will stay the same
7483
// https://github.com/googleapis/gapic-showcase/blob/b6c247f153369044d599969f6929ecdeb066c4c6/server/services/echo_service.go
@@ -118,44 +127,50 @@ private void verifyErrorDetailsGrpc(ApiException exception) {
118127
assertThat(localizedMessage.getMessage())
119128
.isEqualTo("This LocalizedMessage should be treated specially");
120129

121-
// Verify custom PoetryError can be unpacked
130+
// Verify custom PoetryError can be unpacked and matches expected poem
122131
PoetryError poetryError = errorDetails.getMessage(PoetryError.class);
123132
assertThat(poetryError).isNotNull();
124-
assertThat(poetryError.getPoem()).isEqualTo("roses are red");
133+
assertThat(poetryError.getPoem()).isEqualTo(expectedPoem);
125134

126135
// Verify mismatched type returns null safely (mismatch unpacking)
127136
EchoResponse mismatchedDetail = errorDetails.getMessage(EchoResponse.class);
128137
assertThat(mismatchedDetail).isNull();
129138
}
130139

140+
// Verifies error details are correctly propagated and unpacked over standard gRPC protocol
131141
@Test
132142
void testGrpc_failEchoWithDetails() {
133143
FailEchoWithDetailsRequest request = FailEchoWithDetailsRequest.newBuilder().build();
134144
ApiException exception =
135145
assertThrows(ApiException.class, () -> grpcClient.failEchoWithDetails(request));
136-
verifyErrorDetailsGrpc(exception);
146+
147+
assertThat(exception.getStatusCode().getCode()).isEqualTo(StatusCode.Code.ABORTED);
148+
assertThat(exception.getErrorDetails()).isNotNull();
149+
150+
// Reuse Transport-neutral Validation
151+
verifyErrorDetailsContent(exception.getErrorDetails(), "roses are red");
137152
}
138153

154+
// Verifies custom Error Details messages reflect user-defined inputs via gRPC
139155
@Test
140156
void testGrpc_failEchoWithDetails_customMessage() {
141157
String customMessage = "this is a custom message to echo back";
142158
FailEchoWithDetailsRequest request =
143159
FailEchoWithDetailsRequest.newBuilder().setMessage(customMessage).build();
144160
ApiException exception =
145161
assertThrows(ApiException.class, () -> grpcClient.failEchoWithDetails(request));
146-
assertThat(exception.getStatusCode().getCode()).isEqualTo(StatusCode.Code.ABORTED);
147162

148-
ErrorDetails errorDetails = exception.getErrorDetails();
149-
assertThat(errorDetails).isNotNull();
163+
assertThat(exception.getStatusCode().getCode()).isEqualTo(StatusCode.Code.ABORTED);
164+
assertThat(exception.getErrorDetails()).isNotNull();
150165

151-
// Verify custom PoetryError can be unpacked and contains the custom message
152-
PoetryError poetryError = errorDetails.getMessage(PoetryError.class);
153-
assertThat(poetryError).isNotNull();
154-
assertThat(poetryError.getPoem()).isEqualTo(customMessage);
166+
// Reuse Transport-neutral Validation
167+
verifyErrorDetailsContent(exception.getErrorDetails(), customMessage);
155168
}
156169

170+
// Verifies error details are accessible in raw form over REST/HTTP and validates manually-parsed
171+
// decompression
157172
@Test
158-
void testHttpJson_failEchoWithDetails() {
173+
void testHttpJson_failEchoWithDetails() throws Exception {
159174
FailEchoWithDetailsRequest request = FailEchoWithDetailsRequest.newBuilder().build();
160175
ApiException exception =
161176
assertThrows(ApiException.class, () -> httpjsonClient.failEchoWithDetails(request));
@@ -168,5 +183,43 @@ void testHttpJson_failEchoWithDetails() {
168183
if (errorDetails != null) {
169184
assertThat(errorDetails.getErrorInfo()).isNull();
170185
}
186+
187+
// Workaround REST limitation: Parse the raw HTTP JSON error response manually using a custom
188+
// TypeRegistry that registers standard types plus the showcase-specific PoetryError type.
189+
assertThat(exception.getCause()).isInstanceOf(HttpResponseException.class);
190+
HttpResponseException httpException = (HttpResponseException) exception.getCause();
191+
String errorJson = httpException.getContent();
192+
assertThat(errorJson).isNotNull();
193+
194+
TypeRegistry typeRegistry =
195+
TypeRegistry.newBuilder()
196+
.add(ErrorInfo.getDescriptor())
197+
.add(RetryInfo.getDescriptor())
198+
.add(DebugInfo.getDescriptor())
199+
.add(QuotaFailure.getDescriptor())
200+
.add(PreconditionFailure.getDescriptor())
201+
.add(BadRequest.getDescriptor())
202+
.add(RequestInfo.getDescriptor())
203+
.add(ResourceInfo.getDescriptor())
204+
.add(Help.getDescriptor())
205+
.add(LocalizedMessage.getDescriptor())
206+
.add(PoetryError.getDescriptor())
207+
.build();
208+
JsonFormat.Parser jsonParser =
209+
JsonFormat.parser().ignoringUnknownFields().usingTypeRegistry(typeRegistry);
210+
211+
// Parse the AIP-193 "error" JSON object into a status builder
212+
JsonObject root = JsonParser.parseString(errorJson).getAsJsonObject();
213+
JsonObject errorObj = root.getAsJsonObject("error");
214+
Status.Builder statusBuilder = Status.newBuilder();
215+
jsonParser.merge(errorObj.toString(), statusBuilder);
216+
Status status = statusBuilder.build();
217+
218+
// Verify we can successfully unpack the details from our custom status instance
219+
ErrorDetails parsedDetails =
220+
ErrorDetails.builder().setRawErrorMessages(status.getDetailsList()).build();
221+
222+
// Reuse Transport-neutral Validation!
223+
verifyErrorDetailsContent(parsedDetails, "roses are red");
171224
}
172225
}

0 commit comments

Comments
 (0)