grape 0.3.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (101) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +70 -0
  4. data/.travis.yml +7 -6
  5. data/CHANGELOG.md +134 -4
  6. data/CONTRIBUTING.md +118 -0
  7. data/Gemfile +5 -2
  8. data/README.md +551 -116
  9. data/RELEASING.md +105 -0
  10. data/Rakefile +29 -8
  11. data/UPGRADING.md +124 -0
  12. data/grape.gemspec +3 -3
  13. data/lib/grape/api.rb +207 -88
  14. data/lib/grape/cookies.rb +4 -8
  15. data/lib/grape/endpoint.rb +198 -144
  16. data/lib/grape/error_formatter/base.rb +5 -7
  17. data/lib/grape/error_formatter/json.rb +3 -5
  18. data/lib/grape/error_formatter/txt.rb +1 -3
  19. data/lib/grape/error_formatter/xml.rb +4 -6
  20. data/lib/grape/exceptions/base.rb +9 -9
  21. data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
  22. data/lib/grape/exceptions/invalid_formatter.rb +1 -4
  23. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -5
  24. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -6
  25. data/lib/grape/exceptions/missing_mime_type.rb +1 -5
  26. data/lib/grape/exceptions/missing_option.rb +1 -4
  27. data/lib/grape/exceptions/missing_vendor_option.rb +1 -4
  28. data/lib/grape/exceptions/unknown_options.rb +1 -5
  29. data/lib/grape/exceptions/unknown_validator.rb +1 -3
  30. data/lib/grape/exceptions/validation.rb +13 -3
  31. data/lib/grape/exceptions/validation_errors.rb +43 -0
  32. data/lib/grape/formatter/base.rb +5 -7
  33. data/lib/grape/formatter/json.rb +0 -3
  34. data/lib/grape/formatter/serializable_hash.rb +15 -15
  35. data/lib/grape/formatter/txt.rb +0 -2
  36. data/lib/grape/formatter/xml.rb +0 -2
  37. data/lib/grape/http/request.rb +26 -0
  38. data/lib/grape/locale/en.yml +8 -5
  39. data/lib/grape/middleware/auth/base.rb +30 -0
  40. data/lib/grape/middleware/auth/basic.rb +3 -20
  41. data/lib/grape/middleware/auth/digest.rb +2 -19
  42. data/lib/grape/middleware/auth/oauth2.rb +31 -24
  43. data/lib/grape/middleware/base.rb +7 -7
  44. data/lib/grape/middleware/error.rb +36 -22
  45. data/lib/grape/middleware/filter.rb +3 -3
  46. data/lib/grape/middleware/formatter.rb +99 -61
  47. data/lib/grape/middleware/globals.rb +13 -0
  48. data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
  49. data/lib/grape/middleware/versioner/header.rb +22 -16
  50. data/lib/grape/middleware/versioner/param.rb +9 -11
  51. data/lib/grape/middleware/versioner/path.rb +10 -13
  52. data/lib/grape/middleware/versioner.rb +3 -1
  53. data/lib/grape/namespace.rb +23 -0
  54. data/lib/grape/parser/base.rb +3 -5
  55. data/lib/grape/parser/json.rb +0 -2
  56. data/lib/grape/parser/xml.rb +0 -2
  57. data/lib/grape/path.rb +70 -0
  58. data/lib/grape/route.rb +10 -6
  59. data/lib/grape/util/content_types.rb +2 -1
  60. data/lib/grape/util/deep_merge.rb +5 -5
  61. data/lib/grape/util/hash_stack.rb +13 -2
  62. data/lib/grape/validations/coerce.rb +11 -10
  63. data/lib/grape/validations/default.rb +25 -0
  64. data/lib/grape/validations/presence.rb +7 -3
  65. data/lib/grape/validations/regexp.rb +2 -5
  66. data/lib/grape/validations/values.rb +17 -0
  67. data/lib/grape/validations.rb +161 -54
  68. data/lib/grape/version.rb +1 -1
  69. data/lib/grape.rb +19 -4
  70. data/spec/grape/api_spec.rb +897 -268
  71. data/spec/grape/endpoint_spec.rb +283 -66
  72. data/spec/grape/entity_spec.rb +132 -29
  73. data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
  74. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  75. data/spec/grape/middleware/auth/basic_spec.rb +8 -8
  76. data/spec/grape/middleware/auth/digest_spec.rb +5 -5
  77. data/spec/grape/middleware/auth/oauth2_spec.rb +81 -36
  78. data/spec/grape/middleware/base_spec.rb +8 -13
  79. data/spec/grape/middleware/error_spec.rb +13 -17
  80. data/spec/grape/middleware/exception_spec.rb +47 -27
  81. data/spec/grape/middleware/formatter_spec.rb +103 -41
  82. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
  83. data/spec/grape/middleware/versioner/header_spec.rb +76 -51
  84. data/spec/grape/middleware/versioner/param_spec.rb +18 -18
  85. data/spec/grape/middleware/versioner/path_spec.rb +6 -6
  86. data/spec/grape/middleware/versioner_spec.rb +5 -2
  87. data/spec/grape/path_spec.rb +229 -0
  88. data/spec/grape/util/hash_stack_spec.rb +31 -32
  89. data/spec/grape/validations/coerce_spec.rb +116 -51
  90. data/spec/grape/validations/default_spec.rb +123 -0
  91. data/spec/grape/validations/presence_spec.rb +42 -44
  92. data/spec/grape/validations/regexp_spec.rb +9 -9
  93. data/spec/grape/validations/values_spec.rb +138 -0
  94. data/spec/grape/validations/zh-CN.yml +4 -3
  95. data/spec/grape/validations_spec.rb +681 -48
  96. data/spec/shared/versioning_examples.rb +22 -6
  97. data/spec/spec_helper.rb +3 -2
  98. data/spec/support/basic_auth_encode_helpers.rb +0 -1
  99. data/spec/support/content_type_helpers.rb +11 -0
  100. data/spec/support/versioned_helpers.rb +13 -5
  101. metadata +34 -84
@@ -2,48 +2,44 @@ require 'spec_helper'
2
2
 
3
3
  describe Grape::Middleware::Error do
4
4
  class ErrApp
5
- class << self
5
+ class << self
6
6
  attr_accessor :error
7
7
  attr_accessor :format
8
-
8
+
9
9
  def call(env)
10
- throw :error, self.error
10
+ throw :error, error
11
11
  end
12
12
  end
13
13
  end
14
-
14
+
15
15
  def app
16
16
  Rack::Builder.app do
17
- use Grape::Middleware::Error, :default_message => 'Aww, hamburgers.'
17
+ use Grape::Middleware::Error, default_message: 'Aww, hamburgers.'
18
18
  run ErrApp
19
19
  end
20
20
  end
21
-
21
+
22
22
  it 'sets the status code appropriately' do
23
- ErrApp.error = {:status => 410}
23
+ ErrApp.error = { status: 410 }
24
24
  get '/'
25
25
  last_response.status.should == 410
26
26
  end
27
-
27
+
28
28
  it 'sets the error message appropriately' do
29
- ErrApp.error = {:message => 'Awesome stuff.'}
29
+ ErrApp.error = { message: 'Awesome stuff.' }
30
30
  get '/'
31
31
  last_response.body.should == 'Awesome stuff.'
32
32
  end
33
-
34
- it 'defaults to a 403 status' do
33
+
34
+ it 'defaults to a 500 status' do
35
35
  ErrApp.error = {}
36
36
  get '/'
37
- last_response.status.should == 403
37
+ last_response.status.should == 500
38
38
  end
39
-
39
+
40
40
  it 'has a default message' do
41
41
  ErrApp.error = {}
42
42
  get '/'
43
43
  last_response.body.should == 'Aww, hamburgers.'
44
44
  end
45
-
46
- context 'with formatting' do
47
-
48
- end
49
45
  end
@@ -15,9 +15,10 @@ describe Grape::Middleware::Error do
15
15
  # raises a hash error
16
16
  class ErrorHashApp
17
17
  class << self
18
- def error!(message, status=403)
19
- throw :error, :message => { :error => message, :detail => "missing widget" }, :status => status
18
+ def error!(message, status)
19
+ throw :error, message: { error: message, detail: "missing widget" }, status: status
20
20
  end
21
+
21
22
  def call(env)
22
23
  error!("rain!", 401)
23
24
  end
@@ -27,9 +28,10 @@ describe Grape::Middleware::Error do
27
28
  # raises an error!
28
29
  class AccessDeniedApp
29
30
  class << self
30
- def error!(message, status=403)
31
- throw :error, :message => message, :status => status
31
+ def error!(message, status)
32
+ throw :error, message: message, status: status
32
33
  end
34
+
33
35
  def call(env)
34
36
  error!("Access Denied", 401)
35
37
  end
@@ -37,18 +39,18 @@ describe Grape::Middleware::Error do
37
39
  end
38
40
 
39
41
  # raises a custom error
40
- class CustomError < Grape::Exceptions::Base; end
42
+ class CustomError < Grape::Exceptions::Base
43
+ end
44
+
41
45
  class CustomErrorApp
42
46
  class << self
43
47
  def call(env)
44
- raise CustomError, :status => 400, :message => 'failed validation'
48
+ raise CustomError, status: 400, message: 'failed validation'
45
49
  end
46
50
  end
47
51
  end
48
52
 
49
- def app
50
- @app
51
- end
53
+ attr_reader :app
52
54
 
53
55
  it 'does not trap errors by default' do
54
56
  @app ||= Rack::Builder.app do
@@ -61,25 +63,25 @@ describe Grape::Middleware::Error do
61
63
  context 'with rescue_all set to true' do
62
64
  it 'sets the message appropriately' do
63
65
  @app ||= Rack::Builder.app do
64
- use Grape::Middleware::Error, :rescue_all => true
66
+ use Grape::Middleware::Error, rescue_all: true
65
67
  run ExceptionApp
66
68
  end
67
69
  get '/'
68
70
  last_response.body.should == "rain!"
69
71
  end
70
72
 
71
- it 'defaults to a 403 status' do
73
+ it 'defaults to a 500 status' do
72
74
  @app ||= Rack::Builder.app do
73
- use Grape::Middleware::Error, :rescue_all => true
75
+ use Grape::Middleware::Error, rescue_all: true
74
76
  run ExceptionApp
75
77
  end
76
78
  get '/'
77
- last_response.status.should == 403
79
+ last_response.status.should == 500
78
80
  end
79
81
 
80
82
  it 'is possible to specify a different default status code' do
81
83
  @app ||= Rack::Builder.app do
82
- use Grape::Middleware::Error, :rescue_all => true, :default_status => 500
84
+ use Grape::Middleware::Error, rescue_all: true, default_status: 500
83
85
  run ExceptionApp
84
86
  end
85
87
  get '/'
@@ -88,7 +90,7 @@ describe Grape::Middleware::Error do
88
90
 
89
91
  it 'is possible to return errors in json format' do
90
92
  @app ||= Rack::Builder.app do
91
- use Grape::Middleware::Error, :rescue_all => true, :format => :json
93
+ use Grape::Middleware::Error, rescue_all: true, format: :json
92
94
  run ExceptionApp
93
95
  end
94
96
  get '/'
@@ -97,7 +99,26 @@ describe Grape::Middleware::Error do
97
99
 
98
100
  it 'is possible to return hash errors in json format' do
99
101
  @app ||= Rack::Builder.app do
100
- use Grape::Middleware::Error, :rescue_all => true, :format => :json
102
+ use Grape::Middleware::Error, rescue_all: true, format: :json
103
+ run ErrorHashApp
104
+ end
105
+ get '/'
106
+ ['{"error":"rain!","detail":"missing widget"}',
107
+ '{"detail":"missing widget","error":"rain!"}'].should include(last_response.body)
108
+ end
109
+
110
+ it 'is possible to return errors in jsonapi format' do
111
+ @app ||= Rack::Builder.app do
112
+ use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
113
+ run ExceptionApp
114
+ end
115
+ get '/'
116
+ last_response.body.should == '{"error":"rain!"}'
117
+ end
118
+
119
+ it 'is possible to return hash errors in jsonapi format' do
120
+ @app ||= Rack::Builder.app do
121
+ use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
101
122
  run ErrorHashApp
102
123
  end
103
124
  get '/'
@@ -107,7 +128,7 @@ describe Grape::Middleware::Error do
107
128
 
108
129
  it 'is possible to return errors in xml format' do
109
130
  @app ||= Rack::Builder.app do
110
- use Grape::Middleware::Error, :rescue_all => true, :format => :xml
131
+ use Grape::Middleware::Error, rescue_all: true, format: :xml
111
132
  run ExceptionApp
112
133
  end
113
134
  get '/'
@@ -116,7 +137,7 @@ describe Grape::Middleware::Error do
116
137
 
117
138
  it 'is possible to return hash errors in xml format' do
118
139
  @app ||= Rack::Builder.app do
119
- use Grape::Middleware::Error, :rescue_all => true, :format => :xml
140
+ use Grape::Middleware::Error, rescue_all: true, format: :xml
120
141
  run ErrorHashApp
121
142
  end
122
143
  get '/'
@@ -126,14 +147,13 @@ describe Grape::Middleware::Error do
126
147
 
127
148
  it 'is possible to specify a custom formatter' do
128
149
  @app ||= Rack::Builder.app do
129
- use Grape::Middleware::Error,
130
- :rescue_all => true,
131
- :format => :custom,
132
- :error_formatters => {
133
- :custom => lambda { |message, backtrace, options, env|
134
- { :custom_formatter => message }.inspect
135
- }
136
- }
150
+ use Grape::Middleware::Error, rescue_all: true,
151
+ format: :custom,
152
+ error_formatters: {
153
+ custom: lambda { |message, backtrace, options, env|
154
+ { custom_formatter: message }.inspect
155
+ }
156
+ }
137
157
  run ExceptionApp
138
158
  end
139
159
  get '/'
@@ -151,7 +171,7 @@ describe Grape::Middleware::Error do
151
171
 
152
172
  it 'responds to custom Grape exceptions appropriately' do
153
173
  @app ||= Rack::Builder.app do
154
- use Grape::Middleware::Error, :rescue_all => false
174
+ use Grape::Middleware::Error, rescue_all: false
155
175
  run CustomErrorApp
156
176
  end
157
177
 
@@ -1,16 +1,16 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Middleware::Formatter do
4
- subject{ Grape::Middleware::Formatter.new(app) }
5
- before{ subject.stub!(:dup).and_return(subject) }
4
+ subject { Grape::Middleware::Formatter.new(app) }
5
+ before { subject.stub(:dup).and_return(subject) }
6
6
 
7
- let(:app){ lambda{|env| [200, {}, [@body || { "foo" => "bar" }]]} }
7
+ let(:app) { lambda { |env| [200, {}, [@body || { "foo" => "bar" }]] } }
8
8
 
9
9
  context 'serialization' do
10
10
  it 'looks at the bodies for possibly serializable data' do
11
- @body = {"abc" => "def"}
12
- status, headers, bodies = *subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'})
13
- bodies.each{|b| b.should == MultiJson.dump(@body) }
11
+ @body = { "abc" => "def" }
12
+ _, _, bodies = *subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json')
13
+ bodies.each { |b| b.should == MultiJson.dump(@body) }
14
14
  end
15
15
 
16
16
  it 'calls #to_json since default format is json' do
@@ -21,7 +21,18 @@ describe Grape::Middleware::Formatter do
21
21
  end
22
22
  end
23
23
 
24
- subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '"bar"'}
24
+ subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json').last.each { |b| b.should == '"bar"' }
25
+ end
26
+
27
+ it 'calls #to_json if the content type is jsonapi' do
28
+ @body = { 'foos' => [{ 'bar' => 'baz' }] }
29
+ @body.instance_eval do
30
+ def to_json
31
+ "{\"foos\":[{\"bar\":\"baz\"}] }"
32
+ end
33
+ end
34
+
35
+ subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/vnd.api+json').last.each { |b| b.should == '{"foos":[{"bar":"baz"}] }' }
25
36
  end
26
37
 
27
38
  it 'calls #to_xml if the content type is xml' do
@@ -32,98 +43,124 @@ describe Grape::Middleware::Formatter do
32
43
  end
33
44
  end
34
45
 
35
- subject.call({'PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '<bar/>'}
46
+ subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json').last.each { |b| b.should == '<bar/>' }
47
+ end
48
+ end
49
+
50
+ context 'error handling' do
51
+ let(:formatter) { double(:formatter) }
52
+ before do
53
+ Grape::Formatter::Base.stub(:formatter_for) { formatter }
54
+ end
55
+
56
+ it 'rescues formatter-specific exceptions' do
57
+ formatter.stub(:call) { raise Grape::Exceptions::InvalidFormatter.new(String, 'xml') }
58
+
59
+ expect {
60
+ catch(:error) { subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }
61
+ }.to_not raise_error
62
+ end
63
+
64
+ it 'does not rescue other exceptions' do
65
+ formatter.stub(:call) { raise StandardError }
66
+
67
+ expect {
68
+ catch(:error) { subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }
69
+ }.to raise_error
36
70
  end
37
71
  end
38
72
 
39
73
  context 'detection' do
40
74
 
41
- it 'uses the extension if one is provided' do
42
- subject.call({'PATH_INFO' => '/info.xml'})
75
+ it 'uses the xml extension if one is provided' do
76
+ subject.call('PATH_INFO' => '/info.xml')
43
77
  subject.env['api.format'].should == :xml
44
- subject.call({'PATH_INFO' => '/info.json'})
78
+ end
79
+
80
+ it 'uses the json extension if one is provided' do
81
+ subject.call('PATH_INFO' => '/info.json')
45
82
  subject.env['api.format'].should == :json
46
83
  end
47
84
 
48
85
  it 'uses the format parameter if one is provided' do
49
- subject.call({'PATH_INFO' => '/info','QUERY_STRING' => 'format=json'})
86
+ subject.call('PATH_INFO' => '/info', 'QUERY_STRING' => 'format=json')
50
87
  subject.env['api.format'].should == :json
51
- subject.call({'PATH_INFO' => '/info','QUERY_STRING' => 'format=xml'})
88
+ subject.call('PATH_INFO' => '/info', 'QUERY_STRING' => 'format=xml')
52
89
  subject.env['api.format'].should == :xml
53
90
  end
54
91
 
55
92
  it 'uses the default format if none is provided' do
56
- subject.call({'PATH_INFO' => '/info'})
93
+ subject.call('PATH_INFO' => '/info')
57
94
  subject.env['api.format'].should == :txt
58
95
  end
59
96
 
60
97
  it 'uses the requested format if provided in headers' do
61
- subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json'})
98
+ subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json')
62
99
  subject.env['api.format'].should == :json
63
100
  end
64
101
 
65
102
  it 'uses the file extension format if provided before headers' do
66
- subject.call({'PATH_INFO' => '/info.txt', 'HTTP_ACCEPT' => 'application/json'})
103
+ subject.call('PATH_INFO' => '/info.txt', 'HTTP_ACCEPT' => 'application/json')
67
104
  subject.env['api.format'].should == :txt
68
105
  end
69
106
  end
70
107
 
71
108
  context 'accept header detection' do
72
109
  it 'detects from the Accept header' do
73
- subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/xml'})
110
+ subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/xml')
74
111
  subject.env['api.format'].should == :xml
75
112
  end
76
113
 
77
114
  it 'looks for case-indifferent headers' do
78
- subject.call({'PATH_INFO' => '/info', 'http_accept' => 'application/xml'})
115
+ subject.call('PATH_INFO' => '/info', 'http_accept' => 'application/xml')
79
116
  subject.env['api.format'].should == :xml
80
117
  end
81
118
 
82
119
  it 'uses quality rankings to determine formats' do
83
- subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=0.3,application/xml; q=1.0'})
120
+ subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=0.3,application/xml; q=1.0')
84
121
  subject.env['api.format'].should == :xml
85
- subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=1.0,application/xml; q=0.3'})
122
+ subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=1.0,application/xml; q=0.3')
86
123
  subject.env['api.format'].should == :json
87
124
  end
88
125
 
89
126
  it 'handles quality rankings mixed with nothing' do
90
- subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json,application/xml; q=1.0'})
127
+ subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json,application/xml; q=1.0')
91
128
  subject.env['api.format'].should == :xml
92
129
  end
93
130
 
94
131
  it 'parses headers with other attributes' do
95
- subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; abc=2.3; q=1.0,application/xml; q=0.7'})
132
+ subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; abc=2.3; q=1.0,application/xml; q=0.7')
96
133
  subject.env['api.format'].should == :json
97
134
  end
98
135
 
99
136
  it 'parses headers with vendor and api version' do
100
- subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test-v1+xml'})
137
+ subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test-v1+xml')
101
138
  subject.env['api.format'].should == :xml
102
139
  end
103
140
 
104
141
  it 'parses headers with symbols as hash keys' do
105
- subject.call({'PATH_INFO' => '/info', 'http_accept' => 'application/xml', :system_time => '091293'})
142
+ subject.call('PATH_INFO' => '/info', 'http_accept' => 'application/xml', system_time: '091293')
106
143
  subject.env[:system_time].should == '091293'
107
144
  end
108
145
  end
109
146
 
110
147
  context 'content-type' do
111
148
  it 'is set for json' do
112
- _, headers, _ = subject.call({'PATH_INFO' => '/info.json'})
149
+ _, headers, _ = subject.call('PATH_INFO' => '/info.json')
113
150
  headers['Content-type'].should == 'application/json'
114
151
  end
115
152
  it 'is set for xml' do
116
- _, headers, _ = subject.call({'PATH_INFO' => '/info.xml'})
153
+ _, headers, _ = subject.call('PATH_INFO' => '/info.xml')
117
154
  headers['Content-type'].should == 'application/xml'
118
155
  end
119
156
  it 'is set for txt' do
120
- _, headers, _ = subject.call({'PATH_INFO' => '/info.txt'})
157
+ _, headers, _ = subject.call('PATH_INFO' => '/info.txt')
121
158
  headers['Content-type'].should == 'text/plain'
122
159
  end
123
160
  it 'is set for custom' do
124
161
  subject.options[:content_types] = {}
125
162
  subject.options[:content_types][:custom] = 'application/x-custom'
126
- _, headers, _ = subject.call({'PATH_INFO' => '/info.custom'})
163
+ _, headers, _ = subject.call('PATH_INFO' => '/info.custom')
127
164
  headers['Content-type'].should == 'application/x-custom'
128
165
  end
129
166
  end
@@ -133,60 +170,85 @@ describe Grape::Middleware::Formatter do
133
170
  subject.options[:content_types] = {}
134
171
  subject.options[:content_types][:custom] = "don't care"
135
172
  subject.options[:formatters][:custom] = lambda { |obj, env| 'CUSTOM FORMAT' }
136
- _, _, body = subject.call({'PATH_INFO' => '/info.custom'})
173
+ _, _, body = subject.call('PATH_INFO' => '/info.custom')
137
174
  body.body.should == ['CUSTOM FORMAT']
138
175
  end
139
176
  it 'uses default json formatter' do
140
177
  @body = ['blah']
141
- _, _, body = subject.call({'PATH_INFO' => '/info.json'})
178
+ _, _, body = subject.call('PATH_INFO' => '/info.json')
142
179
  body.body.should == ['["blah"]']
143
180
  end
144
181
  it 'uses custom json formatter' do
145
182
  subject.options[:formatters][:json] = lambda { |obj, env| 'CUSTOM JSON FORMAT' }
146
- _, _, body = subject.call({'PATH_INFO' => '/info.json'})
183
+ _, _, body = subject.call('PATH_INFO' => '/info.json')
147
184
  body.body.should == ['CUSTOM JSON FORMAT']
148
185
  end
149
186
  end
150
187
 
151
188
  context 'input' do
152
- [ "POST", "PATCH", "PUT" ].each do |method|
153
- [ "application/json", "application/json; charset=utf-8" ].each do |content_type|
189
+ ["POST", "PATCH", "PUT", "DELETE"].each do |method|
190
+ ["application/json", "application/json; charset=utf-8"].each do |content_type|
154
191
  context content_type do
155
192
  it 'parses the body from #{method} and copies values into rack.request.form_hash' do
156
193
  io = StringIO.new('{"is_boolean":true,"string":"thing"}')
157
- subject.call({
194
+ subject.call(
158
195
  'PATH_INFO' => '/info',
159
196
  'REQUEST_METHOD' => method,
160
197
  'CONTENT_TYPE' => content_type,
161
198
  'rack.input' => io,
162
199
  'CONTENT_LENGTH' => io.length
163
- })
164
- subject.env['rack.request.form_hash']['is_boolean'].should be_true
200
+ )
201
+ subject.env['rack.request.form_hash']['is_boolean'].should be true
165
202
  subject.env['rack.request.form_hash']['string'].should == 'thing'
166
203
  end
167
204
  end
168
205
  end
206
+ it "parses the chunked body from #{method} and copies values into rack.request.from_hash" do
207
+ io = StringIO.new('{"is_boolean":true,"string":"thing"}')
208
+ subject.call(
209
+ 'PATH_INFO' => '/infol',
210
+ 'REQUEST_METHOD' => method,
211
+ 'CONTENT_TYPE' => 'application/json',
212
+ 'rack.input' => io,
213
+ 'HTTP_TRANSFER_ENCODING' => 'chunked'
214
+ )
215
+ subject.env['rack.request.form_hash']['is_boolean'].should be true
216
+ subject.env['rack.request.form_hash']['string'].should == 'thing'
217
+ end
218
+ it "rewinds IO" do
219
+ io = StringIO.new('{"is_boolean":true,"string":"thing"}')
220
+ io.read
221
+ subject.call(
222
+ 'PATH_INFO' => '/infol',
223
+ 'REQUEST_METHOD' => method,
224
+ 'CONTENT_TYPE' => 'application/json',
225
+ 'rack.input' => io,
226
+ 'HTTP_TRANSFER_ENCODING' => 'chunked'
227
+ )
228
+ subject.env['rack.request.form_hash']['is_boolean'].should be true
229
+ subject.env['rack.request.form_hash']['string'].should == 'thing'
230
+ end
169
231
  it 'parses the body from an xml #{method} and copies values into rack.request.from_hash' do
170
232
  io = StringIO.new('<thing><name>Test</name></thing>')
171
- subject.call({
233
+ subject.call(
172
234
  'PATH_INFO' => '/info.xml',
173
235
  'REQUEST_METHOD' => method,
174
236
  'CONTENT_TYPE' => 'application/xml',
175
237
  'rack.input' => io,
176
238
  'CONTENT_LENGTH' => io.length
177
- })
239
+ )
178
240
  subject.env['rack.request.form_hash']['thing']['name'].should == 'Test'
179
241
  end
180
- [ Rack::Request::FORM_DATA_MEDIA_TYPES, Rack::Request::PARSEABLE_DATA_MEDIA_TYPES ].flatten.each do |content_type|
242
+ [Rack::Request::FORM_DATA_MEDIA_TYPES, Rack::Request::PARSEABLE_DATA_MEDIA_TYPES].flatten.each do |content_type|
181
243
  it "ignores #{content_type}" do
182
244
  io = StringIO.new('name=Other+Test+Thing')
183
- subject.call({
245
+ subject.call(
184
246
  'PATH_INFO' => '/info',
185
247
  'REQUEST_METHOD' => method,
186
248
  'CONTENT_TYPE' => content_type,
187
249
  'rack.input' => io,
188
250
  'CONTENT_LENGTH' => io.length
189
- })
251
+ )
190
252
  subject.env['rack.request.form_hash'].should be_nil
191
253
  end
192
254
  end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Versioner::AcceptVersionHeader do
4
+ let(:app) { lambda { |env| [200, env, env] } }
5
+ subject { Grape::Middleware::Versioner::AcceptVersionHeader.new(app, @options || {}) }
6
+
7
+ before do
8
+ @options = {
9
+ version_options: {
10
+ using: :accept_version_header
11
+ },
12
+ }
13
+ end
14
+
15
+ context 'api.version' do
16
+ before do
17
+ @options[:versions] = ['v1']
18
+ end
19
+
20
+ it 'is set' do
21
+ status, _, env = subject.call('HTTP_ACCEPT_VERSION' => 'v1')
22
+ env['api.version'].should eql 'v1'
23
+ status.should == 200
24
+ end
25
+
26
+ it 'is set if format provided' do
27
+ status, _, env = subject.call('HTTP_ACCEPT_VERSION' => 'v1')
28
+ env['api.version'].should eql 'v1'
29
+ status.should == 200
30
+ end
31
+
32
+ it 'fails with 406 Not Acceptable if version is not supported' do
33
+ expect {
34
+ subject.call('HTTP_ACCEPT_VERSION' => 'v2').last
35
+ }.to throw_symbol(
36
+ :error,
37
+ status: 406,
38
+ headers: { 'X-Cascade' => 'pass' },
39
+ message: 'The requested version is not supported.'
40
+ )
41
+ end
42
+ end
43
+
44
+ it 'succeeds if :strict is not set' do
45
+ subject.call('HTTP_ACCEPT_VERSION' => '').first.should == 200
46
+ subject.call({}).first.should == 200
47
+ end
48
+
49
+ it 'succeeds if :strict is set to false' do
50
+ @options[:version_options][:strict] = false
51
+ subject.call('HTTP_ACCEPT_VERSION' => '').first.should == 200
52
+ subject.call({}).first.should == 200
53
+ end
54
+
55
+ context 'when :strict is set' do
56
+ before do
57
+ @options[:versions] = ['v1']
58
+ @options[:version_options][:strict] = true
59
+ end
60
+
61
+ it 'fails with 406 Not Acceptable if header is not set' do
62
+ expect {
63
+ subject.call({}).last
64
+ }.to throw_symbol(
65
+ :error,
66
+ status: 406,
67
+ headers: { 'X-Cascade' => 'pass' },
68
+ message: 'Accept-Version header must be set.'
69
+ )
70
+ end
71
+
72
+ it 'fails with 406 Not Acceptable if header is empty' do
73
+ expect {
74
+ subject.call('HTTP_ACCEPT_VERSION' => '').last
75
+ }.to throw_symbol(
76
+ :error,
77
+ status: 406,
78
+ headers: { 'X-Cascade' => 'pass' },
79
+ message: 'Accept-Version header must be set.'
80
+ )
81
+ end
82
+
83
+ it 'succeeds if proper header is set' do
84
+ subject.call('HTTP_ACCEPT_VERSION' => 'v1').first.should == 200
85
+ end
86
+ end
87
+
88
+ context 'when :strict and :cascade=>false are set' do
89
+ before do
90
+ @options[:versions] = ['v1']
91
+ @options[:version_options][:strict] = true
92
+ @options[:version_options][:cascade] = false
93
+ end
94
+
95
+ it 'fails with 406 Not Acceptable if header is not set' do
96
+ expect {
97
+ subject.call({}).last
98
+ }.to throw_symbol(
99
+ :error,
100
+ status: 406,
101
+ headers: {},
102
+ message: 'Accept-Version header must be set.'
103
+ )
104
+ end
105
+
106
+ it 'fails with 406 Not Acceptable if header is empty' do
107
+ expect {
108
+ subject.call('HTTP_ACCEPT_VERSION' => '').last
109
+ }.to throw_symbol(
110
+ :error,
111
+ status: 406,
112
+ headers: {},
113
+ message: 'Accept-Version header must be set.'
114
+ )
115
+ end
116
+
117
+ it 'succeeds if proper header is set' do
118
+ subject.call('HTTP_ACCEPT_VERSION' => 'v1').first.should == 200
119
+ end
120
+ end
121
+ end