3
require File.dirname(__FILE__) + '/../../../../spec_helper'
4
require 'puppet/network/http'
6
describe "Puppet::Network::HTTP::MongrelREST", "when initializing" do
7
confine "Mongrel is not available" => Puppet.features.mongrel?
10
require 'puppet/network/http/mongrel/rest'
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 }
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)
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)
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)
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)
38
describe "Puppet::Network::HTTP::MongrelREST", "when receiving a request" do
39
confine "Mongrel is not available" => Puppet.features.mongrel?
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)
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)
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([])
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)
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)
84
@mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'POST', Mongrel::Const::REQUEST_PATH => '/foos'})
87
it "should call the model find method if the request represents a singular HTTP GET" do
89
@mock_model_class.expects(:find).with { |key, args| key == 'key' }
90
@handler.process(@mock_request, @mock_response)
93
it "should call the model search method if the request represents a plural HTTP GET" do
95
@mock_model_class.expects(:search).returns([])
96
@handler.process(@mock_request, @mock_response)
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)
105
it "should call the model save method if the request represents an HTTP PUT" do
107
@mock_model_instance.expects(:save)
108
@handler.process(@mock_request, @mock_response)
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)
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)
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)
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)
135
describe "and determining the request parameters", :shared => true do
136
confine "Mongrel is not available" => Puppet.features.mongrel?
139
@mock_request.stubs(:params).returns({})
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"
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"
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)
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"
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)
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
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
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
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)
204
describe "when finding a model instance" do |variable|
205
confine "Mongrel is not available" => Puppet.features.mongrel?
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)
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
219
@handler.process(@mock_request, @mock_response)
222
it "should generate a 200 response when a model find call succeeds" do
224
@mock_response.expects(:start).with(200)
225
@handler.process(@mock_request, @mock_response)
228
it "should return a serialized object when a model find call succeeds" do
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)
236
it "should serialize a controller exception when an exception is thrown by find" do
238
@mock_model_class.expects(:find).raises(ArgumentError)
239
@mock_response.expects(:start).with(404)
240
@handler.process(@mock_request, @mock_response)
244
describe "when destroying a model instance" do |variable|
245
confine "Mongrel is not available" => Puppet.features.mongrel?
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)
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
259
@handler.process(@mock_request, @mock_response)
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'
267
@handler.process(@mock_request, @mock_response)
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)
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)
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)
291
describe "when saving a model instance" do |variable|
292
confine "Mongrel is not available" => Puppet.features.mongrel?
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)
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
307
@handler.process(@mock_request, @mock_response)
310
it "should generate a 200 response when a model save call succeeds" do
312
@mock_response.expects(:start).with(200)
313
@handler.process(@mock_request, @mock_response)
316
it "should return a serialized object when a model save call succeeds" do
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)
323
it "should serialize a controller exception when an exception is thrown by save" do
325
@mock_model_instance.expects(:save).raises(ArgumentError)
326
@mock_response.expects(:start).with(404)
327
@handler.process(@mock_request, @mock_response)
331
describe "when searching for model instances" do |variable|
332
confine "Mongrel is not available" => Puppet.features.mongrel?
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
340
@handler.process(@mock_request, @mock_response)
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'
348
@handler.process(@mock_request, @mock_response)
351
it "should generate a 200 response when a model search call succeeds" do
353
@mock_response.expects(:start).with(200)
354
@handler.process(@mock_request, @mock_response)
357
it "should return a list of serialized objects when a model search call succeeds" do
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)
364
it "should serialize a controller exception when an exception is thrown by search" do
366
@mock_model_class.expects(:search).raises(ArgumentError)
367
@mock_response.expects(:start).with(404)
368
@handler.process(@mock_request, @mock_response)
372
it "should serialize a controller exception if the request fails" do
374
@mock_response.expects(:start).with(404)
375
@handler.process(@mock_request, @mock_response)