Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
* [#2893](https://github.com/ruby-grape/grape/pull/2893): Scrub the format extension rather than the whole request path when negotiating a format - [@ericproulx](https://github.com/ericproulx).
* [#2894](https://github.com/ruby-grape/grape/pull/2894): Read the request method once in `default_status` instead of asking through `post?` and `delete?` - [@ericproulx](https://github.com/ericproulx).
* [#2896](https://github.com/ruby-grape/grape/pull/2896): Bring test suite line coverage to 100% - [@dblock](https://github.com/dblock).
* [#2897](https://github.com/ruby-grape/grape/pull/2897): Improve test suite branch coverage - [@dblock](https://github.com/dblock).
* Your contribution here.

#### Fixes
Expand Down
28 changes: 28 additions & 0 deletions spec/grape/exceptions/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,32 @@
end
end
end

describe '#translate_message (private)' do
subject(:translate_message) { described_class.new.__send__(:translate_message, translation_key) }

context 'when given a Proc' do
let(:translation_key) { -> { 'from a proc' } }

it 'calls the Proc' do
expect(translate_message).to eq('from a proc')
end
end

context 'when given a Hash matching {key:, **opts}' do
let(:translation_key) { { key: :invalid_formatter, klass: String, to_format: 'xml' } }

it 'translates using the key and forwards the remaining pairs as opts' do
expect(translate_message).to eq('cannot convert String to xml')
end
end

context 'when given anything else (e.g. a plain String)' do
let(:translation_key) { 'a literal message' }

it 'returns it unchanged' do
expect(translate_message).to eq('a literal message')
end
end
end
end
34 changes: 34 additions & 0 deletions spec/grape/exceptions/request_error_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

describe Grape::Exceptions::RequestError do
describe '#initialize' do
context 'when raised inside a rescue block' do
it 'captures the current exception message' do
error = begin
raise 'boom'
rescue RuntimeError
described_class.new
end
expect(error.message).to eq('boom')
end
end

context 'when there is no current exception' do
it 'has no message from a prior exception' do
# $ERROR_INFO ($!) is read-only and only set by an active rescue, so
# simulate "no exception" the same way: outside any rescue block.
# `StandardError#message` defaults to the class name when no message
# was given, so this pins the $ERROR_INFO&.message safe-nav's nil case.
expect(described_class.new.message).to eq(described_class.name)
end
end

it 'defaults status to 400' do
expect(described_class.new.status).to eq(400)
end

it 'accepts a custom status' do
expect(described_class.new(status: 422).status).to eq(422)
end
end
end
12 changes: 12 additions & 0 deletions spec/grape/exceptions/validation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
expect { described_class.new(message: 'presence') }.to raise_error(ArgumentError, /missing keyword:.+?params/)
end

context 'when message is omitted' do
subject(:error) { described_class.new(params: ['id']) }

it 'has a nil message_key' do
expect(error.message_key).to be_nil
end

it 'has no message from the given options' do
expect(error.message).to eq(described_class.name)
end
end

context 'when message is a Symbol' do
subject(:error) { described_class.new(params: ['id'], message: :presence) }

Expand Down
18 changes: 18 additions & 0 deletions spec/grape/middleware/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@
end
end

context 'when a middleware defines an instance-level #default_options' do
let(:example_ware) do
Class.new(Grape::Middleware::Base) do
def default_options
{ monkey: true }
end
end
end

it 'merges options through the instance method instead of a constant' do
expect(example_ware.new(blank_app).options[:monkey]).to be true
end

it 'overrides default options when provided' do
expect(example_ware.new(blank_app, monkey: false).options[:monkey]).to be false
end
end

context 'when a middleware declares its own Options Data class' do
let(:example_ware) do
Class.new(Grape::Middleware::Base) do
Expand Down
30 changes: 30 additions & 0 deletions spec/grape/middleware/error_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -463,4 +463,34 @@ def initialize
expect(middleware.__send__(:error?, 'not an error')).to be false
end
end

describe '#resolved_backtrace' do
subject(:middleware) { described_class.new(->(_env) {}, rescue_options: Grape::DSL::RescueOptions.new(backtrace: true)) }

context 'when the raw response has no backtrace of its own' do
it 'falls back to the original exception backtrace' do
original_exception = RuntimeError.new('boom')
original_exception.set_backtrace(['original.rb:1'])
raw = Grape::Exceptions::ErrorResponse.new(original_exception:)

expect(middleware.__send__(:resolved_backtrace, raw)).to eq(['original.rb:1'])
end
end

context 'when neither the raw response nor the original exception have a backtrace' do
it 'returns an empty array' do
raw = Grape::Exceptions::ErrorResponse.new
expect(middleware.__send__(:resolved_backtrace, raw)).to eq([])
end
end
end

describe '#grape_exceptions_precedence_handler' do
subject(:middleware) { described_class.new(->(_env) {}, rescue_grape_exceptions: true) }

it 'leaves InvalidVersionHeader alone so it keeps reaching Rack' do
handler = middleware.__send__(:grape_exceptions_precedence_handler, Grape::Exceptions::InvalidVersionHeader, nil)
expect(handler).to be_nil
end
end
end
11 changes: 11 additions & 0 deletions spec/grape/namespace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,15 @@
expect(namespace.hash).not_to eq(other.hash)
end
end

describe '.joined_space' do
it 'maps a list of Namespace objects to their #space' do
other = described_class.new('bar')
expect(described_class.joined_space([namespace, other])).to eq(%w[foo bar])
end

it 'returns nil for a nil settings list' do
expect(described_class.joined_space(nil)).to be_nil
end
end
end
29 changes: 29 additions & 0 deletions spec/grape/router/base_route_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

describe Grape::Router::BaseRoute do
let(:pattern) { instance_double(Grape::Router::Pattern) }

describe '#initialize' do
context 'when options is a plain Hash' do
subject(:route) { described_class.new(pattern, { foo: 'bar' }) }

it 'wraps it in an ActiveSupport::OrderedOptions' do
expect(route.options).to be_a(ActiveSupport::OrderedOptions)
end

it 'reads back the given options' do
expect(route.options[:foo]).to eq('bar')
end
end

context 'when options is already an ActiveSupport::OrderedOptions' do
subject(:route) { described_class.new(pattern, options) }

let(:options) { ActiveSupport::OrderedOptions.new.update(foo: 'bar') }

it 'uses it as-is, without wrapping it again' do
expect(route.options).to equal(options)
end
end
end
end
9 changes: 9 additions & 0 deletions spec/grape/router/pattern/path_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ def path_settings(**attrs)
end
end

context 'when versioning is used but not via path (e.g. header)' do
it "does not include a '/'" do
path = described_class.new(
nil, nil, path_settings(version: :v1, version_options: Grape::DSL::VersionOptions.new(using: :header))
)
expect(path.suffix).to eql('(.:format)')
end
end

context 'when path versioning is not used' do
it "does not include a '/' when the path has a namespace" do
path = described_class.new(nil, 'namespace', path_settings)
Expand Down
10 changes: 10 additions & 0 deletions spec/grape/testing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,15 @@
Grape::Endpoint.reset_before_each
expect { get '/' }.to raise_error(NoMethodError, /undefined method [`']authenticate_user!' for/)
end

it 'raises an ArgumentError when no block is given' do
expect { Grape::Endpoint.before_each }.to raise_error(ArgumentError, 'a block is required')
end

it 'does nothing when no before_each hooks were registered' do
subject.get('/') { 'hello' }
expect { get '/' }.not_to raise_error
expect(last_response.body).to eq('hello')
end
end
end
13 changes: 13 additions & 0 deletions spec/grape/util/translation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,18 @@ def translate_message(key, **opts)
expect { translator.translate_message(:reserved_key_test) }.to raise_error(I18n::ReservedInterpolationKey)
end
end

context 'when an explicit locale is given' do
it 'passes the locale through to I18n.translate' do
expect(I18n).to receive(:translate).with(:missing_key, hash_including(locale: :en)).and_call_original
translator.translate_message(:missing_key, locale: :en)
end
end

context 'when an explicit default is given and the key is missing' do
it 'returns the given default instead of the dotted key path' do
expect(translator.translate_message(:missing_key, default: 'fallback')).to eq('fallback')
end
end
end
end
Loading