strelka 0.0.1pre4 → 0.0.1.pre129

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.
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
+