grape 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (50) hide show
  1. data/{CHANGELOG.markdown → CHANGELOG.md} +21 -1
  2. data/Gemfile +1 -0
  3. data/{README.markdown → README.md} +178 -125
  4. data/grape.gemspec +1 -1
  5. data/lib/grape.rb +25 -3
  6. data/lib/grape/api.rb +43 -20
  7. data/lib/grape/endpoint.rb +32 -13
  8. data/lib/grape/exceptions/base.rb +50 -1
  9. data/lib/grape/exceptions/invalid_formatter.rb +13 -0
  10. data/lib/grape/exceptions/invalid_versioner_option.rb +14 -0
  11. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +15 -0
  12. data/lib/grape/exceptions/missing_mime_type.rb +14 -0
  13. data/lib/grape/exceptions/missing_option.rb +13 -0
  14. data/lib/grape/exceptions/missing_vendor_option.rb +13 -0
  15. data/lib/grape/exceptions/unknown_options.rb +14 -0
  16. data/lib/grape/exceptions/unknown_validator.rb +12 -0
  17. data/lib/grape/exceptions/{validation_error.rb → validation.rb} +3 -1
  18. data/lib/grape/formatter/xml.rb +2 -1
  19. data/lib/grape/locale/en.yml +20 -0
  20. data/lib/grape/middleware/base.rb +0 -5
  21. data/lib/grape/middleware/error.rb +1 -2
  22. data/lib/grape/middleware/formatter.rb +9 -5
  23. data/lib/grape/middleware/versioner.rb +1 -1
  24. data/lib/grape/middleware/versioner/header.rb +16 -6
  25. data/lib/grape/middleware/versioner/param.rb +1 -1
  26. data/lib/grape/middleware/versioner/path.rb +1 -1
  27. data/lib/grape/util/content_types.rb +0 -2
  28. data/lib/grape/validations.rb +7 -14
  29. data/lib/grape/validations/coerce.rb +2 -1
  30. data/lib/grape/validations/presence.rb +2 -1
  31. data/lib/grape/validations/regexp.rb +2 -1
  32. data/lib/grape/version.rb +1 -1
  33. data/spec/grape/api_spec.rb +150 -5
  34. data/spec/grape/endpoint_spec.rb +51 -157
  35. data/spec/grape/entity_spec.rb +142 -520
  36. data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
  37. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
  38. data/spec/grape/exceptions/missing_mime_type_spec.rb +24 -0
  39. data/spec/grape/exceptions/missing_option_spec.rb +18 -0
  40. data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
  41. data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
  42. data/spec/grape/middleware/formatter_spec.rb +40 -34
  43. data/spec/grape/middleware/versioner/header_spec.rb +78 -20
  44. data/spec/grape/middleware/versioner/path_spec.rb +12 -8
  45. data/spec/grape/validations/coerce_spec.rb +1 -0
  46. data/spec/grape/validations/presence_spec.rb +8 -8
  47. data/spec/grape/validations_spec.rb +26 -3
  48. data/spec/spec_helper.rb +3 -6
  49. metadata +44 -9
  50. data/lib/grape/entity.rb +0 -386
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class UnknownOptions < Base
5
+
6
+ def initialize(options)
7
+ super(:message => compose_message("unknown_options", :options => options))
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class UnknownValidator < Base
5
+
6
+ def initialize(validator_type)
7
+ super(:message => compose_message("unknown_validator", :validator_type => validator_type))
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -2,11 +2,13 @@ require 'grape/exceptions/base'
2
2
 
3
3
  module Grape
4
4
  module Exceptions
5
- class ValidationError < Grape::Exceptions::Base
5
+ class Validation < Grape::Exceptions::Base
6
6
  attr_accessor :param
7
7
 
8
8
  def initialize(args = {})
9
9
  @param = args[:param].to_s if args.has_key? :param
10
+ attribute = translate_attribute(@param)
11
+ args[:message] = translate_message(args[:message_key], :attribute => attribute)
10
12
  super
11
13
  end
12
14
  end
@@ -4,7 +4,8 @@ module Grape
4
4
  class << self
5
5
 
6
6
  def call(object, env)
7
- object.respond_to?(:to_xml) ? object.to_xml : object.to_s
7
+ return object.to_xml if object.respond_to?(:to_xml)
8
+ raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
8
9
  end
9
10
 
10
11
  end
@@ -5,3 +5,23 @@ en:
5
5
  coerce: 'invalid parameter: %{attribute}'
6
6
  presence: 'missing parameter: %{attribute}'
7
7
  regexp: 'invalid parameter: %{attribute}'
8
+ missing_vendor_option:
9
+ problem: 'missing :vendor option.'
10
+ summary: 'when version using header, you must specify :verdor option. '
11
+ resolution: "eg: version 'v1', :using => :header, :vendor => 'twitter'"
12
+ missing_mime_type:
13
+ problem: 'missing mime type for %{new_format}'
14
+ resolution:
15
+ "you can choose exist mime type from Grape::ContentTypes::CONTENT_TYPES
16
+ or add your own with content_type :%{new_format}, 'application/%{new_format}'
17
+ "
18
+ invalid_with_option_for_represent:
19
+ problem: 'You must specify an entity class in the :with option.'
20
+ resolution: 'eg: represent User, :with => Entity::User'
21
+ missing_option: 'You must specify :%{option} options.'
22
+ invalid_formatter: 'cannot convert %{klass} to %{to_format}'
23
+ invalid_versioner_option:
24
+ problem: 'Unknown :using for versioner: %{strategy}'
25
+ resolution: 'available strategy for :using is :path, :header, :param'
26
+ unknown_validator: 'unknown validator: %{validator_type}'
27
+ unknown_options: 'unknown options: %{options}'
@@ -1,8 +1,3 @@
1
- require 'active_support/core_ext/hash/indifferent_access'
2
- require 'grape/util/content_types'
3
- require 'multi_json'
4
- require 'multi_xml'
5
-
6
1
  module Grape
7
2
  module Middleware
8
3
  class Base
@@ -1,5 +1,4 @@
1
1
  require 'grape/middleware/base'
2
- require 'multi_json'
3
2
 
4
3
  module Grape
5
4
  module Middleware
@@ -62,7 +61,7 @@ module Grape
62
61
  def format_message(message, backtrace)
63
62
  format = env['api.format'] || options[:format]
64
63
  formatter = Grape::ErrorFormatter::Base.formatter_for(format, options)
65
- throw :error, :status => 406, :message => "The requested format #{format} is not supported." unless formatter
64
+ throw :error, :status => 406, :message => "The requested format '#{format}' is not supported." unless formatter
66
65
  formatter.call(message, backtrace, options, env)
67
66
  end
68
67
 
@@ -24,8 +24,12 @@ module Grape
24
24
  def after
25
25
  status, headers, bodies = *@app_response
26
26
  formatter = Grape::Formatter::Base.formatter_for env['api.format'], options
27
- bodymap = bodies.collect do |body|
28
- formatter.call body, env
27
+ begin
28
+ bodymap = bodies.collect do |body|
29
+ formatter.call body, env
30
+ end
31
+ rescue Exception => e
32
+ throw :error, :status => 500, :message => e.message
29
33
  end
30
34
  headers['Content-Type'] = content_type_for(env['api.format']) unless headers['Content-Type']
31
35
  Rack::Response.new(bodymap, status, headers).to_a
@@ -34,7 +38,7 @@ module Grape
34
38
  private
35
39
 
36
40
  def read_body_input
37
- if (request.post? || request.put?) && (! request.form_data?) && (! request.parseable_data?) && (request.content_length.to_i > 0)
41
+ if (request.post? || request.put? || request.patch?) && (! request.form_data?) && (! request.parseable_data?) && (request.content_length.to_i > 0)
38
42
  if env['rack.input'] && (body = env['rack.input'].read).length > 0
39
43
  begin
40
44
  fmt = mime_types[request.media_type] if request.media_type
@@ -50,7 +54,7 @@ module Grape
50
54
  end
51
55
  end
52
56
  else
53
- throw :error, :status => 406, :message => 'The requested content-type is not supported.'
57
+ throw :error, :status => 406, :message => "The requested content-type '#{request.media_type}' is not supported."
54
58
  end
55
59
  ensure
56
60
  env['rack.input'].rewind
@@ -64,7 +68,7 @@ module Grape
64
68
  if content_type_for(fmt)
65
69
  env['api.format'] = fmt
66
70
  else
67
- throw :error, :status => 406, :message => 'The requested format is not supported.'
71
+ throw :error, :status => 406, :message => "The requested format '#{fmt}' is not supported."
68
72
  end
69
73
  end
70
74
 
@@ -22,7 +22,7 @@ module Grape
22
22
  when :param
23
23
  Param
24
24
  else
25
- raise ArgumentError.new("Unknown :using for versioner: #{strategy}")
25
+ raise Grape::Exceptions::InvalidVersionerOption.new(strategy)
26
26
  end
27
27
  end
28
28
  end
@@ -1,5 +1,4 @@
1
1
  require 'grape/middleware/base'
2
- require 'rack/accept'
3
2
 
4
3
  module Grape
5
4
  module Middleware
@@ -30,7 +29,7 @@ module Grape
30
29
  if strict?
31
30
  # If no Accept header:
32
31
  if header.qvalues.empty?
33
- throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => 'Accept header must be set'
32
+ throw :error, :status => 406, :headers => error_headers, :message => 'Accept header must be set.'
34
33
  end
35
34
  # Remove any acceptable content types with ranges.
36
35
  header.qvalues.reject! do |media_type,_|
@@ -38,7 +37,7 @@ module Grape
38
37
  end
39
38
  # If all Accept headers included a range:
40
39
  if header.qvalues.empty?
41
- throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => 'Accept header must not contain ranges ("*")'
40
+ throw :error, :status => 406, :headers => error_headers, :message => 'Accept header must not contain ranges ("*").'
42
41
  end
43
42
  end
44
43
 
@@ -56,10 +55,10 @@ module Grape
56
55
  end
57
56
  # If none of the available content types are acceptable:
58
57
  elsif strict?
59
- throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => '406 Not Acceptable'
58
+ throw :error, :status => 406, :headers => error_headers, :message => '406 Not Acceptable'
60
59
  # If all acceptable content types specify a vendor or version that doesn't exist:
61
- elsif header.values.all?{|media_type| has_vendor?(media_type) || has_version?(media_type)}
62
- throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => 'API vendor or version not found'
60
+ elsif header.values.all?{ |media_type| has_vendor?(media_type) || has_version?(media_type)}
61
+ throw :error, :status => 406, :headers => error_headers, :message => 'API vendor or version not found.'
63
62
  end
64
63
  end
65
64
 
@@ -96,6 +95,17 @@ module Grape
96
95
  options[:version_options] && options[:version_options][:strict]
97
96
  end
98
97
 
98
+ # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
99
+ # of routes (see [Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent
100
+ # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
101
+ def cascade?
102
+ options[:version_options] && (options[:version_options].has_key?(:cascade) ? options[:version_options][:cascade] : true)
103
+ end
104
+
105
+ def error_headers
106
+ cascade? ? { 'X-Cascade' => 'pass' } : {}
107
+ end
108
+
99
109
  # @param [String] media_type a content type
100
110
  # @return [Boolean] whether the content type sets a vendor
101
111
  def has_vendor?(media_type)
@@ -30,7 +30,7 @@ module Grape
30
30
  potential_version = request.params[paramkey]
31
31
 
32
32
  unless potential_version.nil?
33
- if options[:versions] && !options[:versions].include?(potential_version)
33
+ if options[:versions] && ! options[:versions].find { |v| v.to_s == potential_version }
34
34
  throw :error, :status => 404, :message => "404 API Version Not Found", :headers => {'X-Cascade' => 'pass'}
35
35
  end
36
36
  env['api.version'] = potential_version
@@ -34,7 +34,7 @@ module Grape
34
34
  pieces = path.split('/')
35
35
  potential_version = pieces[1]
36
36
  if potential_version =~ options[:pattern]
37
- if options[:versions] && !options[:versions].include?(potential_version)
37
+ if options[:versions] && ! options[:versions].find { |v| v.to_s == potential_version }
38
38
  throw :error, :status => 404, :message => "404 API Version Not Found"
39
39
  end
40
40
 
@@ -1,5 +1,3 @@
1
- require 'active_support/ordered_hash'
2
-
3
1
  module Grape
4
2
  module ContentTypes
5
3
  # Content types are listed in order of preference.
@@ -1,7 +1,3 @@
1
- require 'virtus'
2
- require 'i18n'
3
-
4
- I18n.load_path << File.expand_path('../locale/en.yml', __FILE__)
5
1
  module Grape
6
2
 
7
3
  module Validations
@@ -18,25 +14,22 @@ module Grape
18
14
  @scope = scope
19
15
 
20
16
  if options.is_a?(Hash) && !options.empty?
21
- raise "unknown options: #{options.keys}"
17
+ raise Grape::Exceptions.UnknownOptions.new(options.keys)
22
18
  end
23
19
  end
24
20
 
25
21
  def validate!(params)
26
22
  params = @scope.params(params)
27
23
 
28
- @attrs.each do |attr_name|
29
- if @required || params.has_key?(attr_name)
30
- validate_param!(attr_name, params)
24
+ (params.is_a?(Array) ? params : [params]).each do |resource_params|
25
+ @attrs.each do |attr_name|
26
+ if @required || resource_params.has_key?(attr_name)
27
+ validate_param!(attr_name, resource_params)
28
+ end
31
29
  end
32
30
  end
33
31
  end
34
32
 
35
- def i18n_message(type, attribute)
36
- i18n_attr = I18n.t("grape.errors.attributes.#{attribute}", :default => attribute.to_s)
37
- I18n.t("grape.errors.messages.#{type}", :attribute => i18n_attr)
38
- end
39
-
40
33
  private
41
34
 
42
35
  def self.convert_to_short_name(klass)
@@ -167,7 +160,7 @@ module Grape
167
160
  if validator_class
168
161
  (@api.settings.peek[:validations] ||= []) << validator_class.new(attrs, options, doc_attrs[:required], self)
169
162
  else
170
- raise "unknown validator: #{type}"
163
+ raise Grape::Exceptions::UnknownValidator.new(type)
171
164
  end
172
165
  end
173
166
 
@@ -12,7 +12,8 @@ module Grape
12
12
  if valid_type?(new_value)
13
13
  params[attr_name] = new_value
14
14
  else
15
- raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message => i18n_message(:coerce, attr_name)
15
+ raise Grape::Exceptions::Validation, :status => 400,
16
+ :param => @scope.full_name(attr_name), :message_key => :coerce
16
17
  end
17
18
  end
18
19
 
@@ -3,7 +3,8 @@ module Grape
3
3
  class PresenceValidator < Validator
4
4
  def validate_param!(attr_name, params)
5
5
  unless params.has_key?(attr_name)
6
- raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message => i18n_message(:presence, attr_name)
6
+ raise Grape::Exceptions::Validation, :status => 400,
7
+ :param => @scope.full_name(attr_name), :message_key => :presence
7
8
  end
8
9
  end
9
10
  end
@@ -4,7 +4,8 @@ module Grape
4
4
  class RegexpValidator < SingleOptionValidator
5
5
  def validate_param!(attr_name, params)
6
6
  if params[attr_name] && !( params[attr_name].to_s =~ @option )
7
- raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message => i18n_message(:regexp, attr_name)
7
+ raise Grape::Exceptions::Validation, :status => 400,
8
+ :param => @scope.full_name(attr_name), :message_key => :regexp
8
9
  end
9
10
  end
10
11
  end
data/lib/grape/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Grape
2
- VERSION = '0.2.6'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -100,7 +100,7 @@ describe Grape::API do
100
100
 
101
101
  describe '.represent' do
102
102
  it 'requires a :with option' do
103
- expect{ subject.represent Object, {} }.to raise_error(ArgumentError)
103
+ expect{ subject.represent Object, {} }.to raise_error(Grape::Exceptions::InvalidWithOptionForRepresent)
104
104
  end
105
105
 
106
106
  it 'adds the association to the :representations setting' do
@@ -108,6 +108,7 @@ describe Grape::API do
108
108
  subject.represent Object, :with => klass
109
109
  subject.settings[:representations][Object].should == klass
110
110
  end
111
+
111
112
  end
112
113
 
113
114
  describe '.namespace' do
@@ -359,7 +360,7 @@ describe Grape::API do
359
360
  send(verb, '/example')
360
361
  last_response.body.should eql verb == 'head' ? '' : verb
361
362
  # Call it with a method other than the properly constrained one.
362
- send(used_verb = verbs[(verbs.index(verb) + 1) % verbs.size], '/example')
363
+ send(used_verb = verbs[(verbs.index(verb) + 2) % verbs.size], '/example')
363
364
  last_response.status.should eql used_verb == 'options' ? 204 :405
364
365
  end
365
366
  end
@@ -391,18 +392,70 @@ describe Grape::API do
391
392
  "example"
392
393
  end
393
394
  put '/example'
394
- last_response.headers['Allow'].should eql 'OPTIONS, GET, POST'
395
+ last_response.headers['Allow'].should eql 'OPTIONS, GET, POST, HEAD'
395
396
  end
396
397
 
397
398
  it 'adds an OPTIONS route that returns a 204 and an Allow header' do
398
399
  subject.get 'example' do
399
400
  "example"
400
401
  end
402
+ options '/example'
403
+ last_response.status.should eql 204
404
+ last_response.body.should eql ''
405
+ last_response.headers['Allow'].should eql 'OPTIONS, GET, HEAD'
406
+ end
407
+
408
+ it 'allows HEAD on a GET request' do
409
+ subject.get 'example' do
410
+ "example"
411
+ end
412
+ head '/example'
413
+ last_response.status.should eql 200
414
+ last_response.body.should eql ''
415
+ end
416
+
417
+ it 'overwrites the default HEAD request' do
418
+ subject.head 'example' do
419
+ error! 'nothing to see here', 400
420
+ end
421
+ subject.get 'example' do
422
+ "example"
423
+ end
424
+ head '/example'
425
+ last_response.status.should eql 400
426
+ end
427
+ end
428
+
429
+ context "do_not_route_head!" do
430
+ before :each do
431
+ subject.do_not_route_head!
432
+ subject.get 'example' do
433
+ "example"
434
+ end
435
+ end
436
+ it 'options does not contain HEAD' do
401
437
  options '/example'
402
438
  last_response.status.should eql 204
403
439
  last_response.body.should eql ''
404
440
  last_response.headers['Allow'].should eql 'OPTIONS, GET'
405
441
  end
442
+ it 'does not allow HEAD on a GET request' do
443
+ head '/example'
444
+ last_response.status.should eql 405
445
+ end
446
+ end
447
+
448
+ context "do_not_route_options!" do
449
+ before :each do
450
+ subject.do_not_route_options!
451
+ subject.get 'example' do
452
+ "example"
453
+ end
454
+ end
455
+ it 'options does not exist' do
456
+ options '/example'
457
+ last_response.status.should eql 405
458
+ end
406
459
  end
407
460
 
408
461
  describe 'filters' do
@@ -1013,7 +1066,7 @@ describe Grape::API do
1013
1066
  end
1014
1067
  get '/excel.json'
1015
1068
  last_response.status.should == 406
1016
- last_response.body.should == "The requested format is not supported."
1069
+ last_response.body.should == "The requested format 'txt' is not supported."
1017
1070
  end
1018
1071
  end
1019
1072
 
@@ -1116,7 +1169,7 @@ describe Grape::API do
1116
1169
  last_response.body.should eql "elpmis"
1117
1170
  end
1118
1171
  end
1119
- context "muti_xml" do
1172
+ context "multi_xml" do
1120
1173
  it "doesn't parse yaml" do
1121
1174
  subject.put :yaml do
1122
1175
  params[:tag]
@@ -1512,6 +1565,34 @@ describe Grape::API do
1512
1565
  subject.routes.first.route_path.should =~ /\/cool\/awesome/
1513
1566
  subject.routes.last.route_path.should =~ /\/cool\/sauce/
1514
1567
  end
1568
+
1569
+ it 'mounts on a path' do
1570
+ subject.namespace :cool do
1571
+ app = Class.new(Grape::API)
1572
+ app.get '/awesome' do
1573
+ "sauce"
1574
+ end
1575
+ mount app => '/mounted'
1576
+ end
1577
+ get "/mounted/cool/awesome"
1578
+ last_response.status.should == 200
1579
+ last_response.body.should == "sauce"
1580
+ end
1581
+
1582
+ it 'mounts on a nested path' do
1583
+ app1 = Class.new(Grape::API)
1584
+ app2 = Class.new(Grape::API)
1585
+ app2.get '/nice' do
1586
+ "play"
1587
+ end
1588
+ # note that the reverse won't work, mount from outside-in
1589
+ subject.mount app1 => '/app1'
1590
+ app1.mount app2 => '/app2'
1591
+ get "/app1/app2/nice"
1592
+ last_response.status.should == 200
1593
+ last_response.body.should == "play"
1594
+ end
1595
+
1515
1596
  end
1516
1597
  end
1517
1598
 
@@ -1690,6 +1771,55 @@ describe Grape::API do
1690
1771
  last_response.body.should == '[{"abc":"def"},{"abc":"def"}]'
1691
1772
  end
1692
1773
  end
1774
+ context ":xml" do
1775
+ before(:each) do
1776
+ subject.format :xml
1777
+ end
1778
+ it 'string' do
1779
+ subject.get "/example" do
1780
+ "example"
1781
+ end
1782
+ get '/example'
1783
+ last_response.status.should == 500
1784
+ last_response.body.should == <<-XML
1785
+ <?xml version="1.0" encoding="UTF-8"?>
1786
+ <error>
1787
+ <message>cannot convert String to xml</message>
1788
+ </error>
1789
+ XML
1790
+ end
1791
+ it 'hash' do
1792
+ subject.get "/example" do
1793
+ ActiveSupport::OrderedHash[
1794
+ :example1, "example1",
1795
+ :example2, "example2"
1796
+ ]
1797
+ end
1798
+ get '/example'
1799
+ last_response.status.should == 200
1800
+ last_response.body.should == <<-XML
1801
+ <?xml version="1.0" encoding="UTF-8"?>
1802
+ <hash>
1803
+ <example1>example1</example1>
1804
+ <example2>example2</example2>
1805
+ </hash>
1806
+ XML
1807
+ end
1808
+ it 'array' do
1809
+ subject.get "/example" do
1810
+ [ "example1", "example2" ]
1811
+ end
1812
+ get '/example'
1813
+ last_response.status.should == 200
1814
+ last_response.body.should == <<-XML
1815
+ <?xml version="1.0" encoding="UTF-8"?>
1816
+ <strings type="array">
1817
+ <string>example1</string>
1818
+ <string>example2</string>
1819
+ </strings>
1820
+ XML
1821
+ end
1822
+ end
1693
1823
  end
1694
1824
 
1695
1825
  context "catch-all" do
@@ -1725,4 +1855,19 @@ describe Grape::API do
1725
1855
  end
1726
1856
  end
1727
1857
 
1858
+ context "cascading" do
1859
+ it "cascades" do
1860
+ subject.version 'v1', :using => :path, :cascade => true
1861
+ get "/v1/hello"
1862
+ last_response.status.should == 404
1863
+ last_response.headers["X-Cascade"].should == "pass"
1864
+ end
1865
+
1866
+ it "does not cascade" do
1867
+ subject.version 'v2', :using => :path, :cascade => false
1868
+ get "/v2/hello"
1869
+ last_response.status.should == 404
1870
+ last_response.headers.keys.should_not include "X-Cascade"
1871
+ end
1872
+ end
1728
1873
  end