puppet 2.7.21 → 2.7.22

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 (101) hide show
  1. data/CHANGELOG +14 -0
  2. data/Gemfile.lock +2 -2
  3. data/ext/build_defaults.yaml +2 -3
  4. data/ext/debian/control +1 -1
  5. data/ext/packaging/README.md +496 -8
  6. data/ext/packaging/spec/tasks/00_utils_spec.rb +7 -7
  7. data/ext/packaging/spec/tasks/build_object_spec.rb +3 -0
  8. data/ext/packaging/tasks/00_utils.rake +2 -2
  9. data/ext/packaging/tasks/10_setupvars.rake +8 -1
  10. data/ext/packaging/tasks/build.rake +2 -0
  11. data/ext/packaging/tasks/deb_repos.rake +48 -15
  12. data/ext/packaging/tasks/jenkins.rake +30 -2
  13. data/ext/packaging/tasks/mock.rake +3 -2
  14. data/ext/packaging/tasks/pe_remote.rake +1 -1
  15. data/ext/packaging/tasks/pe_ship.rake +4 -5
  16. data/ext/packaging/tasks/pe_sign.rake +8 -0
  17. data/ext/packaging/tasks/pe_sles.rake +8 -7
  18. data/ext/packaging/tasks/pre_tasks.rake +0 -0
  19. data/ext/packaging/tasks/retrieve.rake +11 -1
  20. data/ext/packaging/tasks/rpm_repos.rake +71 -49
  21. data/ext/packaging/tasks/ship.rake +14 -2
  22. data/ext/packaging/tasks/sign.rake +9 -3
  23. data/ext/packaging/tasks/tar.rake +5 -0
  24. data/ext/packaging/tasks/vendor_gems.rake +110 -0
  25. data/install.rb +1 -1
  26. data/lib/puppet.rb +11 -0
  27. data/lib/puppet/indirector/report/processor.rb +1 -1
  28. data/lib/puppet/indirector/report/rest.rb +7 -0
  29. data/lib/puppet/indirector/resource/rest.rb +9 -0
  30. data/lib/puppet/indirector/rest.rb +81 -47
  31. data/lib/puppet/indirector/run/rest.rb +6 -0
  32. data/lib/puppet/network/formats.rb +20 -10
  33. data/lib/puppet/network/http/handler.rb +1 -1
  34. data/lib/puppet/node.rb +25 -0
  35. data/lib/puppet/node/facts.rb +23 -4
  36. data/lib/puppet/resource.rb +2 -4
  37. data/lib/puppet/resource/status.rb +28 -0
  38. data/lib/puppet/run.rb +24 -2
  39. data/lib/puppet/status.rb +6 -2
  40. data/lib/puppet/transaction/event.rb +19 -0
  41. data/lib/puppet/transaction/report.rb +39 -0
  42. data/lib/puppet/util/log.rb +19 -0
  43. data/lib/puppet/util/metric.rb +6 -0
  44. data/lib/puppet/util/monkey_patches.rb +0 -16
  45. data/lib/puppet/vendor.rb +55 -0
  46. data/lib/puppet/vendor/load_safe_yaml.rb +1 -0
  47. data/lib/puppet/vendor/require_vendored.rb +4 -0
  48. data/lib/puppet/vendor/safe_yaml/CHANGES.md +104 -0
  49. data/lib/puppet/vendor/safe_yaml/Gemfile +11 -0
  50. data/lib/puppet/vendor/safe_yaml/LICENSE.txt +22 -0
  51. data/lib/puppet/vendor/safe_yaml/README.md +179 -0
  52. data/lib/puppet/vendor/safe_yaml/Rakefile +6 -0
  53. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb +253 -0
  54. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb +34 -0
  55. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/date.rb +27 -0
  56. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/hexadecimal.rb +12 -0
  57. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/sexagesimal.rb +26 -0
  58. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_handler.rb +92 -0
  59. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_resolver.rb +52 -0
  60. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/resolver.rb +94 -0
  61. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/safe_to_ruby_visitor.rb +17 -0
  62. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_hack.rb +36 -0
  63. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_node_monkeypatch.rb +43 -0
  64. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_resolver.rb +38 -0
  65. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform.rb +41 -0
  66. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_boolean.rb +21 -0
  67. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_date.rb +11 -0
  68. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_float.rb +33 -0
  69. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_integer.rb +25 -0
  70. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_nil.rb +18 -0
  71. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_symbol.rb +13 -0
  72. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/transformation_map.rb +47 -0
  73. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/version.rb +3 -0
  74. data/lib/puppet/vendor/safe_yaml/run_specs_all_ruby_versions.sh +21 -0
  75. data/lib/puppet/vendor/safe_yaml/safe_yaml.gemspec +18 -0
  76. data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.2.yaml +2 -0
  77. data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.3.yaml +2 -0
  78. data/lib/puppet/vendor/safe_yaml/spec/psych_resolver_spec.rb +10 -0
  79. data/lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb +250 -0
  80. data/lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb +702 -0
  81. data/lib/puppet/vendor/safe_yaml/spec/spec_helper.rb +18 -0
  82. data/lib/puppet/vendor/safe_yaml/spec/support/exploitable_back_door.rb +29 -0
  83. data/lib/puppet/vendor/safe_yaml/spec/syck_resolver_spec.rb +10 -0
  84. data/lib/puppet/vendor/safe_yaml/spec/transform/base64_spec.rb +11 -0
  85. data/lib/puppet/vendor/safe_yaml/spec/transform/to_date_spec.rb +34 -0
  86. data/lib/puppet/vendor/safe_yaml/spec/transform/to_float_spec.rb +42 -0
  87. data/lib/puppet/vendor/safe_yaml/spec/transform/to_integer_spec.rb +59 -0
  88. data/lib/puppet/vendor/safe_yaml/spec/transform/to_symbol_spec.rb +49 -0
  89. data/lib/puppet/version.rb +1 -1
  90. data/spec/lib/puppet_spec/matchers.rb +8 -0
  91. data/spec/unit/file_serving/metadata_spec.rb +20 -28
  92. data/spec/unit/indirector/report/rest_spec.rb +41 -0
  93. data/spec/unit/indirector/rest_spec.rb +314 -339
  94. data/spec/unit/network/formats_spec.rb +36 -27
  95. data/spec/unit/network/http/handler_spec.rb +3 -12
  96. data/spec/unit/node_spec.rb +81 -0
  97. data/spec/unit/resource_spec.rb +5 -35
  98. data/spec/unit/run_spec.rb +22 -8
  99. data/spec/unit/status_spec.rb +6 -0
  100. data/test/network/handler/report.rb +0 -36
  101. metadata +148 -102
@@ -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
@@ -6,7 +6,7 @@
6
6
  # Raketasks and such to set the version based on the output of `git describe`
7
7
  #
8
8
  module Puppet
9
- PUPPETVERSION = '2.7.21'
9
+ PUPPETVERSION = '2.7.22'
10
10
 
11
11
  def self.version
12
12
  @puppet_version || PUPPETVERSION
@@ -85,3 +85,11 @@ RSpec::Matchers.define :have_printed do |expected|
85
85
 
86
86
  diffable
87
87
  end
88
+
89
+ RSpec::Matchers.define :equal_attributes_of do |expected|
90
+ match do |actual|
91
+ actual.instance_variables.all? do |attr|
92
+ actual.instance_variable_get(attr) == expected.instance_variable_get(attr)
93
+ end
94
+ end
95
+ end
@@ -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
@@ -24,4 +24,45 @@ describe Puppet::Transaction::Report::Rest do
24
24
  Puppet::Transaction::Report::Rest.server.should_not be_nil
25
25
  Puppet::Transaction::Report::Rest.port.should_not be_nil
26
26
  end
27
+
28
+ let(:model) { Puppet::Transaction::Report }
29
+ let(:terminus_class) { Puppet::Transaction::Report::Rest }
30
+ let(:terminus) { model.indirection.terminus(:rest) }
31
+ let(:indirection) { model.indirection }
32
+
33
+ before(:each) do
34
+ Puppet::Transaction::Report.indirection.terminus_class = :rest
35
+ end
36
+
37
+ def mock_response(code, body, content_type='text/plain', encoding=nil)
38
+ obj = stub('http 200 ok', :code => code.to_s, :body => body)
39
+ obj.stubs(:[]).with('content-type').returns(content_type)
40
+ obj.stubs(:[]).with('content-encoding').returns(encoding)
41
+ obj
42
+ end
43
+
44
+ def save_request(key, instance)
45
+ Puppet::Indirector::Request.new(:report, :find, key, instance)
46
+ end
47
+
48
+ describe "#save" do
49
+ let(:http_method) { :put }
50
+ let(:response) { mock_response(200, 'body') }
51
+ let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) }
52
+ let(:instance) { model.new('the thing', 'some contents') }
53
+ let(:request) { save_request(instance.name, instance) }
54
+
55
+ before :each do
56
+ terminus.stubs(:network).returns(connection)
57
+ end
58
+
59
+ it "deserializes the response as an array of report processor names" do
60
+ processors = ["store", "http"]
61
+ body = YAML.dump(processors)
62
+ response = mock_response('200', body, 'text/yaml')
63
+ connection.expects(:put).returns response
64
+
65
+ terminus.save(request).should == ["store", "http"]
66
+ end
67
+ end
27
68
  end
@@ -1,57 +1,140 @@
1
1
  #!/usr/bin/env rspec
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, options)
122
+ end
123
+
124
+ def head_request(key, options={})
125
+ Puppet::Indirector::Request.new(:test_model, :head, key, options)
126
+ end
127
+
128
+ def search_request(key, options={})
129
+ Puppet::Indirector::Request.new(:test_model, :search, key, 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, options)
134
+ end
52
135
 
53
- @searcher = @rest_class.new
54
- @searcher.stubs(:model).returns @model
136
+ def save_request(key, instance)
137
+ Puppet::Indirector::Request.new(:test_model, :save, key, instance)
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 :servset
67
- Puppet.settings.expects(:value).with(:servset).returns "myserver"
68
- @rest_class.server.should == "myserver"
149
+ terminus_class.expects(:server_setting).returns :inventory_server
150
+ Puppet[:inventory_server] = "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
73
- Puppet.settings.expects(:value).with(:server).returns "myserver"
74
- @rest_class.server.should == "myserver"
155
+ terminus_class.expects(:server_setting).returns nil
156
+ Puppet[:server] = "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 :servset
83
- Puppet.settings.expects(:value).with(:servset).returns "321"
84
- @rest_class.port.should == 321
165
+ terminus_class.expects(:port_setting).returns :ca_port
166
+ Puppet[:ca_port] = "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
89
- Puppet.settings.expects(:value).with(:masterport).returns "543"
90
- @rest_class.port.should == 543
171
+ terminus_class.expects(:port_setting).returns nil
172
+ Puppet[:masterport] = "543"
173
+ terminus_class.port.should == 543
91
174
  end
92
175
 
93
176
  describe "when making http requests" do
@@ -112,24 +195,24 @@ describe Puppet::Indirector::REST do
112
195
  end
113
196
 
114
197
  it "should provide a useful error message when one is available and certificate validation fails", :unless => Puppet.features.microsoft_windows? do
115
- @searcher.stubs(:network).
198
+ terminus.stubs(:network).
116
199
  returns(a_connection_that_verifies(:has_passed_pre_checks => false,
117
200
  :in_context => a_store_context(:for_server => 'not_my_server',
118
201
  :with_error_string => 'shady looking signature'),
119
202
  :fails_with => 'certificate verify failed'))
120
203
  expect do
121
- @searcher.http_request(:get, stub('request'))
204
+ terminus.http_request(:get, stub('request'))
122
205
  end.to raise_error(Puppet::Error, "certificate verify failed: [shady looking signature for /CN=not_my_server]")
123
206
  end
124
207
 
125
208
  it "should provide a helpful error message when hostname was not match with server certificate", :unless => Puppet.features.microsoft_windows? do
126
- @searcher.stubs(:network).
209
+ terminus.stubs(:network).
127
210
  returns(a_connection_that_verifies(:has_passed_pre_checks => true,
128
211
  :in_context => a_store_context(:for_server => 'not_my_server',
129
212
  :for_aliases => 'foo,bar,baz'),
130
213
  :fails_with => 'hostname was not match with server certificate'))
131
214
 
132
- expect { @searcher.http_request(:get, stub('request')) }.
215
+ expect { terminus.http_request(:get, stub('request')) }.
133
216
  to raise_error(Puppet::Error) do |error|
134
217
  error.message =~ /Server hostname 'my_server' did not match server certificate; expected one of (.+)/
135
218
  $1.split(', ').should =~ %w[DNS:foo DNS:bar DNS:baz DNS:not_my_server not_my_server]
@@ -138,115 +221,16 @@ describe Puppet::Indirector::REST do
138
221
 
139
222
  it "should pass along the error message otherwise" do
140
223
  connection = Net::HTTP.new('my_server', 8140)
141
- @searcher.stubs(:network).returns(connection)
224
+ terminus.stubs(:network).returns(connection)
142
225
 
143
226
  connection.stubs(:get).raises(OpenSSL::SSL::SSLError.new('some other message'))
144
227
 
145
228
  expect do
146
- @searcher.http_request(:get, stub('request'))
229
+ terminus.http_request(:get, stub('request'))
147
230
  end.to raise_error(/some other message/)
148
231
  end
149
232
  end
150
233
 
151
- describe "when deserializing responses" do
152
- it "should return nil if the response code is 404" do
153
- response = mock 'response'
154
- response.expects(:code).returns "404"
155
-
156
- @searcher.deserialize(response).should be_nil
157
- end
158
-
159
- [300,400,403,405,500,501,502,503,504].each { |rc|
160
- describe "when the response code is #{rc}" do
161
- before :each do
162
- @model.expects(:convert_from).never
163
-
164
- @response = mock 'response'
165
- @response.stubs(:code).returns rc.to_s
166
- @response.stubs(:[]).with('content-encoding').returns nil
167
- @response.stubs(:message).returns "There was a problem (header)"
168
- end
169
-
170
- it "should fail" do
171
- @response.stubs(:body).returns nil
172
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError)
173
- end
174
-
175
- it "should take the error message from the body, if present" do
176
- @response.stubs(:body).returns "There was a problem (body)"
177
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (body)")
178
- end
179
-
180
- it "should take the error message from the response header if the body is empty" do
181
- @response.stubs(:body).returns ""
182
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
183
- end
184
-
185
- it "should take the error message from the response header if the body is absent" do
186
- @response.stubs(:body).returns nil
187
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
188
- end
189
-
190
- describe "and with http compression" do
191
- it "should uncompress the body" do
192
- @response.stubs(:body).returns("compressed body")
193
- @searcher.expects(:uncompress_body).with(@response).returns("uncompressed")
194
- lambda { @searcher.deserialize(@response) }.should raise_error { |e| e.message =~ /uncompressed/ }
195
- end
196
- end
197
- end
198
- }
199
-
200
- 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
201
- @model.expects(:convert_from).with("myformat", "mydata").returns "myobject"
202
-
203
- response = mock 'response'
204
- response.stubs(:[]).with("content-type").returns "myformat"
205
- response.stubs(:[]).with("content-encoding").returns nil
206
- response.stubs(:body).returns "mydata"
207
- response.stubs(:code).returns "200"
208
-
209
- @searcher.deserialize(response).should == "myobject"
210
- end
211
-
212
- it "should convert and return multiple instances if the return code is in the 200s and 'multiple' is specified" do
213
- @model.expects(:convert_from_multiple).with("myformat", "mydata").returns "myobjects"
214
-
215
- response = mock 'response'
216
- response.stubs(:[]).with("content-type").returns "myformat"
217
- response.stubs(:[]).with("content-encoding").returns nil
218
- response.stubs(:body).returns "mydata"
219
- response.stubs(:code).returns "200"
220
-
221
- @searcher.deserialize(response, true).should == "myobjects"
222
- end
223
-
224
- it "should strip the content-type header to keep only the mime-type" do
225
- @model.expects(:convert_from).with("text/plain", "mydata").returns "myobject"
226
-
227
- response = mock 'response'
228
- response.stubs(:[]).with("content-type").returns "text/plain; charset=utf-8"
229
- response.stubs(:[]).with("content-encoding").returns nil
230
- response.stubs(:body).returns "mydata"
231
- response.stubs(:code).returns "200"
232
-
233
- @searcher.deserialize(response)
234
- end
235
-
236
- it "should uncompress the body" do
237
- @model.expects(:convert_from).with("myformat", "uncompressed mydata").returns "myobject"
238
-
239
- response = mock 'response'
240
- response.stubs(:[]).with("content-type").returns "myformat"
241
- response.stubs(:body).returns "compressed mydata"
242
- response.stubs(:code).returns "200"
243
-
244
- @searcher.expects(:uncompress_body).with(response).returns("uncompressed mydata")
245
-
246
- @searcher.deserialize(response).should == "myobject"
247
- end
248
- end
249
-
250
234
  describe "when creating an HTTP client" do
251
235
  before do
252
236
  Puppet.settings.stubs(:value).returns("rest_testing")
@@ -254,38 +238,42 @@ describe Puppet::Indirector::REST do
254
238
 
255
239
  it "should use the class's server and port if the indirection request provides neither" do
256
240
  @request = stub 'request', :key => "foo", :server => nil, :port => nil
257
- @searcher.class.expects(:port).returns 321
258
- @searcher.class.expects(:server).returns "myserver"
241
+ terminus.class.expects(:port).returns 321
242
+ terminus.class.expects(:server).returns "myserver"
259
243
  Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
260
- @searcher.network(@request).should == "myconn"
244
+ terminus.network(@request).should == "myconn"
261
245
  end
262
246
 
263
247
  it "should use the server from the indirection request if one is present" do
264
248
  @request = stub 'request', :key => "foo", :server => "myserver", :port => nil
265
- @searcher.class.stubs(:port).returns 321
249
+ terminus.class.stubs(:port).returns 321
266
250
  Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
267
- @searcher.network(@request).should == "myconn"
251
+ terminus.network(@request).should == "myconn"
268
252
  end
269
253
 
270
254
  it "should use the port from the indirection request if one is present" do
271
255
  @request = stub 'request', :key => "foo", :server => nil, :port => 321
272
- @searcher.class.stubs(:server).returns "myserver"
256
+ terminus.class.stubs(:server).returns "myserver"
273
257
  Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
274
- @searcher.network(@request).should == "myconn"
258
+ terminus.network(@request).should == "myconn"
275
259
  end
276
260
  end
277
261
 
278
- describe "when doing a find" do
279
- before :each do
280
- @connection = stub('mock http connection', :get => @response, :verify_callback= => nil)
281
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
262
+ describe "#find" do
263
+ let(:http_method) { :get }
264
+ let(:response) { mock_response(200, 'body') }
265
+ let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) }
266
+ let(:request) { find_request('foo') }
282
267
 
283
- # Use a key with spaces, so we can test escaping
284
- @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", :environment => "myenv")
268
+ before :each do
269
+ terminus.stubs(:network).returns(connection)
285
270
  end
286
271
 
287
- describe "with a large body" do
288
- it "should use the POST http method" do
272
+ it_behaves_like 'a REST terminus method', :find
273
+ it_behaves_like 'a deserializing terminus method', :find
274
+
275
+ describe "with a long set of parameters" do
276
+ it "calls post on the connection with the query params in the body" do
289
277
  params = {}
290
278
  'aa'.upto('zz') do |s|
291
279
  params[s] = 'foo'
@@ -296,283 +284,270 @@ describe Puppet::Indirector::REST do
296
284
  # to avoid a failure.
297
285
  params.delete('ip')
298
286
 
299
- @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar", params.merge(:environment => "myenv"))
287
+ request = find_request('whoa', params)
300
288
 
301
- @connection.expects(:post).with do |uri, body|
302
- uri == "/myenv/foo/foo%20bar" and body.split("&").sort == params.map {|key,value| "#{key}=#{value}"}.sort
303
- end.returns(@response)
289
+ connection.expects(:post).with do |uri, body|
290
+ body.split("&").sort == params.map {|key,value| "#{key}=#{value}"}.sort
291
+ end.returns(mock_response(200, 'body'))
304
292
 
305
- @searcher.find(@request)
293
+ terminus.find(request)
306
294
  end
307
295
  end
308
296
 
309
- describe "with a small body" do
310
- it "should use the GET http method" do
311
- @searcher.expects(:network).returns @connection
312
- @connection.expects(:get).returns @response
313
- @searcher.find(@request)
297
+ describe "with no parameters" do
298
+ it "calls get on the connection" do
299
+ request = find_request('foo bar')
300
+
301
+ connection.expects(:get).with('/production/test_model/foo%20bar?', anything).returns(mock_response('200', 'response body'))
302
+
303
+ terminus.find(request).should == model.new('foo bar', 'response body')
314
304
  end
315
305
  end
316
306
 
317
- it "should deserialize and return the http response, setting name" do
318
- @connection.expects(:get).returns @response
307
+ it "returns nil on 404" do
308
+ response = mock_response('404', nil)
319
309
 
320
- instance = stub 'object'
321
- instance.expects(:name=)
322
- @searcher.expects(:deserialize).with(@response).returns instance
310
+ connection.expects(:get).returns(response)
323
311
 
324
- @searcher.find(@request).should == instance
312
+ terminus.find(request).should == nil
325
313
  end
326
314
 
327
- it "should deserialize and return the http response, and not require name=" do
328
- @connection.expects(:get).returns @response
315
+ it "asks the model to deserialize the response body and sets the name on the resulting object to the find key" do
316
+ connection.expects(:get).returns response
329
317
 
330
- instance = stub 'object'
331
- @searcher.expects(:deserialize).with(@response).returns instance
318
+ model.expects(:convert_from).with(response['content-type'], response.body).returns(
319
+ model.new('overwritten', 'decoded body')
320
+ )
332
321
 
333
- @searcher.find(@request).should == instance
322
+ terminus.find(request).should == model.new('foo', 'decoded body')
334
323
  end
335
324
 
336
- it "should use the URI generated by the Handler module" do
337
- @connection.expects(:get).with { |path, args| path == "/myenv/foo/foo%20bar?" }.returns(@response)
338
- @searcher.find(@request)
339
- end
325
+ it "doesn't require the model to support name=" do
326
+ connection.expects(:get).returns response
327
+ instance = model.new('name', 'decoded body')
340
328
 
341
- it "should provide an Accept header containing the list of supported formats joined with commas" do
342
- @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
329
+ model.expects(:convert_from).with(response['content-type'], response.body).returns(instance)
330
+ instance.expects(:respond_to?).with(:name=).returns(false)
331
+ instance.expects(:name=).never
343
332
 
344
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
345
- @searcher.find(@request)
333
+ terminus.find(request).should == model.new('name', 'decoded body')
346
334
  end
347
335
 
348
- it "should add Accept-Encoding header" do
349
- @searcher.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"})
336
+ it "provides an Accept header containing the list of supported formats joined with commas" do
337
+ connection.expects(:get).with(anything, has_entry("Accept" => "supported, formats")).returns(response)
350
338
 
351
- @connection.expects(:get).with { |path, args| args["accept-encoding"] == "gzip" }.returns(@response)
352
- @searcher.find(@request)
339
+ terminus.model.expects(:supported_formats).returns %w{supported formats}
340
+ terminus.find(request)
353
341
  end
354
342
 
355
- it "should deserialize and return the network response" do
356
- @searcher.expects(:deserialize).with(@response).returns @instance
357
- @searcher.find(@request).should equal(@instance)
358
- end
343
+ it "adds an Accept-Encoding header" do
344
+ terminus.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"})
359
345
 
360
- it "should set the name of the resulting instance to the asked-for name" do
361
- @searcher.expects(:deserialize).with(@response).returns @instance
362
- @instance.expects(:name=).with "foo bar"
363
- @searcher.find(@request)
364
- end
346
+ connection.expects(:get).with(anything, has_entry("accept-encoding" => "gzip")).returns(response)
365
347
 
366
- it "should generate an error when result data deserializes fails" do
367
- @searcher.expects(:deserialize).raises(ArgumentError)
368
- lambda { @searcher.find(@request) }.should raise_error(ArgumentError)
348
+ terminus.find(request)
369
349
  end
370
- end
371
350
 
372
- describe "when doing a head" do
373
- before :each do
374
- @connection = stub('mock http connection', :head => @response, :verify_callback= => nil)
375
- @searcher.stubs(:network).returns(@connection)
351
+ it "uses only the mime-type from the content-type header when asking the model to deserialize" do
352
+ response = mock_response('200', 'mydata', "text/plain; charset=utf-8")
353
+ connection.expects(:get).returns(response)
354
+
355
+ model.expects(:convert_from).with("text/plain", "mydata").returns "myobject"
376
356
 
377
- # Use a key with spaces, so we can test escaping
378
- @request = Puppet::Indirector::Request.new(:foo, :head, "foo bar")
357
+ terminus.find(request).should == "myobject"
379
358
  end
380
359
 
381
- it "should call the HEAD http method on a network connection" do
382
- @searcher.expects(:network).returns @connection
383
- @connection.expects(:head).returns @response
384
- @searcher.head(@request)
360
+ it "decompresses the body before passing it to the model for deserialization" do
361
+ uncompressed_body = "Why hello there"
362
+ compressed_body = Zlib::Deflate.deflate(uncompressed_body)
363
+
364
+ response = mock_response('200', compressed_body, 'text/plain', 'deflate')
365
+ connection.expects(:get).returns(response)
366
+
367
+ model.expects(:convert_from).with("text/plain", uncompressed_body).returns "myobject"
368
+
369
+ terminus.find(request).should == "myobject"
385
370
  end
371
+ end
386
372
 
387
- it "should return true if there was a successful http response" do
388
- @connection.expects(:head).returns @response
389
- @response.stubs(:code).returns "200"
373
+ describe "#head" do
374
+ let(:http_method) { :head }
375
+ let(:response) { mock_response(200, nil) }
376
+ let(:connection) { stub('mock http connection', :head => response, :verify_callback= => nil) }
377
+ let(:request) { head_request('foo') }
390
378
 
391
- @searcher.head(@request).should == true
379
+ before :each do
380
+ terminus.stubs(:network).returns(connection)
392
381
  end
393
382
 
394
- it "should return false if there was a successful http response" do
395
- @connection.expects(:head).returns @response
396
- @response.stubs(:code).returns "404"
383
+ it_behaves_like 'a REST terminus method', :head
384
+
385
+ it "returns true if there was a successful http response" do
386
+ connection.expects(:head).returns mock_response('200', nil)
397
387
 
398
- @searcher.head(@request).should == false
388
+ terminus.head(request).should == true
399
389
  end
400
390
 
401
- it "should use the URI generated by the Handler module" do
402
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
403
- @connection.expects(:head).with { |path, args| path == "/my/uri" }.returns(@response)
404
- @searcher.head(@request)
391
+ it "returns false on a 404 response" do
392
+ connection.expects(:head).returns mock_response('404', nil)
393
+
394
+ terminus.head(request).should == false
405
395
  end
406
396
  end
407
397
 
408
- describe "when doing a search" do
409
- before :each do
410
- @connection = stub('mock http connection', :get => @response, :verify_callback= => nil)
411
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
412
-
413
- @model.stubs(:convert_from_multiple)
398
+ describe "#search" do
399
+ let(:http_method) { :get }
400
+ let(:response) { mock_response(200, 'data1,data2,data3') }
401
+ let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) }
402
+ let(:request) { search_request('foo') }
414
403
 
415
- @request = Puppet::Indirector::Request.new(:foo, :search, "foo bar")
404
+ before :each do
405
+ terminus.stubs(:network).returns(connection)
416
406
  end
417
407
 
408
+ it_behaves_like 'a REST terminus method', :search
409
+ it_behaves_like 'a deserializing terminus method', :search
410
+
418
411
  it "should call the GET http method on a network connection" do
419
- @searcher.expects(:network).returns @connection
420
- @connection.expects(:get).returns @response
421
- @searcher.search(@request)
412
+ connection.expects(:get).with('/production/test_models/foo', has_key('Accept')).returns mock_response(200, 'data3, data4')
413
+
414
+ terminus.search(request)
422
415
  end
423
416
 
424
- it "should deserialize as multiple instances and return the http response" do
425
- @connection.expects(:get).returns @response
426
- @searcher.expects(:deserialize).with(@response, true).returns "myobject"
417
+ it "returns an empty list on 404" do
418
+ response = mock_response('404', nil)
419
+
420
+ connection.expects(:get).returns(response)
427
421
 
428
- @searcher.search(@request).should == 'myobject'
422
+ terminus.search(request).should == []
429
423
  end
430
424
 
431
- it "should use the URI generated by the Handler module" do
432
- @searcher.expects(:indirection2uri).with(@request).returns "/mys/uri"
433
- @connection.expects(:get).with { |path, args| path == "/mys/uri" }.returns(@response)
434
- @searcher.search(@request)
425
+ it "asks the model to deserialize the response body into multiple instances" do
426
+ terminus.search(request).should == [model.new('', 'data1'), model.new('', 'data2'), model.new('', 'data3')]
435
427
  end
436
428
 
437
429
  it "should provide an Accept header containing the list of supported formats joined with commas" do
438
- @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
430
+ connection.expects(:get).with(anything, has_entry("Accept" => "supported, formats")).returns(mock_response(200, ''))
439
431
 
440
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
441
- @searcher.search(@request)
432
+ terminus.model.expects(:supported_formats).returns %w{supported formats}
433
+ terminus.search(request)
442
434
  end
443
435
 
444
436
  it "should return an empty array if serialization returns nil" do
445
- @model.stubs(:convert_from_multiple).returns nil
437
+ model.stubs(:convert_from_multiple).returns nil
446
438
 
447
- @searcher.search(@request).should == []
448
- end
449
-
450
- it "should generate an error when result data deserializes fails" do
451
- @searcher.expects(:deserialize).raises(ArgumentError)
452
- lambda { @searcher.search(@request) }.should raise_error(ArgumentError)
439
+ terminus.search(request).should == []
453
440
  end
454
441
  end
455
442
 
456
- describe "when doing a destroy" do
457
- before :each do
458
- @connection = stub('mock http connection', :delete => @response, :verify_callback= => nil)
459
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
443
+ describe "#destroy" do
444
+ let(:http_method) { :delete }
445
+ let(:response) { mock_response(200, 'body') }
446
+ let(:connection) { stub('mock http connection', :delete => response, :verify_callback= => nil) }
447
+ let(:request) { delete_request('foo') }
460
448
 
461
- @request = Puppet::Indirector::Request.new(:foo, :destroy, "foo bar")
449
+ before :each do
450
+ terminus.stubs(:network).returns(connection)
462
451
  end
463
452
 
453
+ it_behaves_like 'a REST terminus method', :destroy
454
+ it_behaves_like 'a deserializing terminus method', :destroy
455
+
464
456
  it "should call the DELETE http method on a network connection" do
465
- @searcher.expects(:network).returns @connection
466
- @connection.expects(:delete).returns @response
467
- @searcher.destroy(@request)
457
+ connection.expects(:delete).with('/production/test_model/foo', has_key('Accept')).returns(response)
458
+
459
+ terminus.destroy(request)
468
460
  end
469
461
 
470
462
  it "should fail if any options are provided, since DELETE apparently does not support query options" do
471
- @request.stubs(:options).returns(:one => "two", :three => "four")
463
+ request = delete_request('foo', :one => "two", :three => "four")
472
464
 
473
- lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
465
+ expect { terminus.destroy(request) }.to raise_error(ArgumentError)
474
466
  end
475
467
 
476
468
  it "should deserialize and return the http response" do
477
- @connection.expects(:delete).returns @response
478
- @searcher.expects(:deserialize).with(@response).returns "myobject"
479
-
480
- @searcher.destroy(@request).should == 'myobject'
481
- end
469
+ connection.expects(:delete).returns response
482
470
 
483
- it "should use the URI generated by the Handler module" do
484
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
485
- @connection.expects(:delete).with { |path, args| path == "/my/uri" }.returns(@response)
486
- @searcher.destroy(@request)
471
+ terminus.destroy(request).should == model.new('', 'body')
487
472
  end
488
473
 
489
- it "should not include the query string" do
490
- @connection.stubs(:delete).returns @response
491
- @searcher.destroy(@request)
492
- end
474
+ it "returns nil on 404" do
475
+ response = mock_response('404', nil)
493
476
 
494
- it "should provide an Accept header containing the list of supported formats joined with commas" do
495
- @connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
477
+ connection.expects(:delete).returns(response)
496
478
 
497
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
498
- @searcher.destroy(@request)
479
+ terminus.destroy(request).should == nil
499
480
  end
500
481
 
501
- it "should deserialize and return the network response" do
502
- @searcher.expects(:deserialize).with(@response).returns @instance
503
- @searcher.destroy(@request).should equal(@instance)
504
- end
482
+ it "should provide an Accept header containing the list of supported formats joined with commas" do
483
+ connection.expects(:delete).with(anything, has_entry("Accept" => "supported, formats")).returns(response)
505
484
 
506
- it "should generate an error when result data deserializes fails" do
507
- @searcher.expects(:deserialize).raises(ArgumentError)
508
- lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
485
+ terminus.model.expects(:supported_formats).returns %w{supported formats}
486
+ terminus.destroy(request)
509
487
  end
510
488
  end
511
489
 
512
- describe "when doing a save" do
513
- before :each do
514
- @connection = stub('mock http connection', :put => @response, :verify_callback= => nil)
515
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
490
+ describe "#save" do
491
+ let(:http_method) { :put }
492
+ let(:response) { mock_response(200, 'body') }
493
+ let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) }
494
+ let(:instance) { model.new('the thing', 'some contents') }
495
+ let(:request) { save_request(instance.name, instance) }
516
496
 
517
- @instance = stub 'instance', :render => "mydata", :mime => "mime"
518
- @request = Puppet::Indirector::Request.new(:foo, :save, "foo bar")
519
- @request.instance = @instance
497
+ before :each do
498
+ terminus.stubs(:network).returns(connection)
520
499
  end
521
500
 
522
- it "should call the PUT http method on a network connection" do
523
- @searcher.expects(:network).returns @connection
524
- @connection.expects(:put).returns @response
525
- @searcher.save(@request)
526
- end
501
+ it_behaves_like 'a REST terminus method', :save
527
502
 
528
- it "should fail if any options are provided, since DELETE apparently does not support query options" do
529
- @request.stubs(:options).returns(:one => "two", :three => "four")
503
+ it "should call the PUT http method on a network connection" do
504
+ connection.expects(:put).with('/production/test_model/the%20thing', anything, has_key("Content-Type")).returns response
530
505
 
531
- lambda { @searcher.save(@request) }.should raise_error(ArgumentError)
506
+ terminus.save(request)
532
507
  end
533
508
 
534
- it "should use the URI generated by the Handler module" do
535
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
536
- @connection.expects(:put).with { |path, args| path == "/my/uri" }.returns(@response)
537
- @searcher.save(@request)
509
+ it "should fail if any options are provided, since PUT apparently does not support query options" do
510
+ request = save_request(instance, :one => "two", :three => "four")
511
+
512
+ expect { terminus.save(request) }.to raise_error(ArgumentError)
538
513
  end
539
514
 
540
515
  it "should serialize the instance using the default format and pass the result as the body of the request" do
541
- @instance.expects(:render).returns "serial_instance"
542
- @connection.expects(:put).with { |path, data, args| data == "serial_instance" }.returns @response
516
+ instance.expects(:render).returns "serial_instance"
517
+ connection.expects(:put).with(anything, "serial_instance", anything).returns response
543
518
 
544
- @searcher.save(@request)
519
+ terminus.save(request)
545
520
  end
546
521
 
547
- it "should deserialize and return the http response" do
548
- @connection.expects(:put).returns @response
549
- @searcher.expects(:deserialize).with(@response).returns "myobject"
522
+ it "returns nil on 404" do
523
+ response = mock_response('404', nil)
550
524
 
551
- @searcher.save(@request).should == 'myobject'
525
+ connection.expects(:put).returns(response)
526
+
527
+ terminus.save(request).should == nil
552
528
  end
553
529
 
554
- it "should provide an Accept header containing the list of supported formats joined with commas" do
555
- @connection.expects(:put).with { |path, data, args| args["Accept"] == "supported, formats" }.returns(@response)
530
+ it "returns nil" do
531
+ connection.expects(:put).returns response
556
532
 
557
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
558
- @searcher.save(@request)
533
+ terminus.save(request).should be_nil
559
534
  end
560
535
 
561
- it "should provide a Content-Type header containing the mime-type of the sent object" do
562
- @connection.expects(:put).with { |path, data, args| args['Content-Type'] == "mime" }.returns(@response)
536
+ it "should provide an Accept header containing the list of supported formats joined with commas" do
537
+ connection.expects(:put).with(anything, anything, has_entry("Accept" => "supported, formats")).returns(response)
563
538
 
564
- @instance.expects(:mime).returns "mime"
565
- @searcher.save(@request)
566
- end
539
+ instance.expects(:render).returns('')
540
+ model.expects(:supported_formats).returns %w{supported formats}
541
+ instance.expects(:mime).returns "supported"
567
542
 
568
- it "should deserialize and return the network response" do
569
- @searcher.expects(:deserialize).with(@response).returns @instance
570
- @searcher.save(@request).should equal(@instance)
543
+ terminus.save(request)
571
544
  end
572
545
 
573
- it "should generate an error when result data deserializes fails" do
574
- @searcher.expects(:deserialize).raises(ArgumentError)
575
- lambda { @searcher.save(@request) }.should raise_error(ArgumentError)
546
+ it "should provide a Content-Type header containing the mime-type of the sent object" do
547
+ instance.expects(:mime).returns "mime"
548
+ connection.expects(:put).with(anything, anything, has_entry('Content-Type' => "mime")).returns(response)
549
+
550
+ terminus.save(request)
576
551
  end
577
552
  end
578
553
  end