3
require File.dirname(__FILE__) + '/../../spec_helper'
5
require 'puppet/indirector/indirection'
7
describe "Indirection Delegator", :shared => true do
8
it "should create a request object with the appropriate method name and all of the passed arguments" do
9
request = stub 'request', :node => nil
11
@indirection.expects(:request).with(@method, "mystuff", :one => :two).returns request
13
@terminus.stubs(@method)
15
@indirection.send(@method, "mystuff", :one => :two)
18
it "should let the :select_terminus method choose the terminus using the created request if the :select_terminus method is available" do
19
# Define the method, so our respond_to? hook matches.
21
def select_terminus(request)
25
request = stub 'request', :key => "me", :options => {}
27
@indirection.stubs(:request).returns request
29
@indirection.expects(:select_terminus).with(request).returns :test_terminus
31
@indirection.stubs(:check_authorization)
32
@terminus.expects(@method)
34
@indirection.send(@method, "me")
37
it "should choose the terminus returned by the :terminus_class method if no :select_terminus method is available" do
38
@indirection.expects(:terminus_class).returns :test_terminus
40
@terminus.expects(@method)
42
@indirection.send(@method, "me")
45
it "should let the appropriate terminus perform the lookup" do
46
@terminus.expects(@method).with { |r| r.is_a?(Puppet::Indirector::Request) }
47
@indirection.send(@method, "me")
51
describe "Delegation Authorizer", :shared => true do
53
# So the :respond_to? turns out correctly.
60
it "should not check authorization if a node name is not provided" do
61
@terminus.expects(:authorized?).never
62
@terminus.stubs(@method)
64
# The quotes are necessary here, else it looks like a block.
65
@request.stubs(:options).returns({})
66
@indirection.send(@method, "/my/key")
69
it "should pass the request to the terminus's authorization method" do
70
@terminus.expects(:authorized?).with { |r| r.is_a?(Puppet::Indirector::Request) }.returns(true)
71
@terminus.stubs(@method)
73
@indirection.send(@method, "/my/key", :node => "mynode")
76
it "should fail if authorization returns false" do
77
@terminus.expects(:authorized?).returns(false)
78
@terminus.stubs(@method)
79
proc { @indirection.send(@method, "/my/key", :node => "mynode") }.should raise_error(ArgumentError)
82
it "should continue if authorization returns true" do
83
@terminus.expects(:authorized?).returns(true)
84
@terminus.stubs(@method)
85
@indirection.send(@method, "/my/key", :node => "mynode")
89
describe Puppet::Indirector::Indirection do
90
describe "when initializing" do
91
# (LAK) I've no idea how to test this, really.
92
it "should store a reference to itself before it consumes its options" do
93
proc { @indirection = Puppet::Indirector::Indirection.new(Object.new, :testingness, :not_valid_option) }.should raise_error
94
Puppet::Indirector::Indirection.instance(:testingness).should be_instance_of(Puppet::Indirector::Indirection)
95
Puppet::Indirector::Indirection.instance(:testingness).delete
98
it "should keep a reference to the indirecting model" do
100
@indirection = Puppet::Indirector::Indirection.new(model, :myind)
101
@indirection.model.should equal(model)
104
it "should set the name" do
105
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind)
106
@indirection.name.should == :myind
109
it "should require indirections to have unique names" do
110
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
111
proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError)
114
it "should extend itself with any specified module" do
116
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test, :extend => mod)
117
@indirection.metaclass.included_modules.should include(mod)
121
@indirection.delete if defined? @indirection
125
describe "when an instance" do
127
@terminus_class = mock 'terminus_class'
128
@terminus = mock 'terminus'
129
@terminus_class.stubs(:new).returns(@terminus)
130
@cache = mock 'cache'
131
@cache_class = mock 'cache_class'
132
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class)
133
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class)
135
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
136
@indirection.terminus_class = :test_terminus
138
@instance = stub 'instance', :expiration => nil, :expiration= => nil, :name => "whatever"
141
#@request = stub 'instance', :key => "/my/key", :instance => @instance, :options => {}
142
@request = mock 'instance'
145
it "should allow setting the ttl" do
146
@indirection.ttl = 300
147
@indirection.ttl.should == 300
150
it "should default to the :runinterval setting, converted to an integer, for its ttl" do
151
Puppet.settings.expects(:value).returns "1800"
152
@indirection.ttl.should == 1800
155
it "should calculate the current expiration by adding the TTL to the current time" do
156
@indirection.stubs(:ttl).returns(100)
158
Time.stubs(:now).returns now
159
@indirection.expiration.should == (Time.now + 100)
162
it "should have a method for creating an indirection request instance" do
163
@indirection.should respond_to(:request)
166
describe "creates a request" do
167
it "should create it with its name as the request's indirection name" do
168
Puppet::Indirector::Request.expects(:new).with { |name, *other| @indirection.name == name }
169
@indirection.request(:funtest, "yayness")
172
it "should require a method and key" do
173
Puppet::Indirector::Request.expects(:new).with { |name, method, key, *other| method == :funtest and key == "yayness" }
174
@indirection.request(:funtest, "yayness")
177
it "should support optional arguments" do
178
Puppet::Indirector::Request.expects(:new).with { |name, method, key, other| other == {:one => :two} }
179
@indirection.request(:funtest, "yayness", :one => :two)
182
it "should default to the arguments being nil" do
183
Puppet::Indirector::Request.expects(:new).with { |name, method, key, args| args.nil? }
184
@indirection.request(:funtest, "yayness")
187
it "should return the request" do
188
request = mock 'request'
189
Puppet::Indirector::Request.expects(:new).returns request
190
@indirection.request(:funtest, "yayness").should equal(request)
194
describe "and looking for a model instance" do
195
before { @method = :find }
197
it_should_behave_like "Indirection Delegator"
198
it_should_behave_like "Delegation Authorizer"
200
it "should return the results of the delegation" do
201
@terminus.expects(:find).returns(@instance)
202
@indirection.find("me").should equal(@instance)
205
it "should set the expiration date on any instances without one set" do
206
@terminus.stubs(:find).returns(@instance)
208
@indirection.expects(:expiration).returns :yay
210
@instance.expects(:expiration).returns(nil)
211
@instance.expects(:expiration=).with(:yay)
213
@indirection.find("/my/key")
216
it "should not override an already-set expiration date on returned instances" do
217
@terminus.stubs(:find).returns(@instance)
219
@indirection.expects(:expiration).never
221
@instance.expects(:expiration).returns(:yay)
222
@instance.expects(:expiration=).never
224
@indirection.find("/my/key")
227
describe "when caching is enabled" do
229
@indirection.cache_class = :cache_terminus
230
@cache_class.expects(:new).returns(@cache)
232
@instance.stubs(:expired?).returns false
235
it "should first look in the cache for an instance" do
236
@terminus.stubs(:find).never
237
@cache.expects(:find).returns @instance
239
@indirection.find("/my/key")
242
it "should use a request to look in the cache for cached objects" do
243
@cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns @instance
247
@indirection.find("/my/key")
250
it "should return the cached object if it is not expired" do
251
@instance.stubs(:expired?).returns false
253
@cache.stubs(:find).returns @instance
254
@indirection.find("/my/key").should equal(@instance)
257
it "should send a debug log if it is using the cached object" do
258
Puppet.expects(:debug)
259
@cache.stubs(:find).returns @instance
261
@indirection.find("/my/key")
264
it "should not return the cached object if it is expired" do
265
@instance.stubs(:expired?).returns true
267
@cache.stubs(:find).returns @instance
268
@terminus.stubs(:find).returns nil
269
@indirection.find("/my/key").should be_nil
272
it "should send an info log if it is using the cached object" do
273
Puppet.expects(:info)
274
@instance.stubs(:expired?).returns true
276
@cache.stubs(:find).returns @instance
277
@terminus.stubs(:find).returns nil
278
@indirection.find("/my/key")
281
it "should cache any objects not retrieved from the cache" do
282
@cache.expects(:find).returns nil
284
@terminus.expects(:find).returns(@instance)
285
@cache.expects(:save)
287
@indirection.find("/my/key")
290
it "should use a request to look in the cache for cached objects" do
291
@cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns nil
293
@terminus.stubs(:find).returns(@instance)
296
@indirection.find("/my/key")
299
it "should cache the instance using a request with the instance set to the cached object" do
300
@cache.stubs(:find).returns nil
302
@terminus.stubs(:find).returns(@instance)
304
@cache.expects(:save).with { |r| r.method == :save and r.instance == @instance }
306
@indirection.find("/my/key")
309
it "should send an info log that the object is being cached" do
310
@cache.stubs(:find).returns nil
312
@terminus.stubs(:find).returns(@instance)
315
Puppet.expects(:info)
317
@indirection.find("/my/key")
322
describe "and storing a model instance" do
323
before { @method = :save }
325
it_should_behave_like "Indirection Delegator"
326
it_should_behave_like "Delegation Authorizer"
328
it "should return nil" do
329
@terminus.stubs(:save)
330
@indirection.save(@instance).should be_nil
333
describe "when caching is enabled" do
335
@indirection.cache_class = :cache_terminus
336
@cache_class.expects(:new).returns(@cache)
338
@instance.stubs(:expired?).returns false
341
it "should use a request to save the object to the cache" do
342
request = stub 'request', :instance => @instance, :node => nil
344
@indirection.expects(:request).returns request
346
@cache.expects(:save).with(request)
347
@terminus.stubs(:save)
348
@indirection.save(@instance)
353
describe "and removing a model instance" do
354
before { @method = :destroy }
356
it_should_behave_like "Indirection Delegator"
357
it_should_behave_like "Delegation Authorizer"
359
it "should return the result of removing the instance" do
360
@terminus.stubs(:destroy).returns "yayness"
361
@indirection.destroy("/my/key").should == "yayness"
364
describe "when caching is enabled" do
366
@indirection.cache_class = :cache_terminus
367
@cache_class.expects(:new).returns(@cache)
369
@instance.stubs(:expired?).returns false
372
it "should use a request instance to search in and remove objects from the cache" do
373
destroy = stub 'destroy_request', :key => "/my/key", :node => nil
374
find = stub 'destroy_request', :key => "/my/key", :node => nil
376
@indirection.expects(:request).with(:destroy, "/my/key").returns destroy
377
@indirection.expects(:request).with(:find, "/my/key").returns find
379
cached = mock 'cache'
381
@cache.expects(:find).with(find).returns cached
382
@cache.expects(:destroy).with(destroy)
384
@terminus.stubs(:destroy)
386
@indirection.destroy("/my/key")
391
describe "and searching for multiple model instances" do
392
before { @method = :search }
394
it_should_behave_like "Indirection Delegator"
395
it_should_behave_like "Delegation Authorizer"
397
it "should set the expiration date on any instances without one set" do
398
@terminus.stubs(:search).returns([@instance])
400
@indirection.expects(:expiration).returns :yay
402
@instance.expects(:expiration).returns(nil)
403
@instance.expects(:expiration=).with(:yay)
405
@indirection.search("/my/key")
408
it "should not override an already-set expiration date on returned instances" do
409
@terminus.stubs(:search).returns([@instance])
411
@indirection.expects(:expiration).never
413
@instance.expects(:expiration).returns(:yay)
414
@instance.expects(:expiration=).never
416
@indirection.search("/my/key")
419
it "should return the results of searching in the terminus" do
420
@terminus.expects(:search).returns([@instance])
421
@indirection.search("/my/key").should == [@instance]
425
describe "and expiring a model instance" do
426
describe "when caching is not enabled" do
427
it "should do nothing" do
428
@cache_class.expects(:new).never
430
@indirection.expire("/my/key")
434
describe "when caching is enabled" do
436
@indirection.cache_class = :cache_terminus
437
@cache_class.expects(:new).returns(@cache)
439
@instance.stubs(:expired?).returns false
441
@cached = stub 'cached', :expiration= => nil, :name => "/my/key"
444
it "should use a request to find within the cache" do
445
@cache.expects(:find).with { |r| r.is_a?(Puppet::Indirector::Request) and r.method == :find }
446
@indirection.expire("/my/key")
449
it "should do nothing if no such instance is cached" do
450
@cache.expects(:find).returns nil
452
@indirection.expire("/my/key")
455
it "should log that it is expiring any found instance" do
456
@cache.expects(:find).returns @cached
459
Puppet.expects(:info)
461
@indirection.expire("/my/key")
464
it "should set the cached instance's expiration to a time in the past" do
465
@cache.expects(:find).returns @cached
468
@cached.expects(:expiration=).with { |t| t < Time.now }
470
@indirection.expire("/my/key")
473
it "should save the now expired instance back into the cache" do
474
@cache.expects(:find).returns @cached
476
@cached.expects(:expiration=).with { |t| t < Time.now }
478
@cache.expects(:save)
480
@indirection.expire("/my/key")
483
it "should use a request to save the expired resource to the cache" do
484
@cache.expects(:find).returns @cached
486
@cached.expects(:expiration=).with { |t| t < Time.now }
488
@cache.expects(:save).with { |r| r.is_a?(Puppet::Indirector::Request) and r.instance == @cached and r.method == :save }.returns(@cached)
490
@indirection.expire("/my/key")
497
Puppet::Indirector::Indirection.clear_cache
502
describe "when managing indirection instances" do
503
it "should allow an indirection to be retrieved by name" do
504
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
505
Puppet::Indirector::Indirection.instance(:test).should equal(@indirection)
508
it "should return nil when the named indirection has not been created" do
509
Puppet::Indirector::Indirection.instance(:test).should be_nil
512
it "should allow an indirection's model to be retrieved by name" do
513
mock_model = mock('model')
514
@indirection = Puppet::Indirector::Indirection.new(mock_model, :test)
515
Puppet::Indirector::Indirection.model(:test).should equal(mock_model)
518
it "should return nil when no model matches the requested name" do
519
Puppet::Indirector::Indirection.model(:test).should be_nil
523
@indirection.delete if defined? @indirection
527
describe "when routing to the correct the terminus class" do
529
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
530
@terminus = mock 'terminus'
531
@terminus_class = stub 'terminus class', :new => @terminus
532
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :default).returns(@terminus_class)
535
it "should fail if no terminus class can be picked" do
536
proc { @indirection.terminus_class }.should raise_error(Puppet::DevError)
539
it "should choose the default terminus class if one is specified" do
540
@indirection.terminus_class = :default
541
@indirection.terminus_class.should equal(:default)
544
it "should use the provided Puppet setting if told to do so" do
545
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :my_terminus).returns(mock("terminus_class2"))
546
Puppet.settings.expects(:value).with(:my_setting).returns("my_terminus")
547
@indirection.terminus_setting = :my_setting
548
@indirection.terminus_class.should equal(:my_terminus)
551
it "should fail if the provided terminus class is not valid" do
552
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :nosuchclass).returns(nil)
553
proc { @indirection.terminus_class = :nosuchclass }.should raise_error(ArgumentError)
557
@indirection.delete if defined? @indirection
561
describe "when specifying the terminus class to use" do
563
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
564
@terminus = mock 'terminus'
565
@terminus_class = stub 'terminus class', :new => @terminus
568
it "should allow specification of a terminus type" do
569
@indirection.should respond_to(:terminus_class=)
572
it "should fail to redirect if no terminus type has been specified" do
573
proc { @indirection.find("blah") }.should raise_error(Puppet::DevError)
576
it "should fail when the terminus class name is an empty string" do
577
proc { @indirection.terminus_class = "" }.should raise_error(ArgumentError)
580
it "should fail when the terminus class name is nil" do
581
proc { @indirection.terminus_class = nil }.should raise_error(ArgumentError)
584
it "should fail when the specified terminus class cannot be found" do
585
Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil)
586
proc { @indirection.terminus_class = :foo }.should raise_error(ArgumentError)
589
it "should select the specified terminus class if a terminus class name is provided" do
590
Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class)
591
@indirection.terminus(:foo).should equal(@terminus)
594
it "should use the configured terminus class if no terminus name is specified" do
595
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class)
596
@indirection.terminus_class = :foo
597
@indirection.terminus().should equal(@terminus)
601
@indirection.delete if defined? @indirection
605
describe "when managing terminus instances" do
607
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
608
@terminus = mock 'terminus'
609
@terminus_class = mock 'terminus class'
610
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class)
613
it "should create an instance of the chosen terminus class" do
614
@terminus_class.stubs(:new).returns(@terminus)
615
@indirection.terminus(:foo).should equal(@terminus)
618
it "should allow the clearance of cached terminus instances" do
619
terminus1 = mock 'terminus1'
620
terminus2 = mock 'terminus2'
621
@terminus_class.stubs(:new).returns(terminus1, terminus2, ArgumentError)
622
@indirection.terminus(:foo).should equal(terminus1)
623
@indirection.class.clear_cache
624
@indirection.terminus(:foo).should equal(terminus2)
627
# Make sure it caches the terminus.
628
it "should return the same terminus instance each time for a given name" do
629
@terminus_class.stubs(:new).returns(@terminus)
630
@indirection.terminus(:foo).should equal(@terminus)
631
@indirection.terminus(:foo).should equal(@terminus)
634
it "should not create a terminus instance until one is actually needed" do
635
Puppet::Indirector.expects(:terminus).never
636
indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest)
641
Puppet::Indirector::Indirection.clear_cache
645
describe "when deciding whether to cache" do
647
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
648
@terminus = mock 'terminus'
649
@terminus_class = mock 'terminus class'
650
@terminus_class.stubs(:new).returns(@terminus)
651
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class)
652
@indirection.terminus_class = :foo
655
it "should provide a method for setting the cache terminus class" do
656
@indirection.should respond_to(:cache_class=)
659
it "should fail to cache if no cache type has been specified" do
660
proc { @indirection.cache }.should raise_error(Puppet::DevError)
663
it "should fail to set the cache class when the cache class name is an empty string" do
664
proc { @indirection.cache_class = "" }.should raise_error(ArgumentError)
667
it "should fail to set the cache class when the cache class name is nil" do
668
proc { @indirection.cache_class = nil }.should raise_error(ArgumentError)
671
it "should fail to set the cache class when the specified cache class cannot be found" do
672
Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil)
673
proc { @indirection.cache_class = :foo }.should raise_error(ArgumentError)
678
Puppet::Indirector::Indirection.clear_cache
682
describe "when using a cache" do
684
Puppet.settings.stubs(:value).with("test_terminus").returns("test_terminus")
685
@terminus_class = mock 'terminus_class'
686
@terminus = mock 'terminus'
687
@terminus_class.stubs(:new).returns(@terminus)
688
@cache = mock 'cache'
689
@cache_class = mock 'cache_class'
690
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class)
691
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class)
692
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
693
@indirection.terminus_class = :test_terminus
696
describe "and managing the cache terminus" do
697
it "should not create a cache terminus at initialization" do
698
# This is weird, because all of the code is in the setup. If we got
699
# new() called on the cache class, we'd get an exception here.
702
it "should reuse the cache terminus" do
703
@cache_class.expects(:new).returns(@cache)
704
Puppet.settings.stubs(:value).with("test_cache").returns("cache_terminus")
705
@indirection.cache_class = :cache_terminus
706
@indirection.cache.should equal(@cache)
707
@indirection.cache.should equal(@cache)
710
it "should remove the cache terminus when all other terminus instances are cleared" do
711
cache2 = mock 'cache2'
712
@cache_class.stubs(:new).returns(@cache, cache2)
713
@indirection.cache_class = :cache_terminus
714
@indirection.cache.should equal(@cache)
715
@indirection.clear_cache
716
@indirection.cache.should equal(cache2)
720
describe "and saving" do
723
describe "and finding" do
728
Puppet::Indirector::Indirection.clear_cache