~ubuntu-branches/ubuntu/oneiric/puppet/oneiric-security

« back to all changes in this revision

Viewing changes to spec/unit/network/http/mongrel/rest.rb

  • Committer: Bazaar Package Importer
  • Author(s): Micah Anderson
  • Date: 2008-07-26 15:43:45 UTC
  • mto: (3.1.1 lenny) (1.3.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 16.
  • Revision ID: james.westby@ubuntu.com-20080726154345-1fmgo76b4l72ulvc
ImportĀ upstreamĀ versionĀ 0.24.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env ruby
 
2
 
 
3
require File.dirname(__FILE__) + '/../../../../spec_helper'
 
4
require 'puppet/network/http'
 
5
 
 
6
describe "Puppet::Network::HTTP::MongrelREST", "when initializing" do
 
7
    confine "Mongrel is not available" => Puppet.features.mongrel?
 
8
 
 
9
    before do
 
10
        require 'puppet/network/http/mongrel/rest'
 
11
 
 
12
        @mock_mongrel = mock('Mongrel server')
 
13
        @mock_mongrel.stubs(:register)
 
14
        @mock_model = mock('indirected model')
 
15
        Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@mock_model)
 
16
        @params = { :server => @mock_mongrel, :handler => :foo }
 
17
    end
 
18
 
 
19
    it "should require access to a Mongrel server" do
 
20
        Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params.delete_if {|k,v| :server == k })}.should raise_error(ArgumentError)
 
21
    end
 
22
 
 
23
    it "should require an indirection name" do
 
24
        Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params.delete_if {|k,v| :handler == k })}.should raise_error(ArgumentError)        
 
25
    end
 
26
 
 
27
    it "should look up the indirection model from the indirection name" do
 
28
        Puppet::Indirector::Indirection.expects(:model).with(:foo).returns(@mock_model)
 
29
        Puppet::Network::HTTP::MongrelREST.new(@params)
 
30
    end
 
31
 
 
32
    it "should fail if the indirection is not known" do
 
33
        Puppet::Indirector::Indirection.expects(:model).with(:foo).returns(nil)
 
34
        Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params) }.should raise_error(ArgumentError)
 
35
    end
 
36
end
 
37
 
 
38
describe "Puppet::Network::HTTP::MongrelREST", "when receiving a request" do
 
39
    confine "Mongrel is not available" => Puppet.features.mongrel?
 
40
 
 
41
    before do
 
42
        @mock_request = stub('mongrel http request')
 
43
        @mock_head = stub('response head')
 
44
        @mock_body = stub('response body', :write => true)
 
45
        @mock_response = stub('mongrel http response')
 
46
        @mock_response.stubs(:start).yields(@mock_head, @mock_body)
 
47
        @mock_model_class = stub('indirected model class')
 
48
        @mock_mongrel = stub('mongrel http server', :register => true)
 
49
        Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@mock_model_class)
 
50
        @handler = Puppet::Network::HTTP::MongrelREST.new(:server => @mock_mongrel, :handler => :foo)
 
51
    end
 
52
 
 
53
    def setup_find_request(params = {})
 
54
        @mock_request.stubs(:params).returns({  Mongrel::Const::REQUEST_METHOD => 'GET', 
 
55
                                                Mongrel::Const::REQUEST_PATH => '/foo/key',
 
56
                                                'QUERY_STRING' => ''}.merge(params))
 
57
        @mock_model_class.stubs(:find)
 
58
    end
 
59
 
 
60
    def setup_search_request(params = {})
 
61
        @mock_request.stubs(:params).returns({  Mongrel::Const::REQUEST_METHOD => 'GET', 
 
62
                                                Mongrel::Const::REQUEST_PATH => '/foos',
 
63
                                                'QUERY_STRING' => '' }.merge(params))
 
64
        @mock_model_class.stubs(:search).returns([])        
 
65
    end
 
66
 
 
67
    def setup_destroy_request(params = {})
 
68
        @mock_request.stubs(:params).returns({  Mongrel::Const::REQUEST_METHOD => 'DELETE', 
 
69
                                                Mongrel::Const::REQUEST_PATH => '/foo/key',
 
70
                                                'QUERY_STRING' => '' }.merge(params))
 
71
        @mock_model_class.stubs(:destroy)
 
72
    end
 
73
 
 
74
    def setup_save_request(params = {})
 
75
        @mock_request.stubs(:params).returns({  Mongrel::Const::REQUEST_METHOD => 'PUT', 
 
76
                                                Mongrel::Const::REQUEST_PATH => '/foo',
 
77
                                                'QUERY_STRING' => '' }.merge(params))
 
78
        @mock_request.stubs(:body).returns('this is a fake request body')
 
79
        @mock_model_instance = stub('indirected model instance', :save => true)
 
80
        @mock_model_class.stubs(:from_yaml).returns(@mock_model_instance)
 
81
    end
 
82
 
 
83
    def setup_bad_request
 
84
        @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'POST', Mongrel::Const::REQUEST_PATH => '/foos'})        
 
85
    end
 
86
 
 
87
    it "should call the model find method if the request represents a singular HTTP GET" do
 
88
        setup_find_request
 
89
        @mock_model_class.expects(:find).with { |key, args| key == 'key' }
 
90
        @handler.process(@mock_request, @mock_response)
 
91
    end
 
92
 
 
93
    it "should call the model search method if the request represents a plural HTTP GET" do
 
94
        setup_search_request
 
95
        @mock_model_class.expects(:search).returns([])
 
96
        @handler.process(@mock_request, @mock_response)
 
97
    end
 
98
 
 
99
    it "should call the model destroy method if the request represents an HTTP DELETE" do
 
100
        setup_destroy_request
 
101
        @mock_model_class.expects(:destroy).with { |key, args| key == 'key' }
 
102
        @handler.process(@mock_request, @mock_response)
 
103
    end
 
104
 
 
105
    it "should call the model save method if the request represents an HTTP PUT" do
 
106
        setup_save_request
 
107
        @mock_model_instance.expects(:save)
 
108
        @handler.process(@mock_request, @mock_response)
 
109
    end
 
110
 
 
111
    it "should fail if the HTTP method isn't supported" do
 
112
        @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'POST', Mongrel::Const::REQUEST_PATH => '/foo'})
 
113
        @mock_response.expects(:start).with(404)
 
114
        @handler.process(@mock_request, @mock_response)
 
115
    end
 
116
 
 
117
    it "should fail if the request's pluralization is wrong" do
 
118
        @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE', Mongrel::Const::REQUEST_PATH => '/foos/key'})
 
119
        @mock_response.expects(:start).with(404)
 
120
        @handler.process(@mock_request, @mock_response)
 
121
 
 
122
        @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT', Mongrel::Const::REQUEST_PATH => '/foos/key'})
 
123
        @mock_response.expects(:start).with(404)
 
124
        @handler.process(@mock_request, @mock_response)
 
125
    end
 
126
 
 
127
    it "should fail if the request is for an unknown path" do
 
128
        @mock_request.stubs(:params).returns({  Mongrel::Const::REQUEST_METHOD => 'GET', 
 
129
                                                Mongrel::Const::REQUEST_PATH => '/bar/key',
 
130
                                                'QUERY_STRING' => '' })
 
131
        @mock_response.expects(:start).with(404)
 
132
        @handler.process(@mock_request, @mock_response)
 
133
    end
 
134
 
 
135
    describe "and determining the request parameters", :shared => true do
 
136
        confine "Mongrel is not available" => Puppet.features.mongrel?
 
137
 
 
138
        before do
 
139
            @mock_request.stubs(:params).returns({})
 
140
        end
 
141
 
 
142
        it "should include the HTTP request parameters" do
 
143
            @mock_request.expects(:params).returns('QUERY_STRING' => 'foo=baz&bar=xyzzy')
 
144
            result = @handler.params(@mock_request)
 
145
            result["foo"].should == "baz"
 
146
            result["bar"].should == "xyzzy"
 
147
        end
 
148
 
 
149
        it "should pass the client's ip address to model find" do
 
150
            @mock_request.stubs(:params).returns("REMOTE_ADDR" => "ipaddress")
 
151
            @handler.params(@mock_request)[:ip].should == "ipaddress"
 
152
        end
 
153
 
 
154
        it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do
 
155
            Puppet.settings.stubs(:value).returns "eh"
 
156
            Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
 
157
            @mock_request.stubs(:params).returns("myheader" => "/CN=host.domain.com")
 
158
            @handler.params(@mock_request)
 
159
        end
 
160
 
 
161
        it "should retrieve the hostname by matching the certificate parameter" do
 
162
            Puppet.settings.stubs(:value).returns "eh"
 
163
            Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
 
164
            @mock_request.stubs(:params).returns("myheader" => "/CN=host.domain.com")
 
165
            @handler.params(@mock_request)[:node].should == "host.domain.com"
 
166
        end
 
167
 
 
168
        it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do
 
169
            Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
 
170
            Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader"
 
171
            @mock_request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com")
 
172
            @handler.params(@mock_request)
 
173
        end
 
174
 
 
175
        it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do
 
176
            Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
 
177
            Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
 
178
            @mock_request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com")
 
179
            @handler.params(@mock_request)[:authenticated].should be_true
 
180
        end
 
181
 
 
182
        it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do
 
183
            Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
 
184
            Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
 
185
            @mock_request.stubs(:params).returns("myheader" => "whatever", "certheader" => "/CN=host.domain.com")
 
186
            @handler.params(@mock_request)[:authenticated].should be_false
 
187
        end
 
188
 
 
189
        it "should consider the host unauthenticated if no certificate information is present" do
 
190
            Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
 
191
            Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
 
192
            @mock_request.stubs(:params).returns("myheader" => nil, "certheader" => "SUCCESS")
 
193
            @handler.params(@mock_request)[:authenticated].should be_false
 
194
        end
 
195
 
 
196
        it "should not pass a node name to model method if no certificate information is present" do
 
197
            Puppet.settings.stubs(:value).returns "eh"
 
198
            Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
 
199
            @mock_request.stubs(:params).returns("myheader" => nil)
 
200
            @handler.params(@mock_request).should_not be_include(:node)
 
201
        end
 
202
    end
 
203
 
 
204
    describe "when finding a model instance" do |variable|
 
205
        confine "Mongrel is not available" => Puppet.features.mongrel?
 
206
 
 
207
        it "should fail to find model if key is not specified" do
 
208
            @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET', Mongrel::Const::REQUEST_PATH => '/foo'})
 
209
            @mock_response.expects(:start).with(404)
 
210
            @handler.process(@mock_request, @mock_response)
 
211
        end
 
212
 
 
213
        it "should use a common method for determining the request parameters" do
 
214
            setup_find_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
 
215
            @handler.expects(:params).returns(:foo => :baz, :bar => :xyzzy)
 
216
            @mock_model_class.expects(:find).with do |key, args|
 
217
                args[:foo] == :baz and args[:bar] == :xyzzy
 
218
            end
 
219
            @handler.process(@mock_request, @mock_response)
 
220
        end
 
221
 
 
222
        it "should generate a 200 response when a model find call succeeds" do
 
223
            setup_find_request
 
224
            @mock_response.expects(:start).with(200)
 
225
            @handler.process(@mock_request, @mock_response)
 
226
        end
 
227
 
 
228
        it "should return a serialized object when a model find call succeeds" do
 
229
            setup_find_request
 
230
            @mock_model_instance = stub('model instance')
 
231
            @mock_model_instance.expects(:to_yaml)
 
232
            @mock_model_class.stubs(:find).returns(@mock_model_instance)
 
233
            @handler.process(@mock_request, @mock_response)                  
 
234
        end
 
235
 
 
236
        it "should serialize a controller exception when an exception is thrown by find" do
 
237
           setup_find_request
 
238
           @mock_model_class.expects(:find).raises(ArgumentError) 
 
239
           @mock_response.expects(:start).with(404)
 
240
           @handler.process(@mock_request, @mock_response)        
 
241
        end
 
242
    end
 
243
 
 
244
    describe "when destroying a model instance" do |variable|
 
245
        confine "Mongrel is not available" => Puppet.features.mongrel?
 
246
 
 
247
        it "should fail to destroy model if key is not specified" do
 
248
            @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE', Mongrel::Const::REQUEST_PATH => '/foo'})
 
249
            @mock_response.expects(:start).with(404)
 
250
            @handler.process(@mock_request, @mock_response)
 
251
        end
 
252
 
 
253
        it "should use a common method for determining the request parameters" do
 
254
            setup_destroy_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
 
255
            @handler.expects(:params).returns(:foo => :baz, :bar => :xyzzy)
 
256
            @mock_model_class.expects(:destroy).with do |key, args|
 
257
                args[:foo] == :baz and args[:bar] == :xyzzy
 
258
            end
 
259
            @handler.process(@mock_request, @mock_response)
 
260
        end
 
261
 
 
262
        it "should pass HTTP request parameters to model destroy" do
 
263
            setup_destroy_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
 
264
            @mock_model_class.expects(:destroy).with do |key, args|
 
265
                key == 'key' and args['foo'] == 'baz' and args['bar'] == 'xyzzy'
 
266
            end
 
267
            @handler.process(@mock_request, @mock_response)
 
268
        end
 
269
 
 
270
        it "should generate a 200 response when a model destroy call succeeds" do
 
271
            setup_destroy_request
 
272
            @mock_response.expects(:start).with(200)
 
273
            @handler.process(@mock_request, @mock_response)
 
274
        end
 
275
 
 
276
        it "should return a serialized success result when a model destroy call succeeds" do
 
277
            setup_destroy_request
 
278
            @mock_model_class.stubs(:destroy).returns(true)
 
279
            @mock_body.expects(:write).with("--- true\n")
 
280
            @handler.process(@mock_request, @mock_response)
 
281
        end
 
282
 
 
283
        it "should serialize a controller exception when an exception is thrown by destroy" do
 
284
            setup_destroy_request
 
285
            @mock_model_class.expects(:destroy).raises(ArgumentError) 
 
286
            @mock_response.expects(:start).with(404)
 
287
            @handler.process(@mock_request, @mock_response)                 
 
288
        end
 
289
    end
 
290
 
 
291
    describe "when saving a model instance" do |variable|    
 
292
        confine "Mongrel is not available" => Puppet.features.mongrel?
 
293
 
 
294
        it "should fail to save model if data is not specified" do
 
295
            @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT', Mongrel::Const::REQUEST_PATH => '/foo'})
 
296
            @mock_request.stubs(:body).returns('')
 
297
            @mock_response.expects(:start).with(404)
 
298
            @handler.process(@mock_request, @mock_response)
 
299
        end
 
300
 
 
301
        it "should use a common method for determining the request parameters" do
 
302
            setup_save_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
 
303
            @handler.expects(:params).returns(:foo => :baz, :bar => :xyzzy)
 
304
            @mock_model_instance.expects(:save).with do |args|
 
305
                args[:foo] == :baz and args[:bar] == :xyzzy
 
306
            end
 
307
            @handler.process(@mock_request, @mock_response)
 
308
        end
 
309
 
 
310
        it "should generate a 200 response when a model save call succeeds" do
 
311
            setup_save_request
 
312
            @mock_response.expects(:start).with(200)
 
313
            @handler.process(@mock_request, @mock_response)
 
314
        end
 
315
 
 
316
        it "should return a serialized object when a model save call succeeds" do
 
317
            setup_save_request
 
318
            @mock_model_instance.stubs(:save).returns(@mock_model_instance)
 
319
            @mock_model_instance.expects(:to_yaml).returns('foo')
 
320
            @handler.process(@mock_request, @mock_response)        
 
321
        end
 
322
 
 
323
        it "should serialize a controller exception when an exception is thrown by save" do
 
324
            setup_save_request
 
325
            @mock_model_instance.expects(:save).raises(ArgumentError) 
 
326
            @mock_response.expects(:start).with(404)
 
327
            @handler.process(@mock_request, @mock_response)                         
 
328
        end
 
329
    end
 
330
 
 
331
    describe "when searching for model instances" do |variable|
 
332
        confine "Mongrel is not available" => Puppet.features.mongrel?
 
333
 
 
334
        it "should use a common method for determining the request parameters" do
 
335
            setup_search_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
 
336
            @handler.expects(:params).returns(:foo => :baz, :bar => :xyzzy)
 
337
            @mock_model_class.expects(:search).with do |args|
 
338
                args[:foo] == :baz and args[:bar] == :xyzzy
 
339
            end
 
340
            @handler.process(@mock_request, @mock_response)
 
341
        end
 
342
 
 
343
        it "should pass HTTP request parameters to model search" do
 
344
            setup_search_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
 
345
            @mock_model_class.expects(:search).with do |args|
 
346
                args['foo'] == 'baz' and args['bar'] == 'xyzzy'
 
347
            end.returns([])
 
348
            @handler.process(@mock_request, @mock_response)
 
349
        end      
 
350
 
 
351
        it "should generate a 200 response when a model search call succeeds" do
 
352
            setup_search_request
 
353
            @mock_response.expects(:start).with(200)
 
354
            @handler.process(@mock_request, @mock_response)
 
355
        end
 
356
 
 
357
        it "should return a list of serialized objects when a model search call succeeds" do
 
358
            setup_search_request
 
359
            mock_matches = [1..5].collect {|i| mock("model instance #{i}", :to_yaml => "model instance #{i}") }
 
360
            @mock_model_class.stubs(:search).returns(mock_matches)
 
361
            @handler.process(@mock_request, @mock_response)                          
 
362
        end
 
363
 
 
364
        it "should serialize a controller exception when an exception is thrown by search" do
 
365
            setup_search_request
 
366
            @mock_model_class.expects(:search).raises(ArgumentError) 
 
367
            @mock_response.expects(:start).with(404)
 
368
            @handler.process(@mock_request, @mock_response)                
 
369
        end
 
370
    end    
 
371
 
 
372
    it "should serialize a controller exception if the request fails" do
 
373
        setup_bad_request     
 
374
        @mock_response.expects(:start).with(404)
 
375
        @handler.process(@mock_request, @mock_response)        
 
376
    end
 
377
end