1 /* 2 * Copyright 2022 Google LLC 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google LLC nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 package com.google.api.gax.grpc; 31 32 import static com.google.api.gax.grpc.GrpcApiExceptionFactory.ERROR_DETAIL_KEY; 33 34 import com.google.api.gax.rpc.ApiException; 35 import com.google.api.gax.rpc.ErrorDetails; 36 import com.google.common.collect.ImmutableList; 37 import com.google.common.truth.Truth; 38 import com.google.protobuf.Any; 39 import com.google.protobuf.Duration; 40 import com.google.rpc.ErrorInfo; 41 import com.google.rpc.RetryInfo; 42 import com.google.rpc.Status; 43 import io.grpc.Metadata; 44 import io.grpc.StatusException; 45 import io.grpc.StatusRuntimeException; 46 import java.util.Collections; 47 import org.junit.Before; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.junit.runners.JUnit4; 51 52 @RunWith(JUnit4.class) 53 public class GrpcApiExceptionFactoryTest { 54 55 private static final ErrorInfo ERROR_INFO = 56 ErrorInfo.newBuilder() 57 .setDomain("googleapis.com") 58 .setReason("SERVICE_DISABLED") 59 .putAllMetadata(Collections.emptyMap()) 60 .build(); 61 62 private static final RetryInfo RETRY_INFO = 63 RetryInfo.newBuilder().setRetryDelay(Duration.newBuilder().setSeconds(213).build()).build(); 64 65 private static final ImmutableList<Any> RAW_ERROR_MESSAGES = 66 ImmutableList.of(Any.pack(ERROR_INFO), Any.pack(RETRY_INFO)); 67 68 private static final ErrorDetails ERROR_DETAILS = 69 ErrorDetails.builder().setRawErrorMessages(RAW_ERROR_MESSAGES).build(); 70 71 private static final io.grpc.Status GRPC_STATUS = io.grpc.Status.CANCELLED; 72 73 private GrpcApiExceptionFactory factory; 74 75 @Before setUp()76 public void setUp() throws Exception { 77 factory = new GrpcApiExceptionFactory(Collections.emptySet()); 78 } 79 80 @Test create_shouldCreateApiExceptionWithErrorDetailsForStatusException()81 public void create_shouldCreateApiExceptionWithErrorDetailsForStatusException() { 82 Metadata trailers = new Metadata(); 83 Status status = Status.newBuilder().addAllDetails(RAW_ERROR_MESSAGES).build(); 84 trailers.put( 85 Metadata.Key.of(ERROR_DETAIL_KEY, Metadata.BINARY_BYTE_MARSHALLER), status.toByteArray()); 86 StatusException statusException = new StatusException(GRPC_STATUS, trailers); 87 88 ApiException actual = factory.create(statusException); 89 90 Truth.assertThat(actual.getErrorDetails()).isEqualTo(ERROR_DETAILS); 91 } 92 93 @Test create_shouldCreateApiExceptionWithErrorDetailsForStatusRuntimeException()94 public void create_shouldCreateApiExceptionWithErrorDetailsForStatusRuntimeException() { 95 Metadata trailers = new Metadata(); 96 Status status = Status.newBuilder().addAllDetails(RAW_ERROR_MESSAGES).build(); 97 trailers.put( 98 Metadata.Key.of(ERROR_DETAIL_KEY, Metadata.BINARY_BYTE_MARSHALLER), status.toByteArray()); 99 StatusRuntimeException statusException = new StatusRuntimeException(GRPC_STATUS, trailers); 100 101 ApiException actual = factory.create(statusException); 102 103 Truth.assertThat(actual.getErrorDetails()).isEqualTo(ERROR_DETAILS); 104 } 105 106 @Test create_shouldCreateApiExceptionWithNoErrorDetailsIfMetadataIsNull()107 public void create_shouldCreateApiExceptionWithNoErrorDetailsIfMetadataIsNull() { 108 StatusRuntimeException statusException = new StatusRuntimeException(GRPC_STATUS, null); 109 110 ApiException actual = factory.create(statusException); 111 112 Truth.assertThat(actual.getErrorDetails()).isNull(); 113 } 114 115 @Test create_shouldCreateApiExceptionWithNoErrorDetailsIfMetadataDoesNotHaveErrorDetails()116 public void create_shouldCreateApiExceptionWithNoErrorDetailsIfMetadataDoesNotHaveErrorDetails() { 117 StatusRuntimeException statusException = 118 new StatusRuntimeException(GRPC_STATUS, new Metadata()); 119 120 ApiException actual = factory.create(statusException); 121 122 Truth.assertThat(actual.getErrorDetails()).isNull(); 123 } 124 125 @Test create_shouldCreateApiExceptionWithNoErrorDetailsIfStatusIsMalformed()126 public void create_shouldCreateApiExceptionWithNoErrorDetailsIfStatusIsMalformed() { 127 Metadata trailers = new Metadata(); 128 Status status = Status.newBuilder().addDetails(Any.pack(ERROR_INFO)).build(); 129 byte[] bytes = status.toByteArray(); 130 // manually manipulate status bytes array 131 bytes[0] = 123; 132 trailers.put(Metadata.Key.of(ERROR_DETAIL_KEY, Metadata.BINARY_BYTE_MARSHALLER), bytes); 133 StatusRuntimeException statusException = new StatusRuntimeException(GRPC_STATUS, trailers); 134 135 ApiException actual = factory.create(statusException); 136 137 Truth.assertThat(actual.getErrorDetails()).isNull(); 138 } 139 } 140