diff --git a/.gitignore b/.gitignore index 94bb6bb..c3ebfa2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ # Ignore bundler config /.bundle Gemfile.lock + +spec/examples.txt + diff --git a/.rspec b/.rspec index b3eb8b4..c99d2e7 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1 @@ ---color ---format documentation \ No newline at end of file +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..4f67231 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,26 @@ +inherit_from: .rubocop_todo.yml + +AllCops: + TargetRubyVersion: 2.2 + +# Keep diffs clean +Layout/TrailingBlankLines: + EnforcedStyle: final_blank_line + +# No way to avoid large blocks in RSpec +Metrics/BlockLength: + Exclude: + - spec/**/* + +# Allow the gem to have a non-snakecase name +Naming/FileName: + Exclude: + - 'lib/onfleet-ruby.rb' + +Style/Documentation: + Enabled: false + +Style/StringLiterals: + Exclude: + - spec/**/* + diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..996ac2c --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,38 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2018-05-13 20:30:59 -0400 using RuboCop version 0.55.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 2 +Metrics/AbcSize: + Max: 26 + +# Offense count: 1 +Metrics/CyclomaticComplexity: + Max: 7 + +# Offense count: 4 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 22 + +# Offense count: 1 +Metrics/PerceivedComplexity: + Max: 8 + +# Offense count: 29 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: when_needed, always, never +Style/FrozenStringLiteralComment: + Enabled: false + +# Offense count: 9 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 138 + diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..025e764 --- /dev/null +++ b/.ruby-version @@ -0,0 +1,2 @@ +2.5.1 + diff --git a/Gemfile b/Gemfile index c398068..12f6e01 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ -source "https://rubygems.org" +source 'https://rubygems.org' -gemspec -gem 'rest-client', '~> 1.6.8' +ruby '2.5.1' +gemspec diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..22deda5 --- /dev/null +++ b/Rakefile @@ -0,0 +1,10 @@ +require 'bundler/gem_tasks' + +require 'rubocop/rake_task' +RuboCop::RakeTask.new + +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) + +task default: %i[spec rubocop] + diff --git a/lib/onfleet-ruby.rb b/lib/onfleet-ruby.rb index bbbcd3d..149ab78 100644 --- a/lib/onfleet-ruby.rb +++ b/lib/onfleet-ruby.rb @@ -3,9 +3,6 @@ require 'base64' require 'uri' -# Utils -require 'onfleet-ruby/util' - # Errors require 'onfleet-ruby/errors/onfleet_error' require 'onfleet-ruby/errors/authentication_error' @@ -35,68 +32,68 @@ require 'onfleet-ruby/worker' require 'onfleet-ruby/webhook' - module Onfleet - @base_url = "https://onfleet.com/api/v2" + @base_url = 'https://onfleet.com/api/v2/' class << self - attr_accessor :api_key, :base_url, :encoded_api_key - end + attr_accessor :api_key, :base_url - def self.request api_url, method, params={} - raise AuthenticationError.new("Set your API Key using Onfleet.api_key = ") unless @api_key + def request(api_url, method, params = {}) + raise(AuthenticationError, 'Set your API Key using Onfleet.api_key = ') unless api_key - begin - response = RestClient::Request.execute(method: method, url: self.base_url+api_url, payload: params.to_json, headers: self.request_headers) - - if response != '' - JSON.parse(response) - end - rescue RestClient::ExceptionWithResponse => e - if response_code = e.http_code and response_body = e.http_body - handle_api_error(response_code, JSON.parse(response_body)) - else + begin + url = URI.join(base_url, api_url).to_s + response = RestClient::Request.execute(method: method, url: url, payload: params.to_json, headers: request_headers) + JSON.parse(response) unless response.empty? + rescue RestClient::ExceptionWithResponse => e + if (response_code = e.http_code) && (response_body = e.http_body) + handle_api_error(response_code, JSON.parse(response_body)) + else + handle_restclient_error(e) + end + rescue RestClient::Exception, Errno::ECONNREFUSED => e handle_restclient_error(e) end - rescue RestClient::Exception, Errno::ECONNREFUSED => e - handle_restclient_error(e) end - end - private - def self.request_headers + private + + def request_headers { - Authorization: "Basic #{self.encoded_api_key}", + Authorization: "Basic #{encoded_api_key}", content_type: :json, accept: :json } end - def self.encoded_api_key - @encoded_api_key ||= Base64.urlsafe_encode64(@api_key) + def encoded_api_key + @encoded_api_key ||= Base64.urlsafe_encode64(api_key) end - def self.handle_api_error code, body + def handle_api_error(code, body) case code when 400, 404 - raise InvalidRequestError.new(body["message"]) + raise InvalidRequestError, body['message'] when 401 - raise AuthenticationError.new(body["message"]) + raise AuthenticationError, body['message'] else - raise OnfleetError.new(body["message"]) + raise OnfleetError, body['message'] end end - def self.handle_restclient_error e - case e - when RestClient::RequestTimeout - message = "Could not connect to Onfleet. Check your internet connection and try again." - when RestClient::ServerBrokeConnection - message = "The connetion with onfleet terminated before the request completed. Please try again." - else - message = "There was a problem connection with Onfleet. Please try again. If the problem persists contact contact@onfleet.com" - end + def handle_restclient_error(exception) + message = + case exception + when RestClient::RequestTimeout + 'Could not connect to Onfleet. Check your internet connection and try again.' + when RestClient::ServerBrokeConnection + 'The connetion with onfleet terminated before the request completed. Please try again.' + else + 'There was a problem connection with Onfleet. Please try again. If the problem persists contact contact@onfleet.com' + end - raise ConnectionError.new(message) + raise ConnectionError, message end + end end + diff --git a/lib/onfleet-ruby/actions/create.rb b/lib/onfleet-ruby/actions/create.rb index 984d247..d5db07c 100644 --- a/lib/onfleet-ruby/actions/create.rb +++ b/lib/onfleet-ruby/actions/create.rb @@ -1,15 +1,18 @@ +require 'active_support/core_ext/hash' + module Onfleet module Actions module Create module ClassMethods - def create params={} - self.new(params).save + def create(params = {}) + new(params.symbolize_keys.except(:id)).save end end - def self.included base + def self.included(base) base.extend(ClassMethods) end end end end + diff --git a/lib/onfleet-ruby/actions/delete.rb b/lib/onfleet-ruby/actions/delete.rb index 648e563..e9f4a44 100644 --- a/lib/onfleet-ruby/actions/delete.rb +++ b/lib/onfleet-ruby/actions/delete.rb @@ -2,16 +2,16 @@ module Onfleet module Actions module Delete module ClassMethods - def delete id - api_url = "#{self.api_url}/#{id}" - response = Onfleet.request(api_url, :delete) + def delete(id) + Onfleet.request("#{api_url}/#{id}", :delete) true end end - def self.included base + def self.included(base) base.extend(ClassMethods) end end end end + diff --git a/lib/onfleet-ruby/actions/find.rb b/lib/onfleet-ruby/actions/find.rb index 83cfe1b..52411f4 100644 --- a/lib/onfleet-ruby/actions/find.rb +++ b/lib/onfleet-ruby/actions/find.rb @@ -2,17 +2,19 @@ module Onfleet module Actions module Find module ClassMethods - def find field, search_term - encoded_term = URI::encode(search_term) - api_url = "#{self.api_url}/#{field}/#{encoded_term}" - response = Onfleet.request(api_url, :get, search_term) - Util.constantize("#{self}").new(response) + def find(field, search_term) + encoded_term = URI.encode_www_form_component(search_term) + url = "#{api_url}/#{field}/#{encoded_term}" + + response = Onfleet.request(url, :get) + new(response) end end - def self.included base + def self.included(base) base.extend(ClassMethods) end end end end + diff --git a/lib/onfleet-ruby/actions/get.rb b/lib/onfleet-ruby/actions/get.rb index 186cace..df386c1 100644 --- a/lib/onfleet-ruby/actions/get.rb +++ b/lib/onfleet-ruby/actions/get.rb @@ -2,16 +2,16 @@ module Onfleet module Actions module Get module ClassMethods - def get id - api_url = "#{self.api_url}/#{id}" - response = Onfleet.request(api_url, :get) - Util.constantize("#{self}").new(response) + def get(id) + url = "#{api_url}/#{id}" + new(Onfleet.request(url, :get)) end end - def self.included base + def self.included(base) base.extend(ClassMethods) end end end end + diff --git a/lib/onfleet-ruby/actions/list.rb b/lib/onfleet-ruby/actions/list.rb index 77316ae..6bd10e3 100644 --- a/lib/onfleet-ruby/actions/list.rb +++ b/lib/onfleet-ruby/actions/list.rb @@ -2,26 +2,28 @@ module Onfleet module Actions module List module ClassMethods - def list query_params={} - api_url = "#{self.api_url}" + def list(filters = {}) + response = Onfleet.request(list_url_for(filters), :get) + response.compact.map { |item| new(item) } + end + + private - if !query_params.empty? - api_url += "?" - query_params.each do |key, value| - api_url += "#{key}=#{value}&" - end - end + def list_url_for(filters) + [api_url, query_params(filters)].compact.join('?') + end - response = Onfleet.request(api_url, :get) - response.compact.map do |listObj| - Util.constantize("#{self}").new(listObj) - end + def query_params(filters) + filters && filters + .collect { |key, value| "#{key}=#{URI.encode_www_form_component(value)}" } + .join('&') end end - def self.included base + def self.included(base) base.extend(ClassMethods) end end end end + diff --git a/lib/onfleet-ruby/actions/query_metadata.rb b/lib/onfleet-ruby/actions/query_metadata.rb index 7da9026..bdcfca7 100644 --- a/lib/onfleet-ruby/actions/query_metadata.rb +++ b/lib/onfleet-ruby/actions/query_metadata.rb @@ -2,16 +2,16 @@ module Onfleet module Actions module QueryMetadata module ClassMethods - def query_by_metadata metadata - api_url = "#{self.api_url}/metadata" - response = Onfleet.request(api_url, :post, metadata) - response.map { |item| Util.constantize("#{self}").new(item) } if response.is_a? Array + def query_by_metadata(metadata) + response = Onfleet.request("#{api_url}/metadata", :post, metadata) + [*response].compact.map { |item| new(item) } end end - def self.included base + def self.included(base) base.extend(ClassMethods) end end end end + diff --git a/lib/onfleet-ruby/actions/save.rb b/lib/onfleet-ruby/actions/save.rb index 8a5e39f..205d5d2 100644 --- a/lib/onfleet-ruby/actions/save.rb +++ b/lib/onfleet-ruby/actions/save.rb @@ -2,16 +2,20 @@ module Onfleet module Actions module Save def save - if respond_to?('id') && self.id - request_type = :put - api_url = "#{self.api_url}/#{self.id}" - else - request_type = :post - api_url = self.api_url - end - response = Onfleet.request(api_url, request_type, self.attributes) - self.parse_response(response) + response = Onfleet.request(save_url, request_type, attributes) + parse_response(response) + end + + private + + def request_type + id ? :put : :post + end + + def save_url + [api_url, id].compact.join('/') end end end end + diff --git a/lib/onfleet-ruby/actions/update.rb b/lib/onfleet-ruby/actions/update.rb index 72e0733..bcb3974 100644 --- a/lib/onfleet-ruby/actions/update.rb +++ b/lib/onfleet-ruby/actions/update.rb @@ -2,15 +2,15 @@ module Onfleet module Actions module Update module ClassMethods - def update id, params - params.merge!(id: id) - self.new(params).save + def update(id, params) + new(params.merge(id: id)).save end end - def self.included base + def self.included(base) base.extend(ClassMethods) end end end end + diff --git a/lib/onfleet-ruby/address.rb b/lib/onfleet-ruby/address.rb index 426b493..e1a0a65 100644 --- a/lib/onfleet-ruby/address.rb +++ b/lib/onfleet-ruby/address.rb @@ -2,3 +2,4 @@ module Onfleet class Address < OnfleetObject end end + diff --git a/lib/onfleet-ruby/admin.rb b/lib/onfleet-ruby/admin.rb index fb78166..887c308 100644 --- a/lib/onfleet-ruby/admin.rb +++ b/lib/onfleet-ruby/admin.rb @@ -8,7 +8,8 @@ class Admin < OnfleetObject include Onfleet::Actions::QueryMetadata def self.api_url - '/admins' + 'admins' end end end + diff --git a/lib/onfleet-ruby/destination.rb b/lib/onfleet-ruby/destination.rb index 6f73e84..0aa0309 100644 --- a/lib/onfleet-ruby/destination.rb +++ b/lib/onfleet-ruby/destination.rb @@ -6,7 +6,8 @@ class Destination < OnfleetObject include Onfleet::Actions::QueryMetadata def self.api_url - '/destinations' + 'destinations' end end end + diff --git a/lib/onfleet-ruby/errors/authentication_error.rb b/lib/onfleet-ruby/errors/authentication_error.rb index 9bc5dd4..8d78981 100644 --- a/lib/onfleet-ruby/errors/authentication_error.rb +++ b/lib/onfleet-ruby/errors/authentication_error.rb @@ -1,3 +1,4 @@ module Onfleet class AuthenticationError < OnfleetError; end end + diff --git a/lib/onfleet-ruby/errors/connection_error.rb b/lib/onfleet-ruby/errors/connection_error.rb index 42ba19e..790178d 100644 --- a/lib/onfleet-ruby/errors/connection_error.rb +++ b/lib/onfleet-ruby/errors/connection_error.rb @@ -1,3 +1,4 @@ module Onfleet class ConnectionError < OnfleetError; end end + diff --git a/lib/onfleet-ruby/errors/invalid_request_error.rb b/lib/onfleet-ruby/errors/invalid_request_error.rb index 4bdaa01..1e6260d 100644 --- a/lib/onfleet-ruby/errors/invalid_request_error.rb +++ b/lib/onfleet-ruby/errors/invalid_request_error.rb @@ -1,3 +1,4 @@ module Onfleet class InvalidRequestError < OnfleetError; end end + diff --git a/lib/onfleet-ruby/errors/onfleet_error.rb b/lib/onfleet-ruby/errors/onfleet_error.rb index 7c59b7f..66ee07e 100644 --- a/lib/onfleet-ruby/errors/onfleet_error.rb +++ b/lib/onfleet-ruby/errors/onfleet_error.rb @@ -1,3 +1,4 @@ module Onfleet class OnfleetError < StandardError; end end + diff --git a/lib/onfleet-ruby/onfleet_object.rb b/lib/onfleet-ruby/onfleet_object.rb index 37900ef..549b6a9 100644 --- a/lib/onfleet-ruby/onfleet_object.rb +++ b/lib/onfleet-ruby/onfleet_object.rb @@ -1,102 +1,119 @@ +require 'active_support/core_ext/string/inflections' + module Onfleet class OnfleetObject attr_reader :params - def initialize params - if params.kind_of?(Hash) + attr_accessor :id + + def initialize(params) + if params.is_a?(Hash) @params = params - set_attributes(@params) - elsif params.kind_of?(String) - @params = {id: params} - set_attributes(@params) + assign_attributes(@params) + elsif params.is_a?(String) + @params = { id: params } + assign_attributes(@params) else @params = {} end end - def parse_response response + def parse_response(response) @params = response - set_attributes(response) + assign_attributes(response) self end def attributes - attrs = Hash.new - instance_variables.select {|var| var != '@params'}.each do |var| - str = var.to_s.gsub /^@/, '' - if respond_to?("#{str}=") - instance_var = instance_variable_get(var) - if klass = Util.object_classes[str] - if instance_var.is_a?(OnfleetObject) - attrs[Util.to_camel_case_lower(str).to_sym] = parse_onfleet_obj(instance_var) - elsif instance_var.is_a?(Array) - objs = [] - instance_var.each do |object| - objs << parse_onfleet_obj(object) - end - attrs[Util.to_camel_case_lower(str).to_sym] = objs - else - attrs[Util.to_camel_case_lower(str).to_sym] = instance_var + attrs = {} + instance_variables.reject { |var| var == '@params' }.each do |var| + str = var.to_s.gsub(/^@/, '') + next unless respond_to?("#{str}=") + instance_var = instance_variable_get(var) + if object_classes[str] + if instance_var.is_a?(OnfleetObject) + attrs[camelize(str).to_sym] = parse_onfleet_obj(instance_var) + elsif instance_var.is_a?(Array) + objs = [] + instance_var.each do |object| + objs << parse_onfleet_obj(object) end + attrs[camelize(str).to_sym] = objs else - attrs[Util.to_camel_case_lower(str).to_sym] = instance_var + attrs[camelize(str).to_sym] = instance_var end + else + attrs[camelize(str).to_sym] = instance_var end end attrs end def class_name - self.class.name.split("::").last + self.class.name.split('::').last end def api_url - "/#{CGI.escape(class_name.downcase)}s" + "#{CGI.escape(class_name.downcase)}s" end private - def parse_onfleet_obj obj - if obj.is_a?(OnfleetObject) - if obj.respond_to?('id') && obj.id && (obj.is_a?(Destination) || obj.is_a?(Recipient) || obj.is_a?(Task)) - obj.id - else - obj.attributes - end - end + def camelize(string) + camelized = string.camelize(:lower) + camelized.gsub('Sms', 'SMS') + end + + def object_classes + @object_classes ||= { + 'address' => Address, + 'recipients' => Recipient, + 'recipient' => Recipient, + 'tasks' => Task, + 'destination' => Destination, + 'vehicle' => Vehicle + } + end + + def parse_onfleet_obj(obj) + return unless obj.is_a?(OnfleetObject) + if obj.respond_to?('id') && obj.id && (obj.is_a?(Destination) || obj.is_a?(Recipient) || obj.is_a?(Task)) + obj.id + else + obj.attributes end + end - def set_attributes params - params.each do |key, value| - key_underscore = Util.to_underscore(key) + def assign_attributes(params) + params.each do |key, value| + key_underscore = key.to_s.underscore - if klass = Util.object_classes[key.to_s] - case value - when Array - objs = [] - value.each do |v| - objs << klass.new(v) - end - value = objs - when Hash - value = klass.new(value) + if (klass = object_classes[key.to_s]) + case value + when Array + objs = [] + value.each do |v| + objs << klass.new(v) end + value = objs + when Hash + value = klass.new(value) end - - if respond_to?("#{key_underscore}=") - send(:"#{key_underscore}=", value) - else - add_attrs({"#{key_underscore}" => value}) - end - end - end - def add_attrs attrs - attrs.each do |var, value| - self.class.class_eval { attr_accessor var } - instance_variable_set "@#{var}", value + if respond_to?("#{key_underscore}=") + send(:"#{key_underscore}=", value) + else + add_attrs(key_underscore.to_s => value) end end + end + def add_attrs(attrs) + attrs.each do |var, value| + self.class.class_eval { attr_accessor var } + instance_variable_set "@#{var}", value + end + end end end + diff --git a/lib/onfleet-ruby/organization.rb b/lib/onfleet-ruby/organization.rb index 95d5ad6..c67b512 100644 --- a/lib/onfleet-ruby/organization.rb +++ b/lib/onfleet-ruby/organization.rb @@ -1,19 +1,14 @@ module Onfleet class Organization < OnfleetObject - class << self def get - url = "/organization" - response = Onfleet.request(url, :get) - Util.constantize("#{self}").new(response) + new(Onfleet.request('organization', :get)) end - def get_delegatee_details id - url = "/organizations/#{id}" - response = Onfleet.request(url, :get) - Util.constantize("#{self}").new(response) + def get_delegatee_details(id) + new(Onfleet.request("organizations/#{id}", :get)) end end - end end + diff --git a/lib/onfleet-ruby/recipient.rb b/lib/onfleet-ruby/recipient.rb index cc9ce68..5041cb5 100644 --- a/lib/onfleet-ruby/recipient.rb +++ b/lib/onfleet-ruby/recipient.rb @@ -8,7 +8,8 @@ class Recipient < OnfleetObject include Onfleet::Actions::QueryMetadata def self.api_url - "/recipients" + 'recipients' end end end + diff --git a/lib/onfleet-ruby/task.rb b/lib/onfleet-ruby/task.rb index c3deeac..46749e5 100644 --- a/lib/onfleet-ruby/task.rb +++ b/lib/onfleet-ruby/task.rb @@ -9,16 +9,16 @@ class Task < OnfleetObject include Onfleet::Actions::QueryMetadata def self.api_url - '/tasks' + 'tasks' end def complete # CURRENTLY DOESN'T WORK - url = "#{self.url}/#{self.id}/complete" - params = {"completionDetails" => {"success" => true }} + url = "#{self.url}/#{id}/complete" + params = { 'completionDetails' => { 'success' => true } } Onfleet.request(url, :post, params) true end - end end + diff --git a/lib/onfleet-ruby/team.rb b/lib/onfleet-ruby/team.rb index a4485d3..9a75350 100644 --- a/lib/onfleet-ruby/team.rb +++ b/lib/onfleet-ruby/team.rb @@ -4,7 +4,8 @@ class Team < OnfleetObject include Onfleet::Actions::Get def self.api_url - '/teams' + 'teams' end end end + diff --git a/lib/onfleet-ruby/util.rb b/lib/onfleet-ruby/util.rb deleted file mode 100644 index 9ca8062..0000000 --- a/lib/onfleet-ruby/util.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Onfleet - class Util - SPECIAL_PARSE = { "skip_sms_notifications" => "skipSMSNotifications" } - - def self.constantize class_name - Object.const_get(class_name) - end - - def self.to_underscore key - if key.kind_of?(Symbol) - key = key.to_s - end - key.gsub(/::/, '/'). - gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). - gsub(/([a-z\d])([A-Z])/,'\1_\2'). - tr("-", "_"). - downcase - end - - def self.to_camel_case_lower str - SPECIAL_PARSE[str] || str.camelize(:lower) - end - - def self.object_classes - @object_classes ||= { - "address" => Address, - "recipients" => Recipient, - "recipient" => Recipient, - "tasks" => Task, - "destination" => Destination, - "vehicle" => Vehicle - } - end - end - -end diff --git a/lib/onfleet-ruby/vehicle.rb b/lib/onfleet-ruby/vehicle.rb index 0ecd324..4baaaf6 100644 --- a/lib/onfleet-ruby/vehicle.rb +++ b/lib/onfleet-ruby/vehicle.rb @@ -2,3 +2,4 @@ module Onfleet class Vehicle < OnfleetObject end end + diff --git a/lib/onfleet-ruby/webhook.rb b/lib/onfleet-ruby/webhook.rb index b6e234e..611640f 100644 --- a/lib/onfleet-ruby/webhook.rb +++ b/lib/onfleet-ruby/webhook.rb @@ -5,9 +5,9 @@ class Webhook < OnfleetObject include Onfleet::Actions::Save include Onfleet::Actions::Delete - def self.api_url - '/webhooks' + 'webhooks' end end end + diff --git a/lib/onfleet-ruby/worker.rb b/lib/onfleet-ruby/worker.rb index 7fa419c..2485cee 100644 --- a/lib/onfleet-ruby/worker.rb +++ b/lib/onfleet-ruby/worker.rb @@ -9,7 +9,8 @@ class Worker < OnfleetObject include Onfleet::Actions::QueryMetadata def self.api_url - '/workers' + 'workers' end end end + diff --git a/onfleet-ruby.gemspec b/onfleet-ruby.gemspec index 5e4e916..f5ccc59 100644 --- a/onfleet-ruby.gemspec +++ b/onfleet-ruby.gemspec @@ -2,19 +2,24 @@ Gem::Specification.new do |s| s.name = 'onfleet-ruby' s.version = '0.1.4' s.date = '2016-04-08' - s.summary = "Onfleet ruby api" + s.summary = 'Onfleet ruby api' s.description = "To interact with Onfleet's API" - s.authors = ["Nick Wargnier"] + s.authors = ['Nick Wargnier'] s.email = 'nick@stylelend.com' s.homepage = 'http://rubygems.org/gems/onfleet-ruby' s.license = 'MIT' + s.add_dependency('activesupport', '>= 4.2') s.add_dependency('rest-client', '~> 1.4') - s.add_development_dependency("rspec",'~> 3.3.0', '>= 3.0.0') - + s.add_development_dependency('rake') + s.add_development_dependency('rspec', '~> 3.3') + s.add_development_dependency('rspec-its') + s.add_development_dependency('rubocop', '~> 0.55') + s.add_development_dependency('webmock', '~> 3.4') s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.require_paths = ['lib'] end + diff --git a/spec/onfleet/admin_spec.rb b/spec/onfleet/admin_spec.rb new file mode 100644 index 0000000..2f45d5c --- /dev/null +++ b/spec/onfleet/admin_spec.rb @@ -0,0 +1,70 @@ +RSpec.describe Onfleet::Admin do + let(:admin) { described_class.new(params) } + let(:params) { { id: 'an-admin', name: 'An Admin' } } + + describe ".list" do + subject { -> { described_class.list(query_params) } } + + context "with no filter" do + let(:query_params) { nil } + it_should_behave_like Onfleet::Actions::List, path: 'admins' + end + + context "with query params" do + let(:query_params) { { food: 'pizza', topping: 'mushroom' } } + it_should_behave_like Onfleet::Actions::List, path: 'admins?food=pizza&topping=mushroom' + end + + context "with a URL-unsafe query param" do + let(:query_params) { { food: 'green eggs & ham' } } + it_should_behave_like Onfleet::Actions::List, path: 'admins?food=green+eggs+%26+ham' + end + end + + describe ".create" do + subject { -> { described_class.create(params) } } + it_should_behave_like Onfleet::Actions::Create, path: 'admins' + end + + describe ".update" do + subject { -> { described_class.update(id, params) } } + let(:id) { 'an-admin' } + it_should_behave_like Onfleet::Actions::Update, path: 'admins/an-admin' + end + + describe ".delete" do + subject { -> { described_class.delete(id) } } + let(:id) { 'an-admin' } + it_should_behave_like Onfleet::Actions::Delete, path: 'admins/an-admin' + end + + describe ".query_by_metadata" do + subject { -> { described_class.query_by_metadata(metadata) } } + let(:metadata) { [{ name: 'color', type: 'string', value: 'ochre' }] } + it_should_behave_like Onfleet::Actions::QueryMetadata, path: 'admins' + end + + describe "#save" do + subject { -> { admin.save } } + + context "with an ID attribute" do + before { expect(params[:id]).to be } + it_should_behave_like Onfleet::Actions::Update, path: 'admins/an-admin' + end + + context "without an ID attribute" do + let(:params) { { name: 'An Admin' } } + it_should_behave_like Onfleet::Actions::Create, path: 'admins' + end + end + + %i[id name email type metadata].each do |attr| + describe "##{attr}" do + subject { admin.public_send(attr) } + let(:params) { { attr => value } } + let(:value) { 'pizza' } + it { should == value } + end + end +end + diff --git a/spec/onfleet/destination_spec.rb b/spec/onfleet/destination_spec.rb new file mode 100644 index 0000000..d02172f --- /dev/null +++ b/spec/onfleet/destination_spec.rb @@ -0,0 +1,62 @@ +RSpec.describe Onfleet::Destination do + let(:destination) { described_class.new(params) } + let(:params) { { id: 'a-destination', address: address_params } } + let(:address_params) { { street: '123 Main', city: 'Foo', state: 'TX' } } + + describe ".create" do + subject { -> { described_class.create(params) } } + it_should_behave_like Onfleet::Actions::Create, path: 'destinations' + end + + describe ".get" do + subject { -> { described_class.get(id) } } + let(:id) { 'a-destination' } + it_should_behave_like Onfleet::Actions::Get, path: 'destinations/a-destination' + end + + describe ".query_by_metadata" do + subject { -> { described_class.query_by_metadata(metadata) } } + let(:metadata) { [{ name: 'color', type: 'string', value: 'ochre' }] } + it_should_behave_like Onfleet::Actions::QueryMetadata, path: 'destinations' + end + + describe "#save" do + subject { -> { destination.save } } + + context "with an ID attribute" do + before { expect(params[:id]).to be } + it_should_behave_like Onfleet::Actions::Update, path: 'destinations/a-destination' + end + + context "without an ID attribute" do + let(:params) { { address: address_params } } + it_should_behave_like Onfleet::Actions::Create, path: 'destinations' + end + end + + describe "#address" do + subject { destination.address } + + context "when initialized with address params" do + let(:address_params) do + { + number: '123', + street: 'Main St.', + apartment: '', + city: 'Foo', + state: 'TX', + postalCode: '99999', + country: 'United States' + } + end + its(:number) { should == '123' } + its(:postal_code) { should == '99999' } + end + + context "when initialized with no address params" do + let(:address_params) { nil } + it { should be_nil } + end + end +end + diff --git a/spec/onfleet/organization_spec.rb b/spec/onfleet/organization_spec.rb new file mode 100644 index 0000000..efc3069 --- /dev/null +++ b/spec/onfleet/organization_spec.rb @@ -0,0 +1,25 @@ +RSpec.describe Onfleet::Organization do + let(:organization) { described_class.new(params) } + let(:params) { { id: 'an-org' } } + + describe ".get" do + subject { -> { described_class.get } } + it_should_behave_like Onfleet::Actions::Get, path: 'organization' + end + + describe ".get_delegatee_details" do + subject { -> { described_class.get_delegatee_details(id) } } + let(:id) { 'my-org' } + it_should_behave_like Onfleet::Actions::Get, path: 'organizations/my-org' + end + + %i[id name email country timezone time_created time_last_modified].each do |attr| + describe "##{attr}" do + subject { organization.public_send(attr) } + let(:params) { { attr => value } } + let(:value) { 'pizza' } + it { should == value } + end + end +end + diff --git a/spec/onfleet/recipient_spec.rb b/spec/onfleet/recipient_spec.rb new file mode 100644 index 0000000..0314486 --- /dev/null +++ b/spec/onfleet/recipient_spec.rb @@ -0,0 +1,75 @@ +RSpec.describe Onfleet::Recipient do + let(:recipient) { described_class.new(params) } + let(:params) { { id: 'a-recipient', name: 'Recipient Jones' } } + + describe ".create" do + subject { -> { described_class.create(params) } } + it_should_behave_like Onfleet::Actions::Create, path: 'recipients' + + context "with the `skip_sms_notifications` attribute" do + set_up_request_stub(:post, 'recipients') + let(:params) { { skip_sms_notifications: true } } + let(:response_body) { { id: 'an-object' } } + + it "should camelize the attribute name properly" do + subject.call + expect( + a_request(:post, url).with(body: { 'skipSMSNotifications' => true }.to_json) + ).to have_been_made.once + end + end + end + + describe ".get" do + subject { -> { described_class.get(id) } } + let(:id) { 'a-recipient' } + it_should_behave_like Onfleet::Actions::Get, path: 'recipients/a-recipient' + end + + describe ".update" do + subject { -> { described_class.update(id, params) } } + let(:id) { 'a-recipient' } + it_should_behave_like Onfleet::Actions::Update, path: 'recipients/a-recipient' + + context "with the `skip_sms_notifications` attribute" do + set_up_request_stub(:put, 'recipients/a-recipient') + let(:params) { { id: 'a-recipient', skip_sms_notifications: true } } + let(:response_body) { { id: 'an-object' } } + + it "should camelize the attribute name properly" do + subject.call + expect( + a_request(:put, url).with(body: { id: 'a-recipient', 'skipSMSNotifications' => true }.to_json) + ).to have_been_made.once + end + end + end + + describe ".find" do + subject { -> { described_class.find(attribute, value) } } + let(:attribute) { 'name' } + let(:value) { 'Ma Bell' } + it_should_behave_like Onfleet::Actions::Find, path: "recipients/name/Ma+Bell" + end + + describe ".query_by_metadata" do + subject { -> { described_class.query_by_metadata(metadata) } } + let(:metadata) { [{ name: 'color', type: 'string', value: 'ochre' }] } + it_should_behave_like Onfleet::Actions::QueryMetadata, path: 'recipients' + end + + describe "#save" do + subject { -> { recipient.save } } + + context "with an ID attribute" do + before { expect(params[:id]).to be } + it_should_behave_like Onfleet::Actions::Update, path: 'recipients/a-recipient' + end + + context "without an ID attribute" do + let(:params) { { name: 'Recipient Jones' } } + it_should_behave_like Onfleet::Actions::Create, path: 'recipients' + end + end +end + diff --git a/spec/onfleet/task_spec.rb b/spec/onfleet/task_spec.rb new file mode 100644 index 0000000..593abdf --- /dev/null +++ b/spec/onfleet/task_spec.rb @@ -0,0 +1,123 @@ +RSpec.describe Onfleet::Task do + let(:task) { described_class.new(params) } + let(:params) { { id: 'a-task', short_id: 'at', recipients: ['jeff'] } } + + describe ".list" do + subject { -> { described_class.list(query_params) } } + + context "with no filter" do + let(:query_params) { nil } + it_should_behave_like Onfleet::Actions::List, path: 'tasks' + end + + context "with query params" do + let(:query_params) { { food: 'pizza', topping: 'mushroom' } } + it_should_behave_like Onfleet::Actions::List, path: 'tasks?food=pizza&topping=mushroom' + end + + context "with a URL-unsafe query param" do + let(:query_params) { { food: 'green eggs & ham' } } + it_should_behave_like Onfleet::Actions::List, path: 'tasks?food=green+eggs+%26+ham' + end + end + + describe ".create" do + subject { -> { described_class.create(params) } } + it_should_behave_like Onfleet::Actions::Create, path: 'tasks' + + context "with the skip_sms_notification override attribute" do + set_up_request_stub(:post, 'tasks') + let(:params) { { recipient_skip_sms_notifications: true } } + let(:response_body) { { id: 'an-object' } } + + it "should camelize the attribute name properly" do + subject.call + expect( + a_request(:post, url).with(body: { recipientSkipSMSNotifications: true }.to_json) + ).to have_been_made.once + end + end + + context "with barcode attributes" do + set_up_request_stub(:post, 'tasks') + let(:params) { { barcodes: [{ data: 'abc', block_completion: true }] } } + let(:response_body) { { id: 'an-object' } } + + it "should camelize the attribute name properly" do + pending('JSON subkeys of non-Onfleet objects do not camelize') + subject.call + expect( + a_request(:post, url).with(body: { barcodes: [{ data: 'abc', 'blockCompletion' => true }] }.to_json) + ).to have_been_made.once + end + end + end + + describe ".get" do + subject { -> { described_class.get(id) } } + let(:id) { 'a-task' } + it_should_behave_like Onfleet::Actions::Get, path: 'tasks/a-task' + end + + describe ".update" do + subject { -> { described_class.update(id, params) } } + let(:id) { 'a-task' } + it_should_behave_like Onfleet::Actions::Update, path: 'tasks/a-task' + + context "with the skip_sms_notification override attribute" do + set_up_request_stub(:put, 'tasks/a-task') + let(:params) { { id: 'a-task', recipient_skip_sms_notifications: true } } + let(:response_body) { { id: 'an-object' } } + + # The current implementation -- using instance variables -- makes it impossible + # to have this example pass deterministically. + xit "should camelize the attribute name properly" do + subject.call + expect( + a_request(:put, url).with(body: { id: 'a-task', recipientSkipSMSNotifications: true }.to_json) + ).to have_been_made.once + end + end + + context "with barcode attributes" do + set_up_request_stub(:put, 'tasks/a-task') + let(:params) { { id: 'a-task', barcodes: [{ data: 'abc', block_completion: true }] } } + let(:response_body) { { id: 'an-object' } } + + it "should camelize the attribute name properly" do + pending('JSON subkeys of non-Onfleet objects do not camelize') + subject.call + expect( + a_request(:put, url).with(body: { barcodes: [{ data: 'abc', 'blockCompletion' => true }] }.to_json) + ).to have_been_made.once + end + end + end + + describe ".delete" do + subject { -> { described_class.delete(id) } } + let(:id) { 'an-task' } + it_should_behave_like Onfleet::Actions::Delete, path: 'tasks/an-task' + end + + describe ".query_by_metadata" do + subject { -> { described_class.query_by_metadata(metadata) } } + let(:metadata) { [{ name: 'color', type: 'string', value: 'ochre' }] } + it_should_behave_like Onfleet::Actions::QueryMetadata, path: 'tasks' + end + + describe "#save" do + subject { -> { task.save } } + + context "with an ID attribute" do + before { expect(params[:id]).to be } + it_should_behave_like Onfleet::Actions::Update, path: 'tasks/a-task' + end + + context "without an ID attribute" do + let(:params) { { short_id: 'at', recipients: ['jeff'] } } + it_should_behave_like Onfleet::Actions::Create, path: 'tasks' + end + end +end + diff --git a/spec/onfleet/team_spec.rb b/spec/onfleet/team_spec.rb new file mode 100644 index 0000000..5a62461 --- /dev/null +++ b/spec/onfleet/team_spec.rb @@ -0,0 +1,30 @@ +RSpec.describe Onfleet::Team do + let(:team) { described_class.new(params) } + let(:params) { { id: 'a-team', name: 'Detroit Redwings' } } + + describe ".list" do + subject { -> { described_class.list(query_params) } } + + context "with no filter" do + let(:query_params) { nil } + it_should_behave_like Onfleet::Actions::List, path: 'teams' + end + + context "with query params" do + let(:query_params) { { food: 'pizza', topping: 'mushroom' } } + it_should_behave_like Onfleet::Actions::List, path: 'teams?food=pizza&topping=mushroom' + end + + context "with a URL-unsafe query param" do + let(:query_params) { { food: 'green eggs & ham' } } + it_should_behave_like Onfleet::Actions::List, path: 'teams?food=green+eggs+%26+ham' + end + end + + describe ".get" do + subject { -> { described_class.get(id) } } + let(:id) { 'a-team' } + it_should_behave_like Onfleet::Actions::Get, path: 'teams/a-team' + end +end + diff --git a/spec/onfleet/webhook_spec.rb b/spec/onfleet/webhook_spec.rb new file mode 100644 index 0000000..05eec79 --- /dev/null +++ b/spec/onfleet/webhook_spec.rb @@ -0,0 +1,49 @@ +RSpec.describe Onfleet::Webhook do + let(:webhook) { described_class.new(params) } + let(:params) { { id: 'a-webhook', url: 'https://example.com', is_enabled: true } } + + describe ".list" do + subject { -> { described_class.list(query_params) } } + + context "with no filter" do + let(:query_params) { nil } + it_should_behave_like Onfleet::Actions::List, path: 'webhooks' + end + + context "with query params" do + let(:query_params) { { food: 'pizza', topping: 'mushroom' } } + it_should_behave_like Onfleet::Actions::List, path: 'webhooks?food=pizza&topping=mushroom' + end + + context "with a URL-unsafe query param" do + let(:query_params) { { food: 'green eggs & ham' } } + it_should_behave_like Onfleet::Actions::List, path: 'webhooks?food=green+eggs+%26+ham' + end + end + + describe ".create" do + subject { -> { described_class.create(params) } } + it_should_behave_like Onfleet::Actions::Create, path: 'webhooks' + end + + describe ".delete" do + subject { -> { described_class.delete(id) } } + let(:id) { 'a-webhook' } + it_should_behave_like Onfleet::Actions::Delete, path: 'webhooks/a-webhook' + end + + describe "#save" do + subject { -> { webhook.save } } + + context "with an ID attribute" do + before { expect(params[:id]).to be } + it_should_behave_like Onfleet::Actions::Update, path: 'webhooks/a-webhook' + end + + context "without an ID attribute" do + let(:params) { { name: 'An Webhook' } } + it_should_behave_like Onfleet::Actions::Create, path: 'webhooks' + end + end +end + diff --git a/spec/onfleet/worker_spec.rb b/spec/onfleet/worker_spec.rb new file mode 100644 index 0000000..dded9b6 --- /dev/null +++ b/spec/onfleet/worker_spec.rb @@ -0,0 +1,67 @@ +RSpec.describe Onfleet::Worker do + let(:worker) { described_class.new(params) } + let(:params) { { id: 'a-worker', name: 'F. Prefect', phone: '5551212' } } + + describe ".list" do + subject { -> { described_class.list(query_params) } } + + context "with no filter" do + let(:query_params) { nil } + it_should_behave_like Onfleet::Actions::List, path: 'workers' + end + + context "with query params" do + let(:query_params) { { food: 'pizza', topping: 'mushroom' } } + it_should_behave_like Onfleet::Actions::List, path: 'workers?food=pizza&topping=mushroom' + end + + context "with a URL-unsafe query param" do + let(:query_params) { { food: 'green eggs & ham' } } + it_should_behave_like Onfleet::Actions::List, path: 'workers?food=green+eggs+%26+ham' + end + end + + describe ".create" do + subject { -> { described_class.create(params) } } + it_should_behave_like Onfleet::Actions::Create, path: 'workers' + end + + describe ".get" do + subject { -> { described_class.get(id) } } + let(:id) { 'a-worker' } + it_should_behave_like Onfleet::Actions::Get, path: 'workers/a-worker' + end + + describe ".update" do + subject { -> { described_class.update(id, params) } } + let(:id) { 'a-worker' } + it_should_behave_like Onfleet::Actions::Update, path: 'workers/a-worker' + end + + describe ".delete" do + subject { -> { described_class.delete(id) } } + let(:id) { 'a-worker' } + it_should_behave_like Onfleet::Actions::Delete, path: 'workers/a-worker' + end + + describe ".query_by_metadata" do + subject { -> { described_class.query_by_metadata(metadata) } } + let(:metadata) { [{ name: 'color', type: 'string', value: 'ochre' }] } + it_should_behave_like Onfleet::Actions::QueryMetadata, path: 'workers' + end + + describe "#save" do + subject { -> { worker.save } } + + context "with an ID attribute" do + before { expect(params[:id]).to be } + it_should_behave_like Onfleet::Actions::Update, path: 'workers/a-worker' + end + + context "without an ID attribute" do + let(:params) { { name: 'An Worker' } } + it_should_behave_like Onfleet::Actions::Create, path: 'workers' + end + end +end + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8c9991f..feb56fb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,2 +1,78 @@ -require 'onfleet-ruby' -require File.expand_path('../test_data', __FILE__) +require 'rspec/its' +require 'webmock/rspec' +require File.expand_path(File.join('..', 'lib', 'onfleet-ruby'), __dir__) + +Dir.glob(File.expand_path(File.join('support', '**', '*.rb'), __dir__)).each { |file| require file } + +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = 'spec/examples.txt' + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed + + config.before { Onfleet.api_key = 'TEST API KEY' } +end + diff --git a/spec/support/http_requests/shared_examples.rb b/spec/support/http_requests/shared_examples.rb new file mode 100644 index 0000000..48d5c22 --- /dev/null +++ b/spec/support/http_requests/shared_examples.rb @@ -0,0 +1,126 @@ +RSpec.shared_examples_for "an action that makes a request to Onfleet" do |method:| + it "should include the base64-encoded API key in the auth header" do + encoded_api_key = Base64.urlsafe_encode64(Onfleet.api_key) + + subject.call + expect( + a_request(method, url).with(headers: { 'Authorization' => "Basic #{encoded_api_key}" }) + ).to have_been_made.once + end + + it "should specify that it will accept JSON" do + subject.call + expect( + a_request(method, url).with(headers: { 'Accept' => 'application/json' }) + ).to have_been_made.once + end + + if %i[post put patch].include?(method.to_sym) + it "should set the content type to JSON" do + subject.call + expect( + a_request(method, url).with(headers: { 'Content-Type' => 'application/json' }) + ).to have_been_made.once + end + end + + context "without valid authentication" do + let(:response) { { status: 401, body: { message: 'bad auth' }.to_json } } + it { should raise_error(Onfleet::AuthenticationError) } + end + + context "without valid authorization" do + let(:response) { { status: 404, body: { message: 'bad auth' }.to_json } } + it { should raise_error(Onfleet::InvalidRequestError) } + end + + context "when an unspecified error occurs" do + let(:response) { { status: 500, body: { message: 'all bad' }.to_json } } + it { should raise_error(Onfleet::OnfleetError) } + end +end + +RSpec.shared_examples_for Onfleet::Actions::Get do |path:| + set_up_request_stub(:get, path) + let(:response_body) { { id: 'an-object' } } + it_should_behave_like "an action that makes a request to Onfleet", method: :get +end + +RSpec.shared_examples_for Onfleet::Actions::List do |path:| + set_up_request_stub(:get, path) + let(:response_body) { [{ id: 'an-object' }, { id: 'another-object' }] } + it_should_behave_like "an action that makes a request to Onfleet", method: :get +end + +RSpec.shared_examples_for Onfleet::Actions::Create do |path:| + set_up_request_stub(:post, path) + let(:response_body) { { id: 'an-object' } } + + it_should_behave_like "an action that makes a request to Onfleet", method: :post + + it "should send the object params, not including ID, in JSON" do + expected_params = camelize_keys(params.stringify_keys.except('id')) + + subject.call + expect( + a_request(:post, url).with(body: expected_params.to_json) + ).to have_been_made.once + end +end + +RSpec.shared_examples_for Onfleet::Actions::Update do |path:| + set_up_request_stub(:put, path) + let(:response_body) { { id: 'an-object' } } + + it_should_behave_like "an action that makes a request to Onfleet", method: :put + + # The current implementation -- using instance variables -- makes it impossible + # to have this example pass deterministically. + xit "should send the object params, including ID, in JSON" do + expected_params = camelize_keys(params.merge(id: id)) + + subject.call + expect( + a_request(:put, url).with(body: expected_params.to_json) + ).to have_been_made.once + end +end + +RSpec.shared_examples_for Onfleet::Actions::Delete do |path:| + set_up_request_stub(:delete, path) + let(:response_body) { '' } + + it_should_behave_like "an action that makes a request to Onfleet", method: :delete +end + +RSpec.shared_examples_for Onfleet::Actions::Find do |path:| + set_up_request_stub(:get, path) + let(:response_body) { { id: 'an-object' } } + it_should_behave_like "an action that makes a request to Onfleet", method: :get +end + +RSpec.shared_examples_for Onfleet::Actions::QueryMetadata do |path:| + set_up_request_stub(:post, path + '/metadata') + let(:response_body) { [{ id: 'an-object' }, { id: 'another-object' }] } + it_should_behave_like "an action that makes a request to Onfleet", method: :post + + it "should send metadata in JSON" do + subject.call + expect( + a_request(:post, url).with(body: metadata.to_json) + ).to have_been_made.once + end +end + +def set_up_request_stub(method, path) + let(:url) { URI.join(Onfleet.base_url, path).to_s } + let(:response) { { status: 200, body: response_body.to_json } } + before { stub_request(method, url).to_return(response) } +end + +def camelize_keys(hash) + hash.inject({}) do |accumulator, (key, value)| + accumulator.merge(key.camelize(:lower) => value) + end +end + diff --git a/spec/test_data.rb b/spec/test_data.rb deleted file mode 100644 index df73c60..0000000 --- a/spec/test_data.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Onfleet - module TestData - def recipient - - end - - def destination - - end - - def task - end - end -end