|
78 | 78 | stub.unknown_method(request) |
79 | 79 | end.to raise_exception(NoMethodError) |
80 | 80 | end |
| 81 | + |
| 82 | + with "error handling" do |
| 83 | + let(:error_service_name) {"test.ErrorService"} |
| 84 | + let(:error_interface_class) do |
| 85 | + Class.new(Protocol::GRPC::Interface) do |
| 86 | + rpc :ReturnError, request_class: Protocol::GRPC::Fixtures::TestMessage, |
| 87 | + response_class: Protocol::GRPC::Fixtures::TestMessage, streaming: :unary |
| 88 | + end |
| 89 | + end |
| 90 | + let(:error_service) do |
| 91 | + Class.new(Async::GRPC::Service) do |
| 92 | + define_method(:return_error) do |input, output, call| |
| 93 | + request = input.read |
| 94 | + |
| 95 | + # Set error status and message based on request value |
| 96 | + case request.value |
| 97 | + when "internal" |
| 98 | + status = Protocol::GRPC::Status::INTERNAL |
| 99 | + message = "Internal server error" |
| 100 | + when "not_found" |
| 101 | + status = Protocol::GRPC::Status::NOT_FOUND |
| 102 | + message = "Resource not found" |
| 103 | + when "with_backtrace" |
| 104 | + status = Protocol::GRPC::Status::INTERNAL |
| 105 | + message = "Error with backtrace" |
| 106 | + # Add backtrace to metadata (comma-separated string, Split header will parse into array) |
| 107 | + call.response.headers["backtrace"] = "/path/to/file.rb:10:in `method', /path/to/file.rb:5:in `block'" |
| 108 | + when "with_metadata" |
| 109 | + status = Protocol::GRPC::Status::INVALID_ARGUMENT |
| 110 | + message = "Invalid argument" |
| 111 | + # Add custom metadata |
| 112 | + call.response.headers["custom-key"] = "custom-value" |
| 113 | + else |
| 114 | + status = Protocol::GRPC::Status::UNKNOWN |
| 115 | + message = "Unknown error" |
| 116 | + end |
| 117 | + |
| 118 | + Protocol::GRPC::Metadata.add_status!(call.response.headers, status: status, message: message) |
| 119 | + end |
| 120 | + end.new(error_interface_class, error_service_name) |
| 121 | + end |
| 122 | + let(:app) {Async::GRPC::DispatcherMiddleware.new(services: { error_service_name => error_service })} |
| 123 | + |
| 124 | + it "raises Protocol::GRPC::Error with correct status code" do |
| 125 | + grpc_client = Async::GRPC::Client.new(client) |
| 126 | + stub = grpc_client.stub(error_interface_class, error_service_name) |
| 127 | + |
| 128 | + request = Protocol::GRPC::Fixtures::TestMessage.new(value: "internal") |
| 129 | + |
| 130 | + begin |
| 131 | + stub.return_error(request) |
| 132 | + expect(false).to be == true # Should not reach here |
| 133 | + rescue Protocol::GRPC::Internal => error |
| 134 | + expect(error.status_code).to be == Protocol::GRPC::Status::INTERNAL |
| 135 | + # Message comes from RemoteError (cause), not the Protocol::GRPC::Error itself |
| 136 | + expect(error.cause.message).to be == "Internal server error" |
| 137 | + end |
| 138 | + end |
| 139 | + |
| 140 | + it "raises correct error class for status code" do |
| 141 | + grpc_client = Async::GRPC::Client.new(client) |
| 142 | + stub = grpc_client.stub(error_interface_class, error_service_name) |
| 143 | + |
| 144 | + request = Protocol::GRPC::Fixtures::TestMessage.new(value: "not_found") |
| 145 | + |
| 146 | + begin |
| 147 | + stub.return_error(request) |
| 148 | + expect(false).to be == true # Should not reach here |
| 149 | + rescue Protocol::GRPC::NotFound => error |
| 150 | + expect(error.status_code).to be == Protocol::GRPC::Status::NOT_FOUND |
| 151 | + expect(error.cause.message).to be == "Resource not found" |
| 152 | + end |
| 153 | + end |
| 154 | + |
| 155 | + it "extracts and sets backtrace from metadata on RemoteError" do |
| 156 | + grpc_client = Async::GRPC::Client.new(client) |
| 157 | + stub = grpc_client.stub(error_interface_class, error_service_name) |
| 158 | + |
| 159 | + request = Protocol::GRPC::Fixtures::TestMessage.new(value: "with_backtrace") |
| 160 | + |
| 161 | + begin |
| 162 | + stub.return_error(request) |
| 163 | + expect(false).to be == true # Should not reach here |
| 164 | + rescue Protocol::GRPC::Internal => error |
| 165 | + expect(error.cause).to be_a(Async::GRPC::RemoteError) |
| 166 | + expect(error.cause.message).to be == "Error with backtrace" |
| 167 | + # Backtrace comes as array from Split header format |
| 168 | + backtrace = error.cause.backtrace |
| 169 | + expect(backtrace).to be_a(Array) |
| 170 | + # Split header splits comma-separated string into array |
| 171 | + expect(backtrace.length).to be >= 1 |
| 172 | + expect(backtrace.any?{|line| line.include?("file.rb:10")}).to be == true |
| 173 | + expect(backtrace.any?{|line| line.include?("file.rb:5")}).to be == true |
| 174 | + end |
| 175 | + end |
| 176 | + |
| 177 | + it "preserves metadata in error" do |
| 178 | + grpc_client = Async::GRPC::Client.new(client) |
| 179 | + stub = grpc_client.stub(error_interface_class, error_service_name) |
| 180 | + |
| 181 | + request = Protocol::GRPC::Fixtures::TestMessage.new(value: "with_metadata") |
| 182 | + |
| 183 | + begin |
| 184 | + stub.return_error(request) |
| 185 | + expect(false).to be == true # Should not reach here |
| 186 | + rescue Protocol::GRPC::InvalidArgument => error |
| 187 | + expect(error.metadata.key?("custom-key")).to be == true |
| 188 | + expect(error.metadata["custom-key"]).to be == "custom-value" |
| 189 | + end |
| 190 | + end |
| 191 | + |
| 192 | + it "sets RemoteError as cause of Protocol::GRPC::Error" do |
| 193 | + grpc_client = Async::GRPC::Client.new(client) |
| 194 | + stub = grpc_client.stub(error_interface_class, error_service_name) |
| 195 | + |
| 196 | + request = Protocol::GRPC::Fixtures::TestMessage.new(value: "internal") |
| 197 | + |
| 198 | + begin |
| 199 | + stub.return_error(request) |
| 200 | + expect(false).to be == true # Should not reach here |
| 201 | + rescue Protocol::GRPC::Internal => error |
| 202 | + expect(error.cause).to be_a(Async::GRPC::RemoteError) |
| 203 | + expect(error.cause.message).to be == "Internal server error" |
| 204 | + end |
| 205 | + end |
| 206 | + end |
81 | 207 | end |
82 | 208 |
|
83 | 209 | describe Async::GRPC::Client do |
|
0 commit comments