diff --git a/lib/webpush/errors.rb b/lib/webpush/errors.rb index 0f7060b..d65e4a3 100644 --- a/lib/webpush/errors.rb +++ b/lib/webpush/errors.rb @@ -3,7 +3,21 @@ class Error < RuntimeError; end class ConfigurationError < Error; end - class ResponseError < Error; end + class ResponseError < Error; + attr_reader :response, :host + + def initialize(response, host) + @response = response + @host = host + super "host: #{host}, #{@response.inspect}\nbody:\n#{@response.body}" + end + end class InvalidSubscription < ResponseError; end + + class ExpiredSubscription < ResponseError; end + + class PayloadTooLarge < ResponseError; end + + class TooManyRequests < ResponseError; end end diff --git a/lib/webpush/request.rb b/lib/webpush/request.rb index 8647966..33ce1a7 100644 --- a/lib/webpush/request.rb +++ b/lib/webpush/request.rb @@ -24,9 +24,15 @@ def perform if resp.is_a?(Net::HTTPGone) || #Firefox unsubscribed response (resp.is_a?(Net::HTTPBadRequest) && resp.message == "UnauthorizedRegistration") #Chrome unsubscribed response - raise InvalidSubscription.new(resp.inspect) - elsif !resp.is_a?(Net::HTTPSuccess) #unknown/unhandled response error - raise ResponseError.new "host: #{uri.host}, #{resp.inspect}\nbody:\n#{resp.body}" + raise InvalidSubscription.new(resp, uri.host) + elsif resp.is_a?(Net::HTTPNotFound) # 404 + raise ExpiredSubscription.new(resp, uri.host) + elsif resp.is_a?(Net::HTTPRequestEntityTooLarge) # 413 + raise PayloadTooLarge.new(resp, uri.host) + elsif resp.is_a?(Net::HTTPTooManyRequests) # 429, try again later! + raise TooManyRequests.new(resp, uri.host) + elsif !resp.is_a?(Net::HTTPSuccess) # unknown/unhandled response error + raise ResponseError.new(resp, uri.host) end resp diff --git a/spec/webpush_spec.rb b/spec/webpush_spec.rb index 8c39f7a..50dc503 100644 --- a/spec/webpush_spec.rb +++ b/spec/webpush_spec.rb @@ -5,6 +5,77 @@ expect(Webpush::VERSION).not_to be nil end + shared_examples 'web push protocol standard error handling' do + it 'raises InvalidSubscription if and only if the combination of status code and message indicate an invalid subscription' do + stub_request(:post, expected_endpoint). + to_return(status: 410, body: "", headers: {}) + expect { subject }.to raise_error(Webpush::InvalidSubscription) + + stub_request(:post, expected_endpoint). + to_return(status: [400, "UnauthorizedRegistration"], body: "", headers: {}) + expect { subject }.to raise_error(Webpush::InvalidSubscription) + + stub_request(:post, expected_endpoint). + to_return(status: 400, body: "", headers: {}) + expect { subject }.not_to raise_error(Webpush::InvalidSubscription) + end + + it 'raises ExpiredSubscription if the API returns a 404 Error' do + stub_request(:post, expected_endpoint). + to_return(status: 404, body: "", headers: {}) + expect { subject }.to raise_error(Webpush::ExpiredSubscription) + end + + it 'raises PayloadTooLarge if the API returns a 413 Error' do + stub_request(:post, expected_endpoint). + to_return(status: 413, body: "", headers: {}) + expect { subject }.to raise_error(Webpush::PayloadTooLarge) + end + + it 'raises TooManyRequests if the API returns a 429 Error' do + stub_request(:post, expected_endpoint). + to_return(status: 429, body: "", headers: {}) + expect { subject }.to raise_error(Webpush::TooManyRequests) + end + + it 'raises ResponseError for unsuccessful status code by default' do + stub_request(:post, expected_endpoint). + to_return(status: 401, body: "", headers: {}) + + expect { subject }.to raise_error(Webpush::ResponseError) + end + + it 'supplies the original status code on the ResponseError' do + stub_request(:post, expected_endpoint). + to_return(status: 401, body: "Oh snap", headers: {}) + + expect { subject }.to raise_error { |error| + expect(error).to be_a(Webpush::ResponseError) + expect(error.response.code).to eq '401' + expect(error.response.body).to eq 'Oh snap' + } + end + + it 'sets the error message to be the host + stringified response' do + stub_request(:post, expected_endpoint). + to_return(status: 401, body: "Oh snap", headers: {}) + + host = URI.parse(expected_endpoint).host + + expect { subject }.to raise_error { |error| + expect(error.message).to eq( + "host: #{host}, #\nbody:\nOh snap" + ) + } + end + + it 'raises exception on error by default' do + stub_request(:post, expected_endpoint).to_raise(StandardError) + + expect { subject }.to raise_error + end + end + shared_examples 'request headers with VAPID' do let(:message) { JSON.generate({ body: 'body' }) } let(:p256dh) { 'BN4GvZtEZiZuqFxSKVZfSfluwKBD7UxHNBmWkfiZfCtgDE8Bwh-_MtLXbBxTBAWH9r7IPKL0lhdcaqtL1dfxU5E=' } @@ -64,32 +135,7 @@ expect(result.code).to eql('201') end - it 'raises InvalidSubscription if and only if the combination of status code and message indicate an invalid subscription' do - stub_request(:post, expected_endpoint). - to_return(status: 410, body: "", headers: {}) - expect { subject }.to raise_error(Webpush::InvalidSubscription) - - stub_request(:post, expected_endpoint). - to_return(status: [400, "UnauthorizedRegistration"], body: "", headers: {}) - expect { subject }.to raise_error(Webpush::InvalidSubscription) - - stub_request(:post, expected_endpoint). - to_return(status: 400, body: "", headers: {}) - expect { subject }.not_to raise_error(Webpush::InvalidSubscription) - end - - it 'raises ResponseError for unsuccessful status code by default' do - stub_request(:post, expected_endpoint). - to_return(status: 401, body: "", headers: {}) - - expect { subject }.to raise_error(Webpush::ResponseError) - end - - it 'raises exception on error by default' do - stub_request(:post, expected_endpoint).to_raise(StandardError) - - expect { subject }.to raise_error - end + include_examples 'web push protocol standard error handling' it 'message is optional' do expect(Webpush::Encryption).to_not receive(:encrypt) @@ -187,32 +233,7 @@ expect(result.code).to eql('201') end - it 'raises InvalidSubscription if and only if the combination of status code and message indicate an invalid subscription' do - stub_request(:post, expected_endpoint). - to_return(status: 410, body: "", headers: {}) - expect { subject }.to raise_error(Webpush::InvalidSubscription) - - stub_request(:post, expected_endpoint). - to_return(status: [400, "UnauthorizedRegistration"], body: "", headers: {}) - expect { subject }.to raise_error(Webpush::InvalidSubscription) - - stub_request(:post, expected_endpoint). - to_return(status: 400, body: "", headers: {}) - expect { subject }.not_to raise_error(Webpush::InvalidSubscription) - end - - it 'raises ResponseError for unsuccessful status code by default' do - stub_request(:post, expected_endpoint). - to_return(status: 401, body: "", headers: {}) - - expect { subject }.to raise_error(Webpush::ResponseError) - end - - it 'raises exception on error by default' do - stub_request(:post, expected_endpoint).to_raise(StandardError) - - expect { subject }.to raise_error - end + include_examples 'web push protocol standard error handling' it 'message and encryption keys are optional' do expect(Webpush::Encryption).to_not receive(:encrypt)