google_rpc_status_utils_spec.rb 11 KB

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