jellyfish 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,8 +8,13 @@ Bacon.summary_on_exit
8
8
  shared :jellyfish do
9
9
  %w[options get head post put delete patch].each do |method|
10
10
  instance_eval <<-RUBY
11
- def #{method} path='/', app=app
12
- app.call('PATH_INFO' => path, 'REQUEST_METHOD' => '#{method}'.upcase)
11
+ def #{method} path='/', app=app, env={}
12
+ File.open(File::NULL) do |input|
13
+ app.call({'PATH_INFO' => path ,
14
+ 'REQUEST_METHOD' => '#{method}'.upcase,
15
+ 'SCRIPT_NAME' => '' ,
16
+ 'rack.input' => input }.merge(env))
17
+ end
13
18
  end
14
19
  RUBY
15
20
  end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module Jellyfish
3
- VERSION = '0.6.0'
3
+ VERSION = '0.8.0'
4
4
  end
data/task/gemgem.rb CHANGED
@@ -14,6 +14,7 @@ module Gemgem
14
14
 
15
15
  s.description = description.join
16
16
  s.summary = description.first
17
+ s.license = readme['LICENSE'].sub(/.+\n\n/, '').lines.first.strip
17
18
 
18
19
  s.rubygems_version = Gem::VERSION
19
20
  s.date = Time.now.strftime('%Y-%m-%d')
@@ -33,8 +34,8 @@ module Gemgem
33
34
  @readme ||=
34
35
  if path
35
36
  ps = "##{File.read(path)}".
36
- scan(/((#+)[^\n]+\n\n.+?(?=\n\n\2[^#\n]+\n))/m).map(&:first)
37
- ps.inject({'HEADER' => ps.first}){ |r, s, i|
37
+ scan(/((#+)[^\n]+\n\n.+?(?=(\n\n\2[^#\n]+\n)|\Z))/m).map(&:first)
38
+ ps.inject('HEADER' => ps.first){ |r, s, i|
38
39
  r[s[/\w+/]] = s
39
40
  r
40
41
  }
@@ -44,7 +45,7 @@ module Gemgem
44
45
  end
45
46
 
46
47
  def description
47
- @description ||= (readme['DESCRIPTION']||'').sub(/.+\n\n/, '').lines.to_a
48
+ @description ||= (readme['DESCRIPTION']||'').sub(/.+\n\n/, '').lines
48
49
  end
49
50
 
50
51
  def changes
@@ -104,9 +105,9 @@ module Gemgem
104
105
  end
105
106
 
106
107
  def split_lines ruby
107
- ruby.gsub(/(.+?)\[(.+?)\]/){ |s|
108
+ ruby.gsub(/(.+?)\s*=\s*\[(.+?)\]/){ |s|
108
109
  if $2.index(',')
109
- "#{$1}[\n #{$2.split(',').map(&:strip).join(",\n ")}]"
110
+ "#{$1} = [\n #{$2.split(',').map(&:strip).join(",\n ")}]"
110
111
  else
111
112
  s
112
113
  end
@@ -179,7 +180,7 @@ namespace :gem do
179
180
 
180
181
  desc 'Install gem'
181
182
  task :install => [:build] do
182
- sh("#{Gem.ruby} -S gem install pkg/#{Gemgem.gem_tag}")
183
+ sh("#{Gem.ruby} -S gem install pkg/#{Gemgem.gem_tag}.gem")
183
184
  end
184
185
 
185
186
  desc 'Build gem'
@@ -0,0 +1,110 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ # stolen from sinatra
5
+ describe 'Sinatra base_test.rb' do
6
+ behaves_like :jellyfish
7
+
8
+ should 'process requests with #call' do
9
+ app = Class.new{
10
+ include Jellyfish
11
+ get '/' do
12
+ 'Hello World'
13
+ end
14
+ }.new
15
+ app.respond_to?(:call).should.eq true
16
+ status, _, body = get('/', app)
17
+ status.should.eq 200
18
+ body .should.eq ['Hello World']
19
+ end
20
+
21
+ should 'not maintain state between requests' do
22
+ app = Class.new{
23
+ include Jellyfish
24
+ get '/state' do
25
+ @foo ||= 'new'
26
+ body = "Foo: #{@foo}"
27
+ @foo = 'discard'
28
+ body
29
+ end
30
+ }.new
31
+
32
+ 2.times do
33
+ status, _, body = get('/state', app)
34
+ status.should.eq 200
35
+ body .should.eq ['Foo: new']
36
+ end
37
+ end
38
+
39
+ describe 'Jellyfish as a Rack middleware' do
40
+ behaves_like :jellyfish
41
+
42
+ def app
43
+ @app ||= Class.new{
44
+ include Jellyfish
45
+ get '/' do
46
+ 'Hello from middleware'
47
+ end
48
+
49
+ get '/low-level-forward' do
50
+ status, headers, body = jellyfish.app.call(env)
51
+ self.status status
52
+ self.headers headers
53
+ body
54
+ end
55
+
56
+ get '/explicit-forward' do
57
+ headers_merge 'X-Middleware' => 'true'
58
+ status, headers, _ = jellyfish.app.call(env)
59
+ self.status status
60
+ self.headers headers
61
+ 'Hello after explicit forward'
62
+ end
63
+ }.new(inner_app)
64
+ end
65
+
66
+ def inner_app
67
+ @inner_app ||= lambda{ |env|
68
+ [210, {'X-Downstream' => 'true'}, ['Hello from downstream']]
69
+ }
70
+ end
71
+
72
+ should 'create a middleware that responds to #call with .new' do
73
+ app.respond_to?(:call).should.eq true
74
+ end
75
+
76
+ should 'expose the downstream app' do
77
+ app.app.object_id.should.eq inner_app.object_id
78
+ end
79
+
80
+ should 'intercept requests' do
81
+ status, _, body = get('/')
82
+ status.should.eq 200
83
+ body .should.eq ['Hello from middleware']
84
+ end
85
+
86
+ should 'forward requests downstream when no matching route found' do
87
+ status, headers, body = get('/missing')
88
+ status .should.eq 210
89
+ headers['X-Downstream'].should.eq 'true'
90
+ body .should.eq ['Hello from downstream']
91
+ end
92
+
93
+ should 'call the downstream app directly and return result' do
94
+ status, headers, body = get('/low-level-forward')
95
+ status .should.eq 210
96
+ headers['X-Downstream'].should.eq 'true'
97
+ body .should.eq ['Hello from downstream']
98
+ end
99
+
100
+ should 'forward the request and integrate the response' do
101
+ status, headers, body =
102
+ get('/explicit-forward', Rack::ContentLength.new(app))
103
+
104
+ status .should.eq 210
105
+ headers['X-Downstream'] .should.eq 'true'
106
+ headers['Content-Length'].should.eq '28'
107
+ body .should.eq ['Hello after explicit forward']
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,43 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ # stolen from sinatra
5
+ describe 'Sinatra streaming_test.rb' do
6
+ behaves_like :jellyfish
7
+
8
+ should 'return the concatinated body' do
9
+ app = Class.new{
10
+ include Jellyfish
11
+ get '/' do
12
+ Jellyfish::ChunkedBody.new{ |out|
13
+ out['Hello']
14
+ out[' ']
15
+ out['World!']
16
+ }
17
+ end
18
+ }.new
19
+ _, _, body = get('/', app)
20
+ body.to_a.join.should.eq 'Hello World!'
21
+ end
22
+
23
+ should 'postpone body generation' do
24
+ stream = Jellyfish::ChunkedBody.new{ |out|
25
+ 10.times{ |i| out[i] }
26
+ }
27
+
28
+ stream.each.with_index do |s, i|
29
+ s.should.eq i
30
+ end
31
+ end
32
+
33
+ should 'give access to route specific params' do
34
+ app = Class.new{
35
+ include Jellyfish
36
+ get(%r{/(?<name>\w+)}){ |m|
37
+ Jellyfish::ChunkedBody.new{ |o| o[m[:name]] }
38
+ }
39
+ }.new
40
+ _, _, body = get('/foo', app)
41
+ body.to_a.join.should.eq 'foo'
42
+ end
43
+ end
@@ -0,0 +1,145 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ describe 'Sinatra mapped_error_test.rb' do
5
+ behaves_like :jellyfish
6
+
7
+ exp = Class.new(RuntimeError)
8
+
9
+ should 'invoke handlers registered with handle when raised' do
10
+ app = Class.new{
11
+ include Jellyfish
12
+ handle(exp){ 'Foo!' }
13
+ get '/' do
14
+ raise exp
15
+ end
16
+ }.new
17
+
18
+ status, _, body = get('/', app)
19
+ status.should.eq 200
20
+ body .should.eq ['Foo!']
21
+ end
22
+
23
+ should 'pass the exception object to the error handler' do
24
+ app = Class.new{
25
+ include Jellyfish
26
+ handle(exp){ |e| e.should.kind_of?(exp) }
27
+ get('/'){ raise exp }
28
+ }.new
29
+ get('/', app)
30
+ end
31
+
32
+ should 'use the Exception handler if no matching handler found' do
33
+ app = Class.new{
34
+ include Jellyfish
35
+ handle(Exception){ 'Exception!' }
36
+ get('/'){ raise exp }
37
+ }.new
38
+
39
+ status, _, body = get('/', app)
40
+ status.should.eq 200
41
+ body .should.eq ['Exception!']
42
+ end
43
+
44
+ should 'favour subclass handler over superclass handler if available' do
45
+ app = Class.new{
46
+ include Jellyfish
47
+ handle(Exception) { 'Exception!' }
48
+ handle(RuntimeError){ 'RuntimeError!' }
49
+ get('/'){ raise exp }
50
+ }.new
51
+
52
+ status, _, body = get('/', app)
53
+ status.should.eq 200
54
+ body .should.eq ['RuntimeError!']
55
+
56
+ handlers = app.class.handlers
57
+ handlers.size.should.eq 3
58
+ handlers[exp].should.eq handlers[RuntimeError]
59
+ end
60
+
61
+ should 'pass the exception to the handler' do
62
+ app = Class.new{
63
+ include Jellyfish
64
+ handle(exp){ |e|
65
+ e.should.kind_of?(exp)
66
+ 'looks good'
67
+ }
68
+ get('/'){ raise exp }
69
+ }.new
70
+
71
+ _, _, body = get('/', app)
72
+ body.should.eq ['looks good']
73
+ end
74
+
75
+ should 'raise errors from the app when handle_exceptions is false' do
76
+ app = Class.new{
77
+ include Jellyfish
78
+ handle_exceptions false
79
+ get('/'){ raise exp }
80
+ }.new
81
+
82
+ lambda{ get('/', app) }.should.raise(exp)
83
+ end
84
+
85
+ should 'call error handlers even when handle_exceptions is false' do
86
+ app = Class.new{
87
+ include Jellyfish
88
+ handle_exceptions false
89
+ handle(exp){ "she's there." }
90
+ get('/'){ raise exp }
91
+ }.new
92
+
93
+ _, _, body = get('/', app)
94
+ body.should.eq ["she's there."]
95
+ end
96
+
97
+ should 'never raises Jellyfish::NotFound beyond the application' do
98
+ app = Class.new{
99
+ include Jellyfish
100
+ get('/'){ raise Jellyfish::NotFound }
101
+ }.new
102
+
103
+ status, _, _ = get('/', app)
104
+ status.should.eq 404
105
+ end
106
+
107
+ should 'cascade for subclasses of Jellyfish::NotFound' do
108
+ e = Class.new(Jellyfish::NotFound)
109
+ app = Class.new{
110
+ include Jellyfish
111
+ get('/'){ raise e }
112
+ }.new
113
+
114
+ status, _, body = get('/', app)
115
+ status.should.eq 404
116
+ end
117
+
118
+ should 'inherit error mappings from base class' do
119
+ sup = Class.new{
120
+ include Jellyfish
121
+ handle(exp){ 'sup' }
122
+ }
123
+ app = Class.new(sup){
124
+ get('/'){ raise exp }
125
+ }.new
126
+
127
+ _, _, body = get('/', app)
128
+ body.should.eq ['sup']
129
+ end
130
+
131
+ should 'override error mappings in base class' do
132
+ sup = Class.new{
133
+ include Jellyfish
134
+ handle(exp){ 'sup' }
135
+ }
136
+ app = Class.new(sup){
137
+ handle(exp){ 'sub' }
138
+ get('/'){ raise exp }
139
+ }.new
140
+
141
+
142
+ _, _, body = get('/', app)
143
+ body.should.eq ['sub']
144
+ end
145
+ end
@@ -0,0 +1,217 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ # stolen from sinatra
5
+ describe 'Sinatra filter_test.rb' do
6
+ behaves_like :jellyfish
7
+
8
+ def new_app base=Object, &block
9
+ Class.new(base){
10
+ include Jellyfish
11
+ controller_include(Jellyfish::MultiActions)
12
+ instance_eval(&block)
13
+ }.new
14
+ end
15
+
16
+ should 'executes filters in the order defined' do
17
+ count = 0
18
+ app = new_app{
19
+ get { count.should.eq 0; count = 1 }
20
+ get { count.should.eq 1; count = 2 }
21
+ get('/'){ 'Hello World' }
22
+ }
23
+
24
+ status, _, body = get('/', app)
25
+ status.should.eq 200
26
+ count .should.eq 2
27
+ body .should.eq ['Hello World']
28
+ end
29
+
30
+ should 'modify env' do
31
+ app = new_app{
32
+ get{ env['BOO'] = 'MOO' }
33
+ get('/foo'){ env['BOO'] }
34
+ }
35
+
36
+ status, _, body = get('/foo', app)
37
+ status.should.eq 200
38
+ body .should.eq ['MOO']
39
+ end
40
+
41
+ should 'modify instance variables available to routes' do
42
+ app = new_app{
43
+ get{ @foo = 'bar' }
44
+ get('/foo') { @foo }
45
+ }
46
+
47
+ status, _, body = get('/foo', app)
48
+ status.should.eq 200
49
+ body .should.eq ['bar']
50
+ end
51
+
52
+ should 'allows redirects' do
53
+ app = new_app{
54
+ get{ found '/bar' }
55
+ get('/foo') do
56
+ fail 'before block should have halted processing'
57
+ 'ORLY?!'
58
+ end
59
+ }
60
+
61
+ status, headers, body = get('/foo', app)
62
+ status .should.eq 302
63
+ headers['Location'].should.eq '/bar'
64
+ body.join .should =~ %r{<h1>Jellyfish found: /bar</h1>}
65
+ end
66
+
67
+ should 'not modify the response with its return value' do
68
+ app = new_app{
69
+ get{ 'Hello World!' }
70
+ get '/foo' do
71
+ body.should.eq nil
72
+ 'cool'
73
+ end
74
+ }
75
+
76
+ status, _, body = get('/foo', app)
77
+ status.should.eq 200
78
+ body .should.eq ['cool']
79
+ end
80
+
81
+ should 'modify the response with halt' do
82
+ app = new_app{
83
+ get('/foo'){ halt [302, {}, ['Hi']] }
84
+ get('/foo'){ 'should not happen' }
85
+ get('/bar'){ status 402; body 'Ho'; halt }
86
+ get('/bar'){ 'should not happen' }
87
+ }
88
+
89
+ get('/foo', app).should.eq [302, {}, ['Hi']]
90
+ get('/bar', app).should.eq [402, {}, ['Ho']]
91
+ end
92
+
93
+ should 'give you access to params' do
94
+ app = new_app{
95
+ get{ @foo = Rack::Request.new(env).params['foo'] }
96
+ get('/foo'){ @foo.reverse }
97
+ }
98
+
99
+ status, _, body = get('/foo', app, 'QUERY_STRING' => 'foo=cool')
100
+ status.should.eq 200
101
+ body .should.eq ['looc']
102
+ end
103
+
104
+ should 'run filters defined in superclasses' do
105
+ sup = new_app{ get{ @foo = 'hello from superclass' } }.class
106
+ app = new_app(sup){ get('/foo'){ @foo } }
107
+
108
+ _, _, body = get('/foo', app)
109
+ body.should.eq ['hello from superclass']
110
+
111
+ sup .routes['get'].size.should.eq 1
112
+ app.class.routes['get'].size.should.eq 2
113
+ end
114
+
115
+ should 'take an optional route pattern' do
116
+ ran_filter = false
117
+ app = new_app{
118
+ get(%r{^/b}){ ran_filter = true }
119
+ get('/foo') {}
120
+ get('/bar') {}
121
+ }
122
+ get('/foo', app)
123
+ ran_filter.should.eq false
124
+ get('/bar', app)
125
+ ran_filter.should.eq true
126
+ end
127
+
128
+ should 'generate block arguments from route pattern' do
129
+ subpath = nil
130
+ app = new_app{
131
+ get(%r{^/foo/(\w+)}){ |m| subpath = m[1] }
132
+ }
133
+ get('/foo/bar', app)
134
+ subpath.should.eq 'bar'
135
+ end
136
+
137
+ should 'execute before and after filters in correct order' do
138
+ invoked = 0
139
+ app = new_app{
140
+ get { invoked = 2 }
141
+ get('/'){ invoked += 2; body 'hello' }
142
+ get { invoked *= 2 }
143
+ }
144
+
145
+ status, _, body = get('/', app)
146
+ status .should.eq 200
147
+ body .should.eq ['hello']
148
+ invoked.should.eq 8
149
+ end
150
+
151
+ should 'execute filters in the order defined' do
152
+ count = 0
153
+ app = new_app{
154
+ get('/'){ body 'Hello World' }
155
+ get{
156
+ count.should.eq 0
157
+ count = 1
158
+ }
159
+ get{
160
+ count.should.eq 1
161
+ count = 2
162
+ }
163
+ }
164
+
165
+ status, _, body = get('/', app)
166
+ status.should.eq 200
167
+ count .should.eq 2
168
+ body .should.eq ['Hello World']
169
+ end
170
+
171
+ should 'allow redirects' do
172
+ app = new_app{
173
+ get('/foo'){ 'ORLY' }
174
+ get { found '/bar' }
175
+ }
176
+
177
+ status, headers, body = get('/foo', app)
178
+ status .should.eq 302
179
+ headers['Location'].should.eq '/bar'
180
+ body.join .should =~ %r{<h1>Jellyfish found: /bar</h1>}
181
+ end
182
+
183
+ should 'not modify the response with its return value' do
184
+ app = new_app{
185
+ get('/foo'){ body 'cool' }
186
+ get { 'Hello World!' }
187
+ }
188
+
189
+ status, _, body = get('/foo', app)
190
+ status.should.eq 200
191
+ body .should.eq ['cool']
192
+ end
193
+
194
+ should 'modify the response with halt' do
195
+ app = new_app{
196
+ get('/foo'){ 'should not be returned' }
197
+ get{ halt [302, {}, ['Hi']] }
198
+ }
199
+
200
+ status, _, body = get('/foo', app)
201
+ status.should.eq 302
202
+ body .should.eq ['Hi']
203
+ end
204
+
205
+ should 'take an optional route pattern' do
206
+ ran_filter = false
207
+ app = new_app{
208
+ get('/foo') {}
209
+ get('/bar') {}
210
+ get(%r{^/b}){ ran_filter = true }
211
+ }
212
+ get('/foo', app)
213
+ ran_filter.should.eq false
214
+ get('/bar', app)
215
+ ran_filter.should.eq true
216
+ end
217
+ end