From 7e1af2c3e278d6ae3dac63ccafe769e236053647 Mon Sep 17 00:00:00 2001 From: Sean Edge Date: Fri, 8 Jun 2018 10:18:33 -0400 Subject: [PATCH 1/2] Do not try to access an ObjectifiedHash with #[]. The method is currently undefined and when #method_missing is invoked, only expects 1 param, it is handed 2 parameters: [] and the key. This causes a confusing ArgumentError on #method_missing. --- lib/gitlab/error.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/error.rb b/lib/gitlab/error.rb index dd420f6de..19f938351 100644 --- a/lib/gitlab/error.rb +++ b/lib/gitlab/error.rb @@ -11,6 +11,8 @@ class Parsing < Error; end # Custom error class for rescuing from HTTP response errors. class ResponseError < Error + POSSIBLE_MESSAGE_KEYS = %i(message error_description error) + def initialize(response) @response = response super(build_error_message) @@ -37,14 +39,19 @@ def response_message # @return [String] def build_error_message parsed_response = @response.parsed_response - message = parsed_response.respond_to?(:message) ? parsed_response.message : parsed_response['message'] - message = parsed_response.respond_to?(:error) ? parsed_response.error : parsed_response['error'] unless message - + message = check_error_keys(parsed_response) "Server responded with code #{@response.code}, message: " \ "#{handle_message(message)}. " \ "Request URI: #{@response.request.base_uri}#{@response.request.path}" end + # Error keys vary across the API, find the first key that the parsed_response + # object responds to and return that, otherwise return the original. + def check_error_keys(resp) + key = POSSIBLE_MESSAGE_KEYS.find { |k| resp.respond_to?(k) } + key ? resp.send(key) : resp + end + # Handle error response message in case of nested hashes def handle_message(message) case message From 0e674acd38990479f7fbef8a49d2a3c617482b00 Mon Sep 17 00:00:00 2001 From: Sean Edge Date: Wed, 20 Jun 2018 22:12:17 -0400 Subject: [PATCH 2/2] Rewrote the tests for Gitlab::Error::ResponseError. I tried to simplify the set up for additional test cases so all that would be needed is a new response scenario and a new expected message. --- spec/gitlab/error_spec.rb | 85 ++++++++++----------------------------- 1 file changed, 22 insertions(+), 63 deletions(-) diff --git a/spec/gitlab/error_spec.rb b/spec/gitlab/error_spec.rb index ec834513b..14f9ca36a 100644 --- a/spec/gitlab/error_spec.rb +++ b/spec/gitlab/error_spec.rb @@ -1,72 +1,31 @@ -require "spec_helper" -require "stringio" - -describe Gitlab::Error do - let(:request_object) { HTTParty::Request.new(Net::HTTP::Get, '/') } - let(:response_object) { Net::HTTPOK.new('1.1', 200, 'OK') } - let(:body) { StringIO.new("{foo:'bar'}") } - let(:parsed_response) { -> { body } } - - let(:response) do - HTTParty::Response.new( - request_object, - response_object, - parsed_response, - body: body - ) - end - - let(:error) { Gitlab::Error::ResponseError.new(response) } - let(:date) { Date.new(2010, 1, 15).to_s } +require 'spec_helper' +describe Gitlab::Error::ResponseError do before do - def body.message - string - end - - response_object['last-modified'] = date - response_object['content-length'] = "1024" + @request_double = double(base_uri: 'https://gitlab.com/api/v3', path: '/foo') end - describe "#handle_message" do - let(:array) { Array.new(['First message.', 'Second message.']) } - let(:obj_h) do - Gitlab::ObjectifiedHash.new( - user: ['not set'], - password: ['too short'], - embed_entity: { foo: ['bar'], sna: ['fu'] } - ) - end - - context "when passed an ObjectifiedHash" do - it "returns a joined string of error messages sorted by key" do - expect(error.send(:handle_message, obj_h)). - to eq( - "'embed_entity' (foo: bar) (sna: fu), 'password' too short, 'user' not set" - ) - end - end - - context "when passed an Array" do - it "returns a joined string of messages" do - expect(error.send(:handle_message, array)). - to eq("First message. Second message.") - end - end - - context "when passed a String" do - it "returns the String untouched" do - error_str = 'this is an error string' - - expect(error.send(:handle_message, error_str)). - to eq(error_str) - end - end + let(:expected_messages) do + [ + %r{Server responded with code \d+, message: Displayed message. Request URI: https://gitlab.com/api/v3/foo}, + %r{Server responded with code \d+, message: Displayed error_description. Request URI: https://gitlab.com/api/v3/foo}, + %r{Server responded with code \d+, message: Displayed error. Request URI: https://gitlab.com/api/v3/foo}, + %r{Server responded with code \d+, message: 'embed_entity' \(foo: bar\) \(sna: fu\), 'password' too short. Request URI: https://gitlab.com/api/v3/foo}, + %r{Server responded with code \d+, message: First message. Second message.. Request URI: https://gitlab.com/api/v3/foo}, + ] end - describe "#response_message" do - it "returns the message of the parsed_response" do - expect(error.response_message).to eq(body.string) + # Set up some response scenarios to test. + [ + { code: 401, parsed_response: Gitlab::ObjectifiedHash.new(message: 'Displayed message', error_description: 'should not be displayed', error: 'also will not be displayed')}, + { code: 404, parsed_response: Gitlab::ObjectifiedHash.new(error_description: 'Displayed error_description', error: 'also will not be displayed')}, + { code: 401, parsed_response: Gitlab::ObjectifiedHash.new(error: 'Displayed error')}, + { code: 500, parsed_response: Gitlab::ObjectifiedHash.new(embed_entity: { foo: ['bar'], sna: ['fu']}, password: ['too short'])}, + { code: 403, parsed_response: Array.new(['First message.', 'Second message.'])}, + ].each_with_index do |data, index| + it 'returns the expected message' do + response_double = double(**data, request: @request_double) + expect(described_class.new(response_double).message).to match expected_messages[index] end end end