jellyfish 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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