puppet 3.2.1 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (78) hide show
  1. data/install.rb +1 -1
  2. data/lib/puppet.rb +11 -0
  3. data/lib/puppet/indirector/report/processor.rb +1 -1
  4. data/lib/puppet/indirector/report/rest.rb +7 -0
  5. data/lib/puppet/indirector/resource/rest.rb +8 -0
  6. data/lib/puppet/indirector/rest.rb +80 -52
  7. data/lib/puppet/indirector/run/rest.rb +6 -0
  8. data/lib/puppet/network/formats.rb +20 -10
  9. data/lib/puppet/network/http/handler.rb +1 -1
  10. data/lib/puppet/node.rb +1 -1
  11. data/lib/puppet/node/facts.rb +23 -4
  12. data/lib/puppet/resource.rb +2 -4
  13. data/lib/puppet/resource/status.rb +28 -0
  14. data/lib/puppet/run.rb +24 -2
  15. data/lib/puppet/status.rb +6 -2
  16. data/lib/puppet/transaction/event.rb +19 -0
  17. data/lib/puppet/transaction/report.rb +40 -0
  18. data/lib/puppet/util/log.rb +19 -0
  19. data/lib/puppet/util/metric.rb +6 -0
  20. data/lib/puppet/util/monkey_patches.rb +0 -15
  21. data/lib/puppet/vendor.rb +55 -0
  22. data/lib/puppet/vendor/load_safe_yaml.rb +1 -0
  23. data/lib/puppet/vendor/require_vendored.rb +5 -0
  24. data/lib/puppet/vendor/safe_yaml/CHANGES.md +104 -0
  25. data/lib/puppet/vendor/safe_yaml/Gemfile +11 -0
  26. data/lib/puppet/vendor/safe_yaml/LICENSE.txt +22 -0
  27. data/lib/puppet/vendor/safe_yaml/README.md +179 -0
  28. data/lib/puppet/vendor/safe_yaml/Rakefile +6 -0
  29. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb +253 -0
  30. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb +34 -0
  31. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/date.rb +27 -0
  32. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/hexadecimal.rb +12 -0
  33. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/sexagesimal.rb +26 -0
  34. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_handler.rb +92 -0
  35. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_resolver.rb +52 -0
  36. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/resolver.rb +94 -0
  37. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/safe_to_ruby_visitor.rb +17 -0
  38. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_hack.rb +36 -0
  39. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_node_monkeypatch.rb +43 -0
  40. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_resolver.rb +38 -0
  41. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform.rb +41 -0
  42. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_boolean.rb +21 -0
  43. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_date.rb +11 -0
  44. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_float.rb +33 -0
  45. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_integer.rb +25 -0
  46. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_nil.rb +18 -0
  47. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_symbol.rb +13 -0
  48. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/transformation_map.rb +47 -0
  49. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/version.rb +3 -0
  50. data/lib/puppet/vendor/safe_yaml/run_specs_all_ruby_versions.sh +21 -0
  51. data/lib/puppet/vendor/safe_yaml/safe_yaml.gemspec +18 -0
  52. data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.2.yaml +2 -0
  53. data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.3.yaml +2 -0
  54. data/lib/puppet/vendor/safe_yaml/spec/psych_resolver_spec.rb +10 -0
  55. data/lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb +250 -0
  56. data/lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb +702 -0
  57. data/lib/puppet/vendor/safe_yaml/spec/spec_helper.rb +18 -0
  58. data/lib/puppet/vendor/safe_yaml/spec/support/exploitable_back_door.rb +29 -0
  59. data/lib/puppet/vendor/safe_yaml/spec/syck_resolver_spec.rb +10 -0
  60. data/lib/puppet/vendor/safe_yaml/spec/transform/base64_spec.rb +11 -0
  61. data/lib/puppet/vendor/safe_yaml/spec/transform/to_date_spec.rb +34 -0
  62. data/lib/puppet/vendor/safe_yaml/spec/transform/to_float_spec.rb +42 -0
  63. data/lib/puppet/vendor/safe_yaml/spec/transform/to_integer_spec.rb +59 -0
  64. data/lib/puppet/vendor/safe_yaml/spec/transform/to_symbol_spec.rb +49 -0
  65. data/lib/puppet/vendor/safe_yaml_patches.rb +9 -0
  66. data/lib/puppet/version.rb +1 -1
  67. data/spec/lib/puppet_spec/matchers.rb +8 -0
  68. data/spec/unit/application/facts_spec.rb +1 -0
  69. data/spec/unit/file_serving/metadata_spec.rb +20 -28
  70. data/spec/unit/indirector/report/rest_spec.rb +41 -0
  71. data/spec/unit/indirector/rest_spec.rb +307 -334
  72. data/spec/unit/network/formats_spec.rb +36 -27
  73. data/spec/unit/network/http/handler_spec.rb +3 -12
  74. data/spec/unit/node_spec.rb +14 -0
  75. data/spec/unit/resource_spec.rb +5 -35
  76. data/spec/unit/run_spec.rb +25 -6
  77. data/spec/unit/status_spec.rb +6 -0
  78. metadata +2566 -2521
@@ -0,0 +1,18 @@
1
+ HERE = File.dirname(__FILE__) unless defined?(HERE)
2
+ ROOT = File.join(HERE, "..") unless defined?(ROOT)
3
+
4
+ $LOAD_PATH << File.join(ROOT, "lib")
5
+ $LOAD_PATH << File.join(HERE, "support")
6
+
7
+ require "yaml"
8
+ if ENV["YAMLER"] && defined?(YAML::ENGINE)
9
+ YAML::ENGINE.yamler = ENV["YAMLER"]
10
+ puts "Running specs in Ruby #{RUBY_VERSION} with '#{YAML::ENGINE.yamler}' YAML engine."
11
+ end
12
+
13
+ require "safe_yaml"
14
+ require "ostruct"
15
+ require "hashie"
16
+ require "heredoc_unindent"
17
+
18
+ require File.join(HERE, "resolver_specs")
@@ -0,0 +1,29 @@
1
+ class ExploitableBackDoor
2
+ def exploited?
3
+ @exploited_through_setter || @exploited_through_init_with || @exploited_through_ivars
4
+ end
5
+
6
+ def exploited_through_setter?
7
+ @exploited_through_setter
8
+ end
9
+
10
+ def exploited_through_init_with?
11
+ @exploited_through_init_with
12
+ end
13
+
14
+ def exploited_through_ivars?
15
+ self.instance_variables.any?
16
+ end
17
+
18
+ def init_with(command)
19
+ # Note: this is how bad this COULD be.
20
+ # system("#{command}")
21
+ @exploited_through_init_with = true
22
+ end
23
+
24
+ def []=(command, arguments)
25
+ # Note: this is how bad this COULD be.
26
+ # system("#{command} #{arguments}")
27
+ @exploited_through_setter = true
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ if SafeYAML::YAML_ENGINE == "syck"
4
+ require "safe_yaml/syck_resolver"
5
+
6
+ describe SafeYAML::SyckResolver do
7
+ include ResolverSpecs
8
+ let(:resolver) { SafeYAML::SyckResolver.new }
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform do
4
+ it "should return the same encoding when decoding Base64" do
5
+ value = "c3VyZS4="
6
+ decoded = SafeYAML::Transform.to_proper_type(value, false, "!binary")
7
+
8
+ decoded.should == "sure."
9
+ decoded.encoding.should == value.encoding if decoded.respond_to?(:encoding)
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform::ToDate do
4
+ it "returns true when the value matches a valid Date" do
5
+ subject.transform?("2013-01-01").should == [true, Date.parse("2013-01-01")]
6
+ end
7
+
8
+ it "returns false when the value does not match a valid Date" do
9
+ subject.transform?("foobar").should be_false
10
+ end
11
+
12
+ it "returns false when the value does not end with a Date" do
13
+ subject.transform?("2013-01-01\nNOT A DATE").should be_false
14
+ end
15
+
16
+ it "returns false when the value does not begin with a Date" do
17
+ subject.transform?("NOT A DATE\n2013-01-01").should be_false
18
+ end
19
+
20
+ it "correctly parses the remaining formats of the YAML spec" do
21
+ equivalent_values = [
22
+ "2001-12-15T02:59:43.1Z", # canonical
23
+ "2001-12-14t21:59:43.10-05:00", # iso8601
24
+ "2001-12-14 21:59:43.10 -5", # space separated
25
+ "2001-12-15 2:59:43.10" # no time zone (Z)
26
+ ]
27
+
28
+ equivalent_values.each do |value|
29
+ success, result = subject.transform?(value)
30
+ success.should be_true
31
+ result.should == Time.utc(2001, 12, 15, 2, 59, 43, 100000)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform::ToFloat do
4
+ it "returns true when the value matches a valid Float" do
5
+ subject.transform?("20.00").should == [true, 20.0]
6
+ end
7
+
8
+ it "returns false when the value does not match a valid Float" do
9
+ subject.transform?("foobar").should be_false
10
+ end
11
+
12
+ it "returns false when the value spans multiple lines" do
13
+ subject.transform?("20.00\nNOT A FLOAT").should be_false
14
+ end
15
+
16
+ it "correctly parses all formats in the YAML spec" do
17
+ # canonical
18
+ subject.transform?("6.8523015e+5").should == [true, 685230.15]
19
+
20
+ # exponentioal
21
+ subject.transform?("685.230_15e+03").should == [true, 685230.15]
22
+
23
+ # fixed
24
+ subject.transform?("685_230.15").should == [true, 685230.15]
25
+
26
+ # sexagesimal
27
+ subject.transform?("190:20:30.15").should == [true, 685230.15]
28
+
29
+ # infinity
30
+ subject.transform?("-.inf").should == [true, (-1.0 / 0.0)]
31
+
32
+ # not a number
33
+ # NOTE: can't use == here since NaN != NaN
34
+ success, result = subject.transform?(".NaN")
35
+ success.should be_true; result.should be_nan
36
+ end
37
+
38
+ # issue 29
39
+ it "returns false for the string '.'" do
40
+ subject.transform?(".").should be_false
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform::ToInteger do
4
+ it "returns true when the value matches a valid Integer" do
5
+ subject.transform?("10").should == [true, 10]
6
+ end
7
+
8
+ it "returns false when the value does not match a valid Integer" do
9
+ subject.transform?("foobar").should be_false
10
+ end
11
+
12
+ it "returns false when the value spans multiple lines" do
13
+ subject.transform?("10\nNOT AN INTEGER").should be_false
14
+ end
15
+
16
+ it "allows commas in the number" do
17
+ subject.transform?("1,000").should == [true, 1000]
18
+ end
19
+
20
+ it "correctly parses numbers in octal format" do
21
+ subject.transform?("010").should == [true, 8]
22
+ end
23
+
24
+ it "correctly parses numbers in hexadecimal format" do
25
+ subject.transform?("0x1FF").should == [true, 511]
26
+ end
27
+
28
+ it "defaults to a string for a number that resembles octal format but is not" do
29
+ subject.transform?("09").should be_false
30
+ end
31
+
32
+ it "correctly parses 0 in decimal" do
33
+ subject.transform?("0").should == [true, 0]
34
+ end
35
+
36
+ it "defaults to a string for a number that resembles hexadecimal format but is not" do
37
+ subject.transform?("0x1G").should be_false
38
+ end
39
+
40
+ it "correctly parses all formats in the YAML spec" do
41
+ # canonical
42
+ subject.transform?("685230").should == [true, 685230]
43
+
44
+ # decimal
45
+ subject.transform?("+685_230").should == [true, 685230]
46
+
47
+ # octal
48
+ subject.transform?("02472256").should == [true, 685230]
49
+
50
+ # hexadecimal:
51
+ subject.transform?("0x_0A_74_AE").should == [true, 685230]
52
+
53
+ # binary
54
+ subject.transform?("0b1010_0111_0100_1010_1110").should == [true, 685230]
55
+
56
+ # sexagesimal
57
+ subject.transform?("190:20:30").should == [true, 685230]
58
+ end
59
+ end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform::ToSymbol do
4
+ def with_symbol_deserialization_value(value)
5
+ symbol_deserialization_flag = SafeYAML::OPTIONS[:deserialize_symbols]
6
+ SafeYAML::OPTIONS[:deserialize_symbols] = value
7
+
8
+ yield
9
+
10
+ ensure
11
+ SafeYAML::OPTIONS[:deserialize_symbols] = symbol_deserialization_flag
12
+ end
13
+
14
+ def with_symbol_deserialization(&block)
15
+ with_symbol_deserialization_value(true, &block)
16
+ end
17
+
18
+ def without_symbol_deserialization(&block)
19
+ with_symbol_deserialization_value(false, &block)
20
+ end
21
+
22
+ it "returns true when the value matches a valid Symbol" do
23
+ with_symbol_deserialization { subject.transform?(":foo")[0].should be_true }
24
+ end
25
+
26
+ it "returns true when the value matches a valid String+Symbol" do
27
+ with_symbol_deserialization { subject.transform?(':"foo"')[0].should be_true }
28
+ end
29
+
30
+ it "returns false when symbol deserialization is disabled" do
31
+ without_symbol_deserialization { subject.transform?(":foo").should be_false }
32
+ end
33
+
34
+ it "returns false when the value does not match a valid Symbol" do
35
+ with_symbol_deserialization { subject.transform?("foo").should be_false }
36
+ end
37
+
38
+ it "returns false when the symbol does not begin the line" do
39
+ with_symbol_deserialization do
40
+ subject.transform?("NOT A SYMBOL\n:foo").should be_false
41
+ end
42
+ end
43
+
44
+ it "returns false when the symbol does not end the line" do
45
+ with_symbol_deserialization do
46
+ subject.transform?(":foo\nNOT A SYMBOL").should be_false
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ module SafeYAML
2
+ class Parse
3
+ class Date
4
+ def self.value(value)
5
+ Time.parse(value)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -7,7 +7,7 @@
7
7
 
8
8
 
9
9
  module Puppet
10
- PUPPETVERSION = '3.2.1'
10
+ PUPPETVERSION = '3.2.2'
11
11
 
12
12
  ##
13
13
  # version is a public API method intended to always provide a fast and
@@ -95,3 +95,11 @@ end
95
95
  def have_printed(what)
96
96
  HavePrintedMatcher.new(what)
97
97
  end
98
+
99
+ RSpec::Matchers.define :equal_attributes_of do |expected|
100
+ match do |actual|
101
+ actual.instance_variables.all? do |attr|
102
+ actual.instance_variable_get(attr) == expected.instance_variable_get(attr)
103
+ end
104
+ end
105
+ end
@@ -17,6 +17,7 @@ describe Puppet::Application::Facts do
17
17
 
18
18
  it "should return facts if a key is given to find" do
19
19
  Puppet::Node::Facts.indirection.reset_terminus_class
20
+ Puppet::Node::Facts.indirection.expects(:find).returns(Puppet::Node::Facts.new('whatever', {}))
20
21
  subject.command_line.stubs(:args).returns %w{find whatever --render-as yaml}
21
22
 
22
23
  expect {
@@ -33,14 +33,6 @@ describe Puppet::FileServing::Metadata do
33
33
  describe "when serializing" do
34
34
  let(:metadata) { Puppet::FileServing::Metadata.new(foobar) }
35
35
 
36
- it "should perform pson serialization by calling to_pson on it's pson_data_hash" do
37
- pdh = mock "data hash"
38
- pdh_as_pson = mock "data as pson"
39
- metadata.expects(:to_pson_data_hash).returns pdh
40
- pdh.expects(:to_pson).returns pdh_as_pson
41
- metadata.to_pson.should == pdh_as_pson
42
- end
43
-
44
36
  it "should serialize as FileMetadata" do
45
37
  metadata.to_pson_data_hash['document_type'].should == "FileMetadata"
46
38
  end
@@ -49,48 +41,48 @@ describe Puppet::FileServing::Metadata do
49
41
  metadata.to_pson_data_hash['data'].keys.sort.should == %w{ path relative_path links owner group mode checksum type destination }.sort
50
42
  end
51
43
 
52
- it "should pass the path in the hash verbatum" do
53
- metadata.to_pson_data_hash['data']['path'] == metadata.path
44
+ it "should pass the path in the hash verbatim" do
45
+ metadata.to_pson_data_hash['data']['path'].should == metadata.path
54
46
  end
55
47
 
56
- it "should pass the relative_path in the hash verbatum" do
57
- metadata.to_pson_data_hash['data']['relative_path'] == metadata.relative_path
48
+ it "should pass the relative_path in the hash verbatim" do
49
+ metadata.to_pson_data_hash['data']['relative_path'].should == metadata.relative_path
58
50
  end
59
51
 
60
- it "should pass the links in the hash verbatum" do
61
- metadata.to_pson_data_hash['data']['links'] == metadata.links
52
+ it "should pass the links in the hash verbatim" do
53
+ metadata.to_pson_data_hash['data']['links'].should == metadata.links
62
54
  end
63
55
 
64
- it "should pass the path owner in the hash verbatum" do
65
- metadata.to_pson_data_hash['data']['owner'] == metadata.owner
56
+ it "should pass the path owner in the hash verbatim" do
57
+ metadata.to_pson_data_hash['data']['owner'].should == metadata.owner
66
58
  end
67
59
 
68
- it "should pass the group in the hash verbatum" do
69
- metadata.to_pson_data_hash['data']['group'] == metadata.group
60
+ it "should pass the group in the hash verbatim" do
61
+ metadata.to_pson_data_hash['data']['group'].should == metadata.group
70
62
  end
71
63
 
72
- it "should pass the mode in the hash verbatum" do
73
- metadata.to_pson_data_hash['data']['mode'] == metadata.mode
64
+ it "should pass the mode in the hash verbatim" do
65
+ metadata.to_pson_data_hash['data']['mode'].should == metadata.mode
74
66
  end
75
67
 
76
- it "should pass the ftype in the hash verbatum as the 'type'" do
77
- metadata.to_pson_data_hash['data']['type'] == metadata.ftype
68
+ it "should pass the ftype in the hash verbatim as the 'type'" do
69
+ metadata.to_pson_data_hash['data']['type'].should == metadata.ftype
78
70
  end
79
71
 
80
- it "should pass the destination verbatum" do
81
- metadata.to_pson_data_hash['data']['destination'] == metadata.destination
72
+ it "should pass the destination verbatim" do
73
+ metadata.to_pson_data_hash['data']['destination'].should == metadata.destination
82
74
  end
83
75
 
84
76
  it "should pass the checksum in the hash as a nested hash" do
85
77
  metadata.to_pson_data_hash['data']['checksum'].should be_is_a(Hash)
86
78
  end
87
79
 
88
- it "should pass the checksum_type in the hash verbatum as the checksum's type" do
89
- metadata.to_pson_data_hash['data']['checksum']['type'] == metadata.checksum_type
80
+ it "should pass the checksum_type in the hash verbatim as the checksum's type" do
81
+ metadata.to_pson_data_hash['data']['checksum']['type'].should == metadata.checksum_type
90
82
  end
91
83
 
92
- it "should pass the checksum in the hash verbatum as the checksum's value" do
93
- metadata.to_pson_data_hash['data']['checksum']['value'] == metadata.checksum
84
+ it "should pass the checksum in the hash verbatim as the checksum's value" do
85
+ metadata.to_pson_data_hash['data']['checksum']['value'].should == metadata.checksum
94
86
  end
95
87
 
96
88
  end
@@ -22,4 +22,45 @@ describe Puppet::Transaction::Report::Rest do
22
22
  it "should use the :report SRV service" do
23
23
  Puppet::Transaction::Report::Rest.srv_service.should == :report
24
24
  end
25
+
26
+ let(:model) { Puppet::Transaction::Report }
27
+ let(:terminus_class) { Puppet::Transaction::Report::Rest }
28
+ let(:terminus) { model.indirection.terminus(:rest) }
29
+ let(:indirection) { model.indirection }
30
+
31
+ before(:each) do
32
+ Puppet::Transaction::Report.indirection.terminus_class = :rest
33
+ end
34
+
35
+ def mock_response(code, body, content_type='text/plain', encoding=nil)
36
+ obj = stub('http 200 ok', :code => code.to_s, :body => body)
37
+ obj.stubs(:[]).with('content-type').returns(content_type)
38
+ obj.stubs(:[]).with('content-encoding').returns(encoding)
39
+ obj
40
+ end
41
+
42
+ def save_request(key, instance, options={})
43
+ Puppet::Indirector::Request.new(:report, :find, key, instance, options)
44
+ end
45
+
46
+ describe "#save" do
47
+ let(:http_method) { :put }
48
+ let(:response) { mock_response(200, 'body') }
49
+ let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) }
50
+ let(:instance) { model.new('the thing', 'some contents') }
51
+ let(:request) { save_request(instance.name, instance) }
52
+
53
+ before :each do
54
+ terminus.stubs(:network).returns(connection)
55
+ end
56
+
57
+ it "deserializes the response as an array of report processor names" do
58
+ processors = ["store", "http"]
59
+ body = YAML.dump(processors)
60
+ response = mock_response('200', body, 'text/yaml')
61
+ connection.expects(:put).returns response
62
+
63
+ terminus.save(request).should == ["store", "http"]
64
+ end
65
+ end
25
66
  end
@@ -1,57 +1,140 @@
1
1
  #! /usr/bin/env ruby
2
2
  require 'spec_helper'
3
+ require 'puppet/indirector'
4
+ require 'puppet/indirector/errors'
3
5
  require 'puppet/indirector/rest'
4
6
 
5
- shared_examples_for "a REST http call" do
6
- it "should accept a path" do
7
- lambda { @search.send(@method, *@arguments) }.should_not raise_error(ArgumentError)
8
- end
7
+ # Just one from each category since the code makes no real distinctions
8
+ HTTP_ERROR_CODES = [300, 400, 500]
9
+ shared_examples_for "a REST terminus method" do |terminus_method|
10
+ HTTP_ERROR_CODES.each do |code|
11
+ describe "when the response code is #{code}" do
12
+ let(:response) { mock_response(code, 'error messaged!!!') }
13
+
14
+ it "raises an http error with the body of the response" do
15
+ expect {
16
+ terminus.send(terminus_method, request)
17
+ }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{response.body}")
18
+ end
19
+
20
+ it "does not attempt to deserialize the response" do
21
+ model.expects(:convert_from).never
22
+
23
+ expect {
24
+ terminus.send(terminus_method, request)
25
+ }.to raise_error(Net::HTTPError)
26
+ end
27
+
28
+ # I'm not sure what this means or if it's used
29
+ it "if the body is empty raises an http error with the response header" do
30
+ response.stubs(:body).returns ""
31
+ response.stubs(:message).returns "fhqwhgads"
32
+
33
+ expect {
34
+ terminus.send(terminus_method, request)
35
+ }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{response.message}")
36
+ end
9
37
 
10
- it "should require a path" do
11
- lambda { @searcher.send(@method) }.should raise_error(ArgumentError)
38
+ describe "and the body is compressed" do
39
+ it "raises an http error with the decompressed body of the response" do
40
+ uncompressed_body = "why"
41
+ compressed_body = Zlib::Deflate.deflate(uncompressed_body)
42
+
43
+ response = mock_response(code, compressed_body, 'text/plain', 'deflate')
44
+ connection.expects(http_method).returns(response)
45
+
46
+ expect {
47
+ terminus.send(terminus_method, request)
48
+ }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{uncompressed_body}")
49
+ end
50
+ end
51
+ end
12
52
  end
53
+ end
13
54
 
14
- it "should return the results of deserializing the response to the request" do
15
- conn = mock 'connection'
16
- conn.stubs(:put).returns @response
17
- conn.stubs(:delete).returns @response
18
- conn.stubs(:get).returns @response
19
- Puppet::Network::HttpPool.stubs(:http_instance).returns conn
55
+ shared_examples_for "a deserializing terminus method" do |terminus_method|
56
+ describe "when the response has no content-type" do
57
+ let(:response) { mock_response(200, "body", nil, nil) }
58
+ it "raises an error" do
59
+ expect {
60
+ terminus.send(terminus_method, request)
61
+ }.to raise_error(RuntimeError, "No content type in http response; cannot parse")
62
+ end
63
+ end
20
64
 
21
- @searcher.expects(:deserialize).with(@response).returns "myobject"
65
+ it "doesn't catch errors in deserialization" do
66
+ model.expects(:convert_from).raises(Puppet::Error, "Whoa there")
22
67
 
23
- @searcher.send(@method, *@arguments).should == 'myobject'
68
+ expect { terminus.send(terminus_method, request) }.to raise_error(Puppet::Error, "Whoa there")
24
69
  end
25
70
  end
26
71
 
27
72
  describe Puppet::Indirector::REST do
28
73
  before :all do
29
- Puppet::Indirector::Terminus.stubs(:register_terminus_class)
30
- @model = stub('model', :supported_formats => %w{}, :convert_from => nil)
31
- @instance = stub('model instance', :name= => nil)
32
- @indirection = stub('indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model)
33
- Puppet::Indirector::Indirection.expects(:instance).returns(@indirection)
34
-
35
- module This
36
- module Is
37
- module A
38
- module Test
39
- end
40
- end
74
+ class Puppet::TestModel
75
+ extend Puppet::Indirector
76
+ indirects :test_model
77
+ attr_accessor :name, :data
78
+ def initialize(name = "name", data = '')
79
+ @name = name
80
+ @data = data
81
+ end
82
+
83
+ def self.convert_from(format, string)
84
+ new('', string)
85
+ end
86
+
87
+ def self.convert_from_multiple(format, string)
88
+ string.split(',').collect { |s| convert_from(format, s) }
89
+ end
90
+
91
+ def ==(other)
92
+ other.is_a? Puppet::TestModel and other.name == name and other.data == data
41
93
  end
42
94
  end
43
- @rest_class = class This::Is::A::Test::Class < Puppet::Indirector::REST
44
- self
95
+
96
+ # The subclass must not be all caps even though the superclass is
97
+ class Puppet::TestModel::Rest < Puppet::Indirector::REST
45
98
  end
99
+
100
+ Puppet::TestModel.indirection.terminus_class = :rest
101
+ end
102
+
103
+ after :all do
104
+ # Remove the class, unlinking it from the rest of the system.
105
+ Puppet.send(:remove_const, :TestModel)
106
+ end
107
+
108
+ let(:terminus_class) { Puppet::TestModel::Rest }
109
+ let(:terminus) { Puppet::TestModel.indirection.terminus(:rest) }
110
+ let(:indirection) { Puppet::TestModel.indirection }
111
+ let(:model) { Puppet::TestModel }
112
+
113
+ def mock_response(code, body, content_type='text/plain', encoding=nil)
114
+ obj = stub('http 200 ok', :code => code.to_s, :body => body)
115
+ obj.stubs(:[]).with('content-type').returns(content_type)
116
+ obj.stubs(:[]).with('content-encoding').returns(encoding)
117
+ obj
118
+ end
119
+
120
+ def find_request(key, options={})
121
+ Puppet::Indirector::Request.new(:test_model, :find, key, nil, options)
122
+ end
123
+
124
+ def head_request(key, options={})
125
+ Puppet::Indirector::Request.new(:test_model, :head, key, nil, options)
126
+ end
127
+
128
+ def search_request(key, options={})
129
+ Puppet::Indirector::Request.new(:test_model, :search, key, nil, options)
46
130
  end
47
131
 
48
- before :each do
49
- @response = stub('mock response', :body => 'result', :code => "200")
50
- @response.stubs(:[]).with('content-type').returns "text/plain"
51
- @response.stubs(:[]).with('content-encoding').returns nil
132
+ def delete_request(key, options={})
133
+ Puppet::Indirector::Request.new(:test_model, :destroy, key, nil, options)
134
+ end
52
135
 
53
- @searcher = @rest_class.new
54
- @searcher.stubs(:model).returns @model
136
+ def save_request(key, instance, options={})
137
+ Puppet::Indirector::Request.new(:test_model, :save, key, instance, options)
55
138
  end
56
139
 
57
140
  it "should include the v1 REST API module" do
@@ -59,35 +142,35 @@ describe Puppet::Indirector::REST do
59
142
  end
60
143
 
61
144
  it "should have a method for specifying what setting a subclass should use to retrieve its server" do
62
- @rest_class.should respond_to(:use_server_setting)
145
+ terminus_class.should respond_to(:use_server_setting)
63
146
  end
64
147
 
65
148
  it "should use any specified setting to pick the server" do
66
- @rest_class.expects(:server_setting).returns :inventory_server
149
+ terminus_class.expects(:server_setting).returns :inventory_server
67
150
  Puppet[:inventory_server] = "myserver"
68
- @rest_class.server.should == "myserver"
151
+ terminus_class.server.should == "myserver"
69
152
  end
70
153
 
71
154
  it "should default to :server for the server setting" do
72
- @rest_class.expects(:server_setting).returns nil
155
+ terminus_class.expects(:server_setting).returns nil
73
156
  Puppet[:server] = "myserver"
74
- @rest_class.server.should == "myserver"
157
+ terminus_class.server.should == "myserver"
75
158
  end
76
159
 
77
160
  it "should have a method for specifying what setting a subclass should use to retrieve its port" do
78
- @rest_class.should respond_to(:use_port_setting)
161
+ terminus_class.should respond_to(:use_port_setting)
79
162
  end
80
163
 
81
164
  it "should use any specified setting to pick the port" do
82
- @rest_class.expects(:port_setting).returns :ca_port
165
+ terminus_class.expects(:port_setting).returns :ca_port
83
166
  Puppet[:ca_port] = "321"
84
- @rest_class.port.should == 321
167
+ terminus_class.port.should == 321
85
168
  end
86
169
 
87
170
  it "should default to :port for the port setting" do
88
- @rest_class.expects(:port_setting).returns nil
171
+ terminus_class.expects(:port_setting).returns nil
89
172
  Puppet[:masterport] = "543"
90
- @rest_class.port.should == 543
173
+ terminus_class.port.should == 543
91
174
  end
92
175
 
93
176
 
@@ -95,140 +178,45 @@ describe Puppet::Indirector::REST do
95
178
  Puppet::Indirector::REST.srv_service.should == :puppet
96
179
  end
97
180
 
98
- describe "when deserializing responses" do
99
- it "should return nil if the response code is 404" do
100
- response = mock 'response'
101
- response.expects(:code).returns "404"
102
-
103
- @searcher.deserialize(response).should be_nil
104
- end
105
-
106
- [300,400,403,405,500,501,502,503,504].each { |rc|
107
- describe "when the response code is #{rc}" do
108
- before :each do
109
- @model.expects(:convert_from).never
110
-
111
- @response = mock 'response'
112
- @response.stubs(:code).returns rc.to_s
113
- @response.stubs(:[]).with('content-encoding').returns nil
114
- @response.stubs(:message).returns "There was a problem (header)"
115
- end
116
-
117
- it "should fail" do
118
- @response.stubs(:body).returns nil
119
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError)
120
- end
121
-
122
- it "should take the error message from the body, if present" do
123
- @response.stubs(:body).returns "There was a problem (body)"
124
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (body)")
125
- end
126
-
127
- it "should take the error message from the response header if the body is empty" do
128
- @response.stubs(:body).returns ""
129
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
130
- end
131
-
132
- it "should take the error message from the response header if the body is absent" do
133
- @response.stubs(:body).returns nil
134
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
135
- end
136
-
137
- describe "and with http compression" do
138
- it "should uncompress the body" do
139
- @response.stubs(:body).returns("compressed body")
140
- @searcher.expects(:uncompress_body).with(@response).returns("uncompressed")
141
- lambda { @searcher.deserialize(@response) }.should raise_error { |e| e.message =~ /uncompressed/ }
142
- end
143
- end
144
- end
145
- }
146
-
147
- it "should return the results of converting from the format specified by the content-type header if the response code is in the 200s" do
148
- @model.expects(:convert_from).with("myformat", "mydata").returns "myobject"
149
-
150
- response = mock 'response'
151
- response.stubs(:[]).with("content-type").returns "myformat"
152
- response.stubs(:[]).with("content-encoding").returns nil
153
- response.stubs(:body).returns "mydata"
154
- response.stubs(:code).returns "200"
155
-
156
- @searcher.deserialize(response).should == "myobject"
157
- end
158
-
159
- it "should convert and return multiple instances if the return code is in the 200s and 'multiple' is specified" do
160
- @model.expects(:convert_from_multiple).with("myformat", "mydata").returns "myobjects"
161
-
162
- response = mock 'response'
163
- response.stubs(:[]).with("content-type").returns "myformat"
164
- response.stubs(:[]).with("content-encoding").returns nil
165
- response.stubs(:body).returns "mydata"
166
- response.stubs(:code).returns "200"
167
-
168
- @searcher.deserialize(response, true).should == "myobjects"
169
- end
170
-
171
- it "should strip the content-type header to keep only the mime-type" do
172
- @model.expects(:convert_from).with("text/plain", "mydata").returns "myobject"
173
-
174
- response = mock 'response'
175
- response.stubs(:[]).with("content-type").returns "text/plain; charset=utf-8"
176
- response.stubs(:[]).with("content-encoding").returns nil
177
- response.stubs(:body).returns "mydata"
178
- response.stubs(:code).returns "200"
179
-
180
- @searcher.deserialize(response)
181
- end
182
-
183
- it "should uncompress the body" do
184
- @model.expects(:convert_from).with("myformat", "uncompressed mydata").returns "myobject"
185
-
186
- response = mock 'response'
187
- response.stubs(:[]).with("content-type").returns "myformat"
188
- response.stubs(:body).returns "compressed mydata"
189
- response.stubs(:code).returns "200"
190
-
191
- @searcher.expects(:uncompress_body).with(response).returns("uncompressed mydata")
192
-
193
- @searcher.deserialize(response).should == "myobject"
194
- end
195
- end
196
-
197
181
  describe "when creating an HTTP client" do
198
182
  it "should use the class's server and port if the indirection request provides neither" do
199
183
  @request = stub 'request', :key => "foo", :server => nil, :port => nil
200
- @searcher.class.expects(:port).returns 321
201
- @searcher.class.expects(:server).returns "myserver"
184
+ terminus.class.expects(:port).returns 321
185
+ terminus.class.expects(:server).returns "myserver"
202
186
  Puppet::Network::HTTP::Connection.expects(:new).with("myserver", 321).returns "myconn"
203
- @searcher.network(@request).should == "myconn"
187
+ terminus.network(@request).should == "myconn"
204
188
  end
205
189
 
206
190
  it "should use the server from the indirection request if one is present" do
207
191
  @request = stub 'request', :key => "foo", :server => "myserver", :port => nil
208
- @searcher.class.stubs(:port).returns 321
192
+ terminus.class.stubs(:port).returns 321
209
193
  Puppet::Network::HTTP::Connection.expects(:new).with("myserver", 321).returns "myconn"
210
- @searcher.network(@request).should == "myconn"
194
+ terminus.network(@request).should == "myconn"
211
195
  end
212
196
 
213
197
  it "should use the port from the indirection request if one is present" do
214
198
  @request = stub 'request', :key => "foo", :server => nil, :port => 321
215
- @searcher.class.stubs(:server).returns "myserver"
199
+ terminus.class.stubs(:server).returns "myserver"
216
200
  Puppet::Network::HTTP::Connection.expects(:new).with("myserver", 321).returns "myconn"
217
- @searcher.network(@request).should == "myconn"
201
+ terminus.network(@request).should == "myconn"
218
202
  end
219
203
  end
220
204
 
221
- describe "when doing a find" do
222
- before :each do
223
- @connection = stub('mock http connection', :get => @response, :verify_callback= => nil)
224
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
205
+ describe "#find" do
206
+ let(:http_method) { :get }
207
+ let(:response) { mock_response(200, 'body') }
208
+ let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) }
209
+ let(:request) { find_request('foo') }
225
210
 
226
- # Use a key with spaces, so we can test escaping
227
- @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", nil, :environment => "myenv")
211
+ before :each do
212
+ terminus.stubs(:network).returns(connection)
228
213
  end
229
214
 
230
- describe "with a large body" do
231
- it "should use the POST http method" do
215
+ it_behaves_like 'a REST terminus method', :find
216
+ it_behaves_like 'a deserializing terminus method', :find
217
+
218
+ describe "with a long set of parameters" do
219
+ it "calls post on the connection with the query params in the body" do
232
220
  params = {}
233
221
  'aa'.upto('zz') do |s|
234
222
  params[s] = 'foo'
@@ -239,283 +227,270 @@ describe Puppet::Indirector::REST do
239
227
  # to avoid a failure.
240
228
  params.delete('ip')
241
229
 
242
- @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", nil, params.merge(:environment => "myenv"))
230
+ request = find_request('whoa', params)
243
231
 
244
- @connection.expects(:post).with do |uri, body|
245
- uri == "/myenv/foo/foo%20bar" and body.split("&").sort == params.map {|key,value| "#{key}=#{value}"}.sort
246
- end.returns(@response)
232
+ connection.expects(:post).with do |uri, body|
233
+ body.split("&").sort == params.map {|key,value| "#{key}=#{value}"}.sort
234
+ end.returns(mock_response(200, 'body'))
247
235
 
248
- @searcher.find(@request)
236
+ terminus.find(request)
249
237
  end
250
238
  end
251
239
 
252
- describe "with a small body" do
253
- it "should use the GET http method" do
254
- @searcher.expects(:network).returns @connection
255
- @connection.expects(:get).returns @response
256
- @searcher.find(@request)
240
+ describe "with no parameters" do
241
+ it "calls get on the connection" do
242
+ request = find_request('foo bar')
243
+
244
+ connection.expects(:get).with('/production/test_model/foo%20bar?', anything).returns(mock_response('200', 'response body'))
245
+
246
+ terminus.find(request).should == model.new('foo bar', 'response body')
257
247
  end
258
248
  end
259
249
 
260
- it "should deserialize and return the http response, setting name" do
261
- @connection.expects(:get).returns @response
250
+ it "returns nil on 404" do
251
+ response = mock_response('404', nil)
262
252
 
263
- instance = stub 'object'
264
- instance.expects(:name=)
265
- @searcher.expects(:deserialize).with(@response).returns instance
253
+ connection.expects(:get).returns(response)
266
254
 
267
- @searcher.find(@request).should == instance
255
+ terminus.find(request).should == nil
268
256
  end
269
257
 
270
- it "should deserialize and return the http response, and not require name=" do
271
- @connection.expects(:get).returns @response
258
+ it "asks the model to deserialize the response body and sets the name on the resulting object to the find key" do
259
+ connection.expects(:get).returns response
272
260
 
273
- instance = stub 'object'
274
- @searcher.expects(:deserialize).with(@response).returns instance
261
+ model.expects(:convert_from).with(response['content-type'], response.body).returns(
262
+ model.new('overwritten', 'decoded body')
263
+ )
275
264
 
276
- @searcher.find(@request).should == instance
265
+ terminus.find(request).should == model.new('foo', 'decoded body')
277
266
  end
278
267
 
279
- it "should use the URI generated by the Handler module" do
280
- @connection.expects(:get).with { |path, args| path == "/myenv/foo/foo%20bar?" }.returns(@response)
281
- @searcher.find(@request)
282
- end
268
+ it "doesn't require the model to support name=" do
269
+ connection.expects(:get).returns response
270
+ instance = model.new('name', 'decoded body')
283
271
 
284
- it "should provide an Accept header containing the list of supported formats joined with commas" do
285
- @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
272
+ model.expects(:convert_from).with(response['content-type'], response.body).returns(instance)
273
+ instance.expects(:respond_to?).with(:name=).returns(false)
274
+ instance.expects(:name=).never
286
275
 
287
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
288
- @searcher.find(@request)
276
+ terminus.find(request).should == model.new('name', 'decoded body')
289
277
  end
290
278
 
291
- it "should add Accept-Encoding header" do
292
- @searcher.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"})
279
+ it "provides an Accept header containing the list of supported formats joined with commas" do
280
+ connection.expects(:get).with(anything, has_entry("Accept" => "supported, formats")).returns(response)
293
281
 
294
- @connection.expects(:get).with { |path, args| args["accept-encoding"] == "gzip" }.returns(@response)
295
- @searcher.find(@request)
282
+ terminus.model.expects(:supported_formats).returns %w{supported formats}
283
+ terminus.find(request)
296
284
  end
297
285
 
298
- it "should deserialize and return the network response" do
299
- @searcher.expects(:deserialize).with(@response).returns @instance
300
- @searcher.find(@request).should equal(@instance)
301
- end
286
+ it "adds an Accept-Encoding header" do
287
+ terminus.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"})
302
288
 
303
- it "should set the name of the resulting instance to the asked-for name" do
304
- @searcher.expects(:deserialize).with(@response).returns @instance
305
- @instance.expects(:name=).with "foo bar"
306
- @searcher.find(@request)
307
- end
289
+ connection.expects(:get).with(anything, has_entry("accept-encoding" => "gzip")).returns(response)
308
290
 
309
- it "should generate an error when result data deserializes fails" do
310
- @searcher.expects(:deserialize).raises(ArgumentError)
311
- lambda { @searcher.find(@request) }.should raise_error(ArgumentError)
291
+ terminus.find(request)
312
292
  end
313
- end
314
293
 
315
- describe "when doing a head" do
316
- before :each do
317
- @connection = stub('mock http connection', :head => @response, :verify_callback= => nil)
318
- @searcher.stubs(:network).returns(@connection)
294
+ it "uses only the mime-type from the content-type header when asking the model to deserialize" do
295
+ response = mock_response('200', 'mydata', "text/plain; charset=utf-8")
296
+ connection.expects(:get).returns(response)
297
+
298
+ model.expects(:convert_from).with("text/plain", "mydata").returns "myobject"
319
299
 
320
- # Use a key with spaces, so we can test escaping
321
- @request = Puppet::Indirector::Request.new(:foo, :head, "foo bar", nil)
300
+ terminus.find(request).should == "myobject"
322
301
  end
323
302
 
324
- it "should call the HEAD http method on a network connection" do
325
- @searcher.expects(:network).returns @connection
326
- @connection.expects(:head).returns @response
327
- @searcher.head(@request)
303
+ it "decompresses the body before passing it to the model for deserialization" do
304
+ uncompressed_body = "Why hello there"
305
+ compressed_body = Zlib::Deflate.deflate(uncompressed_body)
306
+
307
+ response = mock_response('200', compressed_body, 'text/plain', 'deflate')
308
+ connection.expects(:get).returns(response)
309
+
310
+ model.expects(:convert_from).with("text/plain", uncompressed_body).returns "myobject"
311
+
312
+ terminus.find(request).should == "myobject"
328
313
  end
314
+ end
329
315
 
330
- it "should return true if there was a successful http response" do
331
- @connection.expects(:head).returns @response
332
- @response.stubs(:code).returns "200"
316
+ describe "#head" do
317
+ let(:http_method) { :head }
318
+ let(:response) { mock_response(200, nil) }
319
+ let(:connection) { stub('mock http connection', :head => response, :verify_callback= => nil) }
320
+ let(:request) { head_request('foo') }
333
321
 
334
- @searcher.head(@request).should == true
322
+ before :each do
323
+ terminus.stubs(:network).returns(connection)
335
324
  end
336
325
 
337
- it "should return false if there was a successful http response" do
338
- @connection.expects(:head).returns @response
339
- @response.stubs(:code).returns "404"
326
+ it_behaves_like 'a REST terminus method', :head
327
+
328
+ it "returns true if there was a successful http response" do
329
+ connection.expects(:head).returns mock_response('200', nil)
340
330
 
341
- @searcher.head(@request).should == false
331
+ terminus.head(request).should == true
342
332
  end
343
333
 
344
- it "should use the URI generated by the Handler module" do
345
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
346
- @connection.expects(:head).with { |path, args| path == "/my/uri" }.returns(@response)
347
- @searcher.head(@request)
334
+ it "returns false on a 404 response" do
335
+ connection.expects(:head).returns mock_response('404', nil)
336
+
337
+ terminus.head(request).should == false
348
338
  end
349
339
  end
350
340
 
351
- describe "when doing a search" do
352
- before :each do
353
- @connection = stub('mock http connection', :get => @response, :verify_callback= => nil)
354
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
355
-
356
- @model.stubs(:convert_from_multiple)
341
+ describe "#search" do
342
+ let(:http_method) { :get }
343
+ let(:response) { mock_response(200, 'data1,data2,data3') }
344
+ let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) }
345
+ let(:request) { search_request('foo') }
357
346
 
358
- @request = Puppet::Indirector::Request.new(:foo, :search, "foo bar", nil)
347
+ before :each do
348
+ terminus.stubs(:network).returns(connection)
359
349
  end
360
350
 
351
+ it_behaves_like 'a REST terminus method', :search
352
+ it_behaves_like 'a deserializing terminus method', :search
353
+
361
354
  it "should call the GET http method on a network connection" do
362
- @searcher.expects(:network).returns @connection
363
- @connection.expects(:get).returns @response
364
- @searcher.search(@request)
355
+ connection.expects(:get).with('/production/test_models/foo', has_key('Accept')).returns mock_response(200, 'data3, data4')
356
+
357
+ terminus.search(request)
365
358
  end
366
359
 
367
- it "should deserialize as multiple instances and return the http response" do
368
- @connection.expects(:get).returns @response
369
- @searcher.expects(:deserialize).with(@response, true).returns "myobject"
360
+ it "returns an empty list on 404" do
361
+ response = mock_response('404', nil)
362
+
363
+ connection.expects(:get).returns(response)
370
364
 
371
- @searcher.search(@request).should == 'myobject'
365
+ terminus.search(request).should == []
372
366
  end
373
367
 
374
- it "should use the URI generated by the Handler module" do
375
- @searcher.expects(:indirection2uri).with(@request).returns "/mys/uri"
376
- @connection.expects(:get).with { |path, args| path == "/mys/uri" }.returns(@response)
377
- @searcher.search(@request)
368
+ it "asks the model to deserialize the response body into multiple instances" do
369
+ terminus.search(request).should == [model.new('', 'data1'), model.new('', 'data2'), model.new('', 'data3')]
378
370
  end
379
371
 
380
372
  it "should provide an Accept header containing the list of supported formats joined with commas" do
381
- @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
373
+ connection.expects(:get).with(anything, has_entry("Accept" => "supported, formats")).returns(mock_response(200, ''))
382
374
 
383
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
384
- @searcher.search(@request)
375
+ terminus.model.expects(:supported_formats).returns %w{supported formats}
376
+ terminus.search(request)
385
377
  end
386
378
 
387
379
  it "should return an empty array if serialization returns nil" do
388
- @model.stubs(:convert_from_multiple).returns nil
380
+ model.stubs(:convert_from_multiple).returns nil
389
381
 
390
- @searcher.search(@request).should == []
391
- end
392
-
393
- it "should generate an error when result data deserializes fails" do
394
- @searcher.expects(:deserialize).raises(ArgumentError)
395
- lambda { @searcher.search(@request) }.should raise_error(ArgumentError)
382
+ terminus.search(request).should == []
396
383
  end
397
384
  end
398
385
 
399
- describe "when doing a destroy" do
400
- before :each do
401
- @connection = stub('mock http connection', :delete => @response, :verify_callback= => nil)
402
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
386
+ describe "#destroy" do
387
+ let(:http_method) { :delete }
388
+ let(:response) { mock_response(200, 'body') }
389
+ let(:connection) { stub('mock http connection', :delete => response, :verify_callback= => nil) }
390
+ let(:request) { delete_request('foo') }
403
391
 
404
- @request = Puppet::Indirector::Request.new(:foo, :destroy, "foo bar", nil)
392
+ before :each do
393
+ terminus.stubs(:network).returns(connection)
405
394
  end
406
395
 
396
+ it_behaves_like 'a REST terminus method', :destroy
397
+ it_behaves_like 'a deserializing terminus method', :destroy
398
+
407
399
  it "should call the DELETE http method on a network connection" do
408
- @searcher.expects(:network).returns @connection
409
- @connection.expects(:delete).returns @response
410
- @searcher.destroy(@request)
400
+ connection.expects(:delete).with('/production/test_model/foo', has_key('Accept')).returns(response)
401
+
402
+ terminus.destroy(request)
411
403
  end
412
404
 
413
405
  it "should fail if any options are provided, since DELETE apparently does not support query options" do
414
- @request.stubs(:options).returns(:one => "two", :three => "four")
406
+ request = delete_request('foo', :one => "two", :three => "four")
415
407
 
416
- lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
408
+ expect { terminus.destroy(request) }.to raise_error(ArgumentError)
417
409
  end
418
410
 
419
411
  it "should deserialize and return the http response" do
420
- @connection.expects(:delete).returns @response
421
- @searcher.expects(:deserialize).with(@response).returns "myobject"
422
-
423
- @searcher.destroy(@request).should == 'myobject'
424
- end
412
+ connection.expects(:delete).returns response
425
413
 
426
- it "should use the URI generated by the Handler module" do
427
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
428
- @connection.expects(:delete).with { |path, args| path == "/my/uri" }.returns(@response)
429
- @searcher.destroy(@request)
414
+ terminus.destroy(request).should == model.new('', 'body')
430
415
  end
431
416
 
432
- it "should not include the query string" do
433
- @connection.stubs(:delete).returns @response
434
- @searcher.destroy(@request)
435
- end
417
+ it "returns nil on 404" do
418
+ response = mock_response('404', nil)
436
419
 
437
- it "should provide an Accept header containing the list of supported formats joined with commas" do
438
- @connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
420
+ connection.expects(:delete).returns(response)
439
421
 
440
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
441
- @searcher.destroy(@request)
422
+ terminus.destroy(request).should == nil
442
423
  end
443
424
 
444
- it "should deserialize and return the network response" do
445
- @searcher.expects(:deserialize).with(@response).returns @instance
446
- @searcher.destroy(@request).should equal(@instance)
447
- end
425
+ it "should provide an Accept header containing the list of supported formats joined with commas" do
426
+ connection.expects(:delete).with(anything, has_entry("Accept" => "supported, formats")).returns(response)
448
427
 
449
- it "should generate an error when result data deserializes fails" do
450
- @searcher.expects(:deserialize).raises(ArgumentError)
451
- lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
428
+ terminus.model.expects(:supported_formats).returns %w{supported formats}
429
+ terminus.destroy(request)
452
430
  end
453
431
  end
454
432
 
455
- describe "when doing a save" do
456
- before :each do
457
- @connection = stub('mock http connection', :put => @response, :verify_callback= => nil)
458
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
433
+ describe "#save" do
434
+ let(:http_method) { :put }
435
+ let(:response) { mock_response(200, 'body') }
436
+ let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) }
437
+ let(:instance) { model.new('the thing', 'some contents') }
438
+ let(:request) { save_request(instance.name, instance) }
459
439
 
460
- @instance = stub 'instance', :render => "mydata", :mime => "mime"
461
- @request = Puppet::Indirector::Request.new(:foo, :save, "foo bar", nil)
462
- @request.instance = @instance
440
+ before :each do
441
+ terminus.stubs(:network).returns(connection)
463
442
  end
464
443
 
465
- it "should call the PUT http method on a network connection" do
466
- @searcher.expects(:network).returns @connection
467
- @connection.expects(:put).returns @response
468
- @searcher.save(@request)
469
- end
444
+ it_behaves_like 'a REST terminus method', :save
470
445
 
471
- it "should fail if any options are provided, since DELETE apparently does not support query options" do
472
- @request.stubs(:options).returns(:one => "two", :three => "four")
446
+ it "should call the PUT http method on a network connection" do
447
+ connection.expects(:put).with('/production/test_model/the%20thing', anything, has_key("Content-Type")).returns response
473
448
 
474
- lambda { @searcher.save(@request) }.should raise_error(ArgumentError)
449
+ terminus.save(request)
475
450
  end
476
451
 
477
- it "should use the URI generated by the Handler module" do
478
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
479
- @connection.expects(:put).with { |path, args| path == "/my/uri" }.returns(@response)
480
- @searcher.save(@request)
452
+ it "should fail if any options are provided, since PUT apparently does not support query options" do
453
+ request = save_request(instance.name, instance, :one => "two", :three => "four")
454
+
455
+ expect { terminus.save(request) }.to raise_error(ArgumentError)
481
456
  end
482
457
 
483
458
  it "should serialize the instance using the default format and pass the result as the body of the request" do
484
- @instance.expects(:render).returns "serial_instance"
485
- @connection.expects(:put).with { |path, data, args| data == "serial_instance" }.returns @response
459
+ instance.expects(:render).returns "serial_instance"
460
+ connection.expects(:put).with(anything, "serial_instance", anything).returns response
486
461
 
487
- @searcher.save(@request)
462
+ terminus.save(request)
488
463
  end
489
464
 
490
- it "should deserialize and return the http response" do
491
- @connection.expects(:put).returns @response
492
- @searcher.expects(:deserialize).with(@response).returns "myobject"
465
+ it "returns nil on 404" do
466
+ response = mock_response('404', nil)
493
467
 
494
- @searcher.save(@request).should == 'myobject'
468
+ connection.expects(:put).returns(response)
469
+
470
+ terminus.save(request).should == nil
495
471
  end
496
472
 
497
- it "should provide an Accept header containing the list of supported formats joined with commas" do
498
- @connection.expects(:put).with { |path, data, args| args["Accept"] == "supported, formats" }.returns(@response)
473
+ it "returns nil" do
474
+ connection.expects(:put).returns response
499
475
 
500
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
501
- @searcher.save(@request)
476
+ terminus.save(request).should be_nil
502
477
  end
503
478
 
504
- it "should provide a Content-Type header containing the mime-type of the sent object" do
505
- @connection.expects(:put).with { |path, data, args| args['Content-Type'] == "mime" }.returns(@response)
479
+ it "should provide an Accept header containing the list of supported formats joined with commas" do
480
+ connection.expects(:put).with(anything, anything, has_entry("Accept" => "supported, formats")).returns(response)
506
481
 
507
- @instance.expects(:mime).returns "mime"
508
- @searcher.save(@request)
509
- end
482
+ instance.expects(:render).returns('')
483
+ model.expects(:supported_formats).returns %w{supported formats}
484
+ instance.expects(:mime).returns "supported"
510
485
 
511
- it "should deserialize and return the network response" do
512
- @searcher.expects(:deserialize).with(@response).returns @instance
513
- @searcher.save(@request).should equal(@instance)
486
+ terminus.save(request)
514
487
  end
515
488
 
516
- it "should generate an error when result data deserializes fails" do
517
- @searcher.expects(:deserialize).raises(ArgumentError)
518
- lambda { @searcher.save(@request) }.should raise_error(ArgumentError)
489
+ it "should provide a Content-Type header containing the mime-type of the sent object" do
490
+ instance.expects(:mime).returns "mime"
491
+ connection.expects(:put).with(anything, anything, has_entry('Content-Type' => "mime")).returns(response)
492
+
493
+ terminus.save(request)
519
494
  end
520
495
  end
521
496
 
@@ -529,13 +504,11 @@ describe Puppet::Indirector::REST do
529
504
  ].each do |method|
530
505
  it "##{method} passes the SRV service, and fall-back server & port to the request's do_request method" do
531
506
  request = Puppet::Indirector::Request.new(:indirection, method, 'key', nil)
532
- stub_response = stub 'response'
533
- stub_response.stubs(:code).returns('200')
534
- @searcher.stubs(:deserialize)
507
+ stub_response = mock_response('200', 'body')
535
508
 
536
- request.expects(:do_request).with(@searcher.class.srv_service, @searcher.class.server, @searcher.class.port).returns(stub_response)
509
+ request.expects(:do_request).with(terminus.class.srv_service, terminus.class.server, terminus.class.port).returns(stub_response)
537
510
 
538
- @searcher.send(method, request)
511
+ terminus.send(method, request)
539
512
  end
540
513
  end
541
514
  end