strelka 0.0.1pre4 → 0.0.1.pre129

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/History.rdoc +1 -1
  2. data/IDEAS.rdoc +62 -0
  3. data/Manifest.txt +38 -7
  4. data/README.rdoc +124 -5
  5. data/Rakefile +22 -6
  6. data/bin/leash +102 -157
  7. data/contrib/hoetemplate/.autotest.erb +23 -0
  8. data/contrib/hoetemplate/History.rdoc.erb +4 -0
  9. data/contrib/hoetemplate/Manifest.txt.erb +8 -0
  10. data/contrib/hoetemplate/README.rdoc.erb +17 -0
  11. data/contrib/hoetemplate/Rakefile.erb +24 -0
  12. data/contrib/hoetemplate/data/file_name/apps/file_name_app +36 -0
  13. data/contrib/hoetemplate/data/file_name/templates/layout.tmpl.erb +13 -0
  14. data/contrib/hoetemplate/data/file_name/templates/top.tmpl.erb +8 -0
  15. data/contrib/hoetemplate/lib/file_name.rb.erb +18 -0
  16. data/contrib/hoetemplate/spec/file_name_spec.rb.erb +21 -0
  17. data/data/strelka/apps/hello-world +30 -0
  18. data/lib/strelka/app/defaultrouter.rb +49 -30
  19. data/lib/strelka/app/errors.rb +121 -0
  20. data/lib/strelka/app/exclusiverouter.rb +40 -0
  21. data/lib/strelka/app/filters.rb +18 -7
  22. data/lib/strelka/app/negotiation.rb +122 -0
  23. data/lib/strelka/app/parameters.rb +171 -14
  24. data/lib/strelka/app/paramvalidator.rb +751 -0
  25. data/lib/strelka/app/plugins.rb +66 -46
  26. data/lib/strelka/app/restresources.rb +499 -0
  27. data/lib/strelka/app/router.rb +73 -0
  28. data/lib/strelka/app/routing.rb +140 -18
  29. data/lib/strelka/app/templating.rb +12 -3
  30. data/lib/strelka/app.rb +174 -24
  31. data/lib/strelka/constants.rb +0 -20
  32. data/lib/strelka/exceptions.rb +29 -0
  33. data/lib/strelka/httprequest/acceptparams.rb +377 -0
  34. data/lib/strelka/httprequest/negotiation.rb +257 -0
  35. data/lib/strelka/httprequest.rb +155 -7
  36. data/lib/strelka/httpresponse/negotiation.rb +579 -0
  37. data/lib/strelka/httpresponse.rb +140 -0
  38. data/lib/strelka/logging.rb +4 -1
  39. data/lib/strelka/mixins.rb +53 -0
  40. data/lib/strelka.rb +22 -1
  41. data/spec/data/error.tmpl +1 -0
  42. data/spec/lib/constants.rb +0 -1
  43. data/spec/lib/helpers.rb +21 -0
  44. data/spec/strelka/app/defaultrouter_spec.rb +41 -35
  45. data/spec/strelka/app/errors_spec.rb +212 -0
  46. data/spec/strelka/app/exclusiverouter_spec.rb +220 -0
  47. data/spec/strelka/app/filters_spec.rb +196 -0
  48. data/spec/strelka/app/negotiation_spec.rb +73 -0
  49. data/spec/strelka/app/parameters_spec.rb +149 -0
  50. data/spec/strelka/app/paramvalidator_spec.rb +1059 -0
  51. data/spec/strelka/app/plugins_spec.rb +26 -19
  52. data/spec/strelka/app/restresources_spec.rb +393 -0
  53. data/spec/strelka/app/router_spec.rb +63 -0
  54. data/spec/strelka/app/routing_spec.rb +183 -9
  55. data/spec/strelka/app/templating_spec.rb +1 -2
  56. data/spec/strelka/app_spec.rb +265 -32
  57. data/spec/strelka/exceptions_spec.rb +53 -0
  58. data/spec/strelka/httprequest/acceptparams_spec.rb +282 -0
  59. data/spec/strelka/httprequest/negotiation_spec.rb +246 -0
  60. data/spec/strelka/httprequest_spec.rb +204 -14
  61. data/spec/strelka/httpresponse/negotiation_spec.rb +464 -0
  62. data/spec/strelka/httpresponse_spec.rb +114 -0
  63. data/spec/strelka/mixins_spec.rb +99 -0
  64. data.tar.gz.sig +1 -0
  65. metadata +175 -79
  66. metadata.gz.sig +2 -0
  67. data/IDEAS.textile +0 -174
  68. data/data/strelka/apps/strelka-admin +0 -65
  69. data/data/strelka/apps/strelka-setup +0 -26
  70. data/data/strelka/bootstrap-config.rb +0 -34
  71. data/data/strelka/templates/admin/console.tmpl +0 -21
  72. data/data/strelka/templates/layout.tmpl +0 -30
  73. data/lib/strelka/process.rb +0 -19
@@ -25,6 +25,7 @@ describe Strelka::App::Routing do
25
25
 
26
26
  before( :all ) do
27
27
  setup_logging( :fatal )
28
+ @request_factory = Mongrel2::RequestFactory.new( route: '' )
28
29
  end
29
30
 
30
31
  after( :all ) do
@@ -41,6 +42,9 @@ describe Strelka::App::Routing do
41
42
  Strelka.log.debug "Creating a new Strelka::App"
42
43
  @app = Class.new( Strelka::App ) do
43
44
  plugin :routing
45
+ def initialize( appid='params-test', sspec=TEST_SEND_SPEC, rspec=TEST_RECV_SPEC )
46
+ super
47
+ end
44
48
  end
45
49
  Strelka.log.debug " new instance is: %p, routes array: 0x%016x" %
46
50
  [ @app, @app.routes.object_id * 2 ]
@@ -51,6 +55,32 @@ describe Strelka::App::Routing do
51
55
  @app.routes.should be_a( Array )
52
56
  end
53
57
 
58
+ it "knows what its route methods are" do
59
+ @app.route_methods.should == []
60
+ @app.class_eval do
61
+ get() {}
62
+ post( '/clowns' ) {}
63
+ options( '/clowns' ) {}
64
+ end
65
+
66
+ @app.route_methods.should == [ :GET, :POST_clowns, :OPTIONS_clowns ]
67
+ end
68
+
69
+ # OPTIONS GET/HEAD POST PUT DELETE TRACE CONNECT
70
+
71
+ it "can declare a OPTIONS route" do
72
+ @app.routes.should be_empty()
73
+
74
+ @app.class_eval do
75
+ options do |req|
76
+ end
77
+ end
78
+
79
+ @app.routes.should == [
80
+ [ :OPTIONS, [], {action: @app.instance_method(:OPTIONS), options: {}} ]
81
+ ]
82
+ end
83
+
54
84
  it "can declare a GET route" do
55
85
  @app.routes.should be_empty()
56
86
 
@@ -59,9 +89,76 @@ describe Strelka::App::Routing do
59
89
  end
60
90
  end
61
91
 
62
- @app.routes.should == [[ :GET, [], @app.instance_method(:GET), {} ]]
92
+ @app.routes.should == [
93
+ [ :GET, [], {action: @app.instance_method(:GET), options: {}} ]
94
+ ]
95
+ end
96
+
97
+ it "can declare a POST route" do
98
+ @app.routes.should be_empty()
99
+
100
+ @app.class_eval do
101
+ post do |req|
102
+ end
103
+ end
104
+
105
+ @app.routes.should == [
106
+ [ :POST, [], {action: @app.instance_method(:POST), options: {}} ]
107
+ ]
108
+ end
109
+
110
+ it "can declare a PUT route" do
111
+ @app.routes.should be_empty()
112
+
113
+ @app.class_eval do
114
+ put do |req|
115
+ end
116
+ end
117
+
118
+ @app.routes.should == [[ :PUT, [], {action: @app.instance_method(:PUT), options: {}} ]]
119
+ end
120
+
121
+ it "can declare a DELETE route" do
122
+ @app.routes.should be_empty()
123
+
124
+ @app.class_eval do
125
+ delete do |req|
126
+ end
127
+ end
128
+
129
+ @app.routes.should == [
130
+ [ :DELETE, [], {action: @app.instance_method(:DELETE), options: {}} ]
131
+ ]
132
+ end
133
+
134
+ it "can declare a TRACE route" do
135
+ @app.routes.should be_empty()
136
+
137
+ @app.class_eval do
138
+ trace do |req|
139
+ end
140
+ end
141
+
142
+ @app.routes.should == [
143
+ [ :TRACE, [], {action: @app.instance_method(:TRACE), options: {}} ]
144
+ ]
145
+ end
146
+
147
+ it "can declare a CONNECT route" do
148
+ @app.routes.should be_empty()
149
+
150
+ @app.class_eval do
151
+ connect do |req|
152
+ end
153
+ end
154
+
155
+ @app.routes.should == [
156
+ [ :CONNECT, [], {action: @app.instance_method(:CONNECT), options: {}} ]
157
+ ]
63
158
  end
64
159
 
160
+
161
+
65
162
  it "allows a route to specify a path" do
66
163
  @app.routes.should be_empty()
67
164
 
@@ -70,7 +167,9 @@ describe Strelka::App::Routing do
70
167
  end
71
168
  end
72
169
 
73
- @app.routes.should == [[ :GET, ['info'], @app.instance_method(:GET_info), {} ]]
170
+ @app.routes.should == [
171
+ [ :GET, ['info'], {action: @app.instance_method(:GET_info), options: {}} ]
172
+ ]
74
173
  end
75
174
 
76
175
  it "allows a route to omit the leading '/' when specifying a path" do
@@ -79,23 +178,48 @@ describe Strelka::App::Routing do
79
178
  end
80
179
  end
81
180
 
82
- @app.routes.should == [[ :GET, ['info'], @app.instance_method(:GET_info), {} ]]
181
+ @app.routes.should == [
182
+ [ :GET, ['info'], {action: @app.instance_method(:GET_info), options: {}} ]
183
+ ]
83
184
  end
84
185
 
85
186
 
86
187
  it "uses the Strelka::App::DefaultRouter as it's router by default" do
87
- @app.routerclass.should equal( Strelka::App::DefaultRouter )
188
+ @app.routerclass.should == :default
189
+ @app.new.router.should be_a( Strelka::App::DefaultRouter )
88
190
  end
89
191
 
90
192
  it "can specify a different Router class than the default" do
91
- class MyRouter < Strelka::App::DefaultRouter; end
193
+ class MyRouter < Strelka::App::Router; end
92
194
  @app.class_eval do
93
195
  router MyRouter
94
196
  end
95
197
  @app.routerclass.should equal( MyRouter )
198
+ @app.new.router.should be_a( MyRouter )
96
199
  end
97
200
 
98
201
 
202
+ it "has its routes inherited by subclasses" do
203
+ @app.class_eval do
204
+ get( '/info' ) {}
205
+ get( '/about' ) {}
206
+ get( '/origami' ) {}
207
+ end
208
+ subclass = Class.new( @app ) do
209
+ get( '/origami' ) {}
210
+ end
211
+
212
+ subclass.routes.should have( 3 ).members
213
+
214
+ subclass.routes.
215
+ should include([ :GET, ['info'], {action: @app.instance_method(:GET_info), options: {}} ])
216
+ subclass.routes.
217
+ should include([ :GET, ['about'], {action: @app.instance_method(:GET_about), options: {}} ])
218
+ subclass.routes.should include(
219
+ [ :GET, ['origami'], {action: subclass.instance_method(:GET_origami), options: {}} ]
220
+ )
221
+ end
222
+
99
223
  describe "that also uses the :parameters plugin" do
100
224
 
101
225
  before( :each ) do
@@ -109,15 +233,65 @@ describe Strelka::App::Routing do
109
233
  end
110
234
  end
111
235
 
112
- @app.routes.should ==
113
- [[ :POST, ['userinfo', /(?<username>(?i-mx:[a-z]\w+))/],
114
- @app.instance_method(:POST_userinfo__username), {} ]]
236
+ @app.routes.should ==
237
+ [[ :POST, ['userinfo', /(?<username>(?i-mx:[a-z]\w+))/],
238
+ {action: @app.instance_method(:POST_userinfo__username), options: {}} ]]
239
+ end
240
+
241
+ it "unbinds parameter patterns bound with ^ and $ for the route" do
242
+ @app.class_eval do
243
+ param :username, /^[a-z]\w+$/i
244
+ post '/userinfo/:username' do |req|
245
+ end
246
+ end
247
+
248
+ @app.routes.should ==
249
+ [[ :POST, ['userinfo', /(?<username>(?i-mx:[a-z]\w+))/],
250
+ {action: @app.instance_method(:POST_userinfo__username), options: {}} ]]
251
+ end
252
+
253
+ it "unbinds parameter patterns bound with \\A and \\z for the route" do
254
+ @app.class_eval do
255
+ param :username, /\A[a-z]\w+\z/i
256
+ post '/userinfo/:username' do |req|
257
+ end
258
+ end
259
+
260
+ @app.routes.should ==
261
+ [[ :POST, ['userinfo', /(?<username>(?i-mx:[a-z]\w+))/],
262
+ {action: @app.instance_method(:POST_userinfo__username), options: {}} ]]
263
+ end
264
+
265
+ it "unbinds parameter patterns bound with \\Z for the route" do
266
+ @app.class_eval do
267
+ param :username, /\A[a-z]\w+\Z/i
268
+ post '/userinfo/:username' do |req|
269
+ end
270
+ end
271
+
272
+ @app.routes.should ==
273
+ [[ :POST, ['userinfo', /(?<username>(?i-mx:[a-z]\w+))/],
274
+ {action: @app.instance_method(:POST_userinfo__username), options: {}} ]]
275
+ end
276
+
277
+ it "merges parameters from the route path into the request's param validator" do
278
+ @app.class_eval do
279
+ param :username, /\A[a-z]\w+\Z/i
280
+ get '/userinfo/:username' do |req|
281
+ end
282
+ end
283
+
284
+ req = @request_factory.get( '/userinfo/benthik' )
285
+ @app.new.handle( req )
286
+
287
+ req.params[:username].should == 'benthik'
115
288
  end
116
289
 
117
290
 
118
291
  it "raises a ScriptError if a route is defined with a param without it having first " +
119
292
  "been set up" do
120
- # RSpec's expect {},to only rescues RuntimeErrors, so we have to do this ourselves.
293
+ # RSpec's "expect {}.to" construct only rescues RuntimeErrors, so we have to do
294
+ # this ourselves.
121
295
  begin
122
296
  @app.get( '/userinfo/:username' ) {}
123
297
  rescue ScriptError => err
@@ -87,12 +87,11 @@ describe Strelka::App::Templating do
87
87
  @app.class_eval do
88
88
  templates :main => 'main.tmpl'
89
89
  end
90
- @instance = @app.new( 'template-test', 'tcp://127.0.0.1:9999', 'tcp://127.0.0.1:9998' )
91
90
  end
92
91
 
93
92
 
94
93
  it "can load declared templates by mentioning the symbol" do
95
- @instance.template( :main ).should be_a( Inversion::Template )
94
+ @app.new.template( :main ).should be_a( Inversion::Template )
96
95
  end
97
96
 
98
97
  it "can respond with just a template name" do
@@ -25,6 +25,14 @@ describe Strelka::App do
25
25
  before( :all ) do
26
26
  setup_logging( :fatal )
27
27
  @request_factory = Mongrel2::RequestFactory.new( route: '/mail' )
28
+ Mongrel2::Config.db = Mongrel2::Config.in_memory_db
29
+ Mongrel2::Config.init_database
30
+
31
+ # Skip loading the 'strelka' gem, which probably doesn't exist in the right version
32
+ strelkaspec = make_gemspec( 'strelka', '0.0.1', false )
33
+ loaded_specs = Gem.instance_variable_get( :@loaded_specs )
34
+ loaded_specs['strelka'] = strelkaspec
35
+
28
36
  end
29
37
 
30
38
  before( :each ) do
@@ -32,17 +40,119 @@ describe Strelka::App do
32
40
  def initialize( appid=TEST_APPID, sspec=TEST_SEND_SPEC, rspec=TEST_RECV_SPEC )
33
41
  super
34
42
  end
43
+ def set_signal_handlers; end
44
+ def start_accepting_requests; end
45
+ def restore_signal_handlers; end
35
46
  end
36
47
  @req = @request_factory.get( '/mail/inbox' )
37
48
  end
38
49
 
50
+ after( :each ) do
51
+ @app = nil
52
+ end
53
+
39
54
  after( :all ) do
40
55
  reset_logging()
41
56
  end
42
57
 
43
58
 
44
- it "returns a No Content response by default" do
59
+ #
60
+ # Helpers
61
+ #
62
+
63
+ def make_gemspec( name, version, strelka_dep=true )
64
+ spec = Gem::Specification.new( name, version )
65
+ spec.add_runtime_dependency( 'strelka', '~> 0.0' ) if strelka_dep
66
+ return spec
67
+ end
68
+
69
+
70
+ #
71
+ # Examples
72
+ #
73
+
74
+ it "has a method for loading app class/es from a file" do
75
+ app_file = 'an_app.rb'
76
+ app_path = Pathname( 'an_app.rb' ).expand_path
77
+ app_class = nil
78
+
79
+ Kernel.should_receive( :load ).with( app_path.to_s ).and_return do
80
+ app_class = Class.new( Strelka::App )
81
+ end
82
+ Strelka::App.load( app_file ).should == [ app_class ]
83
+ end
84
+
85
+ it "has a method for discovering installed Strelka app files" do
86
+ specs = {}
87
+ specs[:donkey] = make_gemspec( 'donkey', '1.0.0' )
88
+ specs[:rabbit_old] = make_gemspec( 'rabbit', '1.0.0' )
89
+ specs[:rabbit_new] = make_gemspec( 'rabbit', '1.0.8' )
90
+ specs[:bear] = make_gemspec( 'bear', '1.0.0', false )
91
+ specs[:giraffe] = make_gemspec( 'giraffe', '1.0.0' )
92
+
93
+ expectation = Gem::Specification.should_receive( :each )
94
+ specs.values.each {|spec| expectation.and_yield(spec) }
95
+
96
+ donkey_path = specs[:donkey].full_gem_path
97
+ rabbit_path = specs[:rabbit_new].full_gem_path
98
+ giraffe_path = specs[:giraffe].full_gem_path
99
+
100
+ Dir.should_receive( :glob ).with( "#{giraffe_path}/data/giraffe/{apps,handlers}/**/*" ).
101
+ and_return([ "#{giraffe_path}/data/giraffe/apps/app" ])
102
+ Dir.should_receive( :glob ).with( "#{rabbit_path}/data/rabbit/{apps,handlers}/**/*" ).
103
+ and_return([ "#{rabbit_path}/data/rabbit/apps/subdir/app1.rb",
104
+ "#{rabbit_path}/data/rabbit/apps/subdir/app2.rb" ])
105
+ Dir.should_receive( :glob ).with( "#{donkey_path}/data/donkey/{apps,handlers}/**/*" ).
106
+ and_return([ "#{donkey_path}/data/donkey/apps/app.rb" ])
107
+
108
+ app_paths = Strelka::App.discover_paths
109
+
110
+ # app_paths.should have( 4 ).members
111
+ app_paths.should include(
112
+ 'donkey' => [Pathname("#{donkey_path}/data/donkey/apps/app.rb")],
113
+ 'rabbit' => [Pathname("#{rabbit_path}/data/rabbit/apps/subdir/app1.rb"),
114
+ Pathname("#{rabbit_path}/data/rabbit/apps/subdir/app2.rb")],
115
+ 'giraffe' => [Pathname("#{giraffe_path}/data/giraffe/apps/app")]
116
+ )
117
+ end
118
+
119
+ it "has a method for loading discovered app classes from installed Strelka app files" do
120
+ gemspec = make_gemspec( 'blood-orgy', '0.0.3' )
121
+ Gem::Specification.should_receive( :each ).and_yield( gemspec ).at_least( :once )
122
+
123
+ Dir.should_receive( :glob ).with( "#{gemspec.full_gem_path}/data/blood-orgy/{apps,handlers}/**/*" ).
124
+ and_return([ "#{gemspec.full_gem_path}/data/blood-orgy/apps/kurzweil" ])
125
+
126
+ Kernel.stub( :load ).
127
+ with( "#{gemspec.full_gem_path}/data/blood-orgy/apps/kurzweil" ).
128
+ and_return do
129
+ Class.new( Strelka::App )
130
+ true
131
+ end
132
+
133
+ app_classes = Strelka::App.discover
134
+ app_classes.should have( 1 ).member
135
+ app_classes.first.should be_a( Class )
136
+ app_classes.first.should < Strelka::App
137
+ end
138
+
139
+ it "handles exceptions while loading discovered apps" do
140
+ gemspec = make_gemspec( 'blood-orgy', '0.0.3' )
141
+ Gem::Specification.should_receive( :each ).and_yield( gemspec ).at_least( :once )
142
+
143
+ Dir.should_receive( :glob ).with( "#{gemspec.full_gem_path}/data/blood-orgy/{apps,handlers}/**/*" ).
144
+ and_return([ "#{gemspec.full_gem_path}/data/blood-orgy/apps/kurzweil" ])
145
+
146
+ Kernel.stub( :load ).
147
+ with( "#{gemspec.full_gem_path}/data/blood-orgy/apps/kurzweil" ).
148
+ and_raise( SyntaxError.new("kurzweil:1: syntax error, unexpected coffeeshop philosopher") )
149
+
150
+ app_classes = Strelka::App.discover
151
+ app_classes.should be_empty()
152
+ end
45
153
 
154
+
155
+ it "returns a No Content response by default" do
46
156
  res = @app.new.handle( @req )
47
157
 
48
158
  res.should be_a( Mongrel2::HTTPResponse )
@@ -91,9 +201,30 @@ describe Strelka::App do
91
201
  end
92
202
 
93
203
 
204
+ it "uses the specified content type for error responses" do
205
+ # make an auth plugin that always denies requests
206
+ forbidden_plugin = Module.new do
207
+ extend Strelka::App::Plugin
208
+ def handle_request( r )
209
+ finish_with( HTTP::FORBIDDEN, "You aren't allowed to look at that.",
210
+ :content_type => 'text/html' )
211
+ fail "Shouldn't be reached."
212
+ end
213
+ end
214
+ @app.plugin( forbidden_plugin )
215
+
216
+ res = @app.new.handle( @req )
217
+
218
+ res.should be_a( Mongrel2::HTTPResponse )
219
+ res.status_line.should == 'HTTP/1.1 403 Forbidden'
220
+ res.content_type.should == 'text/html'
221
+ res.body.should == "You aren't allowed to look at that.\n"
222
+ end
223
+
224
+
94
225
  it "provides a declarative for setting the default content type of responses" do
95
226
  @app.class_eval do
96
- default_content_type 'text/css'
227
+ default_type 'text/css'
97
228
  def handle_request( r )
98
229
  r.response.puts( "body { font-family: monospace }" )
99
230
  r.response
@@ -106,54 +237,156 @@ describe Strelka::App do
106
237
  res.content_type.should == 'text/css'
107
238
  end
108
239
 
109
-
110
- it "provides a plugin hook for plugins to manipulate the request before handling it" do
111
- # make a fixup plugin that adds a custom x- header to the request
112
- header_fixup_plugin = Module.new do
113
- extend Strelka::App::Plugin
114
- def fixup_request( r )
115
- r.headers[:x_funted_by] = 'Cragnux/1.1.3'
116
- super
117
- end
240
+ it "doesn't override an explicitly-set content-type header with the default" do
241
+ @app.class_eval do
242
+ default_type 'text/css'
118
243
  def handle_request( r )
119
- res = r.response
120
- res.puts( "Request was funted by %s!" % [r.headers.x_funted_by] )
121
- res.status = HTTP::OK
122
- return res
244
+ r.response.puts( "I lied, I'm actually returning text." )
245
+ r.response.content_type = 'text/plain'
246
+ r.response
123
247
  end
124
248
  end
125
- @app.plugin( header_fixup_plugin )
126
249
 
127
250
  res = @app.new.handle( @req )
128
251
 
129
252
  res.should be_a( Mongrel2::HTTPResponse )
130
- res.status_line.should == 'HTTP/1.1 200 OK'
131
- res.body.should == "Request was funted by Cragnux/1.1.3!\n"
253
+ res.content_type.should == 'text/plain'
132
254
  end
133
255
 
134
256
 
135
- it "provides a plugin hook for plugins to manipulate the response before it's returned to Mongrel2" do
136
- # make a fixup plugin that adds a custom x- header to the response
137
- header_fixup_plugin = Module.new do
138
- extend Strelka::App::Plugin
139
- def fixup_response( req, res )
140
- res.headers.x_funted_by = 'Cragnux/1.1.3'
141
- super
257
+ it "automatically truncates HEAD responses" do
258
+ @app.class_eval do
259
+ default_type 'text/plain'
260
+ def handle_request( r )
261
+ r.response.puts( "Rendered output." )
262
+ r.response
142
263
  end
264
+ end
265
+
266
+ req = @request_factory.head( '/mail/inbox' )
267
+ res = @app.new.handle( req )
268
+
269
+ res.should be_a( Mongrel2::HTTPResponse )
270
+ res.content_type.should == 'text/plain'
271
+ res.body.should be_empty()
272
+ res.headers.content_length.should == "Rendered output.\n".bytesize
273
+ end
274
+
275
+
276
+ it "uses the app's ID constant for the appid if .run is called without one" do
277
+ @app.const_set( :ID, 'testing-app' )
278
+
279
+ Mongrel2::Handler.should_receive( :connection_info_for ).with( 'testing-app' ).
280
+ and_return([ TEST_SEND_SPEC, TEST_RECV_SPEC ])
281
+ Mongrel2::Connection.should_receive( :new ).
282
+ with( 'testing-app', TEST_SEND_SPEC, TEST_RECV_SPEC ).
283
+ and_return( :a_connection )
284
+
285
+ @app.run
286
+ end
287
+
288
+
289
+ it "uses the app's name for the appid if .run is called without one and it has no ID constant" do
290
+ @app.class_eval do
291
+ def self::name; "My::First::Blog" ; end
292
+ end
293
+
294
+ Mongrel2::Handler.should_receive( :connection_info_for ).with( 'my-first-blog' ).
295
+ and_return([ TEST_SEND_SPEC, TEST_RECV_SPEC ])
296
+ Mongrel2::Connection.should_receive( :new ).
297
+ with( 'my-first-blog', TEST_SEND_SPEC, TEST_RECV_SPEC ).
298
+ and_return( :a_connection )
299
+
300
+ @app.run
301
+ end
302
+
303
+
304
+ it "handles uncaught exceptions with a SERVER_ERROR response" do
305
+ @app.class_eval do
143
306
  def handle_request( r )
144
- res = r.response
145
- res.puts( "Funt this" )
146
- res.status = HTTP::OK
147
- return res
307
+ raise "Something went wrong."
148
308
  end
149
309
  end
150
- @app.plugin( header_fixup_plugin )
151
310
 
152
311
  res = @app.new.handle( @req )
153
312
 
154
313
  res.should be_a( Mongrel2::HTTPResponse )
155
- res.status_line.should == 'HTTP/1.1 200 OK'
156
- res.header_data.should =~ %r{X-Funted-By: Cragnux/1.1.3}
314
+ res.status.should == HTTP::SERVER_ERROR
315
+ res.content_type = 'text/plain'
316
+ res.body.should =~ /internal server error/i
317
+ end
318
+
319
+
320
+ describe "process name" do
321
+
322
+ before( :all ) do
323
+ $old_0 = $0
324
+ end
325
+
326
+ after( :all ) do
327
+ $0 = $old_0
328
+ end
329
+
330
+ it "sets the process name to something more interesting than the command line" do
331
+ @app.new.run
332
+
333
+ $0.should =~ /#{@app.inspect}/
334
+ $0.should =~ %r|\{\S+\} tcp://\S+ <-> \S+|
335
+ end
336
+
337
+ end
338
+
339
+ describe "plugin hooks" do
340
+
341
+ it "provides a plugin hook for plugins to manipulate the request before handling it" do
342
+ # make a fixup plugin that adds a custom x- header to the request
343
+ header_fixup_plugin = Module.new do
344
+ extend Strelka::App::Plugin
345
+ def fixup_request( r )
346
+ r.headers[:x_funted_by] = 'Cragnux/1.1.3'
347
+ super
348
+ end
349
+ def handle_request( r )
350
+ res = r.response
351
+ res.puts( "Request was funted by %s!" % [r.headers.x_funted_by] )
352
+ res.status = HTTP::OK
353
+ return res
354
+ end
355
+ end
356
+ @app.plugin( header_fixup_plugin )
357
+
358
+ res = @app.new.handle( @req )
359
+
360
+ res.should be_a( Mongrel2::HTTPResponse )
361
+ res.status_line.should == 'HTTP/1.1 200 OK'
362
+ res.body.should == "Request was funted by Cragnux/1.1.3!\n"
363
+ end
364
+
365
+
366
+ it "provides a plugin hook for plugins to manipulate the response before it's returned to Mongrel2" do
367
+ # make a fixup plugin that adds a custom x- header to the response
368
+ header_fixup_plugin = Module.new do
369
+ extend Strelka::App::Plugin
370
+ def fixup_response( res )
371
+ res.headers.x_funted_by = 'Cragnux/1.1.3'
372
+ super
373
+ end
374
+ def handle_request( r )
375
+ res = r.response
376
+ res.puts( "Funt this" )
377
+ res.status = HTTP::OK
378
+ return res
379
+ end
380
+ end
381
+ @app.plugin( header_fixup_plugin )
382
+
383
+ res = @app.new.handle( @req )
384
+
385
+ res.should be_a( Mongrel2::HTTPResponse )
386
+ res.status_line.should == 'HTTP/1.1 200 OK'
387
+ res.header_data.should =~ %r{X-Funted-By: Cragnux/1.1.3}
388
+ end
389
+
157
390
  end
158
391
 
159
392
  end
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
+ $LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
7
+ }
8
+
9
+ require 'rspec'
10
+ require 'zmq'
11
+ require 'mongrel2'
12
+
13
+ require 'spec/lib/helpers'
14
+
15
+ require 'strelka'
16
+ require 'strelka/exceptions'
17
+
18
+
19
+ #####################################################################
20
+ ### C O N T E X T S
21
+ #####################################################################
22
+
23
+ describe Strelka, "exception classes" do
24
+
25
+ before( :all ) do
26
+ setup_logging( :fatal )
27
+ @request_factory = Mongrel2::RequestFactory.new( route: '/exceptions' )
28
+ end
29
+
30
+ after( :all ) do
31
+ reset_logging()
32
+ end
33
+
34
+
35
+ describe Strelka::RequestError do
36
+
37
+ it "keeps track of the request that had the error" do
38
+ req = @request_factory.get( '/exceptions/spec' )
39
+ exception = nil
40
+
41
+ begin
42
+ raise Strelka::RequestError.new( req, "invalid request" )
43
+ rescue Strelka::RequestError => err
44
+ exception = err
45
+ end
46
+
47
+ exception.request.should == req
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+