grape 0.1.5 → 0.2.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 (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
@@ -0,0 +1,100 @@
1
+ module Grape
2
+ module Util
3
+ # HashStack is a stack of hashes. When retrieving a value, keys of the top
4
+ # hash on the stack take precendent over the lower keys.
5
+ class HashStack
6
+ # Unmerged array of hashes to represent the stack.
7
+ # The top of the stack is the last element.
8
+ attr_reader :stack
9
+
10
+ # TODO: handle aggregates
11
+ def initialize
12
+ @stack = [{}]
13
+ end
14
+
15
+ # Returns the top hash on the stack
16
+ def peek
17
+ @stack.last
18
+ end
19
+
20
+ # Add a new hash to the top of the stack.
21
+ #
22
+ # @param hash [Hash] optional hash to be pushed. Defaults to empty hash
23
+ # @return [HashStack]
24
+ def push(hash = {})
25
+ @stack.push(hash)
26
+ self
27
+ end
28
+
29
+ def pop
30
+ @stack.pop
31
+ end
32
+
33
+ # Looks through the stack for the first frame that matches :key
34
+ #
35
+ # @param key [Symbol] key to look for in hash frames
36
+ # @return value of given key after merging the stack
37
+ def get(key)
38
+ (@stack.length - 1).downto(0).each do |i|
39
+ return @stack[i][key] if @stack[i].key? key
40
+ end
41
+ nil
42
+ end
43
+ alias_method :[], :get
44
+
45
+ # Replace a value on the top hash of the stack.
46
+ #
47
+ # @param key [Symbol] The key to set.
48
+ # @param value [Object] The value to set.
49
+ def set(key, value)
50
+ peek[key.to_sym] = value
51
+ end
52
+ alias_method :[]=, :set
53
+
54
+ # Replace multiple values on the top hash of the stack.
55
+ #
56
+ # @param hash [Hash] Hash of values to be merged in.
57
+ def update(hash)
58
+ peek.merge!(hash)
59
+ self
60
+ end
61
+
62
+ # Adds addition value into the top hash of the stack
63
+ def imbue(key, value)
64
+ current = peek[key.to_sym]
65
+ if current.is_a?(Array)
66
+ current.concat(value)
67
+ elsif current.is_a?(Hash)
68
+ current.merge!(value)
69
+ else
70
+ set(key, value)
71
+ end
72
+ end
73
+
74
+ # Prepend another HashStack's to self
75
+ def prepend(hash_stack)
76
+ @stack.unshift *hash_stack.stack
77
+ self
78
+ end
79
+
80
+ # Concatenate another HashStack's to self
81
+ def concat(hash_stack)
82
+ @stack.concat hash_stack.stack
83
+ self
84
+ end
85
+
86
+ def to_s
87
+ @stack.to_s
88
+ end
89
+
90
+ def clone
91
+ new_stack = HashStack.new
92
+ stack.each do |frame|
93
+ new_stack.push frame.clone
94
+ end
95
+ new_stack.stack.shift
96
+ new_stack
97
+ end
98
+ end
99
+ end
100
+ end
@@ -1,3 +1,3 @@
1
1
  module Grape
2
- VERSION = '0.1.5'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -1,130 +1,105 @@
1
1
  require 'spec_helper'
2
+ require 'shared/versioning_examples'
2
3
 
3
4
  describe Grape::API do
4
5
  subject { Class.new(Grape::API) }
5
- before { subject.default_format :txt }
6
-
6
+
7
7
  def app; subject end
8
-
8
+
9
9
  describe '.prefix' do
10
10
  it 'should route through with the prefix' do
11
11
  subject.prefix 'awesome/sauce'
12
12
  subject.get :hello do
13
13
  "Hello there."
14
14
  end
15
-
15
+
16
16
  get 'awesome/sauce/hello'
17
17
  last_response.body.should eql "Hello there."
18
-
18
+
19
19
  get '/hello'
20
20
  last_response.status.should eql 404
21
21
  end
22
22
  end
23
23
 
24
- describe '.version' do
25
- it 'should set the API version' do
26
- subject.version 'v1'
27
- subject.get :hello do
28
- "Version: #{request.env['api.version']}"
24
+ describe '.version using path' do
25
+ it_should_behave_like 'versioning' do
26
+ let(:macro_options) do
27
+ {
28
+ :using => :path
29
+ }
29
30
  end
30
-
31
- get '/v1/hello'
32
- last_response.body.should eql "Version: v1"
33
31
  end
34
-
35
- it 'should add the prefix before the API version' do
36
- subject.prefix 'api'
37
- subject.version 'v1'
38
- subject.get :hello do
39
- "Version: #{request.env['api.version']}"
32
+ end
33
+
34
+ describe '.version using header' do
35
+ it_should_behave_like 'versioning' do
36
+ let(:macro_options) do
37
+ {
38
+ :using => :header,
39
+ :vendor => 'mycompany',
40
+ :format => 'json'
41
+ }
40
42
  end
41
-
42
- get '/api/v1/hello'
43
- last_response.body.should eql "Version: v1"
44
43
  end
45
-
46
- it 'should be able to specify version as a nesting' do
47
- subject.version 'v2'
48
- subject.get '/awesome' do
49
- "Radical"
50
- end
51
-
52
- subject.version 'v1' do
53
- get '/legacy' do
54
- "Totally"
55
- end
56
- end
57
-
58
- get '/v1/awesome'
59
- last_response.status.should eql 404
60
- get '/v2/awesome'
61
- last_response.status.should eql 200
62
- get '/v1/legacy'
63
- last_response.status.should eql 200
64
- get '/v2/legacy'
65
- last_response.status.should eql 404
44
+
45
+ # Behavior as defined by rfc2616 when no header is defined
46
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
47
+ describe 'no specified accept header' do
48
+ # subject.version 'v1', :using => :header
49
+ # subject.get '/hello' do
50
+ # 'hello'
51
+ # end
52
+
53
+ # it 'should route' do
54
+ # get '/hello'
55
+ # last_response.status.should eql 200
56
+ # end
66
57
  end
67
-
68
- it 'should be able to specify multiple versions' do
69
- subject.version 'v1', 'v2'
70
- subject.get 'awesome' do
71
- "I exist"
72
- end
58
+
59
+ it 'should route if any media type is allowed' do
73
60
 
74
- get '/v1/awesome'
75
- last_response.status.should eql 200
76
- get '/v2/awesome'
77
- last_response.status.should eql 200
78
- get '/v3/awesome'
79
- last_response.status.should eql 404
80
61
  end
81
-
82
- it 'should allow the same endpoint to be implemented for different versions' do
83
- subject.version 'v2'
84
- subject.get 'version' do
85
- request.env['api.version']
86
- end
87
-
88
- subject.version 'v1' do
89
- get 'version' do
90
- "version " + request.env['api.version']
91
- end
92
- end
93
-
94
- get '/v2/version'
95
- last_response.status.should == 200
96
- last_response.body.should == 'v2'
97
- get '/v1/version'
98
- last_response.status.should == 200
99
- last_response.body.should == 'version v1'
62
+ end
63
+
64
+ describe '.represent' do
65
+ it 'should require a :with option' do
66
+ expect{ subject.represent Object, {} }.to raise_error(ArgumentError)
67
+ end
68
+
69
+ it 'should add the association to the :representations setting' do
70
+ klass = Class.new
71
+ subject.represent Object, :with => klass
72
+ subject.settings[:representations][Object].should == klass
100
73
  end
101
-
102
74
  end
103
-
75
+
104
76
  describe '.namespace' do
105
77
  it 'should be retrievable and converted to a path' do
106
78
  subject.namespace :awesome do
107
79
  namespace.should == '/awesome'
108
80
  end
109
81
  end
110
-
82
+
111
83
  it 'should come after the prefix and version' do
112
84
  subject.prefix :rad
113
- subject.version :v1
114
-
85
+ subject.version 'v1', :using => :path
86
+
115
87
  subject.namespace :awesome do
116
- compile_path('hello').should == '/rad/:version/awesome/hello(.:format)'
88
+ get('/hello'){ "worked" }
117
89
  end
90
+
91
+ get "/rad/v1/awesome/hello"
92
+ last_response.body.should == "worked"
118
93
  end
119
-
94
+
120
95
  it 'should cancel itself after the block is over' do
121
96
  subject.namespace :awesome do
122
97
  namespace.should == '/awesome'
123
98
  end
124
-
99
+
125
100
  subject.namespace.should == '/'
126
101
  end
127
-
102
+
128
103
  it 'should be stackable' do
129
104
  subject.namespace :awesome do
130
105
  namespace :rad do
@@ -134,16 +109,34 @@ describe Grape::API do
134
109
  end
135
110
  subject.namespace.should == '/'
136
111
  end
112
+
113
+ it 'should accept path segments correctly' do
114
+ subject.namespace :members do
115
+ namespace "/:member_id" do
116
+ namespace.should == '/members/:member_id'
117
+ get '/' do
118
+ params[:member_id]
119
+ end
120
+ end
121
+ end
122
+ get '/members/23'
123
+ last_response.body.should == "23"
124
+ end
137
125
 
138
126
  it 'should be callable with nil just to push onto the stack' do
139
127
  subject.namespace do
140
- version 'v2'
141
- compile_path('hello').should == '/:version/hello(.:format)'
128
+ version 'v2', :using => :path
129
+ get('/hello'){ "inner" }
142
130
  end
143
- subject.send(:compile_path, 'hello').should == '/hello(.:format)'
131
+ subject.get('/hello'){ "outer" }
132
+
133
+ get '/v2/hello'
134
+ last_response.body.should == "inner"
135
+ get '/hello'
136
+ last_response.body.should == "outer"
144
137
  end
145
138
 
146
- %w(group resource resources).each do |als|
139
+ %w(group resource resources segment).each do |als|
147
140
  it "`.#{als}` should be an alias" do
148
141
  subject.send(als, :awesome) do
149
142
  namespace.should == "/awesome"
@@ -151,61 +144,81 @@ describe Grape::API do
151
144
  end
152
145
  end
153
146
  end
154
-
147
+
155
148
  describe '.route' do
156
149
  it 'should allow for no path' do
157
150
  subject.namespace :votes do
158
151
  get do
159
152
  "Votes"
160
153
  end
161
-
154
+
162
155
  post do
163
156
  "Created a Vote"
164
157
  end
165
158
  end
166
-
159
+
167
160
  get '/votes'
168
161
  last_response.body.should eql 'Votes'
169
162
  post '/votes'
170
163
  last_response.body.should eql 'Created a Vote'
171
164
  end
172
-
165
+
173
166
  it 'should allow for multiple paths' do
174
- subject.get("/abc", "/def") do
167
+ subject.get(["/abc", "/def"]) do
175
168
  "foo"
176
169
  end
177
-
170
+
178
171
  get '/abc'
179
172
  last_response.body.should eql 'foo'
180
173
  get '/def'
181
174
  last_response.body.should eql 'foo'
182
175
  end
183
176
 
184
- it 'should allow for format' do
185
- subject.get("/abc") do
186
- "json"
177
+ context "format" do
178
+ before(:each) do
179
+ subject.get("/abc") do
180
+ RSpec::Mocks::Mock.new(:to_json => 'abc', :to_txt => 'def')
181
+ end
187
182
  end
188
-
189
- get '/abc.json'
190
- last_response.body.should eql '"json"'
183
+
184
+ it "should allow .json" do
185
+ get '/abc.json'
186
+ last_response.status.should == 200
187
+ last_response.body.should eql 'abc' # json-encoded symbol
188
+ end
189
+
190
+ it "should allow .txt" do
191
+ get '/abc.txt'
192
+ last_response.status.should == 200
193
+ last_response.body.should eql 'def' # raw text
194
+ end
195
+ end
196
+
197
+ it 'should allow for format without corrupting a param' do
198
+ subject.get('/:id') do
199
+ {"id" => params[:id]}
200
+ end
201
+
202
+ get '/awesome.json'
203
+ last_response.body.should eql '{"id":"awesome"}'
191
204
  end
192
205
 
193
206
  it 'should allow for format in namespace with no path' do
194
207
  subject.namespace :abc do
195
208
  get do
196
- "json"
209
+ ["json"]
197
210
  end
198
211
  end
199
-
212
+
200
213
  get '/abc.json'
201
- last_response.body.should eql '"json"'
214
+ last_response.body.should eql '["json"]'
202
215
  end
203
-
216
+
204
217
  it 'should allow for multiple verbs' do
205
218
  subject.route([:get, :post], '/abc') do
206
219
  "hiya"
207
220
  end
208
-
221
+
209
222
  get '/abc'
210
223
  last_response.body.should eql 'hiya'
211
224
  post '/abc'
@@ -214,11 +227,9 @@ describe Grape::API do
214
227
 
215
228
  it 'should allow for multipart paths' do
216
229
 
217
-
218
230
  subject.route([:get, :post], '/:id/first') do
219
231
  "first"
220
232
  end
221
-
222
233
 
223
234
  subject.route([:get, :post], '/:id') do
224
235
  "ola"
@@ -226,7 +237,6 @@ describe Grape::API do
226
237
  subject.route([:get, :post], '/:id/first/second') do
227
238
  "second"
228
239
  end
229
-
230
240
 
231
241
  get '/1'
232
242
  last_response.body.should eql 'ola'
@@ -240,19 +250,19 @@ describe Grape::API do
240
250
  last_response.body.should eql 'second'
241
251
 
242
252
  end
243
-
253
+
244
254
  it 'should allow for :any as a verb' do
245
255
  subject.route(:any, '/abc') do
246
256
  "lol"
247
257
  end
248
-
249
- %w(get post put delete).each do |m|
258
+
259
+ %w(get post put delete options patch).each do |m|
250
260
  send(m, '/abc')
251
261
  last_response.body.should eql 'lol'
252
262
  end
253
263
  end
254
-
255
- verbs = %w(post get head delete put)
264
+
265
+ verbs = %w(post get head delete put options patch)
256
266
  verbs.each do |verb|
257
267
  it "should allow and properly constrain a #{verb.upcase} method" do
258
268
  subject.send(verb, '/example') do
@@ -265,18 +275,44 @@ describe Grape::API do
265
275
  last_response.status.should eql 404
266
276
  end
267
277
  end
268
-
278
+
269
279
  it 'should return a 201 response code for POST by default' do
270
280
  subject.post('example') do
271
281
  "Created"
272
282
  end
273
-
283
+
274
284
  post '/example'
275
285
  last_response.status.should eql 201
276
286
  last_response.body.should eql 'Created'
277
287
  end
278
288
  end
279
289
 
290
+ describe 'filters' do
291
+ it 'should add a before filter' do
292
+ subject.before { @foo = 'first' }
293
+ subject.before { @bar = 'second' }
294
+ subject.get '/' do
295
+ "#{@foo} #{@bar}"
296
+ end
297
+
298
+ get '/'
299
+ last_response.body.should eql 'first second'
300
+ end
301
+
302
+ it 'should add a after filter' do
303
+ m = double('after mock')
304
+ subject.after { m.do_something! }
305
+ subject.after { m.do_something! }
306
+ subject.get '/' do
307
+ @var ||= 'default'
308
+ end
309
+
310
+ m.should_receive(:do_something!).exactly(2).times
311
+ get '/'
312
+ last_response.body.should eql 'default'
313
+ end
314
+ end
315
+
280
316
  context 'format' do
281
317
  before do
282
318
  subject.get("/foo") { "bar" }
@@ -311,26 +347,32 @@ describe Grape::API do
311
347
  def initialize(app, *args)
312
348
  @args = args
313
349
  @app = app
350
+ @block = true if block_given?
314
351
  end
315
352
 
316
353
  def call(env)
317
354
  env['phony.args'] ||= []
318
355
  env['phony.args'] << @args
356
+ env['phony.block'] = true if @block
319
357
  @app.call(env)
320
358
  end
321
359
  end
322
360
 
323
361
  describe '.middleware' do
324
362
  it 'should include middleware arguments from settings' do
325
- subject.stub!(:settings_stack).and_return [{:middleware => [[PhonyMiddleware, 'abc', 123]]}]
363
+ settings = Grape::Util::HashStack.new
364
+ settings.stub!(:stack).and_return([{:middleware => [[PhonyMiddleware, 'abc', 123]]}])
365
+ subject.stub!(:settings).and_return(settings)
326
366
  subject.middleware.should eql [[PhonyMiddleware, 'abc', 123]]
327
367
  end
328
368
 
329
369
  it 'should include all middleware from stacked settings' do
330
- subject.stub!(:settings_stack).and_return [
370
+ settings = Grape::Util::HashStack.new
371
+ settings.stub!(:stack).and_return [
331
372
  {:middleware => [[PhonyMiddleware, 123],[PhonyMiddleware, 'abc']]},
332
373
  {:middleware => [[PhonyMiddleware, 'foo']]}
333
374
  ]
375
+ subject.stub!(:settings).and_return(settings)
334
376
 
335
377
  subject.middleware.should eql [
336
378
  [PhonyMiddleware, 123],
@@ -353,7 +395,7 @@ describe Grape::API do
353
395
  middleware.should == [[PhonyMiddleware, 123],[PhonyMiddleware, 'abc']]
354
396
  end
355
397
 
356
- subject.middleware.should eql [[PhonyMiddleware, 123]]
398
+ subject.middleware.should eql [[PhonyMiddleware, 123]]
357
399
  end
358
400
 
359
401
  it 'should actually call the middleware' do
@@ -365,6 +407,36 @@ describe Grape::API do
365
407
  get '/'
366
408
  last_response.body.should eql 'hello'
367
409
  end
410
+
411
+ it 'should add a block if one is given' do
412
+ block = lambda{ }
413
+ subject.use PhonyMiddleware, &block
414
+ subject.middleware.should eql [[PhonyMiddleware, block]]
415
+ end
416
+
417
+ it 'should use a block if one is given' do
418
+ block = lambda{ }
419
+ subject.use PhonyMiddleware, &block
420
+ subject.get '/' do
421
+ env['phony.block'].inspect
422
+ end
423
+
424
+ get '/'
425
+ last_response.body.should == 'true'
426
+ end
427
+
428
+ it 'should not destroy the middleware settings on multiple runs' do
429
+ block = lambda{ }
430
+ subject.use PhonyMiddleware, &block
431
+ subject.get '/' do
432
+ env['phony.block'].inspect
433
+ end
434
+
435
+ 2.times do
436
+ get '/'
437
+ last_response.body.should == 'true'
438
+ end
439
+ end
368
440
  end
369
441
  end
370
442
  describe '.basic' do
@@ -375,39 +447,39 @@ describe Grape::API do
375
447
  subject.get(:hello){ "Hello, world."}
376
448
  get '/hello'
377
449
  last_response.status.should eql 401
378
- get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic('allow','whatever')
450
+ get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow','whatever')
379
451
  last_response.status.should eql 200
380
452
  end
381
-
453
+
382
454
  it 'should be scopable' do
383
455
  subject.get(:hello){ "Hello, world."}
384
456
  subject.namespace :admin do
385
457
  http_basic do |u,p|
386
458
  u == 'allow'
387
459
  end
388
-
460
+
389
461
  get(:hello){ "Hello, world." }
390
462
  end
391
-
463
+
392
464
  get '/hello'
393
465
  last_response.status.should eql 200
394
466
  get '/admin/hello'
395
467
  last_response.status.should eql 401
396
468
  end
397
-
469
+
398
470
  it 'should be callable via .auth as well' do
399
471
  subject.auth :http_basic do |u,p|
400
472
  u == 'allow'
401
473
  end
402
-
474
+
403
475
  subject.get(:hello){ "Hello, world."}
404
476
  get '/hello'
405
477
  last_response.status.should eql 401
406
- get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic('allow','whatever')
478
+ get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow','whatever')
407
479
  last_response.status.should eql 200
408
480
  end
409
481
  end
410
-
482
+
411
483
  describe '.helpers' do
412
484
  it 'should be accessible from the endpoint' do
413
485
  subject.helpers do
@@ -415,97 +487,114 @@ describe Grape::API do
415
487
  "Hello, world."
416
488
  end
417
489
  end
418
-
490
+
419
491
  subject.get '/howdy' do
420
492
  hello
421
493
  end
422
-
494
+
423
495
  get '/howdy'
424
496
  last_response.body.should eql 'Hello, world.'
425
497
  end
426
-
498
+
427
499
  it 'should be scopable' do
428
500
  subject.helpers do
429
501
  def generic
430
502
  'always there'
431
503
  end
432
504
  end
433
-
505
+
434
506
  subject.namespace :admin do
435
507
  helpers do
436
508
  def secret
437
509
  'only in admin'
438
510
  end
439
511
  end
440
-
512
+
441
513
  get '/secret' do
442
514
  [generic, secret].join ':'
443
515
  end
444
516
  end
445
-
517
+
446
518
  subject.get '/generic' do
447
519
  [generic, respond_to?(:secret)].join ':'
448
520
  end
449
-
521
+
450
522
  get '/generic'
451
523
  last_response.body.should eql 'always there:false'
452
524
  get '/admin/secret'
453
525
  last_response.body.should eql 'always there:only in admin'
454
526
  end
455
-
527
+
456
528
  it 'should be reopenable' do
457
529
  subject.helpers do
458
530
  def one
459
531
  1
460
532
  end
461
533
  end
462
-
534
+
463
535
  subject.helpers do
464
536
  def two
465
537
  2
466
538
  end
467
539
  end
468
-
540
+
469
541
  subject.get 'howdy' do
470
542
  [one, two]
471
543
  end
472
-
544
+
473
545
  lambda{get '/howdy'}.should_not raise_error
474
546
  end
547
+
548
+ it 'should allow for modules' do
549
+ mod = Module.new do
550
+ def hello
551
+ "Hello, world."
552
+ end
553
+ end
554
+ subject.helpers mod
555
+
556
+ subject.get '/howdy' do
557
+ hello
558
+ end
559
+
560
+ get '/howdy'
561
+ last_response.body.should eql 'Hello, world.'
562
+ end
475
563
  end
476
-
564
+
477
565
  describe '.scope' do
566
+ # TODO: refactor this to not be tied to versioning. How about a generic
567
+ # .setting macro?
478
568
  it 'should scope the various settings' do
479
- subject.version 'v2'
480
-
569
+ subject.prefix 'new'
570
+
481
571
  subject.scope :legacy do
482
- version 'v1'
483
-
572
+ prefix 'legacy'
484
573
  get '/abc' do
485
- version
574
+ 'abc'
486
575
  end
487
576
  end
488
-
577
+
489
578
  subject.get '/def' do
490
- version
579
+ 'def'
491
580
  end
492
581
 
493
- get '/v2/abc'
582
+ get '/new/abc'
494
583
  last_response.status.should eql 404
495
- get '/v1/abc'
584
+ get '/legacy/abc'
496
585
  last_response.status.should eql 200
497
- get '/v1/def'
586
+ get '/legacy/def'
498
587
  last_response.status.should eql 404
499
- get '/v2/def'
588
+ get '/new/def'
500
589
  last_response.status.should eql 200
501
590
  end
502
591
  end
503
-
592
+
504
593
  describe ".rescue_from" do
505
594
  it 'should not rescue errors when rescue_from is not set' do
506
595
  subject.get '/exception' do
507
596
  raise "rain!"
508
- end
597
+ end
509
598
  lambda { get '/exception' }.should raise_error
510
599
  end
511
600
 
@@ -525,18 +614,18 @@ describe Grape::API do
525
614
 
526
615
  get '/rescued'
527
616
  last_response.status.should eql 403
528
-
617
+
529
618
  lambda{ get '/unrescued' }.should raise_error
530
619
  end
531
620
  end
532
-
621
+
533
622
  describe ".error_format" do
534
623
  it 'should rescue all errors and return :txt' do
535
624
  subject.rescue_from :all
536
625
  subject.error_format :txt
537
626
  subject.get '/exception' do
538
627
  raise "rain!"
539
- end
628
+ end
540
629
  get '/exception'
541
630
  last_response.body.should eql "rain!"
542
631
  end
@@ -546,7 +635,7 @@ describe Grape::API do
546
635
  subject.error_format :txt
547
636
  subject.get '/exception' do
548
637
  raise "rain!"
549
- end
638
+ end
550
639
  get '/exception'
551
640
  last_response.body.start_with?("rain!\r\n").should be_true
552
641
  end
@@ -556,7 +645,7 @@ describe Grape::API do
556
645
  subject.error_format :json
557
646
  subject.get '/exception' do
558
647
  raise "rain!"
559
- end
648
+ end
560
649
  get '/exception'
561
650
  last_response.body.should eql '{"error":"rain!"}'
562
651
  end
@@ -565,9 +654,9 @@ describe Grape::API do
565
654
  subject.error_format :json
566
655
  subject.get '/exception' do
567
656
  raise "rain!"
568
- end
657
+ end
569
658
  get '/exception'
570
- json = JSON.parse(last_response.body)
659
+ json = MultiJson.decode(last_response.body)
571
660
  json["error"].should eql 'rain!'
572
661
  json["backtrace"].length.should > 0
573
662
  end
@@ -575,7 +664,7 @@ describe Grape::API do
575
664
  subject.error_format :txt
576
665
  subject.get '/error' do
577
666
  error!("Access Denied", 401)
578
- end
667
+ end
579
668
  get '/error'
580
669
  last_response.body.should eql "Access Denied"
581
670
  end
@@ -583,19 +672,30 @@ describe Grape::API do
583
672
  subject.error_format :json
584
673
  subject.get '/error' do
585
674
  error!("Access Denied", 401)
586
- end
675
+ end
587
676
  get '/error'
588
677
  last_response.body.should eql '{"error":"Access Denied"}'
589
678
  end
590
679
  end
591
-
680
+
681
+ describe ".content_type" do
682
+ it "sets additional content-type" do
683
+ subject.content_type :xls, "application/vnd.ms-excel"
684
+ subject.get(:hello) do
685
+ "some binary content"
686
+ end
687
+ get '/hello.xls'
688
+ last_response.content_type.should == "application/vnd.ms-excel"
689
+ end
690
+ end
691
+
592
692
  describe ".default_error_status" do
593
693
  it 'should allow setting default_error_status' do
594
694
  subject.rescue_from :all
595
695
  subject.default_error_status 200
596
696
  subject.get '/exception' do
597
697
  raise "rain!"
598
- end
698
+ end
599
699
  get '/exception'
600
700
  last_response.status.should eql 200
601
701
  end
@@ -603,9 +703,398 @@ describe Grape::API do
603
703
  subject.rescue_from :all
604
704
  subject.get '/exception' do
605
705
  raise "rain!"
606
- end
706
+ end
607
707
  get '/exception'
608
708
  last_response.status.should eql 403
609
709
  end
610
710
  end
711
+
712
+ context "routes" do
713
+ describe "empty api structure" do
714
+ it "returns an empty array of routes" do
715
+ subject.routes.should == []
716
+ end
717
+ end
718
+ describe "single method api structure" do
719
+ before(:each) do
720
+ subject.get :ping do
721
+ 'pong'
722
+ end
723
+ end
724
+ it "returns one route" do
725
+ subject.routes.size.should == 1
726
+ route = subject.routes[0]
727
+ route.route_version.should be_nil
728
+ route.route_path.should == "/ping(.:format)"
729
+ route.route_method.should == "GET"
730
+ end
731
+ end
732
+ describe "api structure with two versions and a namespace" do
733
+ class TwitterAPI < Grape::API
734
+ # version v1
735
+ version 'v1', :using => :path
736
+ get "version" do
737
+ api.version
738
+ end
739
+ # version v2
740
+ version 'v2', :using => :path
741
+ prefix 'p'
742
+ namespace "n1" do
743
+ namespace "n2" do
744
+ get "version" do
745
+ api.version
746
+ end
747
+ end
748
+ end
749
+ end
750
+ it "should return versions" do
751
+ TwitterAPI::versions.should == [ 'v1', 'v2' ]
752
+ end
753
+ it "should set route paths" do
754
+ TwitterAPI::routes.size.should >= 2
755
+ TwitterAPI::routes[0].route_path.should == "/:version/version(.:format)"
756
+ TwitterAPI::routes[1].route_path.should == "/p/:version/n1/n2/version(.:format)"
757
+ end
758
+ it "should set route versions" do
759
+ TwitterAPI::routes[0].route_version.should == 'v1'
760
+ TwitterAPI::routes[1].route_version.should == 'v2'
761
+ end
762
+ it "should set a nested namespace" do
763
+ TwitterAPI::routes[1].route_namespace.should == "/n1/n2"
764
+ end
765
+ it "should set prefix" do
766
+ TwitterAPI::routes[1].route_prefix.should == 'p'
767
+ end
768
+ end
769
+ describe "api structure with additional parameters" do
770
+ before(:each) do
771
+ subject.get 'split/:string', { :params => { "token" => "a token" }, :optional_params => { "limit" => "the limit" } } do
772
+ params[:string].split(params[:token], (params[:limit] || 0).to_i)
773
+ end
774
+ end
775
+ it "should split a string" do
776
+ get "/split/a,b,c.json", :token => ','
777
+ last_response.body.should == '["a","b","c"]'
778
+ end
779
+ it "should split a string with limit" do
780
+ get "/split/a,b,c.json", :token => ',', :limit => '2'
781
+ last_response.body.should == '["a","b,c"]'
782
+ end
783
+ it "should set route_params" do
784
+ subject.routes.size.should == 1
785
+ subject.routes[0].route_params.should == { "string" => "", "token" => "a token" }
786
+ subject.routes[0].route_optional_params.should == { "limit" => "the limit" }
787
+ end
788
+ end
789
+ end
790
+
791
+ context "desc" do
792
+ describe "empty api structure" do
793
+ it "returns an empty array of routes" do
794
+ subject.desc "grape api"
795
+ subject.routes.should == []
796
+ end
797
+ end
798
+ describe "single method with a desc" do
799
+ before(:each) do
800
+ subject.desc "ping method"
801
+ subject.get :ping do
802
+ 'pong'
803
+ end
804
+ end
805
+ it "returns route description" do
806
+ subject.routes[0].route_description.should == "ping method"
807
+ end
808
+ end
809
+ describe "single method with a an array of params and a desc hash block" do
810
+ before(:each) do
811
+ subject.desc "ping method", { :params => { "x" => "y" } }
812
+ subject.get "ping/:x" do
813
+ 'pong'
814
+ end
815
+ end
816
+ it "returns route description" do
817
+ subject.routes[0].route_description.should == "ping method"
818
+ end
819
+ end
820
+ describe "api structure with multiple methods and descriptions" do
821
+ before(:each) do
822
+ class JitterAPI < Grape::API
823
+ desc "first method"
824
+ get "first" do; end
825
+ get "second" do; end
826
+ desc "third method"
827
+ get "third" do; end
828
+ end
829
+ end
830
+ it "should return a description for the first method" do
831
+ JitterAPI::routes[0].route_description.should == "first method"
832
+ JitterAPI::routes[1].route_description.should be_nil
833
+ JitterAPI::routes[2].route_description.should == "third method"
834
+ end
835
+ end
836
+ describe "api structure with multiple methods, namespaces, descriptions and options" do
837
+ before(:each) do
838
+ class LitterAPI < Grape::API
839
+ desc "first method"
840
+ get "first" do; end
841
+ get "second" do; end
842
+ namespace "ns" do
843
+ desc "ns second", :foo => "bar"
844
+ get "second" do; end
845
+ end
846
+ desc "third method", :details => "details of third method"
847
+ get "third" do; end
848
+ desc "Reverses a string.", { :params =>
849
+ { "s" => { :desc => "string to reverse", :type => "string" }}
850
+ }
851
+ get "reverse" do
852
+ params[:s].reverse
853
+ end
854
+ end
855
+ end
856
+ it "should return a description for the first method" do
857
+ LitterAPI::routes[0].route_description.should == "first method"
858
+ LitterAPI::routes[1].route_description.should be_nil
859
+ LitterAPI::routes[2].route_description.should == "ns second"
860
+ LitterAPI::routes[2].route_foo.should == "bar"
861
+ LitterAPI::routes[3].route_description.should == "third method"
862
+ LitterAPI::routes[4].route_description.should == "Reverses a string."
863
+ LitterAPI::routes[4].route_params.should == { "s" => { :desc => "string to reverse", :type => "string" }}
864
+ end
865
+ end
866
+ end
867
+
868
+ describe ".rescue_from klass, block" do
869
+ it 'should rescue Exception' do
870
+ subject.rescue_from RuntimeError do |e|
871
+ rack_response("rescued from #{e.message}", 202)
872
+ end
873
+ subject.get '/exception' do
874
+ raise "rain!"
875
+ end
876
+ get '/exception'
877
+ last_response.status.should eql 202
878
+ last_response.body.should == 'rescued from rain!'
879
+ end
880
+ it 'should rescue an error via rescue_from :all' do
881
+ class ConnectionError < RuntimeError; end
882
+ subject.rescue_from :all do |e|
883
+ rack_response("rescued from #{e.class.name}", 500)
884
+ end
885
+ subject.get '/exception' do
886
+ raise ConnectionError
887
+ end
888
+ get '/exception'
889
+ last_response.status.should eql 500
890
+ last_response.body.should == 'rescued from ConnectionError'
891
+ end
892
+ it 'should rescue a specific error' do
893
+ class ConnectionError < RuntimeError; end
894
+ subject.rescue_from ConnectionError do |e|
895
+ rack_response("rescued from #{e.class.name}", 500)
896
+ end
897
+ subject.get '/exception' do
898
+ raise ConnectionError
899
+ end
900
+ get '/exception'
901
+ last_response.status.should eql 500
902
+ last_response.body.should == 'rescued from ConnectionError'
903
+ end
904
+ it 'should rescue multiple specific errors' do
905
+ class ConnectionError < RuntimeError; end
906
+ class DatabaseError < RuntimeError; end
907
+ subject.rescue_from ConnectionError do |e|
908
+ rack_response("rescued from #{e.class.name}", 500)
909
+ end
910
+ subject.rescue_from DatabaseError do |e|
911
+ rack_response("rescued from #{e.class.name}", 500)
912
+ end
913
+ subject.get '/connection' do
914
+ raise ConnectionError
915
+ end
916
+ subject.get '/database' do
917
+ raise DatabaseError
918
+ end
919
+ get '/connection'
920
+ last_response.status.should eql 500
921
+ last_response.body.should == 'rescued from ConnectionError'
922
+ get '/database'
923
+ last_response.status.should eql 500
924
+ last_response.body.should == 'rescued from DatabaseError'
925
+ end
926
+ it 'should not rescue a different error' do
927
+ class CommunicationError < RuntimeError; end
928
+ subject.rescue_from RuntimeError do |e|
929
+ rack_response("rescued from #{e.class.name}", 500)
930
+ end
931
+ subject.get '/uncaught' do
932
+ raise CommunicationError
933
+ end
934
+ lambda { get '/uncaught' }.should raise_error(CommunicationError)
935
+ end
936
+ end
937
+
938
+ describe '.mount' do
939
+ let(:mounted_app){ lambda{|env| [200, {}, ["MOUNTED"]]} }
940
+
941
+ context 'with a bare rack app' do
942
+ before do
943
+ subject.mount mounted_app => '/mounty'
944
+ end
945
+
946
+ it 'should make a bare Rack app available at the endpoint' do
947
+ get '/mounty'
948
+ last_response.body.should == 'MOUNTED'
949
+ end
950
+
951
+ it 'should anchor the routes, passing all subroutes to it' do
952
+ get '/mounty/awesome'
953
+ last_response.body.should == 'MOUNTED'
954
+ end
955
+
956
+ it 'should be able to cascade' do
957
+ subject.mount lambda{ |env|
958
+ headers = {}
959
+ headers['X-Cascade'] == 'pass' unless env['PATH_INFO'].include?('boo')
960
+ [200, headers, ["Farfegnugen"]]
961
+ } => '/'
962
+
963
+ get '/boo'
964
+ last_response.body.should == 'Farfegnugen'
965
+ get '/mounty'
966
+ last_response.body.should == 'MOUNTED'
967
+ end
968
+ end
969
+
970
+ context 'without a hash' do
971
+ it 'should call through setting the route to "/"' do
972
+ subject.mount mounted_app
973
+ get '/'
974
+ last_response.body.should == 'MOUNTED'
975
+ end
976
+ end
977
+
978
+ context 'mounting an API' do
979
+ it 'should apply the settings of the mounting api' do
980
+ subject.version 'v1', :using => :path
981
+
982
+ subject.namespace :cool do
983
+ app = Class.new(Grape::API)
984
+ app.get('/awesome') do
985
+ "yo"
986
+ end
987
+
988
+ mount app
989
+ end
990
+ get '/v1/cool/awesome'
991
+ last_response.body.should == 'yo'
992
+ end
993
+ end
994
+ end
995
+
996
+ describe '.endpoints' do
997
+ it 'should add one for each route created' do
998
+ subject.get '/'
999
+ subject.post '/'
1000
+ subject.endpoints.size.should == 2
1001
+ end
1002
+ end
1003
+
1004
+ describe '.compile' do
1005
+ it 'should set the instance' do
1006
+ subject.instance.should be_nil
1007
+ subject.compile
1008
+ subject.instance.should be_kind_of(subject)
1009
+ end
1010
+ end
1011
+
1012
+ describe '.change!' do
1013
+ it 'should invalidate any compiled instance' do
1014
+ subject.compile
1015
+ subject.change!
1016
+ subject.instance.should be_nil
1017
+ end
1018
+ end
1019
+
1020
+ describe ".route" do
1021
+ context "plain" do
1022
+ before(:each) do
1023
+ subject.get '/' do
1024
+ route.route_path
1025
+ end
1026
+ subject.get '/path' do
1027
+ route.route_path
1028
+ end
1029
+ end
1030
+ it 'should provide access to route info' do
1031
+ get '/'
1032
+ last_response.body.should == "/(.:format)"
1033
+ get '/path'
1034
+ last_response.body.should == "/path(.:format)"
1035
+ end
1036
+ end
1037
+ context "with desc" do
1038
+ before(:each) do
1039
+ subject.desc 'returns description'
1040
+ subject.get '/description' do
1041
+ route.route_description
1042
+ end
1043
+ subject.desc 'returns parameters', { :params => { "x" => "y" }}
1044
+ subject.get '/params/:id' do
1045
+ route.route_params[params[:id]]
1046
+ end
1047
+ end
1048
+ it 'should return route description' do
1049
+ get '/description'
1050
+ last_response.body.should == "returns description"
1051
+ end
1052
+ it 'should return route parameters' do
1053
+ get '/params/x'
1054
+ last_response.body.should == "y"
1055
+ end
1056
+ end
1057
+ end
1058
+ context "format" do
1059
+ context ":txt" do
1060
+ before(:each) do
1061
+ subject.format :txt
1062
+ subject.get '/meaning_of_life' do
1063
+ { :meaning_of_life => 42 }
1064
+ end
1065
+ end
1066
+ it "should force txt without an extension" do
1067
+ get '/meaning_of_life'
1068
+ last_response.body.should == { :meaning_of_life => 42 }.to_s
1069
+ end
1070
+ it "should not force txt with an extension" do
1071
+ get '/meaning_of_life.json'
1072
+ last_response.body.should == { :meaning_of_life => 42 }.to_json
1073
+ end
1074
+ it "should force txt from a non-accepting header" do
1075
+ get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'application/json' }
1076
+ last_response.body.should == { :meaning_of_life => 42 }.to_s
1077
+ end
1078
+ end
1079
+ context ":json" do
1080
+ before(:each) do
1081
+ subject.format :json
1082
+ subject.get '/meaning_of_life' do
1083
+ { :meaning_of_life => 42 }
1084
+ end
1085
+ end
1086
+ it "should force json without an extension" do
1087
+ get '/meaning_of_life'
1088
+ last_response.body.should == { :meaning_of_life => 42 }.to_json
1089
+ end
1090
+ it "should not force json with an extension" do
1091
+ get '/meaning_of_life.txt'
1092
+ last_response.body.should == { :meaning_of_life => 42 }.to_s
1093
+ end
1094
+ it "should force json from a non-accepting header" do
1095
+ get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'text/html' }
1096
+ last_response.body.should == { :meaning_of_life => 42 }.to_json
1097
+ end
1098
+ end
1099
+ end
611
1100
  end