jellyfish 0.6.0 → 0.8.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.
@@ -0,0 +1,425 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ class RegexpLookAlike
5
+ class MatchData
6
+ def captures
7
+ ["this", "is", "a", "test"]
8
+ end
9
+ end
10
+
11
+ def match(string)
12
+ ::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/"
13
+ end
14
+
15
+ def keys
16
+ ["one", "two", "three", "four"]
17
+ end
18
+ end
19
+
20
+ # stolen from sinatra
21
+ describe 'Sinatra routing_test.rb' do
22
+ behaves_like :jellyfish
23
+
24
+ %w[get put post delete options patch head].each do |verb|
25
+ should "define #{verb.upcase} request handlers with #{verb}" do
26
+ app = Class.new{
27
+ include Jellyfish
28
+ send verb, '/hello' do
29
+ 'Hello World'
30
+ end
31
+ }.new
32
+
33
+ status, _, body = send(verb, '/hello', app)
34
+ status.should.eq 200
35
+ body .should.eq ['Hello World']
36
+ end
37
+ end
38
+
39
+ should '404s when no route satisfies the request' do
40
+ app = Class.new{
41
+ include Jellyfish
42
+ get('/foo'){}
43
+ }.new
44
+ status, _, _ = get('/bar', app)
45
+ status.should.eq 404
46
+ end
47
+
48
+ should 'allows using unicode' do
49
+ app = Class.new{
50
+ include Jellyfish
51
+ controller_include Jellyfish::NormalizedPath
52
+ get("/f\u{f6}\u{f6}"){}
53
+ }.new
54
+ status, _, _ = get('/f%C3%B6%C3%B6', app)
55
+ status.should.eq 200
56
+ end
57
+
58
+ should 'handle encoded slashes correctly' do
59
+ app = Class.new{
60
+ include Jellyfish
61
+ controller_include Jellyfish::NormalizedPath
62
+ get(%r{^/(.+)}){ |m| m[1] }
63
+ }.new
64
+ status, _, body = get('/foo%2Fbar', app)
65
+ status.should.eq 200
66
+ body .should.eq ['foo/bar']
67
+ end
68
+
69
+ should 'override the content-type in error handlers' do
70
+ app = Class.new{
71
+ include Jellyfish
72
+ get{
73
+ self.headers 'Content-Type' => 'text/plain'
74
+ status, headers, body = jellyfish.app.call(env)
75
+ self.status status
76
+ self.body body
77
+ headers_merge(headers)
78
+ }
79
+ }.new(Class.new{
80
+ include Jellyfish
81
+ handle Jellyfish::NotFound do
82
+ headers_merge 'Content-Type' => 'text/html'
83
+ status 404
84
+ '<h1>Not Found</h1>'
85
+ end
86
+ }.new)
87
+
88
+ status, headers, body = get('/foo', app)
89
+ status .should.eq 404
90
+ headers['Content-Type'].should.eq 'text/html'
91
+ body .should.eq ['<h1>Not Found</h1>']
92
+ end
93
+
94
+ should 'match empty PATH_INFO to "/" if no route is defined for ""' do
95
+ app = Class.new{
96
+ include Jellyfish
97
+ controller_include Jellyfish::NormalizedPath
98
+ get('/'){ 'worked' }
99
+ }.new
100
+
101
+ status, headers, body = get('', app)
102
+ status.should.eq 200
103
+ body .should.eq ['worked']
104
+ end
105
+
106
+ should 'exposes params with indifferent hash' do
107
+ app = Class.new{
108
+ include Jellyfish
109
+ controller_include Jellyfish::NormalizedParams
110
+
111
+ get %r{^/(?<foo>\w+)} do
112
+ params['foo'].should.eq 'bar'
113
+ params[:foo ].should.eq 'bar'
114
+ 'well, alright'
115
+ end
116
+ }.new
117
+
118
+ _, _, body = get('/bar', app)
119
+ body.should.eq ['well, alright']
120
+ end
121
+
122
+ should 'merges named params and query string params in params' do
123
+ app = Class.new{
124
+ include Jellyfish
125
+ controller_include Jellyfish::NormalizedParams
126
+
127
+ get %r{^/(?<foo>\w+)} do
128
+ params['foo'].should.eq 'bar'
129
+ params['baz'].should.eq 'biz'
130
+ end
131
+ }.new
132
+
133
+ status, _, _ = get('/bar', app, 'QUERY_STRING' => 'baz=biz')
134
+ status.should.eq 200
135
+ end
136
+
137
+ should 'support named captures like %r{/hello/(?<person>[^/?#]+)}' do
138
+ app = Class.new{
139
+ include Jellyfish
140
+ get Regexp.new('/hello/(?<person>[^/?#]+)') do |m|
141
+ "Hello #{m['person']}"
142
+ end
143
+ }.new
144
+
145
+ _, _, body = get('/hello/Frank', app)
146
+ body.should.eq ['Hello Frank']
147
+ end
148
+
149
+ should 'support optional named captures' do
150
+ app = Class.new{
151
+ include Jellyfish
152
+ get Regexp.new('/page(?<format>.[^/?#]+)?') do |m|
153
+ "format=#{m[:format]}"
154
+ end
155
+ }.new
156
+
157
+ status, _, body = get('/page.html', app)
158
+ status.should.eq 200
159
+ body .should.eq ['format=.html']
160
+
161
+ status, _, body = get('/page.xml', app)
162
+ status.should.eq 200
163
+ body .should.eq ['format=.xml']
164
+
165
+ status, _, body = get('/page', app)
166
+ status.should.eq 200
167
+ body .should.eq ['format=']
168
+ end
169
+
170
+ should 'not concatinate params with the same name' do
171
+ app = Class.new{
172
+ include Jellyfish
173
+ controller_include Jellyfish::NormalizedParams
174
+
175
+ get(%r{^/(?<foo>\w+)}){ |m| params[:foo] }
176
+ }.new
177
+
178
+ _, _, body = get('/a', app, 'QUERY_STRING' => 'foo=b')
179
+ body.should.eq ['a']
180
+ end
181
+
182
+ should 'support basic nested params' do
183
+ app = Class.new{
184
+ include Jellyfish
185
+ get('/hi'){ request.params['person']['name'] }
186
+ }.new
187
+
188
+ status, _, body = get('/hi', app,
189
+ 'QUERY_STRING' => 'person[name]=John+Doe')
190
+ status.should.eq 200
191
+ body.should.eq ['John Doe']
192
+ end
193
+
194
+ should "expose nested params with indifferent hash" do
195
+ app = Class.new{
196
+ include Jellyfish
197
+ controller_include Jellyfish::NormalizedParams
198
+
199
+ get '/testme' do
200
+ params['bar']['foo'].should.eq 'baz'
201
+ params['bar'][:foo ].should.eq 'baz'
202
+ 'well, alright'
203
+ end
204
+ }.new
205
+
206
+ _, _, body = get('/testme', app, 'QUERY_STRING' => 'bar[foo]=baz')
207
+ body.should.eq ['well, alright']
208
+ end
209
+
210
+ should 'preserve non-nested params' do
211
+ app = Class.new{
212
+ include Jellyfish
213
+ get '/foo' do
214
+ request.params['article_id'] .should.eq '2'
215
+ request.params['comment']['body'].should.eq 'awesome'
216
+ request.params['comment[body]'] .should.eq nil
217
+ 'looks good'
218
+ end
219
+ }.new
220
+
221
+ status, _, body = get('/foo', app,
222
+ 'QUERY_STRING' => 'article_id=2&comment[body]=awesome')
223
+ status.should.eq 200
224
+ body .should.eq ['looks good']
225
+ end
226
+
227
+ should 'match paths that include spaces encoded with %20' do
228
+ app = Class.new{
229
+ include Jellyfish
230
+ controller_include Jellyfish::NormalizedPath
231
+ get('/path with spaces'){ 'looks good' }
232
+ }.new
233
+
234
+ status, _, body = get('/path%20with%20spaces', app)
235
+ status.should.eq 200
236
+ body .should.eq ['looks good']
237
+ end
238
+
239
+ should 'match paths that include spaces encoded with +' do
240
+ app = Class.new{
241
+ include Jellyfish
242
+ controller_include Jellyfish::NormalizedPath
243
+ get('/path with spaces'){ 'looks good' }
244
+ }.new
245
+
246
+ status, _, body = get('/path+with+spaces', app)
247
+ status.should.eq 200
248
+ body .should.eq ['looks good']
249
+ end
250
+
251
+ should 'make regular expression captures available' do
252
+ app = Class.new{
253
+ include Jellyfish
254
+ get(/^\/fo(.*)\/ba(.*)/) do |m|
255
+ m[1..-1].should.eq ['orooomma', 'f']
256
+ 'right on'
257
+ end
258
+ }.new
259
+
260
+ status, _, body = get('/foorooomma/baf', app)
261
+ status.should.eq 200
262
+ body .should.eq ['right on']
263
+ end
264
+
265
+ it 'supports regular expression look-alike routes' do
266
+ app = Class.new{
267
+ include Jellyfish
268
+ controller_include Jellyfish::NormalizedParams
269
+ matcher = Object.new
270
+ def matcher.match path
271
+ %r{/(?<one>\w+)/(?<two>\w+)/(?<three>\w+)/(?<four>\w+)}.match(path)
272
+ end
273
+
274
+ get(matcher) do |m|
275
+ [m, params].each do |q|
276
+ q[:one] .should.eq 'this'
277
+ q[:two] .should.eq 'is'
278
+ q[:three].should.eq 'a'
279
+ q[:four] .should.eq 'test'
280
+ end
281
+ 'right on'
282
+ end
283
+ }.new
284
+
285
+ status, _, body = get('/this/is/a/test/', app)
286
+ status.should.eq 200
287
+ body .should.eq ['right on']
288
+ end
289
+
290
+ should 'raise a TypeError when pattern is not a String or Regexp' do
291
+ lambda{ Class.new{ include Jellyfish; get(42){} } }.
292
+ should.raise(TypeError)
293
+ end
294
+
295
+ should 'return response immediately on next or halt' do
296
+ app = Class.new{
297
+ include Jellyfish
298
+ controller_include Jellyfish::MultiActions
299
+
300
+ get '/next' do
301
+ body 'Hello World'
302
+ next
303
+ 'Boo-hoo World'
304
+ end
305
+
306
+ get '/halt' do
307
+ body 'Hello World'
308
+ halt
309
+ 'Boo-hoo World'
310
+ end
311
+ }.new
312
+
313
+ %w[/next /halt].each do |path|
314
+ status, _, body = get(path, app)
315
+ status.should.eq 200
316
+ body .should.eq ['Hello World']
317
+ end
318
+ end
319
+
320
+ should 'halt with a response tuple' do
321
+ app = Class.new{
322
+ include Jellyfish
323
+ controller_include Jellyfish::MultiActions
324
+
325
+ get '/' do
326
+ halt [295, {'Content-Type' => 'text/plain'}, ['Hello World']]
327
+ end
328
+ }.new
329
+
330
+ status, headers, body = get('/', app)
331
+ status .should.eq 295
332
+ headers['Content-Type'].should.eq 'text/plain'
333
+ body .should.eq ['Hello World']
334
+ end
335
+
336
+ should 'transition to the next matching route on next' do
337
+ app = Class.new{
338
+ include Jellyfish
339
+ controller_include Jellyfish::MultiActions, Jellyfish::NormalizedParams
340
+ get %r{^/(?<foo>\w+)} do
341
+ params['foo'].should.eq 'bar'
342
+ next
343
+ 'Hello Foo'
344
+ end
345
+
346
+ get do
347
+ params.should.not.include?('foo')
348
+ 'Hello World'
349
+ end
350
+ }.new
351
+
352
+ status, _, body = get('/bar', app)
353
+ status.should.eq 200
354
+ body .should.eq ['Hello World']
355
+ end
356
+
357
+ should 'match routes defined in superclasses' do
358
+ sup = Class.new{
359
+ include Jellyfish
360
+ get('/foo'){ 'foo' }
361
+ }
362
+ app = Class.new(sup){
363
+ get('/bar'){ 'bar' }
364
+ }.new
365
+
366
+ %w[foo bar].each do |path|
367
+ status, _, body = get("/#{path}", app)
368
+ status.should.eq 200
369
+ body .should.eq [path]
370
+ end
371
+ end
372
+
373
+ should 'match routes itself first then downward app' do
374
+ sup = Class.new{
375
+ include Jellyfish
376
+ get('/foo'){ 'foo sup' }
377
+ get('/bar'){ 'bar sup' }
378
+ }
379
+ app = Class.new{
380
+ include Jellyfish
381
+ get('/foo'){ 'foo sub' }
382
+ }.new(sup.new)
383
+
384
+ status, _, body = get('/foo', app)
385
+ status.should.eq 200
386
+ body .should.eq ['foo sub']
387
+
388
+ status, _, body = get('/bar', app)
389
+ status.should.eq 200
390
+ body .should.eq ['bar sup']
391
+ end
392
+
393
+ should 'allow using call to fire another request internally' do
394
+ app = Class.new{
395
+ include Jellyfish
396
+ get '/foo' do
397
+ status, headers, body = call(env.merge('PATH_INFO' => '/bar'))
398
+ self.status status
399
+ self.headers headers
400
+ self.body body.map(&:upcase)
401
+ end
402
+
403
+ get '/bar' do
404
+ 'bar'
405
+ end
406
+ }.new
407
+
408
+ status, _, body = get('/foo', app)
409
+ status.should.eq 200
410
+ body .should.eq ['BAR']
411
+ end
412
+
413
+ should 'play well with other routing middleware' do
414
+ middleware = Class.new{include Jellyfish}
415
+ inner_app = Class.new{include Jellyfish; get('/foo'){ 'hello' } }
416
+ builder = Rack::Builder.new do
417
+ use middleware
418
+ map('/test'){ run inner_app.new }
419
+ end
420
+
421
+ status, _, body = get('/test/foo', builder.to_app)
422
+ status.should.eq 200
423
+ body .should.eq ['hello']
424
+ end
425
+ end
@@ -0,0 +1,39 @@
1
+
2
+ require 'jellyfish/test'
3
+ require 'uri'
4
+
5
+ describe 'from README.md' do
6
+ after do
7
+ [:Tank, :Heater, :Protector].each do |const|
8
+ Object.send(:remove_const, const) if Object.const_defined?(const)
9
+ end
10
+ end
11
+
12
+ readme = File.read(
13
+ "#{File.dirname(File.expand_path(__FILE__))}/../README.md")
14
+ codes = readme.scan(
15
+ /### ([^\n]+).+?``` ruby\n(.+?)\n```\n\n<!---(.+?)-->/m)
16
+
17
+ codes.each.with_index do |(title, code, test), index|
18
+ if title =~ /NewRelic/i
19
+ warn "Skip NewRelic Test" unless Bacon.kind_of?(Bacon::TestUnitOutput)
20
+ next
21
+ end
22
+
23
+ should "pass from README.md #%02d #{title}" % index do
24
+ method_path, expect = test.strip.split("\n", 2)
25
+ method, path = method_path.split(' ')
26
+ uri = URI.parse(path)
27
+ pinfo, query = uri.path, uri.query
28
+
29
+ status, headers, body = File.open(File::NULL) do |input|
30
+ Rack::Builder.new{ eval(code) }.call(
31
+ 'REQUEST_METHOD' => method, 'PATH_INFO' => pinfo,
32
+ 'QUERY_STRING' => query , 'rack.input' => input)
33
+ end
34
+
35
+ body.extend(Enumerable)
36
+ [status, headers, body.to_a].should.eq eval(expect, binding, __FILE__)
37
+ end
38
+ end
39
+ end