grape 0.1.5 → 0.2.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.
Files changed (142) hide show
  1. data/.gitignore +13 -1
  2. data/.rspec +1 -1
  3. data/.travis.yml +1 -0
  4. data/Gemfile +10 -0
  5. data/Guardfile +15 -0
  6. data/README.markdown +472 -67
  7. data/grape.gemspec +4 -4
  8. data/lib/grape.rb +17 -5
  9. data/lib/grape/api.rb +227 -124
  10. data/lib/grape/cookies.rb +41 -0
  11. data/lib/grape/endpoint.rb +256 -27
  12. data/lib/grape/entity.rb +227 -0
  13. data/lib/grape/middleware/auth/oauth2.rb +1 -2
  14. data/lib/grape/middleware/base.rb +59 -6
  15. data/lib/grape/middleware/error.rb +15 -6
  16. data/lib/grape/middleware/filter.rb +17 -0
  17. data/lib/grape/middleware/formatter.rb +25 -31
  18. data/lib/grape/middleware/versioner.rb +20 -20
  19. data/lib/grape/middleware/versioner/header.rb +59 -0
  20. data/lib/grape/middleware/versioner/path.rb +42 -0
  21. data/lib/grape/route.rb +23 -0
  22. data/lib/grape/util/hash_stack.rb +100 -0
  23. data/lib/grape/version.rb +1 -1
  24. data/spec/grape/api_spec.rb +651 -162
  25. data/spec/grape/endpoint_spec.rb +216 -18
  26. data/spec/grape/entity_spec.rb +320 -0
  27. data/spec/grape/middleware/auth/basic_spec.rb +2 -2
  28. data/spec/grape/middleware/auth/digest_spec.rb +4 -6
  29. data/spec/grape/middleware/exception_spec.rb +1 -0
  30. data/spec/grape/middleware/formatter_spec.rb +81 -27
  31. data/spec/grape/middleware/versioner/header_spec.rb +148 -0
  32. data/spec/grape/middleware/versioner/path_spec.rb +40 -0
  33. data/spec/grape/middleware/versioner_spec.rb +6 -34
  34. data/spec/grape/util/hash_stack_spec.rb +133 -0
  35. data/spec/shared/versioning_examples.rb +77 -0
  36. data/spec/spec_helper.rb +11 -3
  37. data/spec/support/basic_auth_encode_helpers.rb +4 -0
  38. data/spec/support/rack_patch.rb +25 -0
  39. data/spec/support/versioned_helpers.rb +34 -0
  40. metadata +140 -241
  41. data/.yardoc/checksums +0 -13
  42. data/.yardoc/objects/Grape.dat +0 -0
  43. data/.yardoc/objects/Grape/API.dat +0 -0
  44. data/.yardoc/objects/Grape/API/auth_c.dat +0 -0
  45. data/.yardoc/objects/Grape/API/build_endpoint_c.dat +0 -0
  46. data/.yardoc/objects/Grape/API/call_c.dat +0 -0
  47. data/.yardoc/objects/Grape/API/compile_path_c.dat +0 -0
  48. data/.yardoc/objects/Grape/API/default_format_c.dat +0 -0
  49. data/.yardoc/objects/Grape/API/delete_c.dat +0 -0
  50. data/.yardoc/objects/Grape/API/get_c.dat +0 -0
  51. data/.yardoc/objects/Grape/API/group_c.dat +0 -0
  52. data/.yardoc/objects/Grape/API/head_c.dat +0 -0
  53. data/.yardoc/objects/Grape/API/helpers_c.dat +0 -0
  54. data/.yardoc/objects/Grape/API/http_basic_c.dat +0 -0
  55. data/.yardoc/objects/Grape/API/inherited_c.dat +0 -0
  56. data/.yardoc/objects/Grape/API/logger_c.dat +0 -0
  57. data/.yardoc/objects/Grape/API/namespace_c.dat +0 -0
  58. data/.yardoc/objects/Grape/API/nest_c.dat +0 -0
  59. data/.yardoc/objects/Grape/API/post_c.dat +0 -0
  60. data/.yardoc/objects/Grape/API/prefix_c.dat +0 -0
  61. data/.yardoc/objects/Grape/API/put_c.dat +0 -0
  62. data/.yardoc/objects/Grape/API/reset_21_c.dat +0 -0
  63. data/.yardoc/objects/Grape/API/resource_c.dat +0 -0
  64. data/.yardoc/objects/Grape/API/resources_c.dat +0 -0
  65. data/.yardoc/objects/Grape/API/route_c.dat +0 -0
  66. data/.yardoc/objects/Grape/API/route_set_c.dat +0 -0
  67. data/.yardoc/objects/Grape/API/scope_c.dat +0 -0
  68. data/.yardoc/objects/Grape/API/set_c.dat +0 -0
  69. data/.yardoc/objects/Grape/API/settings_c.dat +0 -0
  70. data/.yardoc/objects/Grape/API/settings_stack_c.dat +0 -0
  71. data/.yardoc/objects/Grape/API/version_c.dat +0 -0
  72. data/.yardoc/objects/Grape/Endpoint.dat +0 -0
  73. data/.yardoc/objects/Grape/Endpoint/block_3D_c.dat +0 -0
  74. data/.yardoc/objects/Grape/Endpoint/block_c.dat +0 -0
  75. data/.yardoc/objects/Grape/Endpoint/call_c.dat +0 -0
  76. data/.yardoc/objects/Grape/Endpoint/call_i.dat +0 -0
  77. data/.yardoc/objects/Grape/Endpoint/env_i.dat +0 -0
  78. data/.yardoc/objects/Grape/Endpoint/error_21_i.dat +0 -0
  79. data/.yardoc/objects/Grape/Endpoint/generate_c.dat +0 -0
  80. data/.yardoc/objects/Grape/Endpoint/header_i.dat +0 -0
  81. data/.yardoc/objects/Grape/Endpoint/params_i.dat +0 -0
  82. data/.yardoc/objects/Grape/Endpoint/request_i.dat +0 -0
  83. data/.yardoc/objects/Grape/Endpoint/status_i.dat +0 -0
  84. data/.yardoc/objects/Grape/Endpoint/version_i.dat +0 -0
  85. data/.yardoc/objects/Grape/Middleware.dat +0 -0
  86. data/.yardoc/objects/Grape/Middleware/Auth.dat +0 -0
  87. data/.yardoc/objects/Grape/Middleware/Auth/Basic.dat +0 -0
  88. data/.yardoc/objects/Grape/Middleware/Auth/Basic/authenticator_i.dat +0 -0
  89. data/.yardoc/objects/Grape/Middleware/Auth/Basic/basic_request_i.dat +0 -0
  90. data/.yardoc/objects/Grape/Middleware/Auth/Basic/before_i.dat +0 -0
  91. data/.yardoc/objects/Grape/Middleware/Auth/Basic/credentials_i.dat +0 -0
  92. data/.yardoc/objects/Grape/Middleware/Auth/Basic/initialize_i.dat +0 -0
  93. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2.dat +0 -0
  94. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/before_i.dat +0 -0
  95. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/default_options_i.dat +0 -0
  96. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/error_out_i.dat +0 -0
  97. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/parse_authorization_header_i.dat +0 -0
  98. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/token_class_i.dat +0 -0
  99. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/verify_token_i.dat +0 -0
  100. data/.yardoc/objects/Grape/Middleware/Base.dat +0 -0
  101. data/.yardoc/objects/Grape/Middleware/Base/after_i.dat +0 -0
  102. data/.yardoc/objects/Grape/Middleware/Base/app_i.dat +0 -0
  103. data/.yardoc/objects/Grape/Middleware/Base/before_i.dat +0 -0
  104. data/.yardoc/objects/Grape/Middleware/Base/call_21_i.dat +0 -0
  105. data/.yardoc/objects/Grape/Middleware/Base/call_i.dat +0 -0
  106. data/.yardoc/objects/Grape/Middleware/Base/default_options_i.dat +0 -0
  107. data/.yardoc/objects/Grape/Middleware/Base/env_i.dat +0 -0
  108. data/.yardoc/objects/Grape/Middleware/Base/initialize_i.dat +0 -0
  109. data/.yardoc/objects/Grape/Middleware/Base/options_i.dat +0 -0
  110. data/.yardoc/objects/Grape/Middleware/Base/request_i.dat +0 -0
  111. data/.yardoc/objects/Grape/Middleware/Base/response_i.dat +0 -0
  112. data/.yardoc/objects/Grape/Middleware/Error.dat +0 -0
  113. data/.yardoc/objects/Grape/Middleware/Error/call_21_i.dat +0 -0
  114. data/.yardoc/objects/Grape/Middleware/Error/error_response_i.dat +0 -0
  115. data/.yardoc/objects/Grape/Middleware/Formatter.dat +0 -0
  116. data/.yardoc/objects/Grape/Middleware/Formatter/CONTENT_TYPES.dat +0 -0
  117. data/.yardoc/objects/Grape/Middleware/Formatter/after_i.dat +0 -0
  118. data/.yardoc/objects/Grape/Middleware/Formatter/before_i.dat +0 -0
  119. data/.yardoc/objects/Grape/Middleware/Formatter/content_types_i.dat +0 -0
  120. data/.yardoc/objects/Grape/Middleware/Formatter/default_options_i.dat +0 -0
  121. data/.yardoc/objects/Grape/Middleware/Formatter/encode_json_i.dat +0 -0
  122. data/.yardoc/objects/Grape/Middleware/Formatter/encode_txt_i.dat +0 -0
  123. data/.yardoc/objects/Grape/Middleware/Formatter/format_from_extension_i.dat +0 -0
  124. data/.yardoc/objects/Grape/Middleware/Formatter/format_from_header_i.dat +0 -0
  125. data/.yardoc/objects/Grape/Middleware/Formatter/headers_i.dat +0 -0
  126. data/.yardoc/objects/Grape/Middleware/Formatter/mime_array_i.dat +0 -0
  127. data/.yardoc/objects/Grape/Middleware/Formatter/mime_types_i.dat +0 -0
  128. data/.yardoc/objects/Grape/Middleware/Prefixer.dat +0 -0
  129. data/.yardoc/objects/Grape/Middleware/Prefixer/before_i.dat +0 -0
  130. data/.yardoc/objects/Grape/Middleware/Prefixer/prefix_i.dat +0 -0
  131. data/.yardoc/objects/Grape/Middleware/Versioner.dat +0 -0
  132. data/.yardoc/objects/Grape/Middleware/Versioner/before_i.dat +0 -0
  133. data/.yardoc/objects/Grape/Middleware/Versioner/default_options_i.dat +0 -0
  134. data/.yardoc/objects/Grape/MiddlewareStack.dat +0 -0
  135. data/.yardoc/objects/Grape/MiddlewareStack/initialize_i.dat +0 -0
  136. data/.yardoc/objects/Grape/MiddlewareStack/stack_i.dat +0 -0
  137. data/.yardoc/objects/Grape/MiddlewareStack/to_app_i.dat +0 -0
  138. data/.yardoc/objects/Grape/MiddlewareStack/use_i.dat +0 -0
  139. data/.yardoc/objects/root.dat +0 -0
  140. data/.yardoc/proxy_types +0 -2
  141. data/Gemfile.lock +0 -52
  142. data/autotest/discover.rb +0 -1
@@ -20,12 +20,12 @@ describe Grape::Middleware::Auth::Basic do
20
20
  end
21
21
 
22
22
  it 'should authenticate if given valid creds' do
23
- get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic('admin','admin')
23
+ get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin','admin')
24
24
  last_response.status.should == 200
25
25
  end
26
26
 
27
27
  it 'should throw a 401 is wrong auth is given' do
28
- get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic('admin','wrong')
28
+ get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin','wrong')
29
29
  last_response.status.should == 401
30
30
  end
31
31
  end
@@ -9,8 +9,6 @@ RSpec::Matchers.define :be_challenge do
9
9
  end
10
10
 
11
11
  class Test < Grape::API
12
- version '1'
13
-
14
12
  http_digest({:realm => 'Test Api', :opaque => 'secret'}) do |username|
15
13
  {'foo' => 'bar'}[username]
16
14
  end
@@ -26,24 +24,24 @@ describe Grape::Middleware::Auth::Digest do
26
24
  end
27
25
 
28
26
  it 'should be a digest authentication challenge' do
29
- get '/1/test'
27
+ get '/test'
30
28
  last_response.should be_challenge
31
29
  end
32
30
 
33
31
  it 'should throw a 401 if no auth is given' do
34
- get '/1/test'
32
+ get '/test'
35
33
  last_response.status.should == 401
36
34
  end
37
35
 
38
36
  it 'should authenticate if given valid creds' do
39
37
  digest_authorize "foo", "bar"
40
- get '/1/test'
38
+ get '/test'
41
39
  last_response.status.should == 200
42
40
  end
43
41
 
44
42
  it 'should throw a 401 if given invalid creds' do
45
43
  digest_authorize "bar", "foo"
46
- get '/1/test'
44
+ get '/test'
47
45
  last_response.status.should == 401
48
46
  end
49
47
  end
@@ -116,5 +116,6 @@ describe Grape::Middleware::Error do
116
116
  get '/'
117
117
  last_response.status.should == 401
118
118
  end
119
+
119
120
  end
120
121
  end
@@ -1,42 +1,65 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Middleware::Formatter do
4
- subject{ Grape::Middleware::Formatter.new(app, :default_format => :json)}
4
+ subject{ Grape::Middleware::Formatter.new(app) }
5
5
  before{ subject.stub!(:dup).and_return(subject) }
6
-
6
+
7
7
  let(:app){ lambda{|env| [200, {}, [@body]]} }
8
-
8
+
9
9
  context 'serialization' do
10
10
  it 'should look at the bodies for possibly serializable data' do
11
11
  @body = {"abc" => "def"}
12
- status, headers, bodies = *subject.call({'PATH_INFO' => '/somewhere'})
12
+ status, headers, bodies = *subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'})
13
13
  bodies.each{|b| b.should == MultiJson.encode(@body) }
14
14
  end
15
-
15
+
16
16
  it 'should call #to_json first if it is available' do
17
- @body = "string"
17
+ @body = ['foo']
18
18
  @body.instance_eval do
19
19
  def to_json
20
20
  "\"bar\""
21
21
  end
22
22
  end
23
-
24
- subject.call({'PATH_INFO' => '/somewhere'}).last.each{|b| b.should == '"bar"'}
23
+
24
+ subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '"bar"'}
25
25
  end
26
-
26
+
27
27
  it 'should serialize the #serializable_hash if that is available' do
28
28
  class SimpleExample
29
29
  def serializable_hash
30
30
  {:abc => 'def'}
31
31
  end
32
32
  end
33
-
33
+
34
+ @body = [SimpleExample.new, SimpleExample.new]
35
+
36
+ subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '[{"abc":"def"},{"abc":"def"}]'}
37
+ end
38
+
39
+ it 'should serialize multiple objects that respond to #serializable_hash' do
40
+ class SimpleExample
41
+ def serializable_hash
42
+ {:abc => 'def'}
43
+ end
44
+ end
45
+
34
46
  @body = SimpleExample.new
35
-
36
- subject.call({'PATH_INFO' => '/somewhere'}).last.each{|b| b.should == '{"abc":"def"}'}
47
+
48
+ subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '{"abc":"def"}'}
49
+ end
50
+
51
+ it 'should call #to_xml if the content type is xml' do
52
+ @body = "string"
53
+ @body.instance_eval do
54
+ def to_xml
55
+ "<bar/>"
56
+ end
57
+ end
58
+
59
+ subject.call({'PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '<bar/>'}
37
60
  end
38
61
  end
39
-
62
+
40
63
  context 'detection' do
41
64
  it 'should use the extension if one is provided' do
42
65
  subject.call({'PATH_INFO' => '/info.xml'})
@@ -44,45 +67,60 @@ describe Grape::Middleware::Formatter do
44
67
  subject.call({'PATH_INFO' => '/info.json'})
45
68
  subject.env['api.format'].should == :json
46
69
  end
47
-
70
+
48
71
  it 'should use the default format if none is provided' do
49
72
  subject.call({'PATH_INFO' => '/info'})
73
+ subject.env['api.format'].should == :txt
74
+ end
75
+
76
+ it 'should use the requested format if provided in headers' do
77
+ subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json'})
50
78
  subject.env['api.format'].should == :json
51
79
  end
80
+
81
+ it 'should use the file extension format if provided before headers' do
82
+ subject.call({'PATH_INFO' => '/info.txt', 'HTTP_ACCEPT' => 'application/json'})
83
+ subject.env['api.format'].should == :txt
84
+ end
52
85
 
53
86
  it 'should throw an error on an unrecognized format' do
54
87
  err = catch(:error){ subject.call({'PATH_INFO' => '/info.barklar'}) }
55
88
  err.should == {:status => 406, :message => "The requested format is not supported."}
56
89
  end
57
90
  end
58
-
91
+
59
92
  context 'Accept header detection' do
60
93
  it 'should detect from the Accept header' do
61
- subject.call({'PATH_INFO' => '/info', 'Accept' => 'application/xml'})
94
+ subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/xml'})
62
95
  subject.env['api.format'].should == :xml
63
96
  end
64
-
97
+
65
98
  it 'should look for case-indifferent headers' do
66
- subject.call({'PATH_INFO' => '/info', 'accept' => 'application/xml'})
99
+ subject.call({'PATH_INFO' => '/info', 'http_accept' => 'application/xml'})
67
100
  subject.env['api.format'].should == :xml
68
101
  end
69
-
102
+
70
103
  it 'should use quality rankings to determine formats' do
71
- subject.call({'PATH_INFO' => '/info', 'Accept' => 'application/json; q=0.3,application/xml; q=1.0'})
104
+ subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=0.3,application/xml; q=1.0'})
72
105
  subject.env['api.format'].should == :xml
73
- subject.call({'PATH_INFO' => '/info', 'Accept' => 'application/json; q=1.0,application/xml; q=0.3'})
106
+ subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=1.0,application/xml; q=0.3'})
74
107
  subject.env['api.format'].should == :json
75
108
  end
76
-
109
+
77
110
  it 'should handle quality rankings mixed with nothing' do
78
- subject.call({'PATH_INFO' => '/info', 'Accept' => 'application/json,application/xml; q=1.0'})
111
+ subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json,application/xml; q=1.0'})
79
112
  subject.env['api.format'].should == :xml
80
113
  end
81
-
114
+
82
115
  it 'should properly parse headers with other attributes' do
83
- subject.call({'PATH_INFO' => '/info', 'Accept' => 'application/json; abc=2.3; q=1.0,application/xml; q=0.7'})
116
+ subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; abc=2.3; q=1.0,application/xml; q=0.7'})
84
117
  subject.env['api.format'].should == :json
85
118
  end
119
+
120
+ it 'should properly parse headers with vendor and api version' do
121
+ subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test-v1+xml'})
122
+ subject.env['api.format'].should == :xml
123
+ end
86
124
  end
87
125
 
88
126
  context 'Content-type' do
@@ -113,9 +151,9 @@ describe Grape::Middleware::Formatter do
113
151
  body.body.should == ['CUSTOM FORMAT']
114
152
  end
115
153
  it 'should use default json formatter' do
116
- @body = 'blah'
154
+ @body = ['blah']
117
155
  _, _, body = subject.call({'PATH_INFO' => '/info.json'})
118
- body.body.should == ['"blah"']
156
+ body.body.should == ['["blah"]']
119
157
  end
120
158
  it 'should use custom json formatter' do
121
159
  subject.options[:formatters][:json] = lambda { |obj| 'CUSTOM JSON FORMAT' }
@@ -123,4 +161,20 @@ describe Grape::Middleware::Formatter do
123
161
  body.body.should == ['CUSTOM JSON FORMAT']
124
162
  end
125
163
  end
164
+
165
+ context 'Input' do
166
+ it 'should parse the body from a POST/PUT and put the contents into rack.request.form_hash' do
167
+ subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json', 'rack.input' => StringIO.new('{"is_boolean":true,"string":"thing"}')})
168
+ subject.env['rack.request.form_hash']['is_boolean'].should be_true
169
+ subject.env['rack.request.form_hash']['string'].should == 'thing'
170
+ end
171
+ it 'should parse the body from an xml POST/PUT and put the contents into rack.request.from_hash' do
172
+ subject.call({'PATH_INFO' => '/info.xml', 'HTTP_ACCEPT' => 'application/xml', 'rack.input' => StringIO.new('<thing><name>Test</name></thing>')})
173
+ subject.env['rack.request.form_hash']['thing']['name'].should == 'Test'
174
+ end
175
+ it 'should be able to fail gracefully if the body is regular POST content' do
176
+ subject.call({'PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json', 'rack.input' => StringIO.new('name=Other+Test+Thing')})
177
+ subject.env['rack.request.form_hash'].should be_nil
178
+ end
179
+ end
126
180
  end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Versioner::Header do
4
+ let(:app) { lambda{|env| [200, env, env]} }
5
+ let(:accept) { 'application/vnd.vendor-v1+json' }
6
+ subject { Grape::Middleware::Versioner::Header.new(app, @options || {}) }
7
+
8
+ context 'api.type and api.subtype' do
9
+ it 'should set any type and any subtype' do
10
+ env = subject.call('HTTP_ACCEPT' => '*/*').last
11
+ env['api.type'].should eql '*'
12
+ env['api.subtype'].should eql '*'
13
+ end
14
+
15
+ it 'should set preferred type and subtype' do
16
+ env = subject.call('HTTP_ACCEPT' => 'text/html').last
17
+ env['api.type'].should eql 'text'
18
+ env['api.subtype'].should eql 'html'
19
+ end
20
+ end
21
+
22
+ context 'api.format' do
23
+ it 'should be set' do
24
+ env = subject.call('HTTP_ACCEPT' => accept).last
25
+ env['api.format'].should eql 'json'
26
+ end
27
+
28
+ it 'should be nil if not provided' do
29
+ env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1').last
30
+ env['api.format'].should eql nil
31
+ end
32
+ end
33
+
34
+ context 'matched version' do
35
+ before do
36
+ @options = {
37
+ :versions => ['v1'],
38
+ :version_options => {:using => :header}
39
+ }
40
+ end
41
+
42
+ it 'should set api.vendor' do
43
+ env = subject.call('HTTP_ACCEPT' => accept).last
44
+ env['api.vendor'].should eql 'vendor'
45
+ end
46
+
47
+ it 'should set api.version' do
48
+ env = subject.call('HTTP_ACCEPT' => accept).last
49
+ env['api.version'].should eql 'v1'
50
+ end
51
+ end
52
+
53
+ context 'no header' do
54
+ it 'should return a 200 when no header is set and no strict setting is done' do
55
+ @options = {
56
+ :versions => ['v1'],
57
+ :version_options => {:using => :header}
58
+ }
59
+ subject.call('HTTP_ACCEPT' => '').first.should == 200
60
+ subject.call({}).first.should == 200
61
+ end
62
+
63
+ it 'should return a 200 when no header is set but strict header based versioning is disabled' do
64
+ @options = {
65
+ :versions => ['v1'],
66
+ :version_options => {:using => :header, :strict => false}
67
+ }
68
+ subject.call('HTTP_ACCEPT' => '').first.should == 200
69
+ subject.call({}).first.should == 200
70
+ end
71
+
72
+ context 'when strict header versioning is used' do
73
+ it 'should return a 406 when no header' do
74
+ @options = {
75
+ :versions => ['v1'],
76
+ :version_options => {:using => :header, :strict => true}
77
+ }
78
+ expect {
79
+ env = subject.call('HTTP_ACCEPT' => '').last
80
+ }.to throw_symbol(
81
+ :error,
82
+ :status => 406,
83
+ :headers => {'X-Cascade' => 'pass'},
84
+ :message => "406 API Version Not Found"
85
+ )
86
+ end
87
+
88
+ it 'should return a 406 when incorrect header format is used' do
89
+ @options = {
90
+ :versions => ['v1'],
91
+ :version_options => {:using => :header, :strict => true}
92
+ }
93
+ expect {
94
+ env = subject.call('HTTP_ACCEPT' => '*/*').last
95
+ }.to throw_symbol(
96
+ :error,
97
+ :status => 406,
98
+ :headers => {'X-Cascade' => 'pass'},
99
+ :message => "406 API Version Not Found"
100
+ )
101
+ end
102
+
103
+ it 'should return a 200 when proper header is set' do
104
+ @options = {
105
+ :versions => ['v1'],
106
+ :version_options => {:using => :header, :strict => true}
107
+ }
108
+ subject.call('HTTP_ACCEPT' => 'application/vnd.testing-v1+json').first.should == 200
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ context 'vendors' do
115
+ before do
116
+ @options = {
117
+ :version => ['v1'],
118
+ :version_options => {:using => :header, :vendor => 'vendor'}
119
+ }
120
+ end
121
+
122
+ it 'should match with correct vendor' do
123
+ status = subject.call('HTTP_ACCEPT' => accept).first
124
+ status.should == 200
125
+ end
126
+
127
+ it 'should not match with an incorrect vendor' do
128
+ expect {
129
+ env = subject.call('HTTP_ACCEPT' => 'application/vnd.othervendor-v1+json').last
130
+ }.to throw_symbol(:error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found")
131
+ end
132
+ end
133
+
134
+ context 'no matched version' do
135
+ before do
136
+ @options = {
137
+ :versions => ['unknown_version'],
138
+ :version_options => {:using => :header}
139
+ }
140
+ end
141
+
142
+ it 'should throw 406 error with X-Cascade header set to pass' do
143
+ expect {
144
+ env = subject.call('HTTP_ACCEPT' => accept).last
145
+ }.to throw_symbol(:error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found")
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Versioner::Path do
4
+ let(:app) { lambda{|env| [200, env, env['api.version']]} }
5
+ subject { Grape::Middleware::Versioner::Path.new(app, @options || {}) }
6
+
7
+ it 'should set the API version based on the first path' do
8
+ subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
9
+ end
10
+
11
+ it 'should cut the version out of the path' do
12
+ subject.call('PATH_INFO' => '/v1/awesome')[1]['PATH_INFO'].should == '/awesome'
13
+ end
14
+
15
+ it 'should provide a nil version if no path is given' do
16
+ subject.call('PATH_INFO' => '/').last.should be_nil
17
+ end
18
+
19
+ context 'with a pattern' do
20
+ before{ @options = {:pattern => /v./i} }
21
+ it 'should set the version if it matches' do
22
+ subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
23
+ end
24
+
25
+ it 'should ignore the version if it fails to match' do
26
+ subject.call('PATH_INFO' => '/awesome/radical').last.should be_nil
27
+ end
28
+ end
29
+
30
+ context 'with specified versions' do
31
+ before{ @options = {:versions => ['v1', 'v2']}}
32
+ it 'should throw an error if a non-allowed version is specified' do
33
+ catch(:error){subject.call('PATH_INFO' => '/v3/awesome')}[:status].should == 404
34
+ end
35
+
36
+ it 'should allow versions that have been specified' do
37
+ subject.call('PATH_INFO' => '/v1/asoasd').last.should == 'v1'
38
+ end
39
+ end
40
+ end
@@ -1,40 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Middleware::Versioner do
4
- let(:app) { lambda{|env| [200, env, env['api.version']]} }
5
- subject { Grape::Middleware::Versioner.new(app, @options || {}) }
6
-
7
- it 'should set the API version based on the first path' do
8
- subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
4
+ let(:klass) { Grape::Middleware::Versioner }
5
+ it 'should recognize :path' do
6
+ klass.using(:path).should == Grape::Middleware::Versioner::Path
9
7
  end
10
-
11
- it 'should cut the version out of the path' do
12
- subject.call('PATH_INFO' => '/v1/awesome')[1]['PATH_INFO'].should == '/awesome'
13
- end
14
-
15
- it 'should provide a nil version if no path is given' do
16
- subject.call('PATH_INFO' => '/').last.should be_nil
17
- end
18
-
19
- context 'with a pattern' do
20
- before{ @options = {:pattern => /v./i} }
21
- it 'should set the version if it matches' do
22
- subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
23
- end
24
-
25
- it 'should ignore the version if it fails to match' do
26
- subject.call('PATH_INFO' => '/awesome/radical').last.should be_nil
27
- end
28
- end
29
-
30
- context 'with specified versions' do
31
- before{ @options = {:versions => ['v1', 'v2']}}
32
- it 'should throw an error if a non-allowed version is specified' do
33
- catch(:error){subject.call('PATH_INFO' => '/v3/awesome')}[:status].should == 404
34
- end
35
-
36
- it 'should allow versions that have been specified' do
37
- subject.call('PATH_INFO' => '/v1/asoasd').last.should == 'v1'
38
- end
8
+
9
+ it 'should recognize :header' do
10
+ klass.using(:header).should == Grape::Middleware::Versioner::Header
39
11
  end
40
12
  end