1# Copyright 2017 gRPC authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15require 'spec_helper' 16require_relative '../lib/grpc/google_rpc_status_utils' 17require_relative '../pb/src/proto/grpc/testing/messages_pb' 18require_relative '../pb/src/proto/grpc/testing/messages_pb' 19require 'google/protobuf/well_known_types' 20 21include GRPC::Core 22include GRPC::Spec::Helpers 23 24describe 'conversion from a status struct to a google protobuf status' do 25 it 'fails if the input is not a status struct' do 26 begin 27 GRPC::GoogleRpcStatusUtils.extract_google_rpc_status('string') 28 rescue => e 29 exception = e 30 end 31 expect(exception.is_a?(ArgumentError)).to be true 32 expect(exception.message.include?('bad type')).to be true 33 end 34 35 it 'returns nil if the header key is missing' do 36 status = Struct::Status.new(1, 'details', key: 'val') 37 expect(status.metadata.nil?).to be false 38 expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( 39 status)).to be(nil) 40 end 41 42 it 'fails with some error if the header key fails to deserialize' do 43 status = Struct::Status.new(1, 'details', 44 'grpc-status-details-bin' => 'string_val') 45 expect do 46 GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) 47 end.to raise_error(StandardError) 48 end 49 50 it 'silently ignores erroneous mismatch between messages in '\ 51 'status struct and protobuf status' do 52 proto = Google::Rpc::Status.new(code: 1, message: 'proto message') 53 encoded_proto = Google::Rpc::Status.encode(proto) 54 status = Struct::Status.new(1, 'struct message', 55 'grpc-status-details-bin' => encoded_proto) 56 rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) 57 expect(rpc_status).to eq(proto) 58 end 59 60 it 'silently ignores erroneous mismatch between codes in status struct '\ 61 'and protobuf status' do 62 proto = Google::Rpc::Status.new(code: 1, message: 'matching message') 63 encoded_proto = Google::Rpc::Status.encode(proto) 64 status = Struct::Status.new(2, 'matching message', 65 'grpc-status-details-bin' => encoded_proto) 66 rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) 67 expect(rpc_status).to eq(proto) 68 end 69 70 it 'can succesfully convert a status struct into a google protobuf status '\ 71 'when there are no rpcstatus details' do 72 proto = Google::Rpc::Status.new(code: 1, message: 'matching message') 73 encoded_proto = Google::Rpc::Status.encode(proto) 74 status = Struct::Status.new(1, 'matching message', 75 'grpc-status-details-bin' => encoded_proto) 76 out = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) 77 expect(out.code).to eq(1) 78 expect(out.message).to eq('matching message') 79 expect(out.details).to eq([]) 80 end 81 82 it 'can succesfully convert a status struct into a google protobuf '\ 83 'status when there are multiple rpcstatus details' do 84 simple_request_any = Google::Protobuf::Any.new 85 simple_request = Grpc::Testing::SimpleRequest.new( 86 payload: Grpc::Testing::Payload.new(body: 'request')) 87 simple_request_any.pack(simple_request) 88 simple_response_any = Google::Protobuf::Any.new 89 simple_response = Grpc::Testing::SimpleResponse.new( 90 payload: Grpc::Testing::Payload.new(body: 'response')) 91 simple_response_any.pack(simple_response) 92 payload_any = Google::Protobuf::Any.new 93 payload = Grpc::Testing::Payload.new(body: 'payload') 94 payload_any.pack(payload) 95 proto = Google::Rpc::Status.new(code: 1, 96 message: 'matching message', 97 details: [ 98 simple_request_any, 99 simple_response_any, 100 payload_any 101 ]) 102 encoded_proto = Google::Rpc::Status.encode(proto) 103 status = Struct::Status.new(1, 'matching message', 104 'grpc-status-details-bin' => encoded_proto) 105 out = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) 106 expect(out.code).to eq(1) 107 expect(out.message).to eq('matching message') 108 expect(out.details[0].unpack( 109 Grpc::Testing::SimpleRequest)).to eq(simple_request) 110 expect(out.details[1].unpack( 111 Grpc::Testing::SimpleResponse)).to eq(simple_response) 112 expect(out.details[2].unpack( 113 Grpc::Testing::Payload)).to eq(payload) 114 end 115end 116 117# A test service that fills in the "reserved" grpc-status-details-bin trailer, 118# for client-side testing of GoogleRpcStatus protobuf extraction from trailers. 119class GoogleRpcStatusTestService 120 include GRPC::GenericService 121 rpc :an_rpc, EchoMsg, EchoMsg 122 123 def initialize(encoded_rpc_status) 124 @encoded_rpc_status = encoded_rpc_status 125 end 126 127 def an_rpc(_, _) 128 # TODO: create a server-side utility API for sending a google rpc status. 129 # Applications are not expected to set the grpc-status-details-bin 130 # ("grpc"-fixed and reserved for library use) manually. 131 # Doing so here is only for testing of the client-side api for extracting 132 # a google rpc status, which is useful 133 # when the interacting with a server that does fill in this trailer. 134 fail GRPC::Unknown.new('test message', 135 'grpc-status-details-bin' => @encoded_rpc_status) 136 end 137end 138 139GoogleRpcStatusTestStub = GoogleRpcStatusTestService.rpc_stub_class 140 141describe 'receving a google rpc status from a remote endpoint' do 142 def start_server(encoded_rpc_status) 143 @srv = new_rpc_server_for_testing(pool_size: 1) 144 @server_port = @srv.add_http2_port('localhost:0', 145 :this_port_is_insecure) 146 @srv.handle(GoogleRpcStatusTestService.new(encoded_rpc_status)) 147 @server_thd = Thread.new { @srv.run } 148 @srv.wait_till_running 149 end 150 151 def stop_server 152 expect(@srv.stopped?).to be(false) 153 @srv.stop 154 @server_thd.join 155 expect(@srv.stopped?).to be(true) 156 end 157 158 before(:each) do 159 simple_request_any = Google::Protobuf::Any.new 160 simple_request = Grpc::Testing::SimpleRequest.new( 161 payload: Grpc::Testing::Payload.new(body: 'request')) 162 simple_request_any.pack(simple_request) 163 simple_response_any = Google::Protobuf::Any.new 164 simple_response = Grpc::Testing::SimpleResponse.new( 165 payload: Grpc::Testing::Payload.new(body: 'response')) 166 simple_response_any.pack(simple_response) 167 payload_any = Google::Protobuf::Any.new 168 payload = Grpc::Testing::Payload.new(body: 'payload') 169 payload_any.pack(payload) 170 @expected_proto = Google::Rpc::Status.new( 171 code: StatusCodes::UNKNOWN, 172 message: 'test message', 173 details: [simple_request_any, simple_response_any, payload_any]) 174 start_server(Google::Rpc::Status.encode(@expected_proto)) 175 end 176 177 after(:each) do 178 stop_server 179 end 180 181 it 'should receive be able to extract a google rpc status from the '\ 182 'status struct taken from a BadStatus exception' do 183 stub = GoogleRpcStatusTestStub.new("localhost:#{@server_port}", 184 :this_channel_is_insecure) 185 begin 186 stub.an_rpc(EchoMsg.new) 187 rescue GRPC::BadStatus => e 188 rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( 189 e.to_status) 190 end 191 expect(rpc_status).to eq(@expected_proto) 192 end 193 194 it 'should receive be able to extract a google rpc status from the '\ 195 'status struct taken from the op view of a call' do 196 stub = GoogleRpcStatusTestStub.new("localhost:#{@server_port}", 197 :this_channel_is_insecure) 198 op = stub.an_rpc(EchoMsg.new, return_op: true) 199 begin 200 op.execute 201 rescue GRPC::BadStatus => e 202 status_from_exception = e.to_status 203 end 204 rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( 205 op.status) 206 expect(rpc_status).to eq(@expected_proto) 207 # "to_status" on the bad status should give the same result 208 # as "status" on the "op view". 209 expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( 210 status_from_exception)).to eq(rpc_status) 211 end 212end 213 214# A test service that fails without explicitly setting the 215# grpc-status-details-bin trailer. Tests assumptions about value 216# of grpc-status-details-bin on the client side when the trailer wasn't 217# set explicitly. 218class NoStatusDetailsBinTestService 219 include GRPC::GenericService 220 rpc :an_rpc, EchoMsg, EchoMsg 221 222 def an_rpc(_, _) 223 fail GRPC::Unknown 224 end 225end 226 227NoStatusDetailsBinTestServiceStub = NoStatusDetailsBinTestService.rpc_stub_class 228 229describe 'when the endpoint doesnt send grpc-status-details-bin' do 230 def start_server 231 @srv = new_rpc_server_for_testing(pool_size: 1) 232 @server_port = @srv.add_http2_port('localhost:0', 233 :this_port_is_insecure) 234 @srv.handle(NoStatusDetailsBinTestService) 235 @server_thd = Thread.new { @srv.run } 236 @srv.wait_till_running 237 end 238 239 def stop_server 240 expect(@srv.stopped?).to be(false) 241 @srv.stop 242 @server_thd.join 243 expect(@srv.stopped?).to be(true) 244 end 245 246 before(:each) do 247 start_server 248 end 249 250 after(:each) do 251 stop_server 252 end 253 254 it 'should receive nil when we extract try to extract a google '\ 255 'rpc status from a BadStatus exception that didnt have it' do 256 stub = NoStatusDetailsBinTestServiceStub.new("localhost:#{@server_port}", 257 :this_channel_is_insecure) 258 begin 259 stub.an_rpc(EchoMsg.new) 260 rescue GRPC::Unknown => e 261 rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( 262 e.to_status) 263 end 264 expect(rpc_status).to be(nil) 265 end 266 267 it 'should receive nil when we extract try to extract a google '\ 268 'rpc status from an op views status object that didnt have it' do 269 stub = NoStatusDetailsBinTestServiceStub.new("localhost:#{@server_port}", 270 :this_channel_is_insecure) 271 op = stub.an_rpc(EchoMsg.new, return_op: true) 272 begin 273 op.execute 274 rescue GRPC::Unknown => e 275 status_from_exception = e.to_status 276 end 277 expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( 278 status_from_exception)).to be(nil) 279 expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( 280 op.status)).to be nil 281 end 282end 283