diff --git a/lib/mobilize_america_client/errors.rb b/lib/mobilize_america_client/errors.rb index ea5d044..20cdaec 100644 --- a/lib/mobilize_america_client/errors.rb +++ b/lib/mobilize_america_client/errors.rb @@ -1,13 +1,16 @@ module MobilizeAmericaClient - class NotFoundError < StandardError + class ClientError < StandardError end - class UnauthorizedError < StandardError + class UnauthorizedError < ClientError end - class ServerError < StandardError + class NotFoundError < ClientError end - class ClientError < StandardError + class RateLimitedError < ClientError + end + + class ServerError < StandardError end end diff --git a/lib/mobilize_america_client/request.rb b/lib/mobilize_america_client/request.rb index 1fe0845..c379f26 100644 --- a/lib/mobilize_america_client/request.rb +++ b/lib/mobilize_america_client/request.rb @@ -33,6 +33,10 @@ def request(method:, path:, params: {}, body: {}) raise MobilizeAmericaClient::UnauthorizedError, "Unauthorized: #{response.body}" when 404 raise MobilizeAmericaClient::NotFoundError, "Not Found: #{response.body}" + when 429 + retry_after_header = response.headers['Retry-After'] + message = "Rate Limited (Retry-After #{retry_after_header}): #{response.body}" + raise MobilizeAmericaClient::RateLimitedError, message when 400..499 raise MobilizeAmericaClient::ClientError, "Client Error (#{response.status}): #{response.body}" when 500..599 diff --git a/spec/client/attendances_spec.rb b/spec/client/attendances_spec.rb index 57d631a..cc1ba7d 100644 --- a/spec/client/attendances_spec.rb +++ b/spec/client/attendances_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'shared_examples/response_error_handling' RSpec.describe MobilizeAmericaClient::Client::Attendances do let(:api_key) { 'abcde-123456' } @@ -13,20 +14,16 @@ let(:attendances_url) { "#{base_url}/organizations/#{org_id}/attendances" } let(:response) { {'data' => [{'id' => 1, 'event' => {'id' => 1111}}, {'id' => 2, 'event' => {'id' => 2222}}]} } - context 'unauthenticated request' do - let(:api_key) { nil } - - it 'should raise if response status is 401' do - stub_request(:get, attendances_url).with(headers: {'Content-Type' => 'application/json'}).to_return(status: 401, body: {error: 'unauthorized'}.to_json) - - expect { subject.organization_attendances(organization_id: org_id) }.to raise_error MobilizeAmericaClient::UnauthorizedError + it_behaves_like 'response error handling' do + let(:call_client_method) do + -> { subject.organization_attendances(organization_id: org_id) } + end + let(:set_up_stub_request) do + -> do + stub_request(:get, attendances_url).with(headers: request_headers) + .to_return(status: response_status, body: response_body, headers: response_headers) + end end - end - - it 'should raise if response status is 404' do - stub_request(:get, attendances_url).with(headers: request_headers).to_return(status: 404, body: { error: 'not found'}.to_json) - - expect { subject.organization_attendances(organization_id: org_id) }.to raise_error MobilizeAmericaClient::NotFoundError end it 'should call the endpoint and return JSON' do diff --git a/spec/client/enums_spec.rb b/spec/client/enums_spec.rb index 80d28ca..763632e 100644 --- a/spec/client/enums_spec.rb +++ b/spec/client/enums_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'shared_examples/response_error_handling' RSpec.describe MobilizeAmericaClient::Client::Enums do let(:standard_headers) { {'Content-Type' => 'application/json'} } @@ -11,6 +12,18 @@ let(:enums_url) { "#{base_url}/enums" } let(:response) { {'hello' => 'world'} } + it_behaves_like 'response error handling' do + let(:call_client_method) do + -> { subject.enums } + end + let(:set_up_stub_request) do + -> do + stub_request(:get, enums_url).with(headers: standard_headers) + .to_return(status: response_status, body: response_body, headers: response_headers) + end + end + end + it 'should call the endpoint and return JSON' do stub_request(:get, enums_url).with(headers: standard_headers).to_return(body: response.to_json, headers: { 'Content-Type' => 'application/json' }) expect(subject.enums).to eq response diff --git a/spec/client/events_spec.rb b/spec/client/events_spec.rb index fc987d4..0d62e98 100644 --- a/spec/client/events_spec.rb +++ b/spec/client/events_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'shared_examples/response_error_handling' RSpec.describe MobilizeAmericaClient::Client::Events do let(:standard_headers) { {'Content-Type' => 'application/json'} } @@ -12,34 +13,16 @@ let(:events_url) { "#{base_url}/organizations/#{org_id}/events" } let(:response) { {'data' => [{'id' => 1, 'description' => 'event 1'}, {'id' => 2, 'description' => 'event 2'}]} } - it 'should raise if response status is 404' do - stub_request(:get, events_url).with(headers: standard_headers).to_return(status: 404, body: {error: 'not found'}.to_json, headers: standard_headers) - - expect { subject.organization_events(organization_id: org_id) }.to raise_error MobilizeAmericaClient::NotFoundError - end - - it 'should raise ServerError if response status is 500' do - stub_request(:get, events_url).with(headers: standard_headers).to_return(status: 500, body: {error: 'internal server error'}.to_json, headers: standard_headers) - - expect { subject.organization_events(organization_id: org_id) }.to raise_error(MobilizeAmericaClient::ServerError, /Server Error \(500\)/) - end - - it 'should raise ServerError if response status is 503' do - stub_request(:get, events_url).with(headers: standard_headers).to_return(status: 503, body: 'Service Unavailable') - - expect { subject.organization_events(organization_id: org_id) }.to raise_error(MobilizeAmericaClient::ServerError, /Server Error \(503\)/) - end - - it 'should raise ClientError if response status is 400' do - stub_request(:get, events_url).with(headers: standard_headers).to_return(status: 400, body: {error: 'bad request'}.to_json, headers: standard_headers) - - expect { subject.organization_events(organization_id: org_id) }.to raise_error(MobilizeAmericaClient::ClientError, /Client Error \(400\)/) - end - - it 'should raise ClientError if response status is 422' do - stub_request(:get, events_url).with(headers: standard_headers).to_return(status: 422, body: {error: 'unprocessable entity'}.to_json, headers: standard_headers) - - expect { subject.organization_events(organization_id: org_id) }.to raise_error(MobilizeAmericaClient::ClientError, /Client Error \(422\)/) + it_behaves_like 'response error handling' do + let(:call_client_method) do + -> { subject.organization_events(organization_id: org_id) } + end + let(:set_up_stub_request) do + -> do + stub_request(:get, events_url).with(headers: standard_headers) + .to_return(status: response_status, body: response_body, headers: response_headers) + end + end end it 'should call the endpoint and return JSON' do diff --git a/spec/client/organizations_spec.rb b/spec/client/organizations_spec.rb index 77e9c39..32f6428 100644 --- a/spec/client/organizations_spec.rb +++ b/spec/client/organizations_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'shared_examples/response_error_handling' RSpec.describe MobilizeAmericaClient::Client::Organizations do let(:standard_headers) { {'Content-Type' => 'application/json'} } @@ -11,6 +12,18 @@ let(:organizations_url) { "#{base_url}/organizations" } let(:response) { fixture('organizations.json').read } + it_behaves_like 'response error handling' do + let(:call_client_method) do + -> { subject.organizations } + end + let(:set_up_stub_request) do + -> do + stub_request(:get, organizations_url).with(headers: standard_headers) + .to_return(status: response_status, body: response_body, headers: response_headers) + end + end + end + it 'should call the endpoint and return JSON' do stub_request(:get, organizations_url).with(headers: standard_headers).to_return(body: response.to_json, headers: { 'Content-Type' => 'application/json' }) expect(subject.organizations).to eq response diff --git a/spec/shared_examples/response_error_handling.rb b/spec/shared_examples/response_error_handling.rb new file mode 100644 index 0000000..9f5d576 --- /dev/null +++ b/spec/shared_examples/response_error_handling.rb @@ -0,0 +1,79 @@ +RSpec.shared_examples_for 'response error handling' do + let(:response_headers) { {'Content-Type' => 'application/json'} } + + before :each do + set_up_stub_request.call + end + + context 'response status is 400' do + let(:response_status) { 400 } + let(:response_body) { {error: 'bad request'}.to_json } + + it 'should raise ClientError' do + expect{ call_client_method.call }.to raise_error(MobilizeAmericaClient::ClientError, /Client Error \(400\)/) + end + end + + context 'response status is 401' do + let(:response_status) { 401 } + let(:response_body) { {error: 'unauthorized'}.to_json } + + it 'should raise UnauthorizedError' do + expect { call_client_method.call }.to raise_error MobilizeAmericaClient::UnauthorizedError + end + end + + context 'response status is 404' do + let(:response_status) { 404 } + let(:response_body) { {error: 'not found'}.to_json } + + it 'should raise NotFoundError' do + expect{ call_client_method.call }.to raise_error MobilizeAmericaClient::NotFoundError + end + end + + context 'response status is 422' do + let(:response_status) { 422 } + let(:response_body) { {error: 'unprocessable entity'}.to_json } + + it 'should raise ClientError' do + expect{ call_client_method.call }.to raise_error(MobilizeAmericaClient::ClientError, /Client Error \(422\)/) + end + end + + context 'response status is 429' do + let(:response_status) { 429 } + let(:response_body) { {error: 'rate-limited'}.to_json } + + it 'should raise RateLimitedError' do + expect{ call_client_method.call }.to raise_error(MobilizeAmericaClient::RateLimitedError) + end + + context 'with a Retry-After header' do + let(:response_headers) { {'Content-Type' => 'application/json', 'Retry-After' => '3600'} } + + it 'should raise include that info in the error' do + expect{ call_client_method.call }.to raise_error(MobilizeAmericaClient::RateLimitedError, /3600/) + end + end + end + + context 'response status is 500' do + let(:response_status) { 500 } + let(:response_body) { {error: 'internal server error'}.to_json } + + it 'should raise ServerError' do + expect{ call_client_method.call }.to raise_error(MobilizeAmericaClient::ServerError, /Server Error \(500\)/) + end + end + + context 'response status is 503' do + let(:response_status) { 503 } + let(:response_body) { 'Service Unavailable' } + let(:response_headers) { {} } + + it 'should raise ServerError' do + expect{ call_client_method.call }.to raise_error(MobilizeAmericaClient::ServerError, /Server Error \(503\)/) + end + end +end