diff --git a/.gitignore b/.gitignore index 610f23e..dbdee69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ info *.gem Gemfile.lock -.ruby-version \ No newline at end of file +.ruby-version +.zed diff --git a/CHANGES.md b/CHANGES.md index 95534bf..590519d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ - Added Zeitwerk for improved code autoloading +- Added `http_timeout` configuration option. By default it is set to 60 (seconds). + ## 0.9.0 - **Added support for Sidekiq**. You can now use Sidekiq to deliver API requests to Vero. To do so, just specify `config.async = :sidekiq` in your config.rb file. diff --git a/README.markdown b/README.markdown index 417656b..3cde443 100644 --- a/README.markdown +++ b/README.markdown @@ -25,6 +25,8 @@ To get started with a Ruby on Rails application, add the following initializer: # config/initializers/vero.rb Vero::App.init do |config| config.tracking_api_key = ENV['VERO_TRACKING_API_KEY'] + + config.http_timeout = 30 # default timeout per API request is set to 60 (seconds) end ``` diff --git a/lib/vero/api/workers/base_api.rb b/lib/vero/api/workers/base_api.rb index 803c167..0229385 100644 --- a/lib/vero/api/workers/base_api.rb +++ b/lib/vero/api/workers/base_api.rb @@ -13,7 +13,6 @@ def self.perform(domain, options) def initialize(domain, options) @domain = domain self.options = options - setup_logging end def perform @@ -22,21 +21,11 @@ def perform end def options=(val) - @options = options_with_symbolized_keys(val) + @options = val.transform_keys(&:to_sym) end protected - def setup_logging - return unless Vero::App.logger - - RestClient.log = Object.new.tap do |proxy| - def proxy.<<(message) - Vero::App.logger.info message - end - end - end - def url end @@ -45,20 +34,17 @@ def validate! end def request - request_headers = {content_type: :json, accept: :json} - - if request_method == :get - RestClient.get(url, request_headers) - else - RestClient.send(request_method, url, JSON.dump(@options), request_headers) - end + http_client.do_request(request_method, url, @options.except(:_config)) end def request_method raise NotImplementedError, "#{self.class.name}#request_method should be overridden" end - def options_with_symbolized_keys(val) - val.transform_keys(&:to_sym) + def http_client + Vero::HttpClient.new( + logger: Vero::App.logger, + http_timeout: @options.dig(:_config, :http_timeout) + ) end end diff --git a/lib/vero/config.rb b/lib/vero/config.rb index 896c666..f3dbc12 100644 --- a/lib/vero/config.rb +++ b/lib/vero/config.rb @@ -4,9 +4,9 @@ class Vero::Config attr_writer :development_mode # Deprecated field attr_writer :domain - attr_accessor :tracking_api_key, :async, :disabled, :logging + attr_accessor :tracking_api_key, :async, :disabled, :logging, :http_timeout - ACCEPTED_ATTRIBUTES = %i[tracking_api_key async disabled logging domain] + ACCEPTED_ATTRIBUTES = %i[tracking_api_key async disabled logging domain http_timeout] # Extracts accepted attributes from the given object. It isn't necessarily a Vero::Config instance. def self.extract_accepted_attrs_from(object) @@ -24,7 +24,10 @@ def config_params end def request_params - {tracking_api_key: tracking_api_key}.compact + { + tracking_api_key: tracking_api_key, + _config: {http_timeout: http_timeout} + }.compact end def domain @@ -49,6 +52,7 @@ def reset! self.async = true self.logging = false self.tracking_api_key = nil + self.http_timeout = Vero::HttpClient::DEFAULT_HTTP_TIMEOUT end def update_attributes(attributes = {}) diff --git a/lib/vero/http_client.rb b/lib/vero/http_client.rb new file mode 100644 index 0000000..f07aa37 --- /dev/null +++ b/lib/vero/http_client.rb @@ -0,0 +1,40 @@ +class Vero::HttpClient + DEFAULT_HTTP_TIMEOUT = 60 + + def initialize(http_timeout:, logger: nil) + @http_timeout = http_timeout + setup_logging!(logger) if logger + end + + def get(url, headers = {}) + do_request(:get, url, nil, headers) + end + + def post(url, body, headers = {}) + do_request(:post, url, body, headers) + end + + def put(url, body, headers = {}) + do_request(:put, url, body, headers) + end + + def do_request(method, url, body = nil, headers = {}) + request_params = {method: method, url: url, headers: default_headers.merge(headers), timeout: @http_timeout} + request_params[:payload] = JSON.dump(body) unless method == :get + RestClient::Request.execute(request_params) + end + + private + + def default_headers + {content_type: :json, accept: :json} + end + + def setup_logging!(logger) + RestClient.log = Object.new.tap do |proxy| + def proxy.<<(message) + logger.info message + end + end + end +end diff --git a/spec/lib/api/base_api_spec.rb b/spec/lib/api/base_api_spec.rb deleted file mode 100644 index 23b8dcb..0000000 --- a/spec/lib/api/base_api_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -describe Vero::Api::Workers::BaseAPI do - let(:subject) { Vero::Api::Workers::BaseAPI.new("http://www.getvero.com", {}) } - - describe :options_with_symbolized_keys do - it "should create a new options Hash with symbol keys (much like Hash#symbolize_keys in rails)" do - expect(subject.options).to eq({}) - - subject.options = {abc: 123} - expect(subject.options).to eq({abc: 123}) - - subject.options = {"abc" => 123} - expect(subject.options).to eq({abc: 123}) - end - end -end diff --git a/spec/lib/api/events/track_api_spec.rb b/spec/lib/api/events/track_api_spec.rb index 424f742..54945e7 100644 --- a/spec/lib/api/events/track_api_spec.rb +++ b/spec/lib/api/events/track_api_spec.rb @@ -3,7 +3,11 @@ require "spec_helper" describe Vero::Api::Workers::Events::TrackAPI do - subject { Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"}) } + let(:payload) do + {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"} + end + + subject { Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", payload) } it_behaves_like "a Vero wrapper" do let(:end_point) { "/api/v2/events/track.json" } @@ -12,48 +16,44 @@ context "request with properties" do describe :validate! do it "should raise an error if event_name is a blank String" do - options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: nil} - subject.options = options + subject.options = payload.except(:event_name) expect { subject.send(:validate!) }.to raise_error(ArgumentError) - options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"} - subject.options = options + subject.options = payload expect { subject.send(:validate!) }.to_not raise_error end it "should raise an error if data is not either nil or a Hash" do - options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event", data: []} - subject.options = options + subject.options = payload.merge(data: []) expect { subject.send(:validate!) }.to raise_error(ArgumentError) - options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event", data: nil} - subject.options = options + subject.options = payload.merge(data: nil) expect { subject.send(:validate!) }.to_not raise_error - options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event", data: {}} - subject.options = options + subject.options = payload.merge(data: {}) expect { subject.send(:validate!) }.to_not raise_error end it "should not raise an error when the keys are Strings" do - options = {"auth_token" => "abcd", "identity" => {"email" => "test@test.com"}, "event_name" => "test_event", "data" => {}} + options = {"auth_token" => "abcd", "identity" => {"email" => "test@test.com"}, "event_name" => "test_event", + "data" => {}} subject.options = options expect { subject.send(:validate!) }.to_not raise_error end it "should not raise an error when keys are Strings for initialization" do - options = {"auth_token" => "abcd", "identity" => {"email" => "test@test.com"}, "event_name" => "test_event", "data" => {}} - expect { Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", options).send(:validate!) }.to_not raise_error + payload.transform_keys!(&:to_s) + + expect do + Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", payload).send(:validate!) + end.to_not raise_error end end describe "request" do it "should send a request to the Vero API" do stub = stub_request(:post, "https://api.getvero.com/api/v2/events/track.json") - .with( - body: {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"}.to_json, - headers: {"Content-Type" => "application/json", "Accept" => "application/json"} - ) + .with(body: payload.to_json) .to_return(status: 200) subject.send(:request) @@ -65,9 +65,12 @@ describe "integration test" do it "should not raise any errors" do - obj = Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"}) + obj = Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", payload) + + stub_request(:post, "https://api.getvero.com/api/v2/events/track.json") + .with(body: payload.to_json) + .to_return(status: 200) - allow(RestClient).to receive(:post).and_return(200) expect { obj.perform }.to_not raise_error end end diff --git a/spec/lib/api_spec.rb b/spec/lib/api_spec.rb index 55112ff..33c530c 100644 --- a/spec/lib/api_spec.rb +++ b/spec/lib/api_spec.rb @@ -4,21 +4,34 @@ describe Vero::Api::Events do let(:subject) { Vero::Api::Events } + let(:mock_context) { Vero::Context.new } - describe :track! do - it "should call the TrackAPI object via the configured sender" do - input = {event_name: "test_event", identity: {email: "james@getvero.com"}, data: {test: "test"}} - expected = input.merge(tracking_api_key: "abc123") + let(:input) { {event_name: "test_event", identity: {email: "james@getvero.com"}, data: {test: "test"}} } + let(:expected) { input.merge(tracking_api_key: "abc123", _config: {http_timeout: 60}) } - mock_context = Vero::Context.new - allow(mock_context.config).to receive(:configured?).and_return(true) - allow(mock_context.config).to receive(:tracking_api_key).and_return("abc123") + before do + allow(mock_context.config).to receive(:configured?).and_return(true) + allow(mock_context.config).to receive(:tracking_api_key).and_return("abc123") + allow(Vero::App).to receive(:default_context).and_return(mock_context) + end - allow(Vero::App).to receive(:default_context).and_return(mock_context) + it "should pass http_timeout to API requests" do + allow(mock_context.config).to receive(:http_timeout).and_return(30) - expect(Vero::Sender).to receive(:call).with(Vero::Api::Workers::Events::TrackAPI, true, "https://api.getvero.com", expected) + expect(Vero::Sender).to( + receive(:call).with( + Vero::Api::Workers::Events::TrackAPI, true, "https://api.getvero.com", expected.merge(_config: {http_timeout: 30}) + ) + ) + subject.track!(input) + end - subject.track!(input) + describe :track! do + context "should call the TrackAPI object via the configured sender" do + specify do + expect(Vero::Sender).to receive(:call).with(Vero::Api::Workers::Events::TrackAPI, true, "https://api.getvero.com", expected) + subject.track!(input) + end end end end @@ -26,9 +39,9 @@ describe Vero::Api::Users do let(:subject) { Vero::Api::Users } let(:mock_context) { Vero::Context.new } - let(:expected) { input.merge(tracking_api_key: "abc123") } + let(:expected) { input.merge(tracking_api_key: "abc123", _config: {http_timeout: 60}) } - before :each do + before do allow(mock_context.config).to receive(:configured?).and_return(true) allow(mock_context.config).to receive(:tracking_api_key).and_return("abc123") allow(Vero::App).to receive(:default_context).and_return(mock_context) diff --git a/spec/lib/config_spec.rb b/spec/lib/config_spec.rb index 90ff8cc..9ffa2d8 100644 --- a/spec/lib/config_spec.rb +++ b/spec/lib/config_spec.rb @@ -31,12 +31,12 @@ end describe :request_params do - it "should return a hash of tracking_api_key and development_mode if they are set" do + it "should return a hash containing tracking_api_key if set" do config.tracking_api_key = nil - expect(config.request_params).to eq({}) + expect(config.request_params.key?(:tracking_api_key)).to be_falsey config.tracking_api_key = "abcd1234" - expect(config.request_params).to eq({tracking_api_key: "abcd1234"}) + expect(config.request_params).to include(tracking_api_key: "abcd1234") end end diff --git a/spec/lib/trackable_spec.rb b/spec/lib/trackable_spec.rb index 5f03f26..3143451 100644 --- a/spec/lib/trackable_spec.rb +++ b/spec/lib/trackable_spec.rb @@ -13,7 +13,7 @@ def vero_context(user, logging = true, async = false, disabled = true) end describe Vero::Trackable do - before :each do + before do @request_params = { event_name: "test_event", tracking_api_key: "YWJjZDEyMzQ6ZWZnaDU2Nzg=", @@ -65,8 +65,6 @@ def vero_context(user, logging = true, async = false, disabled = true) context = vero_context(@user) allow(@user).to receive(:with_vero_context).and_return(context) - allow(RestClient).to receive(:post).and_return(200) - allow(Vero::Api::Events).to receive(:track!).and_return(200) expect(Vero::Api::Events).to receive(:track!).with(@request_params, context) expect(@user.track!(@request_params[:event_name], @request_params[:data])).to eq(200) @@ -310,17 +308,21 @@ def vero_context(user, logging = true, async = false, disabled = true) event_name: "test_event", tracking_api_key: "YWJjZDEyMzQ6ZWZnaDU2Nzg=", identity: {email: "user@getvero.com", age: 20, _user_type: "UserWithoutInterface"}, - data: {test: 1} + data: {test: 1}, + extras: {} } context = Vero::Context.new(Vero::App.default_context) context.subject = user allow(context).to receive(:post_now).and_return(200) - allow(user).to receive(:with_vero_context).and_return(context) - allow(RestClient).to receive(:post).and_return(200) - expect(user.vero_track(request_params[:event_name], request_params[:data])).to eq(200) + stub_request(:post, "https://api.getvero.com/api/v2/events/track.json") + .with(body: request_params, headers: {content_type: "application/json"}) + .to_return(status: 200) + + resp = user.vero_track(request_params[:event_name], request_params[:data]) + expect(resp.code).to eq(200) end end end diff --git a/spec/lib/view_helpers_spec.rb b/spec/lib/view_helpers_spec.rb index 4df18c9..868457f 100644 --- a/spec/lib/view_helpers_spec.rb +++ b/spec/lib/view_helpers_spec.rb @@ -28,7 +28,7 @@ end context "Vero::App has been properly configured" do - before :each do + before do @tracking_api_key = "abcd1234" Vero::App.init do |c|