# Copyright 2017 gRPC authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'spec_helper' require_relative '../lib/grpc/google_rpc_status_utils' require_relative '../pb/src/proto/grpc/testing/messages_pb' require_relative '../pb/src/proto/grpc/testing/messages_pb' require 'google/protobuf/well_known_types' include GRPC::Core include GRPC::Spec::Helpers describe 'conversion from a status struct to a google protobuf status' do it 'fails if the input is not a status struct' do begin GRPC::GoogleRpcStatusUtils.extract_google_rpc_status('string') rescue => e exception = e end expect(exception.is_a?(ArgumentError)).to be true expect(exception.message.include?('bad type')).to be true end it 'returns nil if the header key is missing' do status = Struct::Status.new(1, 'details', key: 'val') expect(status.metadata.nil?).to be false expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( status)).to be(nil) end it 'fails with some error if the header key fails to deserialize' do status = Struct::Status.new(1, 'details', 'grpc-status-details-bin' => 'string_val') expect do GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) end.to raise_error(StandardError) end it 'silently ignores erroneous mismatch between messages in '\ 'status struct and protobuf status' do proto = Google::Rpc::Status.new(code: 1, message: 'proto message') encoded_proto = Google::Rpc::Status.encode(proto) status = Struct::Status.new(1, 'struct message', 'grpc-status-details-bin' => encoded_proto) rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) expect(rpc_status).to eq(proto) end it 'silently ignores erroneous mismatch between codes in status struct '\ 'and protobuf status' do proto = Google::Rpc::Status.new(code: 1, message: 'matching message') encoded_proto = Google::Rpc::Status.encode(proto) status = Struct::Status.new(2, 'matching message', 'grpc-status-details-bin' => encoded_proto) rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) expect(rpc_status).to eq(proto) end it 'can succesfully convert a status struct into a google protobuf status '\ 'when there are no rpcstatus details' do proto = Google::Rpc::Status.new(code: 1, message: 'matching message') encoded_proto = Google::Rpc::Status.encode(proto) status = Struct::Status.new(1, 'matching message', 'grpc-status-details-bin' => encoded_proto) out = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) expect(out.code).to eq(1) expect(out.message).to eq('matching message') expect(out.details).to eq([]) end it 'can succesfully convert a status struct into a google protobuf '\ 'status when there are multiple rpcstatus details' do simple_request_any = Google::Protobuf::Any.new simple_request = Grpc::Testing::SimpleRequest.new( payload: Grpc::Testing::Payload.new(body: 'request')) simple_request_any.pack(simple_request) simple_response_any = Google::Protobuf::Any.new simple_response = Grpc::Testing::SimpleResponse.new( payload: Grpc::Testing::Payload.new(body: 'response')) simple_response_any.pack(simple_response) payload_any = Google::Protobuf::Any.new payload = Grpc::Testing::Payload.new(body: 'payload') payload_any.pack(payload) proto = Google::Rpc::Status.new(code: 1, message: 'matching message', details: [ simple_request_any, simple_response_any, payload_any ]) encoded_proto = Google::Rpc::Status.encode(proto) status = Struct::Status.new(1, 'matching message', 'grpc-status-details-bin' => encoded_proto) out = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) expect(out.code).to eq(1) expect(out.message).to eq('matching message') expect(out.details[0].unpack( Grpc::Testing::SimpleRequest)).to eq(simple_request) expect(out.details[1].unpack( Grpc::Testing::SimpleResponse)).to eq(simple_response) expect(out.details[2].unpack( Grpc::Testing::Payload)).to eq(payload) end end # A test service that fills in the "reserved" grpc-status-details-bin trailer, # for client-side testing of GoogleRpcStatus protobuf extraction from trailers. class GoogleRpcStatusTestService include GRPC::GenericService rpc :an_rpc, EchoMsg, EchoMsg def initialize(encoded_rpc_status) @encoded_rpc_status = encoded_rpc_status end def an_rpc(_, _) # TODO: create a server-side utility API for sending a google rpc status. # Applications are not expected to set the grpc-status-details-bin # ("grpc"-fixed and reserved for library use) manually. # Doing so here is only for testing of the client-side api for extracting # a google rpc status, which is useful # when the interacting with a server that does fill in this trailer. fail GRPC::Unknown.new('test message', 'grpc-status-details-bin' => @encoded_rpc_status) end end GoogleRpcStatusTestStub = GoogleRpcStatusTestService.rpc_stub_class describe 'receving a google rpc status from a remote endpoint' do def start_server(encoded_rpc_status) @srv = new_rpc_server_for_testing(pool_size: 1) @server_port = @srv.add_http2_port('localhost:0', :this_port_is_insecure) @srv.handle(GoogleRpcStatusTestService.new(encoded_rpc_status)) @server_thd = Thread.new { @srv.run } @srv.wait_till_running end def stop_server expect(@srv.stopped?).to be(false) @srv.stop @server_thd.join expect(@srv.stopped?).to be(true) end before(:each) do simple_request_any = Google::Protobuf::Any.new simple_request = Grpc::Testing::SimpleRequest.new( payload: Grpc::Testing::Payload.new(body: 'request')) simple_request_any.pack(simple_request) simple_response_any = Google::Protobuf::Any.new simple_response = Grpc::Testing::SimpleResponse.new( payload: Grpc::Testing::Payload.new(body: 'response')) simple_response_any.pack(simple_response) payload_any = Google::Protobuf::Any.new payload = Grpc::Testing::Payload.new(body: 'payload') payload_any.pack(payload) @expected_proto = Google::Rpc::Status.new( code: StatusCodes::UNKNOWN, message: 'test message', details: [simple_request_any, simple_response_any, payload_any]) start_server(Google::Rpc::Status.encode(@expected_proto)) end after(:each) do stop_server end it 'should receive be able to extract a google rpc status from the '\ 'status struct taken from a BadStatus exception' do stub = GoogleRpcStatusTestStub.new("localhost:#{@server_port}", :this_channel_is_insecure) begin stub.an_rpc(EchoMsg.new) rescue GRPC::BadStatus => e rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( e.to_status) end expect(rpc_status).to eq(@expected_proto) end it 'should receive be able to extract a google rpc status from the '\ 'status struct taken from the op view of a call' do stub = GoogleRpcStatusTestStub.new("localhost:#{@server_port}", :this_channel_is_insecure) op = stub.an_rpc(EchoMsg.new, return_op: true) begin op.execute rescue GRPC::BadStatus => e status_from_exception = e.to_status end rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( op.status) expect(rpc_status).to eq(@expected_proto) # "to_status" on the bad status should give the same result # as "status" on the "op view". expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( status_from_exception)).to eq(rpc_status) end end # A test service that fails without explicitly setting the # grpc-status-details-bin trailer. Tests assumptions about value # of grpc-status-details-bin on the client side when the trailer wasn't # set explicitly. class NoStatusDetailsBinTestService include GRPC::GenericService rpc :an_rpc, EchoMsg, EchoMsg def an_rpc(_, _) fail GRPC::Unknown end end NoStatusDetailsBinTestServiceStub = NoStatusDetailsBinTestService.rpc_stub_class describe 'when the endpoint doesnt send grpc-status-details-bin' do def start_server @srv = new_rpc_server_for_testing(pool_size: 1) @server_port = @srv.add_http2_port('localhost:0', :this_port_is_insecure) @srv.handle(NoStatusDetailsBinTestService) @server_thd = Thread.new { @srv.run } @srv.wait_till_running end def stop_server expect(@srv.stopped?).to be(false) @srv.stop @server_thd.join expect(@srv.stopped?).to be(true) end before(:each) do start_server end after(:each) do stop_server end it 'should receive nil when we extract try to extract a google '\ 'rpc status from a BadStatus exception that didnt have it' do stub = NoStatusDetailsBinTestServiceStub.new("localhost:#{@server_port}", :this_channel_is_insecure) begin stub.an_rpc(EchoMsg.new) rescue GRPC::Unknown => e rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( e.to_status) end expect(rpc_status).to be(nil) end it 'should receive nil when we extract try to extract a google '\ 'rpc status from an op views status object that didnt have it' do stub = NoStatusDetailsBinTestServiceStub.new("localhost:#{@server_port}", :this_channel_is_insecure) op = stub.an_rpc(EchoMsg.new, return_op: true) begin op.execute rescue GRPC::Unknown => e status_from_exception = e.to_status end expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( status_from_exception)).to be(nil) expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( op.status)).to be nil end end