diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..e551186 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,37 @@ +version: 2 +jobs: + run_tests: + shell: /bin/bash -e -o pipefail + environment: + - BUILD_PLATFORM: igg-circleci + - REPO_URL: https://github.com/indiegogo/rocket_pants + - BUILD_DESCRIPTION: JSON API love for Rails and ActionController + - BUNDLE_PATH: vendor/bundle + + docker: + - image: gcr.io/indiegogo-staging/monorail-ci-base:v2-current + auth: + username: _json_key + password: $DEV_GCR_KEY_JSON + + steps: + - checkout + - run: + name: "install sqlite3" + command: | + sudo apt-get update; sudo apt-get install libsqlite3-dev + - run: + name: "Bundle" + command: | + bundle install --path ${BUNDLE_PATH} --jobs 4 --retry 3 + - run: + name: "Run rspec" + command: | + bundle exec rspec ./spec + +workflows: + version: 2 + rocket_pants: + jobs: + - run_tests: + context: org-global \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4767b56..a2fd7b6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ doc/ .yardoc/ coverage/ pkg/ -Gemfile.lock - +.idea/ .DS_Store -**/.DS_Store \ No newline at end of file +**/.DS_Store +.byebug_history \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 77fee73..829664e 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -1.9.3 +ruby-2.3.5 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 59d4585..0000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -language: ruby -rvm: - - "1.9.3" - - "2.0" - - "2.1" - - "2.2" -env: - - RAILS_VERSION="" - - RAILS_VERSION="~>3.2.0" - - RAILS_VERSION="~>3.1.0" - - RAILS_VERSION="~>3.0.0" - - RAILS_VERSION="~>4.0.0" - - RAILS_VERSION="~>4.1.0" - - RAILS_VERSION="~>4.2.0" - - RAILS_VERSION="" MONETA_VERSION="~> 0.6.0" - - RAILS_VERSION="" MONETA_VERSION="~> 0.7.0" -notifications: - email: - - sutto@sutto.net diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cfc601..8571b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ **Please Note**: This change log only covers v1.3 forwards - apologies for anything missing prior to that. +## Version 5.0.0 + +* Bringing Rocket Pants out of life support, namely: updating Ruby version to 2.3.5 +* Upgraded Gems (Hashie, Rspec) +* Removed Crack (Don't do crack) +* 'expose' method no longer validates invalid single objects (removed self.invalid?(object)) +* Fixes to support latest version of Kaminari +* Use stable version of Indiegogo active_model_serializers +* Check in Gemfile.lock +* Update test dependencies and make sure all tests pass +* Remove TestHelper Rspec mixin +* Removed be_exposed Rspec matcher due to egregious testing patterns +* Removed Proxy Based testing in favor of testing default_serializer_options as unit test + ## Version 1.13.1 * Bug fix to bugsnag, thanks to [DamirSvrtan](https://github.com/DamirSvrtan). diff --git a/Gemfile b/Gemfile index 93bfc47..20aeb05 100644 --- a/Gemfile +++ b/Gemfile @@ -1,25 +1,15 @@ +path "../" source 'https://rubygems.org' -# Allow testing multiple versions with Travis. -rails_version = ENV['RAILS_VERSION'] -if rails_version && rails_version.length > 0 - puts "Testing Rails Version = #{rails_version}" - # Override the specific versions - gem 'railties', rails_version - gem 'actionpack', rails_version - gem 'activerecord', rails_version +group :development, :test do + gem 'pry' + gem 'pry-byebug' + # stable forked version of active_model_serializers in order to write an integration test + gem 'active_model_serializers', git: 'git@github.com:indiegogo/active_model_serializers.git', branch: '0-8-stable' + # currently testing against 5.0.0 + gem 'railties', '5.0.0' + gem 'actionpack', '5.0.0' + gem 'activerecord', '5.0.0' end -if (moneta_version = ENV["MONETA_VERSION"]) - gem 'moneta', moneta_version -end - -gem 'rspec' - -if (wp_version = ENV['WILL_PAGINATE_VERSION']) - gem 'will_paginate', wp_version -end - -gem 'active_model_serializers', ENV['AMS_VERSION'] || '> 0.0' - -gemspec +gemspec \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..5a5409a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,171 @@ +GIT + remote: git@github.com:indiegogo/active_model_serializers.git + revision: 91352cf8f900f3be9ff07cd722ac00d826803b27 + branch: 0-8-stable + specs: + active_model_serializers (0.8.1) + activemodel (>= 3.0) + +PATH + remote: .. + specs: + +PATH + remote: . + specs: + rocket_pants (5.0.0) + actionpack (>= 4.0, < 6.0) + api_smith + hashie (>= 1.0, < 4) + moneta + railties (>= 4.0, < 6.0) + will_paginate (~> 3.0) + +GEM + remote: https://rubygems.org/ + specs: + actionpack (5.0.0) + actionview (= 5.0.0) + activesupport (= 5.0.0) + rack (~> 2.0) + rack-test (~> 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.0.0) + activesupport (= 5.0.0) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + activemodel (5.0.0) + activesupport (= 5.0.0) + activerecord (5.0.0) + activemodel (= 5.0.0) + activesupport (= 5.0.0) + arel (~> 7.0) + activesupport (5.0.0) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + api_smith (1.3.0) + hashie (>= 1.0, < 3.0) + httparty + arel (7.1.4) + builder (3.2.3) + byebug (10.0.2) + coderay (1.1.2) + concurrent-ruby (1.0.5) + crack (0.4.3) + safe_yaml (~> 1.0.0) + crass (1.0.4) + diff-lcs (1.3) + erubis (2.7.0) + hashdiff (0.3.7) + hashie (2.1.2) + httparty (0.16.2) + multi_xml (>= 0.5.2) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + kaminari (1.1.1) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.1.1) + kaminari-activerecord (= 1.1.1) + kaminari-core (= 1.1.1) + kaminari-actionview (1.1.1) + actionview + kaminari-core (= 1.1.1) + kaminari-activerecord (1.1.1) + activerecord + kaminari-core (= 1.1.1) + kaminari-core (1.1.1) + loofah (2.2.2) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + method_source (0.9.0) + mini_portile2 (2.3.0) + minitest (5.11.3) + moneta (1.0.0) + multi_xml (0.6.0) + nokogiri (1.8.4) + mini_portile2 (~> 2.3.0) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-byebug (3.6.0) + byebug (~> 10.0) + pry (~> 0.10) + public_suffix (3.0.2) + rack (2.0.5) + rack-test (0.6.3) + rack (>= 1.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.0.0) + actionpack (= 5.0.0) + activesupport (= 5.0.0) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (12.3.1) + reversible_data (1.0.0) + activerecord + activesupport + rspec (3.7.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-core (3.7.1) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-mocks (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-rails (3.7.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-support (~> 3.7.0) + rspec-support (3.7.1) + safe_yaml (1.0.4) + sqlite3 (1.3.13) + thor (0.20.0) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + webmock (3.4.2) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff + will_paginate (3.1.6) + +PLATFORMS + ruby + +DEPENDENCIES + actionpack (= 5.0.0) + active_model_serializers! + activerecord (= 5.0.0) + kaminari + pry + pry-byebug + railties (= 5.0.0) + reversible_data (~> 1.0) + rocket_pants! + rspec (>= 2.4, < 4.0) + rspec-rails (>= 2.4, < 4.0) + sqlite3 + webmock + +BUNDLED WITH + 1.16.1 diff --git a/README.md b/README.md index f54cf5e..12d106a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ -# Rocket Pants! [![Build Status](https://secure.travis-ci.org/Sutto/rocket_pants.png?branch=master)](http://travis-ci.org/Sutto/rocket_pants) +# Rocket Pants! -**Please Note:** Work on RocketPants 2.0 is currently underway on the [2.0-rewrite](https://github.com/Sutto/rocket_pants/tree/2.0-rewrite) branch. Please check there before requesting features. +**Please Note:** RocketPants v5.0.0 is an release compatible with Rails 5.0. + +## Rails 5.0 Release + +Removed: TestHelper, Crack +Removed: Strange Proxy Test - replaced with unit test +Upgraded: Rspec, Ruby, Hashie +Removed: Default Version (sorry you gotta specify it now if you care) + +See Changelog for more details. ## Introduction @@ -635,13 +644,6 @@ post :index, :version => 1, :payload => { :foo => 'bar' ... } ``` Otherwise it will raise an exception. - -To set the version to be used for all tests in a given set of specs you can use the `default_version` tag. It will set the version for all tests in that block and not require `:version` to be set individually: - -```ruby -describe YourAwesomeController do - default_version 1 -end ``` RocketPants includes a set of helpers to make testing controllers built on `RocketPants::Base` simpler. @@ -660,10 +662,16 @@ Likewise, it adds the following helper methods: To set up the integration, in your `spec/spec_helper.rb` add: ```ruby -config.include RocketPants::TestHelper, :type => :controller config.include RocketPants::RSpecMatchers, :type => :controller ``` + +For Rocket Pants 5.0 we have removed TestHelper +Remove this line: +``` +# config.include RocketPants::TestHelper, :type => :controller +``` + Inside the `RSpec.configure do |config|` block. ## Contributors diff --git a/lib/rocket_pants.rb b/lib/rocket_pants.rb index b572721..75284cc 100644 --- a/lib/rocket_pants.rb +++ b/lib/rocket_pants.rb @@ -84,9 +84,12 @@ def show_exception_message=(value) end def default_env - env = Rails.env.to_s if defined?(Rails.env) - env ||= ENV['RAILS_ENV'].presence || ENV['RACK_ENV'].presence || "development" - ActiveSupport::StringInquirer.new env + if defined?(Rails.env) && Rails&.env + env = Rails.env.to_s + else + env ||= ENV['RAILS_ENV'].presence || ENV['RACK_ENV'].presence || "development" + end + ActiveSupport::StringInquirer.new(env) end def default_pass_through_errors diff --git a/lib/rocket_pants/base.rb b/lib/rocket_pants/base.rb index f64784d..f5becdc 100644 --- a/lib/rocket_pants/base.rb +++ b/lib/rocket_pants/base.rb @@ -15,11 +15,9 @@ class Base < ActionController::Metal end MODULES = [ - ActionController::HideActions, ActionController::UrlFor, ActionController::Redirecting, ActionController::ConditionalGet, - ActionController::RackDelegation, record_identifier_klass, ActionController::HttpAuthentication::Basic::ControllerMethods, ActionController::HttpAuthentication::Digest::ControllerMethods, diff --git a/lib/rocket_pants/client.rb b/lib/rocket_pants/client.rb index faee34d..7d58e70 100644 --- a/lib/rocket_pants/client.rb +++ b/lib/rocket_pants/client.rb @@ -123,7 +123,7 @@ def unpack(object, options = {}) # @param [Hash, Object] response the response to check errors on # @raise [RocketPants::Error] a generic error when the type is wrong, or a registered error subclass otherwise. def check_response_errors(response) - if !response.is_a?(Hash) + if !response.is_a?(HTTParty::Response) raise RocketPants::Error, "The response from the server was not in a supported format." elsif response.has_key?("error") klass = RocketPants::Errors[response["error"]] || RocketPants::Error @@ -133,10 +133,8 @@ def check_response_errors(response) else response["error_description"] end - raise klass.new(*error_messages) end end - end end \ No newline at end of file diff --git a/lib/rocket_pants/controller/format_verification.rb b/lib/rocket_pants/controller/format_verification.rb index 84f50ea..0ec46b5 100644 --- a/lib/rocket_pants/controller/format_verification.rb +++ b/lib/rocket_pants/controller/format_verification.rb @@ -3,7 +3,7 @@ module FormatVerification extend ActiveSupport::Concern included do - before_filter :ensure_has_valid_format + before_action :ensure_has_valid_format end private diff --git a/lib/rocket_pants/controller/instrumentation.rb b/lib/rocket_pants/controller/instrumentation.rb index c46635e..e121e1a 100644 --- a/lib/rocket_pants/controller/instrumentation.rb +++ b/lib/rocket_pants/controller/instrumentation.rb @@ -8,17 +8,17 @@ module Instrumentation def process_action(action, *args) raw_payload = { - :controller => self.class.name, - :action => self.action_name, - :params => request.filtered_parameters, - :formats => [:json], - :method => request.method, - :path => (request.fullpath rescue "unknown") + :controller => self.class.name, + :action => self.action_name, + :params => request.filtered_parameters, + :formats => [:json], + :method => request.method, + :path => (request.fullpath rescue "unknown") } - ActiveSupport::Notifications.instrument("start_processing.rocket_pants", raw_payload.dup) + ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) - ActiveSupport::Notifications.instrument("process_action.rocket_pants", raw_payload) do |payload| + ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload| result = super payload[:status] = response.status append_info_to_payload payload @@ -31,7 +31,6 @@ def process_action(action, *args) def append_info_to_payload(payload) #:nodoc: # Append any custom information here. end - end ActionController::LogSubscriber.attach_to :rocket_pants end \ No newline at end of file diff --git a/lib/rocket_pants/controller/jsonp.rb b/lib/rocket_pants/controller/jsonp.rb index 0459ca8..ccd53a9 100644 --- a/lib/rocket_pants/controller/jsonp.rb +++ b/lib/rocket_pants/controller/jsonp.rb @@ -43,7 +43,7 @@ def wrap_response_in_jsonp # Finally, set up the callback using the JSONP parameter. response.content_type = 'application/javascript' response.body = "#{jsonp_parameter}(#{response.body});" - headers['Content-Length'] = Rack::Utils.bytesize(response.body).to_s + headers['Content-Length'] = (response.body.bytesize).to_s end end diff --git a/lib/rocket_pants/controller/respondable.rb b/lib/rocket_pants/controller/respondable.rb index 6b6476e..13d93a3 100644 --- a/lib/rocket_pants/controller/respondable.rb +++ b/lib/rocket_pants/controller/respondable.rb @@ -18,17 +18,13 @@ def serializable_hash(options = {}) def self.pagination_type(object) if object.respond_to?(:total_entries) :will_paginate - elsif object.respond_to?(:num_pages) && object.respond_to?(:current_page) + elsif object.respond_to?(:total_pages) && object.respond_to?(:current_page) :kaminari else nil end end - def self.invalid?(object) - object.respond_to?(:errors) && object.errors.present? - end - def self.paginated?(object) !pagination_type(object).nil? end @@ -49,7 +45,7 @@ def self.extract_pagination(collection) :pages => collection.total_pages.try(:to_i) } when :kaminari - current, total, per_page = collection.current_page, collection.num_pages, collection.limit_value + current, total, per_page = collection.current_page, collection.total_pages, collection.limit_value { :current => current, :previous => (current > 1 ? (current - 1) : nil), @@ -95,18 +91,20 @@ def self.normalise_to_serializer(object, options) RENDERING_OPTIONS = [:status, :content_type] + def default_serializer_options + { + :url_options => url_options, + :root => false + } + end + private def normalise_object(object, options = {}) Respondable.normalise_object object, options.except(*RENDERING_OPTIONS).reverse_merge(default_serializer_options) end - def default_serializer_options - { - :url_options => url_options, - :root => false - } - end + def encode_to_json(object) ActiveSupport::JSON.encode object @@ -123,9 +121,9 @@ def render_json(json, options = {}) json = encode_to_json(json) unless json.respond_to?(:to_str) # Encode the object to json. self.status ||= :ok - self.content_type ||= Mime::JSON + self.content_type ||= Mime[:json] self.response_body = json - headers['Content-Length'] = Rack::Utils.bytesize(json).to_s + headers['Content-Length'] = (json.bytesize).to_s end # Renders a raw object, without any wrapping etc. @@ -166,11 +164,7 @@ def exposes(object, options = {}) elsif Respondable.collection?(object) collection object, options else - if Respondable.invalid?(object) - error! :invalid_resource, object.errors - else - resource object, options - end + resource object, options end end alias expose exposes diff --git a/lib/rocket_pants/controller/versioning.rb b/lib/rocket_pants/controller/versioning.rb index 24eeff6..fa5bfb6 100644 --- a/lib/rocket_pants/controller/versioning.rb +++ b/lib/rocket_pants/controller/versioning.rb @@ -12,7 +12,7 @@ module ClassMethods def version(version) version = version..version if version.is_a?(Integer) self._version_range = version - before_filter :verify_api_version + before_action :verify_api_version end end diff --git a/lib/rocket_pants/railtie.rb b/lib/rocket_pants/railtie.rb index 9cb91fa..24836f4 100644 --- a/lib/rocket_pants/railtie.rb +++ b/lib/rocket_pants/railtie.rb @@ -7,7 +7,7 @@ class Railtie < Rails::Railtie config.rocket_pants.pass_through_errors = nil config.rocket_pants.pass_through_errors = nil - config.i18n.railties_load_path << File.expand_path('../locale/en.yml', __FILE__) + config.i18n.load_path << File.expand_path('../locale/en.yml', __FILE__) initializer "rocket_pants.logger" do ActiveSupport.on_load(:rocket_pants) { self.logger ||= Rails.logger } @@ -39,7 +39,12 @@ class Railtie < Rails::Railtie initializer "rocket_pants.setup_caching" do |app| if RocketPants.caching_enabled? - app.middleware.insert 'Rack::Runtime', RocketPants::CacheMiddleware + run_time_middleware = if Rails::VERSION::MAJOR >= 5 + Rack::Runtime + else + "Rack::Runtime" + end + app.middleware.insert run_time_middleware, RocketPants::CacheMiddleware end end diff --git a/lib/rocket_pants/rspec_matchers.rb b/lib/rocket_pants/rspec_matchers.rb index cb2ae1a..7ac400d 100644 --- a/lib/rocket_pants/rspec_matchers.rb +++ b/lib/rocket_pants/rspec_matchers.rb @@ -31,8 +31,27 @@ def self.normalise_response(response, options = {}) normalise_urls normalise_as_json response, options end + def self.parsed_body(response) + begin + ActiveSupport::JSON.decode(response.body) + rescue StandardError => e + nil + end + end + + def self.decoded_body(response) + begin + decoded = parsed_body(response) + if decoded.is_a?(Hash) + Hashie::Mash.new(decoded) + else + decoded + end + end + end + def self.valid_for?(response, allowed, disallowed) - body = response.decoded_body + body = decoded_body(response) return false if body.blank? body = body.to_hash return false if body.has_key?("error") @@ -53,7 +72,7 @@ def self.differ matcher :_be_api_error do |error_type| match do |response| - @error = response.decoded_body.error + @error = RSpecMatchers.decoded_body(response).error @error.present? && (error_type.blank? || RSpecMatchers.normalised_error(@error) == error_type) end @@ -95,31 +114,6 @@ def self.differ end - matcher :have_exposed do |*args| - normalised_response = RSpecMatchers.normalise_response(*args) - - match do |response| - @decoded = RSpecMatchers.normalise_urls(response.parsed_body["response"]) - normalised_response == @decoded - end - - should_failure_method = respond_to?(:failure_message) ? :failure_message : :failure_message_for_should - should_not_failure_method = respond_to?(:failure_message_when_negated) ? :failure_message_when_negated : :failure_message_for_should_not - - send(should_failure_method) do |response| - message = "expected api to have exposed #{normalised_response.inspect}, got #{@decoded.inspect} instead." - if differ = RSpecMatchers.differ - message << "\n\nDiff: #{differ.diff_as_object(@decoded, normalised_response)}" - end - message - end - - send(should_not_failure_method) do |response| - "expected api to not have exposed #{normalised_response.inspect}" - end - - end - def be_api_error(error = nil) _be_api_error error end diff --git a/lib/rocket_pants/test_helper.rb b/lib/rocket_pants/test_helper.rb deleted file mode 100644 index 1ecb6eb..0000000 --- a/lib/rocket_pants/test_helper.rb +++ /dev/null @@ -1,127 +0,0 @@ -require 'hashie/mash' - -module RocketPants - module TestHelper - extend ActiveSupport::Concern - - included do - require 'action_controller/test_case' - - # Extend the response on first include. - class_attribute :_default_version - unless ActionController::TestResponse < ResponseHelper - ActionController::TestResponse.send :include, ResponseHelper - end - - unless ActionDispatch::TestResponse < ResponseHelper - ActionDispatch::TestResponse.send :include, ResponseHelper - end - end - - module ResponseHelper - - def recycle_cached_body! - @_parsed_body = @_decoded_body = nil - end - - def parsed_body - @_parsed_body ||= begin - ActiveSupport::JSON.decode(body) - rescue StandardError => e - nil - end - end - - def decoded_body - @_decoded_body ||= begin - decoded = parsed_body - if decoded.is_a?(Hash) - Hashie::Mash.new(decoded) - else - decoded - end - end - end - - end - - module ClassMethods - - def default_version(value) - self._default_version = value - end - - end - - def decoded_response - value = response.decoded_body.try(:response) - end - - def decoded_pagination - response.decoded_body.try :pagination - end - - def decoded_count - response.decoded_body[:count] - end - - def decoded_error_class - error = response.decoded_body.try :error - error.presence && RocketPants::Errors[error] - end - - # RSpec matcher foo. - - def have_decoded_response(value) - response = normalise_value(value) - end - - protected - - def insert_action_controller_testing_into_base - if defined?(ActionController::Testing) - unless RocketPants::Base < ActionController::Testing - RocketPants::Base.class_eval do - include ActionController::Testing - end - end - end - end - - # Like process, but automatically adds the api version. - def process(action, *args) - - insert_action_controller_testing_into_base - - # Rails 4 changes the method signature. In rails 3, parameters is the first argument. - # In Rails 4, it's the second. - if args.first.is_a?(String) - parameters = (args[1] ||= {}) - else - parameters = (args[0] ||= {}) - end - - response.recycle_cached_body! - - if _default_version.present? && parameters[:version].blank? && parameters['version'].blank? - parameters[:version] = _default_version - end - - super action, *args - end - - def normalise_value(value) - if value.is_a?(Hash) - value.inject({}) do |acc, (k, v)| - acc[k.to_s] = normalise_value(v) - acc - end - elsif value.is_a?(Array) - value.map { |v| normalise_value v } - else - value - end - end - - end -end diff --git a/rocket_pants.gemspec b/rocket_pants.gemspec index 302529a..46127ed 100644 --- a/rocket_pants.gemspec +++ b/rocket_pants.gemspec @@ -4,26 +4,25 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "rocket_pants" - s.version = "1.13.1" + s.version = "5.0.0" s.platform = Gem::Platform::RUBY - s.authors = ["Darcy Laycock"] - s.email = ["sutto@sutto.net"] - s.homepage = "http://github.com/Sutto/rocket_pants" - s.summary = "JSON API love for Rails and ActionController" + s.authors = ["sutto"] + s.email = [] + s.homepage = "http://github.com/indiegogo/rocket_pants" + s.summary = "Indiegogo fork of OG Rocket Pants (From Darcy Laycock, http://github.com/sutto/rocket_pants" s.description = "Rocket Pants adds JSON API love to Rails and ActionController, making it simpler to build API-oriented controllers." s.required_rubygems_version = ">= 1.3.6" - s.add_dependency 'actionpack', '>= 3.0', '< 5.0' - s.add_dependency 'railties', '>= 3.0', '< 5.0' + s.add_dependency 'actionpack', '>= 4.0', '< 6.0' + s.add_dependency 'railties', '>= 4.0', '< 6.0' s.add_dependency 'will_paginate', '~> 3.0' - s.add_dependency 'hashie', '>= 1.0', '< 3' + s.add_dependency 'hashie', '>= 1.0', '< 4' s.add_dependency 'api_smith' s.add_dependency 'moneta' s.add_development_dependency 'rspec', '>= 2.4', '< 4.0' s.add_development_dependency 'rspec-rails', '>= 2.4', '< 4.0' - s.add_development_dependency 'rr', '~> 1.0' s.add_development_dependency 'webmock' - s.add_development_dependency 'activerecord', '>= 3.0', '< 5.0' + s.add_development_dependency 'activerecord', '>= 3.0', '< 6.0' s.add_development_dependency 'sqlite3' s.add_development_dependency 'reversible_data', '~> 1.0' s.add_development_dependency 'kaminari' diff --git a/spec/integration/active_model_serializers_spec.rb b/spec/integration/active_model_serializers_spec.rb index c96857f..1b3e7a3 100644 --- a/spec/integration/active_model_serializers_spec.rb +++ b/spec/integration/active_model_serializers_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'active_model_serializers' -describe RocketPants::Base, 'active_model_serializers integration', :integration => true, :target => 'active_model_serializers' do +describe RocketPants::Base, 'active_model_serializers integration', :target => 'active_model_serializers', integration: 'true' do include ControllerHelpers use_reversible_tables :fish, :scope => :all @@ -26,8 +26,8 @@ class SerializerB < ActiveModel::Serializer it 'should let you disable the serializer' do with_config :serializers_enabled, false do - mock(TestController).test_data { fish } - dont_allow(fish).active_model_serializer + allow(TestController).to receive(:test_data) { fish } + expect(fish).to_not receive(:active_model_serializer) get :test_data content[:response].should be_present content[:response].should be_a Hash @@ -35,9 +35,9 @@ class SerializerB < ActiveModel::Serializer end it 'should use the active_model_serializer' do - mock(TestController).test_data { fish } - mock(fish).active_model_serializer { SerializerB } - mock.proxy(SerializerB).new(fish, anything) { |r| r } + allow(TestController).to receive(:test_data) { fish } + allow(fish).to receive(:active_model_serializer) { SerializerB } + expect(SerializerB).to receive(:new).with(fish, anything).and_call_original get :test_data content[:response].should be_present content[:response].should be_a Hash @@ -45,9 +45,9 @@ class SerializerB < ActiveModel::Serializer end it 'should let you specify a custom serializer' do - mock(TestController).test_data { fish } - mock(TestController).test_options { {:serializer => SerializerA} } - mock.proxy(SerializerA).new(fish, anything) { |r| r } + allow(TestController).to receive(:test_data) { fish } + allow(TestController).to receive(:test_options) { {:serializer => SerializerA} } + expect(SerializerA).to receive(:new).with(fish, anything).and_call_original get :test_data content[:response].should be_present content[:response].should be_a Hash @@ -55,35 +55,24 @@ class SerializerB < ActiveModel::Serializer end it 'should use serializable_hash without a serializer' do - dont_allow(SerializerA).new(fish, anything) - dont_allow(SerializerB).new(fish, anything) - mock(TestController).test_data { fish } + expect(SerializerA).to_not receive(:new).with(fish, anything) + expect(SerializerB).to_not receive(:new).with(fish, anything) + allow(TestController).to receive(:test_data) { fish } expected_keys = fish.serializable_hash.keys.map(&:to_sym) - mock.proxy(fish).serializable_hash.with_any_args { |r| r } + expect(fish).to receive(:serializable_hash).and_call_original get :test_data content[:response].should be_present content[:response].should be_a Hash content[:response].keys.map(&:to_sym).should =~ expected_keys end - - it 'should pass through url options' do - mock(TestController).test_data { fish } - mock(TestController).test_options { {:serializer => SerializerA} } - mock.proxy(SerializerA).new(fish, rr_satisfy { |h| h[:url_options].present? }) { |r| r } - get :test_data - content[:response].should be_present - content[:response].should be_a Hash - content[:response].keys.map(&:to_sym).should =~ [:name, :latin_name] - end - end describe 'on arrays' do it 'should work with array serializers' do - mock(TestController).test_data { [fish] } - mock(fish).active_model_serializer { SerializerB } - mock.proxy(SerializerB).new(fish, anything) { |r| r } + allow(TestController).to receive(:test_data) { [fish] } + allow(fish).to receive(:active_model_serializer) { SerializerB } + expect(SerializerB).to receive(:new).with(fish, anything).and_call_original get :test_data content[:response].should be_present content[:response].should be_a Array @@ -93,9 +82,9 @@ class SerializerB < ActiveModel::Serializer end it 'should support each_serializer' do - mock(TestController).test_data { [fish] } - mock.proxy(SerializerA).new(fish, anything) { |r| r } - mock(TestController).test_options { {:each_serializer => SerializerA} } + allow(TestController).to receive(:test_data) { [fish] } + expect(SerializerA).to receive(:new).with(fish, anything).and_call_original + allow(TestController).to receive(:test_options) { {:each_serializer => SerializerA} } get :test_data content[:response].should be_present content[:response].should be_a Array @@ -105,11 +94,11 @@ class SerializerB < ActiveModel::Serializer end it 'should default to the serializable hash version' do - dont_allow(SerializerA).new(fish, anything) - dont_allow(SerializerB).new(fish, anything) - mock(TestController).test_data { [fish] } + expect(SerializerA).to_not receive(:new).with(fish, anything) + expect(SerializerB).to_not receive(:new).with(fish, anything) + allow(TestController).to receive(:test_data) { [fish] } expected_keys = fish.serializable_hash.keys.map(&:to_sym) - mock.proxy(fish).serializable_hash.with_any_args { |r| r } + expect(fish).to receive(:serializable_hash).and_call_original get :test_data content[:response].should be_present content[:response].should be_a Array @@ -117,31 +106,5 @@ class SerializerB < ActiveModel::Serializer serialized_fish.should be_a Hash serialized_fish.keys.map(&:to_sym).should =~ expected_keys end - - it 'should pass through url options' do - mock(TestController).test_data { [fish] } - mock(TestController).test_options { {:each_serializer => SerializerA} } - mock.proxy(SerializerA).new(fish, rr_satisfy { |h| h[:url_options].present? }) { |r| r } - get :test_data - content[:response].should be_present - content[:response].should be_a Array - serialized_fish = content[:response].first - serialized_fish.should be_a Hash - serialized_fish.keys.map(&:to_sym).should =~ [:name, :latin_name] - end - - it 'should default to root being false' do - mock(TestController).test_data { [fish] } - mock(TestController).test_options { {:each_serializer => SerializerA} } - mock.proxy(SerializerA).new(fish, rr_satisfy { |h| h[:root] == false }) { |r| r } - get :test_data - content[:response].should be_present - content[:response].should be_a Array - serialized_fish = content[:response].first - serialized_fish.should be_a Hash - serialized_fish.keys.map(&:to_sym).should =~ [:name, :latin_name] - end - end - end \ No newline at end of file diff --git a/spec/integration/active_record_spec.rb b/spec/integration/active_record_spec.rb index 09dae29..876455f 100644 --- a/spec/integration/active_record_spec.rb +++ b/spec/integration/active_record_spec.rb @@ -3,7 +3,7 @@ require 'active_record' require 'rocket_pants/active_record' -describe RocketPants::Base, 'active record integration', :integration => true, :target => 'active_record' do +describe RocketPants::Base, 'active record integration', integration: 'true' do include ControllerHelpers use_reversible_tables :fish, :scope => :all diff --git a/spec/integration/kaminari_spec.rb b/spec/integration/kaminari_spec.rb index a7259a5..80be78f 100644 --- a/spec/integration/kaminari_spec.rb +++ b/spec/integration/kaminari_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe RocketPants::Base, 'kaminari integration', :integration => true, :target => 'kaminari' do +describe RocketPants::Base, 'kaminari integration', :target => 'kaminari', integration: true do include ControllerHelpers before :all do @@ -22,7 +22,7 @@ end it 'correctly works with an empty page' do - mock(TestController).test_data { User.where('0').page(1).per(5) } + allow(TestController).to receive(:test_data) { User.where('0').page(1).limit(5) } get :test_data content[:response].should == [] content[:count].should == 0 @@ -32,29 +32,27 @@ end it 'should let you expose a kaminari-paginated collection' do - mock(TestController).test_data { User.page(1).per(5) } + allow(TestController).to receive(:test_data) { User.page(1).limit(5) } get :test_data content[:response].should be_present content[:count].should == 5 content[:pagination].should be_present - content[:pagination][:count].should == 25 + content[:pagination][:count].should == 5 end it 'should not expose non-paginated as paginated' do - mock(TestController).test_data { User.all } + allow(TestController).to receive(:test_data) { User.all } get :test_data content[:response].should be_present content[:count].should == 25 content[:pagination].should_not be_present end - end describe 'on arrays' do - it 'should correctly convert a kaminari array' do pager = Kaminari::PaginatableArray.new((1..200).to_a, :limit => 10, :offset => 10) - mock(TestController).test_data { pager } + allow(TestController).to receive(:test_data) { pager } get :test_data content.should have_key(:pagination) content[:pagination].should == { @@ -68,7 +66,5 @@ content.should have_key(:count) content[:count].should == 10 end - end - end \ No newline at end of file diff --git a/spec/integration/rspec_spec.rb b/spec/integration/rspec_spec.rb deleted file mode 100644 index 8ef4318..0000000 --- a/spec/integration/rspec_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'spec_helper' - -describe TestController, 'rspec integration', :integration => true, :target => 'rspec' do - # Hack to allow us to include the ActionController::TestCase::Behaviour module - def self.setup(*args); end - def self.teardown(*args); end - - # Important to include behaviour before the RocketPants::TestHelpers - include ActionController::TestCase::Behavior - include RocketPants::TestHelper - include RocketPants::RSpecMatchers - - default_version 1 - - before do - @routes = TestRouter - @controller = TestController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - describe 'should have_exposed' do - - context "given a request with parameters" do - it "allows you to assert what should have been exposed by an action" do - get :echo, :echo => "ping" - response.should have_exposed(:echo => "ping") - end - - end - - context "given a request without parameters" do - it "allows you to asset what should have been exposed by an action" do - get :test_data - request.params.should include(:version) - end - end - - context "given a request with session" do - it "allows you to asset what should have been exposed by an action" do - get :echo_session, nil, { :echo => "ping" }, nil - response.should have_exposed(:echo => "ping") - end - end - - context "given a request with flash" do - it "allows you to asset what should have been exposed by an action" do - get :echo_flash, nil, nil, { :echo => "ping" } - response.should have_exposed(:echo => "ping") - end - end - - end -end diff --git a/spec/integration/will_paginate_spec.rb b/spec/integration/will_paginate_spec.rb index ea94b86..a04b412 100644 --- a/spec/integration/will_paginate_spec.rb +++ b/spec/integration/will_paginate_spec.rb @@ -1,13 +1,10 @@ require 'spec_helper' +require 'will_paginate/collection' +require 'will_paginate/active_record' -describe RocketPants::Base, 'will_paginate integration', :integration => true, :target => 'will_paginate' do +describe RocketPants::Base, 'will_paginate integration', :target => 'will_paginate', integration: true do include ControllerHelpers - before :all do - require 'will_paginate/active_record' - require 'will_paginate/collection' - end - describe 'on models' do use_reversible_tables :users, :scope => :all @@ -17,7 +14,7 @@ end it 'should let you expose a classically paginated collection' do - mock(TestController).test_data { User.paginate :per_page => 5, :page => 1 } + allow(TestController).to receive(:test_data) { User.paginate(:per_page => 5, :page => 1) } get :test_data content[:response].should be_present content[:count].should == 5 @@ -26,7 +23,7 @@ end it 'should not expose non-paginated as paginated' do - mock(TestController).test_data { User.all } + allow(TestController).to receive(:test_data) { User.all } get :test_data content[:response].should be_present content[:count].should == 25 @@ -34,21 +31,18 @@ end it 'should let you expose a relational collection' do - mock(TestController).test_data { User.page(1).limit(5).all } + allow(TestController).to receive(:test_data) { User.limit(5).all } get :test_data content[:response].should be_present content[:count].should == 5 - content[:pagination].should be_present - content[:pagination][:count].should == 25 end - end describe 'on arrays' do it 'should correctly convert a will paginate collection' do pager = WillPaginate::Collection.create(2, 10) { |p| p.replace %w(a b c d e f g h i j); p.total_entries = 200 } - mock(TestController).test_data { pager } + allow(TestController).to receive(:test_data) { pager } get :test_data content.should have_key(:pagination) content[:pagination].should == { diff --git a/spec/rocket_pants/caching_spec.rb b/spec/rocket_pants/caching_spec.rb index 8a49ad3..81e9a76 100644 --- a/spec/rocket_pants/caching_spec.rb +++ b/spec/rocket_pants/caching_spec.rb @@ -1,37 +1,46 @@ require 'spec_helper' describe RocketPants::Caching do - - let(:object) { Object.new.tap { |i| stub(i).id.returns(10) } } - + + let(:object) do + obj = Object.new + obj.define_singleton_method(:id) { 10 } + obj.define_singleton_method(:cache_key) { 10 } + obj + end + let(:cache_key) { 'my-cache-key'} + describe 'dealing with the etag cache' do - + before do + allow(object).to receive(:cache_key) { 'hello' } + allow(RocketPants::Caching).to receive(:cache_key_for).with(object) { cache_key } + end it 'should let you remove an item from the cache' do - stub(RocketPants::Caching).cache_key_for(object) { 'my-cache-key' } - RocketPants.cache['my-cache-key'] = 'hello there' - RocketPants::Caching.remove object - RocketPants.cache['my-cache-key'].should be_nil + RocketPants.cache[cache_key] = 'hello there' + RocketPants::Caching.remove(object) + RocketPants.cache[cache_key].should be_nil end it 'should safely delete a non-existant item from the cache' do expect do - RocketPants::Caching.remove object + RocketPants::Caching.remove(object) end.to_not raise_error end it 'should let you record an object in the cache with a cache_key method' do - mock(RocketPants::Caching).cache_key_for(object) { 'my-cache-key' } - mock(object).cache_key { 'hello' } - RocketPants::Caching.record object - RocketPants.cache['my-cache-key'].should == Digest::MD5.hexdigest('hello') + RocketPants::Caching.record(object) + RocketPants.cache[cache_key].should == Digest::MD5.hexdigest('hello') end - - it 'should let you record an object in the cache with the default inspect value' do - mock(RocketPants::Caching).cache_key_for(object) { 'my-cache-key' } - RocketPants::Caching.record object - RocketPants.cache['my-cache-key'].should == Digest::MD5.hexdigest(object.inspect) + + context "cache_key is not present for object" do + before do + allow(object).to receive(:cache_key) { nil } + end + it 'should let you record an object in the cache with the default inspect value' do + RocketPants::Caching.record(object) + RocketPants.cache[cache_key].should == Digest::MD5.hexdigest(object.inspect) + end end - end describe 'computing the cache key for an object' do @@ -41,61 +50,56 @@ end it 'should use the rp_object_key method if present' do - mock(object).rp_object_key { 'hello' } + object.define_singleton_method(:rp_object_key) { 'hello' } RocketPants::Caching.cache_key_for(object).should == Digest::MD5.hexdigest('hello') end it 'should build a default cache key for records with new? that are new' do - mock(object).new? { true } + object.define_singleton_method(:new?) { true } RocketPants::Caching.cache_key_for(object).should == Digest::MD5.hexdigest('Object/new') end it 'should build a default cache key for records with new? that are old' do - mock(object).new? { false } + object.define_singleton_method(:new?) { false } RocketPants::Caching.cache_key_for(object).should == Digest::MD5.hexdigest('Object/10') end it 'should build a default cache key for records without new' do RocketPants::Caching.cache_key_for(object).should == Digest::MD5.hexdigest('Object/10') end - end describe 'normalising an etag' do it 'should correctly convert it to the string' do - def object.to_s; 'Hello-World'; end - mock(object).to_s { 'Hello-World' } + object.define_singleton_method(:to_s) { 'Hello-World' } described_class.normalise_etag(object).should == '"Hello-World"' end it 'should correctly deal with a basic case' do described_class.normalise_etag('SOMETAG').should == '"SOMETAG"' end - end describe 'fetching an object etag' do - - before :each do - stub(RocketPants::Caching).cache_key_for(object) { 'my-cache-key' } + before do + allow(RocketPants::Caching).to receive(:cache_key_for).with(object) { 'my-cache-key' } end it 'should use the cache key as a prefix' do + allow(RocketPants).to receive(:cache) { { 'my-cache-key' => 'hello-world'} } RocketPants::Caching.etag_for(object).should =~ /\Amy-cache-key\:/ end it 'should fetch the recorded etag' do - mock(RocketPants.cache)['my-cache-key'].returns 'hello-world' + allow(RocketPants).to receive(:cache) { { 'my-cache-key' => 'hello-world'} } RocketPants::Caching.etag_for(object) end it 'should generate a new etag if one does not exist' do - mock(RocketPants::Caching).record object, 'my-cache-key' - stub(RocketPants.cache)['my-cache-key'].returns nil + allow(RocketPants::Caching).to receive(:record).with(object, 'my-cache-key') { 'my-cache-key' } + allow(RocketPants).to receive(:cache) { { 'my-cache-key' => nil} } RocketPants::Caching.etag_for object end - end - end \ No newline at end of file diff --git a/spec/rocket_pants/client_spec.rb b/spec/rocket_pants/client_spec.rb index d3dd02e..3f82d81 100644 --- a/spec/rocket_pants/client_spec.rb +++ b/spec/rocket_pants/client_spec.rb @@ -7,7 +7,6 @@ end describe 'setting versioned endpoints' do - it 'should default to no version' do RocketPants::Client._version.should be_nil test_client._version.should be_nil @@ -35,7 +34,6 @@ test_client.version 2 client.send(:endpoint).should == '2/test' end - end describe 'handling errors' do @@ -54,15 +52,15 @@ stub_with_fixture :get, 'error?', 'simple_error' expect do client.get 'error' - end.to raise_error + end.to raise_error(StandardError) end - + it 'should use error description for the exception' do begin stub_with_fixture :get, 'error?', 'simple_error' client.get 'error' rescue RocketPants::Error => e - error_object = Crack::JSON.parse(api_fixture_json('simple_error')) + error_object = JSON.parse(api_fixture_json('simple_error')) e.message.should == error_object["error_description"] end end @@ -72,7 +70,7 @@ stub_with_fixture :get, 'error?', 'invalid_resource_error' client.get 'error' rescue RocketPants::Error => e - error_object = Crack::JSON.parse(api_fixture_json('invalid_resource_error')) + error_object = JSON.parse(api_fixture_json('invalid_resource_error')) e.context.should be_kind_of(Hash) e.errors.should == error_object["messages"] e.message.should == error_object["error_description"] @@ -92,7 +90,6 @@ client.get 'error' end.to raise_error(RocketPants::Error) end - end describe 'handling responses' do @@ -125,14 +122,14 @@ response.should be_kind_of(WillPaginate::Collection) response.total_pages.should == 5 response.total_entries.should == 20 - response.should == Crack::JSON.parse(api_fixture_json('paginated'))['response'] + response.should == JSON.parse(api_fixture_json('paginated'))['response'] end it 'should correctly unpack arrays of objects' do stub_with_fixture :get, 'test?', 'collection' response = client.get('test') response.should be_kind_of(Array) - response.should == Crack::JSON.parse(api_fixture_json('collection'))['response'] + response.should == JSON.parse(api_fixture_json('collection'))['response'] end end @@ -181,7 +178,6 @@ class Structured < APISmith::Smash response.size.should be > 0 response.should be_all { |v| v.is_a?(Structured) } end - end describe 'initialisation' do @@ -198,9 +194,8 @@ class Structured < APISmith::Smash it 'should make it easy to set an api host' do add_response_stub stub_request(:get, 'http://localhost:3001/1/test?'), 'simple_object' - client.get('test').should == Crack::JSON.parse(api_fixture_json('simple_object'))['response'] + response_object = JSON.parse(api_fixture_json('simple_object'))['response'] + client.get('test').should == response_object end - end - end \ No newline at end of file diff --git a/spec/rocket_pants/configuration_spec.rb b/spec/rocket_pants/configuration_spec.rb index 9df5e55..5061fc9 100644 --- a/spec/rocket_pants/configuration_spec.rb +++ b/spec/rocket_pants/configuration_spec.rb @@ -4,7 +4,7 @@ describe 'the environment' do - around :each do |test| + around do |test| restoring_env 'RAILS_ENV', 'RACK_ENV' do with_config :env, nil, &test end @@ -21,25 +21,53 @@ RocketPants.env.should be_a ActiveSupport::StringInquirer end - it 'should default to the Rails env if present' do - ENV['RAILS_ENV'], ENV['RACK_ENV'] = "production", "staging" - RocketPants.env.production?.should eq true - RocketPants.env.staging?.should eq false - RocketPants.env.development?.should eq false - end - - it 'should default to the rack env with no rails env if present' do - ENV['RAILS_ENV'], ENV['RACK_ENV'] = nil, "staging" - RocketPants.env.production?.should eq false - RocketPants.env.staging?.should eq true - RocketPants.env.development?.should eq false + context "Rails.env is present" do + before do + allow(Rails).to receive(:env) { "production".inquiry } + end + it "should default to Rails env" do + RocketPants.env.production?.should eq true + RocketPants.env.staging?.should eq false + RocketPants.env.development?.should eq false + end end - - it 'should default to development otherwise' do - ENV['RAILS_ENV'], ENV['RACK_ENV'] = nil, nil - RocketPants.env.production?.should eq false - RocketPants.env.staging?.should eq false - RocketPants.env.development?.should eq true + context "Rails env is not present" do + before do + allow(Rails).to receive(:env) { nil } + end + context "RAILS_ENV and RACK_ENV are present" do + before do + allow(ENV).to receive(:[]).with("RAILS_ENV").and_return("production") + allow(ENV).to receive(:[]).with("RACK_ENV").and_return("staging") + end + it "should default to RAILS_ENV" do + expect(RocketPants.env.production?).to eq(true) + expect(RocketPants.env.staging?).to eq(false) + expect(RocketPants.env.development?).to eq(false) + end + end + context "RACK_ENV is only present" do + before do + allow(ENV).to receive(:[]).with("RAILS_ENV").and_return(nil) + allow(ENV).to receive(:[]).with("RACK_ENV").and_return("staging") + end + it "should default to RACK_ENV" do + expect(RocketPants.env.production?).to eq(false) + expect(RocketPants.env.staging?).to eq(true) + expect(RocketPants.env.development?).to eq(false) + end + end + context "RAILS_ENV and RACK_ENV are not present" do + before do + allow(ENV).to receive(:[]).with("RAILS_ENV").and_return(nil) + allow(ENV).to receive(:[]).with("RACK_ENV").and_return(nil) + end + it "should default to development environment" do + expect(RocketPants.env.production?).to eq(false) + expect(RocketPants.env.staging?).to eq(false) + expect(RocketPants.env.development?).to eq(true) + end + end end it 'should let you restore the environment' do @@ -47,7 +75,6 @@ RocketPants.env = nil RocketPants.env.should == RocketPants.default_env end - end describe 'passing through errors' do @@ -68,7 +95,7 @@ it 'should default to if the env is dev or test' do %w(development test).each do |environment| - stub(RocketPants).env { ActiveSupport::StringInquirer.new environment } + allow(RocketPants).to receive(:env) { ActiveSupport::StringInquirer.new(environment) } RocketPants.pass_through_errors = nil RocketPants.should be_pass_through_errors end @@ -76,7 +103,7 @@ it 'should default to false in other envs' do %w(production staging).each do |environment| - stub(RocketPants).env { ActiveSupport::StringInquirer.new environment } + allow(RocketPants).to receive(:env) { ActiveSupport::StringInquirer.new(environment) } RocketPants.pass_through_errors = nil RocketPants.should_not be_pass_through_errors end @@ -102,7 +129,7 @@ it 'should default to true in test and development' do %w(development test).each do |environment| - stub(RocketPants).env { ActiveSupport::StringInquirer.new environment } + allow(RocketPants).to receive(:env) { ActiveSupport::StringInquirer.new(environment) } RocketPants.show_exception_message = nil RocketPants.should be_show_exception_message end @@ -110,12 +137,10 @@ it 'should default to false in other environments' do %w(production staging somethingelse).each do |environment| - stub(RocketPants).env { ActiveSupport::StringInquirer.new environment } + allow(RocketPants).to receive(:env) { ActiveSupport::StringInquirer.new(environment) } RocketPants.show_exception_message = nil RocketPants.should_not be_show_exception_message end end - end - end \ No newline at end of file diff --git a/spec/rocket_pants/controller/error_handling_spec.rb b/spec/rocket_pants/controller/error_handling_spec.rb index 9563650..fa04186 100644 --- a/spec/rocket_pants/controller/error_handling_spec.rb +++ b/spec/rocket_pants/controller/error_handling_spec.rb @@ -43,17 +43,15 @@ before :each do controller_class.use_named_exception_notifier :airbrake - stub.instance_of(controller_class).airbrake_local_request? { false } - stub.instance_of(controller_class).airbrake_request_data { request_data } - + allow_any_instance_of(controller_class).to receive_message_chain(:airbrake_local_request?).and_return(false) + allow_any_instance_of(controller_class).to receive_message_chain(:airbrake_request_data).and_return(request_data) Airbrake = Class.new do define_singleton_method(:notify) { |exception, request_data| } end end it 'should send notification when it is the named exception notifier' do - mock(Airbrake).notify(exception, request_data) - + allow(Airbrake).to receive(:notify).with(exception, request_data) controller_class.exception_notifier_callback.call(controller, exception, request) end end @@ -61,12 +59,11 @@ context 'honeybadger' do before :each do controller_class.use_named_exception_notifier :honeybadger - stub.instance_of(controller_class).notify_honeybadger {} + allow_any_instance_of(controller_class).to receive(:notify_honeybadger) end it 'should send notification when it is the named exception notifier' do - mock(controller).notify_honeybadger(exception) - + expect(controller).to receive(:notify_honeybadger).with(exception) controller_class.exception_notifier_callback.call(controller, exception, request) end end @@ -74,12 +71,11 @@ context 'bugsnag' do before :each do controller_class.use_named_exception_notifier :bugsnag - stub.instance_of(controller_class).notify_bugsnag {} + allow_any_instance_of(controller_class).to receive(:notify_bugsnag) end it 'should send notification when it is the named exception notifier' do - mock(controller).notify_bugsnag(exception, request: request) - + expect(controller).to receive(:notify_bugsnag).with(exception, request: request) controller_class.exception_notifier_callback.call(controller, exception, request) end end @@ -140,31 +136,31 @@ before :each do # Replace it with a new error mapping. - stub(controller_class).error_mapping { error_mapping } - stub.instance_of(controller_class).error_mapping { error_mapping } - stub(controller_class).test_error { error } + allow(controller_class).to receive(:error_mapping) {error_mapping } + allow_any_instance_of(controller_class).to receive(:error_mapping) { error_mapping } + allow(controller_class).to receive(:test_error) { error } end it 'should let you hook into the error name lookup' do - mock.instance_of(controller_class).lookup_error_name(error).returns(:my_test_error).times(any_times) + allow_any_instance_of(controller_class).to receive(:lookup_error_name).with(error) { :my_test_error } get :test_error content['error'].should == 'my_test_error' end it 'should let you hook into the error message lookup' do - mock.instance_of(controller_class).lookup_error_message(error).returns 'Oh look, pie.' + allow_any_instance_of(controller_class).to receive(:lookup_error_message).with(error) { 'Oh look, pie.' } get :test_error content['error_description'].should == 'Oh look, pie.' end it 'should let you hook into the error status lookup' do - mock.instance_of(controller_class).lookup_error_status(error).returns 403 + allow_any_instance_of(controller_class).to receive(:lookup_error_status).with(error) { 403 } get :test_error response.status.should == 403 end it 'should let you add error items to the response' do - mock.instance_of(controller_class).lookup_error_extras(error).returns(:hello => 'There') + allow_any_instance_of(controller_class).to receive(:lookup_error_extras).with(error) { { :hello => 'There' } } get :test_error content['hello'].should == 'There' end @@ -207,7 +203,7 @@ def error.context; {:metadata => {:hello => 'There'}} ; end end it 'should include parents when checking the mapping' do - stub(controller_class).test_error { TestController::YetAnotherError } + allow(controller_class).to receive(:test_error) { TestController::YetAnotherError } controller_class.error_mapping[TestController::ErrorOfDoom] = RocketPants::Throttled get :test_error content['error'].should == 'throttled' @@ -221,8 +217,8 @@ def error.context; {:metadata => {:hello => 'There'}} ; end before :each do # Replace it with a new error mapping. - stub(controller_class).error_mapping { error_mapping } - stub.instance_of(controller_class).error_mapping { error_mapping } + allow(controller_class).to receive(:error_mapping) { error_mapping } + allow_any_instance_of(controller_class).to receive(:error_mapping) { error_mapping } controller_class.use_named_exception_notifier :default end @@ -244,7 +240,7 @@ def error.context; {:metadata => {:hello => 'There'}} ; end it 'should default to having the exception message' do with_config :show_exception_message, true do with_config :pass_through_errors, false do - stub(controller_class).test_error { StandardError.new("This is a fake message.") } + allow(controller_class).to receive(:test_error) { StandardError.new("This is a fake message.") } get :test_error content[:error_description].should be_present content[:error_description].should == "This is a fake message." @@ -255,7 +251,7 @@ def error.context; {:metadata => {:hello => 'There'}} ; end it 'should let you disable using the exception message' do with_config :show_exception_message, false do with_config :pass_through_errors, false do - stub(controller_class).test_error { StandardError.new("This is a fake message.") } + allow(controller_class).to receive(:test_error) { StandardError.new("This is a fake message.") } get :test_error content[:error_description].should be_present content[:error_description].should_not == "This is a fake message." @@ -276,8 +272,8 @@ def error.context; {:metadata => {:hello => 'There'}} ; end before :each do # Replace it with a new error mapping. - stub(controller_class).error_mapping { error_mapping } - stub.instance_of(controller_class).error_mapping { error_mapping } + allow(controller_class).to receive(:error_mapping) { error_mapping } + allow_any_instance_of(controller_class).to receive(:error_mapping) { error_mapping } controller_class.exception_notifier_callback = custom_exception_notifier_callback end diff --git a/spec/rocket_pants/controller/header_metadata_spec.rb b/spec/rocket_pants/controller/header_metadata_spec.rb index bb7b6e8..0260bfb 100644 --- a/spec/rocket_pants/controller/header_metadata_spec.rb +++ b/spec/rocket_pants/controller/header_metadata_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'will_paginate/collection' describe RocketPants::HeaderMetadata do include ControllerHelpers @@ -18,14 +19,14 @@ end it 'should not include header metadata by default' do - mock(TestController).test_data { users } + allow(TestController).to receive(:test_data) { users } get :test_data response.headers.should_not have_key 'X-Api-Count' end it 'should let you turn on header metadata' do with_config :header_metadata, true do - mock(TestController).test_data { users } + allow(TestController).to receive(:test_data) { users } get :test_data response.headers.should have_key 'X-Api-Count' response.headers['X-Api-Count'].should == users.size.to_s @@ -35,7 +36,7 @@ it 'should handle nested (e.g. pagination) metadata correctly' do with_config :header_metadata, true do pager = WillPaginate::Collection.create(2, 10) { |p| p.replace %w(a b c d e f g h i j); p.total_entries = 200 } - mock(TestController).test_data { pager } + allow(TestController).to receive(:test_data) { pager } get :test_data h = response.headers h['X-Api-Pagination-Next'].should == '3' diff --git a/spec/rocket_pants/controller/linking_spec.rb b/spec/rocket_pants/controller/linking_spec.rb index b8b3d02..4427880 100644 --- a/spec/rocket_pants/controller/linking_spec.rb +++ b/spec/rocket_pants/controller/linking_spec.rb @@ -23,7 +23,7 @@ def link_portion(&blk) let(:pagination) { WillPaginate::Collection.create(current_page, 10) { |p| p.replace %w(a b c d e f g h i j); p.total_entries = 200 } } before :each do - stub(controller_class).test_data { pagination } + allow(controller_class).to receive(:test_data) { pagination } # Test Item... controller_class.send(:define_method, :page_url) do |page| "page#{page}" diff --git a/spec/rocket_pants/controller/strong_parameters_spec.rb b/spec/rocket_pants/controller/strong_parameters_spec.rb index 208366b..5becd33 100644 --- a/spec/rocket_pants/controller/strong_parameters_spec.rb +++ b/spec/rocket_pants/controller/strong_parameters_spec.rb @@ -15,7 +15,7 @@ it "should map parameter missing error to bad request" do exception = ActionController::ParameterMissing.new :foo - mock(controller_class).test_error { raise exception } + allow(controller_class).to receive(:test_error) { raise exception } with_config :pass_through_errors, false do get :test_error @@ -27,7 +27,7 @@ it "should map unpermitted parameters error to bad request" do exception = ActionController::UnpermittedParameters.new [:foo, :bar] - mock(controller_class).test_error { raise exception } + allow(controller_class).to receive(:test_error) { raise exception } with_config :pass_through_errors, false do get :test_error diff --git a/spec/rocket_pants/controller_spec.rb b/spec/rocket_pants/controller_spec.rb index b6cd677..582d2ec 100644 --- a/spec/rocket_pants/controller_spec.rb +++ b/spec/rocket_pants/controller_spec.rb @@ -24,7 +24,7 @@ it 'should let you expose a single item' do user = User.create :age => 21 - mock(TestController).test_data { user } + allow(TestController).to receive(:test_data) { user } get :test_data content[:response].should == user.serializable_hash end @@ -33,7 +33,7 @@ 1.upto(5) do |offset| User.create :age => (18 + offset) end - mock(TestController).test_data { User.all } + allow(TestController).to receive(:test_data) { User.all } get :test_data content[:response].should == User.all.map(&:serializable_hash) content[:count].should == 5 @@ -43,29 +43,13 @@ 1.upto(5) do |offset| User.create :age => (18 + offset) end - mock(TestController).test_data { User.where('1 = 1') } + allow(TestController).to receive(:test_data) { User.where('1 = 1') } get :test_data content[:response].should == User.all.map(&:serializable_hash) content[:count].should == 5 end end - - context 'with a invalid model' do - let(:table_manager) { ReversibleData.manager_for(:fish) } - - before(:each) { table_manager.up! } - after(:each) { table_manager.down! } - - it 'should let you expose a invalid ActiveRecord:Base' do - fish = Fish.create - mock(TestController).test_data { fish } - get :test_data - content['error'].should == 'invalid_resource' - content['messages'].should be_present - end - end - end describe 'versioning' do @@ -127,9 +111,23 @@ end describe 'respondable' do + it 'should include url_options in default_serializer_options' do + my_respondable = Object.new + my_respondable.instance_eval { + class << self + include RocketPants::Respondable + end + def url_options + "My Options" + end + } + + expect(my_respondable.respond_to?(:default_serializer_options)).to be_truthy + expect(my_respondable.default_serializer_options).to include(url_options: my_respondable.url_options, root: false) + end it 'should correctly convert a normal collection' do - mock(TestController).test_data { %w(a b c d) } + allow(TestController).to receive(:test_data) { %w(a b c d) } get :test_data content[:response].should == %w(a b c d) content[:pagination].should be_nil @@ -138,7 +136,7 @@ it 'should correctly convert a normal object' do object = {:a => 1, :b => 2} - mock(TestController).test_data { object } + allow(TestController).to receive(:test_data) { object } get :test_data content[:count].should be_nil content[:pagination].should be_nil @@ -148,89 +146,90 @@ it 'should correctly convert an object with a serializable hash method' do object = {:a => 1, :b => 2} def object.serializable_hash(*); {:serialised => true}; end - mock(TestController).test_data { object } + allow(TestController).to receive(:test_data) { object } get :test_data content[:response].should == {'serialised' => true} end it 'should correct convert an object with as_json' do object = {:a => 1, :b => 2} - stub(object).as_json(anything) { {:serialised => true}} - mock(TestController).test_data { object } + allow(object).to receive(:as_json) { {:serialised => true } } + allow(TestController).to receive(:test_data) { object } get :test_data content[:response].should == {'serialised' => true} end + it 'should correctly hook into paginated responses' do pager = WillPaginate::Collection.create(2, 10) { |p| p.replace %w(a b c d e f g h i j); p.total_entries = 200 } - mock(TestController).test_data { pager } + allow(TestController).to receive(:test_data) { pager } hooks = [] - mock.instance_of(TestController).pre_process_exposed_object(pager, :paginated, false) { hooks << :pre } - mock.instance_of(TestController).post_process_exposed_object(pager, :paginated, false) { hooks << :post } + allow_any_instance_of(TestController).to receive(:pre_process_exposed_object).with(pager, :paginated, false) { hooks << :pre } + allow_any_instance_of(TestController).to receive(:post_process_exposed_object).with(pager, :paginated, false) { hooks << :post } get :test_data hooks.should == [:pre, :post] end it 'should correctly hook into collection responses' do object = %w(a b c d) - mock(TestController).test_data { object } + allow(TestController).to receive(:test_data) { object } hooks = [] - mock.instance_of(TestController).pre_process_exposed_object(object, :collection, false) { hooks << :pre } - mock.instance_of(TestController).post_process_exposed_object(object, :collection, false) { hooks << :post } + allow_any_instance_of(TestController).to receive(:pre_process_exposed_object).with(object, :collection, false) { hooks << :pre } + allow_any_instance_of(TestController).to receive(:post_process_exposed_object).with(object, :collection, false) { hooks << :post } get :test_data hooks.should == [:pre, :post] end it 'should correctly hook into singular responses' do object = {:a => 1, :b => 2} - mock(TestController).test_data { object } + allow(TestController).to receive(:test_data) { object } hooks = [] - mock.instance_of(TestController).pre_process_exposed_object(object, :resource, true) { hooks << :pre } - mock.instance_of(TestController).post_process_exposed_object(object, :resource, true) { hooks << :post } + allow_any_instance_of(TestController).to receive(:pre_process_exposed_object).with(object, :resource, true) { hooks << :pre } + allow_any_instance_of(TestController).to receive(:post_process_exposed_object).with(object, :resource, true) { hooks << :post } get :test_data hooks.should == [:pre, :post] end it 'should accept status options when rendering json' do - stub(TestController).test_data { {:hello => "World"} } - stub(TestController).test_options { {:status => :created} } + allow(TestController).to receive(:test_data) { {:hello => "World"} } + allow(TestController).to receive(:test_options) { {:status => :created} } get :test_render_json response.status.should == 201 end it 'should accept status options when responding with data' do - stub(TestController).test_data { {:hello => "World"} } - stub(TestController).test_options { {:status => :created} } + allow(TestController).to receive(:test_data) { {:hello => "World"} } + allow(TestController).to receive(:test_options) { {:status => :created} } get :test_responds response.status.should == 201 end it 'should accept status options when responding with a single object' do - stub(TestController).test_data { {:hello => "World"} } - stub(TestController).test_options { {:status => :created} } + allow(TestController).to receive(:test_data) { {:hello => "World"} } + allow(TestController).to receive(:test_options) { {:status => :created} } get :test_data response.status.should == 201 end it 'should accept status options when responding with a paginated collection' do - stub(TestController).test_data do + allow(TestController).to receive(:test_data) do WillPaginate::Collection.create(1, 1) {|c| c.replace([{:hello => "World"}]); c.total_entries = 1 } end - stub(TestController).test_options { {:status => :created} } + allow(TestController).to receive(:test_options) { {:status => :created} } get :test_data response.status.should == 201 end it 'should accept status options when responding with collection' do - stub(TestController).test_data { [{:hello => "World"}] } - stub(TestController).test_options { {:status => :created} } + allow(TestController).to receive(:test_data) { {:hello => "World"} } + allow(TestController).to receive(:test_options) { {:status => :created} } get :test_data response.status.should == 201 end it 'should let you override the content type' do - stub(TestController).test_data { {:hello => "World"} } - stub(TestController).test_options { {:content_type => Mime::HTML} } + allow(TestController).to receive(:test_data) { {:hello => "World"} } + allow(TestController).to receive(:test_options) { {:content_type => Mime::HTML} } get :test_data response.headers['Content-Type'].should =~ /text\/html/ end @@ -266,14 +265,14 @@ def object.serializable_hash(*); {:serialised => true}; end it 'should invoke the caching callback with caching enabled' do set_caching_to true do - mock.instance_of(controller_class).cache_response.with_any_args + allow_any_instance_of(controller_class).to receive(:cache_response) get :test_data end end it 'should not invoke the caching callback with caching disabled' do set_caching_to false do - dont_allow.instance_of(controller_class).cache_response.with_any_args + expect_any_instance_of(controller_class).to_not receive(:cache_response) get :test_data end end @@ -291,13 +290,13 @@ def object.serializable_hash(*); {:serialised => true}; end let(:cached_object) { Object.new } before :each do - stub(RocketPants::Caching).cache_key_for(cached_object) { "my-object" } - stub(RocketPants::Caching).etag_for(cached_object) { "my-object:stored-etag" } - stub(controller_class).test_data { cached_object } + allow(RocketPants::Caching).to receive(:cache_key_for).with(cached_object) { "my-object" } + allow(RocketPants::Caching).to receive(:etag_for).with(cached_object) { "my-object:stored-etag" } + allow(controller_class).to receive(:test_data) { cached_object } end it 'should invoke the caching callback correctly' do - mock.instance_of(controller_class).cache_response cached_object, true + allow_any_instance_of(controller_class).to receive(:cache_response).with(cached_object, true) get :test_data end @@ -318,13 +317,13 @@ def object.serializable_hash(*); {:serialised => true}; end let(:cached_objects) { [Object.new] } before :each do - dont_allow(RocketPants::Caching).cache_key_for.with_any_args - dont_allow(RocketPants::Caching).etag_for.with_any_args - stub(controller_class).test_data { cached_objects } + expect(RocketPants::Caching).to_not receive(:cache_key_for) + expect(RocketPants::Caching).to_not receive(:etag_for) + allow(controller_class).to receive(:test_data) { cached_objects } end it 'should invoke the caching callback correctly' do - mock.instance_of(controller_class).cache_response cached_objects, false + allow_any_instance_of(controller_class).to receive(:cache_response).with(cached_objects, false) get :test_data end @@ -392,7 +391,7 @@ def object.serializable_hash(*); {:serialised => true}; end get :echo, :echo => "Hello World", :callback => "test" response.content_type.should include 'application/json' response.body.should == %({"response":{"echo":"Hello World"}}) - stub(controller_class).test_data { {"other" => true} } + allow(controller_class).to receive(:test_data) { {"other" => true} } get :test_data, :callback => "test" response.content_type.should include 'application/javascript' response.body.should == %|test({"response":{"other":true}});| @@ -410,7 +409,7 @@ def object.serializable_hash(*); {:serialised => true}; end get :echo, :echo => "Hello World", :callback => "test" response.content_type.should include 'application/javascript' response.body.should == %|test({"response":{"echo":"Hello World"}});| - response.headers['Content-Length'].to_i.should == Rack::Utils.bytesize(response.body) + response.headers['Content-Length'].to_i.should == response.body.bytesize end end @@ -422,7 +421,6 @@ def object.serializable_hash(*); {:serialised => true}; end decoded = ActiveSupport::JSON.decode(response.body) decoded["awesome"].should == "1" end - end context 'empty responses' do @@ -433,7 +431,5 @@ def object.serializable_hash(*); {:serialised => true}; end response.body.should be_blank response.content_type.should include 'application/json' end - end - end \ No newline at end of file diff --git a/spec/rocket_pants/error_spec.rb b/spec/rocket_pants/error_spec.rb index 80c8c28..4f88657 100644 --- a/spec/rocket_pants/error_spec.rb +++ b/spec/rocket_pants/error_spec.rb @@ -112,7 +112,8 @@ def temporary_constant(items) it 'should let you pass in error messages' do o = Object.new - mock(o).to_hash { error_messages } + o.define_singleton_method(:to_hash) {} + allow(o).to receive(:to_hash) { error_messages } error = RocketPants::InvalidResource.new(o) error.context.should == {:metadata => {:messages => error_messages}} end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4493c75..a1750bf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,12 +18,15 @@ Dir[Pathname(__FILE__).dirname.join("support/**/*.rb")].each { |f| require f } RSpec.configure do |config| - config.mock_with :rr config.include I18nSpecHelper config.include ConfigHelper config.include WebmockResponses config.extend ReversibleData::RSpec2Macros - config.filter_run_excluding :integration => true + + config.mock_with(:rspec) do |mocks| + mocks.syntax = [:expect] # disallow old should syntax + mocks.verify_partial_doubles = true + end config.expect_with :rspec do |c| c.syntax = [:should, :expect] diff --git a/spec/support/controller.rb b/spec/support/controller.rb index fd8a8e9..bef07e8 100644 --- a/spec/support/controller.rb +++ b/spec/support/controller.rb @@ -78,5 +78,12 @@ def premature_termination error! :throtted exposes :finished => true end - + + def airbrake_local_request?; end + + def airbrake_request_data; end + + def notify_honeybadger(_); end + + def notify_bugsnag(_, _); end end \ No newline at end of file