| # 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 successfully 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 successfully 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 |