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.
- data/CHANGELOG +14 -0
- data/Gemfile.lock +2 -2
- data/ext/build_defaults.yaml +2 -3
- data/ext/debian/control +1 -1
- data/ext/packaging/README.md +496 -8
- data/ext/packaging/spec/tasks/00_utils_spec.rb +7 -7
- data/ext/packaging/spec/tasks/build_object_spec.rb +3 -0
- data/ext/packaging/tasks/00_utils.rake +2 -2
- data/ext/packaging/tasks/10_setupvars.rake +8 -1
- data/ext/packaging/tasks/build.rake +2 -0
- data/ext/packaging/tasks/deb_repos.rake +48 -15
- data/ext/packaging/tasks/jenkins.rake +30 -2
- data/ext/packaging/tasks/mock.rake +3 -2
- data/ext/packaging/tasks/pe_remote.rake +1 -1
- data/ext/packaging/tasks/pe_ship.rake +4 -5
- data/ext/packaging/tasks/pe_sign.rake +8 -0
- data/ext/packaging/tasks/pe_sles.rake +8 -7
- data/ext/packaging/tasks/pre_tasks.rake +0 -0
- data/ext/packaging/tasks/retrieve.rake +11 -1
- data/ext/packaging/tasks/rpm_repos.rake +71 -49
- data/ext/packaging/tasks/ship.rake +14 -2
- data/ext/packaging/tasks/sign.rake +9 -3
- data/ext/packaging/tasks/tar.rake +5 -0
- data/ext/packaging/tasks/vendor_gems.rake +110 -0
- data/install.rb +1 -1
- data/lib/puppet.rb +11 -0
- data/lib/puppet/indirector/report/processor.rb +1 -1
- data/lib/puppet/indirector/report/rest.rb +7 -0
- data/lib/puppet/indirector/resource/rest.rb +9 -0
- data/lib/puppet/indirector/rest.rb +81 -47
- data/lib/puppet/indirector/run/rest.rb +6 -0
- data/lib/puppet/network/formats.rb +20 -10
- data/lib/puppet/network/http/handler.rb +1 -1
- data/lib/puppet/node.rb +25 -0
- data/lib/puppet/node/facts.rb +23 -4
- data/lib/puppet/resource.rb +2 -4
- data/lib/puppet/resource/status.rb +28 -0
- data/lib/puppet/run.rb +24 -2
- data/lib/puppet/status.rb +6 -2
- data/lib/puppet/transaction/event.rb +19 -0
- data/lib/puppet/transaction/report.rb +39 -0
- data/lib/puppet/util/log.rb +19 -0
- data/lib/puppet/util/metric.rb +6 -0
- data/lib/puppet/util/monkey_patches.rb +0 -16
- data/lib/puppet/vendor.rb +55 -0
- data/lib/puppet/vendor/load_safe_yaml.rb +1 -0
- data/lib/puppet/vendor/require_vendored.rb +4 -0
- data/lib/puppet/vendor/safe_yaml/CHANGES.md +104 -0
- data/lib/puppet/vendor/safe_yaml/Gemfile +11 -0
- data/lib/puppet/vendor/safe_yaml/LICENSE.txt +22 -0
- data/lib/puppet/vendor/safe_yaml/README.md +179 -0
- data/lib/puppet/vendor/safe_yaml/Rakefile +6 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb +253 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb +34 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/date.rb +27 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/hexadecimal.rb +12 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/sexagesimal.rb +26 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_handler.rb +92 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_resolver.rb +52 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/resolver.rb +94 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/safe_to_ruby_visitor.rb +17 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_hack.rb +36 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_node_monkeypatch.rb +43 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_resolver.rb +38 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform.rb +41 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_boolean.rb +21 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_date.rb +11 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_float.rb +33 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_integer.rb +25 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_nil.rb +18 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_symbol.rb +13 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/transformation_map.rb +47 -0
- data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/version.rb +3 -0
- data/lib/puppet/vendor/safe_yaml/run_specs_all_ruby_versions.sh +21 -0
- data/lib/puppet/vendor/safe_yaml/safe_yaml.gemspec +18 -0
- data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.2.yaml +2 -0
- data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.3.yaml +2 -0
- data/lib/puppet/vendor/safe_yaml/spec/psych_resolver_spec.rb +10 -0
- data/lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb +250 -0
- data/lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb +702 -0
- data/lib/puppet/vendor/safe_yaml/spec/spec_helper.rb +18 -0
- data/lib/puppet/vendor/safe_yaml/spec/support/exploitable_back_door.rb +29 -0
- data/lib/puppet/vendor/safe_yaml/spec/syck_resolver_spec.rb +10 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/base64_spec.rb +11 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/to_date_spec.rb +34 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/to_float_spec.rb +42 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/to_integer_spec.rb +59 -0
- data/lib/puppet/vendor/safe_yaml/spec/transform/to_symbol_spec.rb +49 -0
- data/lib/puppet/version.rb +1 -1
- data/spec/lib/puppet_spec/matchers.rb +8 -0
- data/spec/unit/file_serving/metadata_spec.rb +20 -28
- data/spec/unit/indirector/report/rest_spec.rb +41 -0
- data/spec/unit/indirector/rest_spec.rb +314 -339
- data/spec/unit/network/formats_spec.rb +36 -27
- data/spec/unit/network/http/handler_spec.rb +3 -12
- data/spec/unit/node_spec.rb +81 -0
- data/spec/unit/resource_spec.rb +5 -35
- data/spec/unit/run_spec.rb +22 -8
- data/spec/unit/status_spec.rb +6 -0
- data/test/network/handler/report.rb +0 -36
- 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,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
|
data/lib/puppet/version.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
65
|
+
it "doesn't catch errors in deserialization" do
|
66
|
+
model.expects(:convert_from).raises(Puppet::Error, "Whoa there")
|
22
67
|
|
23
|
-
|
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::
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
44
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
54
|
-
|
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
|
-
|
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
|
-
|
67
|
-
Puppet
|
68
|
-
|
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
|
-
|
73
|
-
Puppet
|
74
|
-
|
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
|
-
|
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
|
-
|
83
|
-
Puppet
|
84
|
-
|
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
|
-
|
89
|
-
Puppet
|
90
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 {
|
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
|
-
|
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
|
-
|
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
|
-
|
258
|
-
|
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
|
-
|
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
|
-
|
249
|
+
terminus.class.stubs(:port).returns 321
|
266
250
|
Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
|
267
|
-
|
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
|
-
|
256
|
+
terminus.class.stubs(:server).returns "myserver"
|
273
257
|
Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
|
274
|
-
|
258
|
+
terminus.network(@request).should == "myconn"
|
275
259
|
end
|
276
260
|
end
|
277
261
|
|
278
|
-
describe "
|
279
|
-
|
280
|
-
|
281
|
-
|
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
|
-
|
284
|
-
|
268
|
+
before :each do
|
269
|
+
terminus.stubs(:network).returns(connection)
|
285
270
|
end
|
286
271
|
|
287
|
-
|
288
|
-
|
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
|
-
|
287
|
+
request = find_request('whoa', params)
|
300
288
|
|
301
|
-
|
302
|
-
|
303
|
-
end.returns(
|
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
|
-
|
293
|
+
terminus.find(request)
|
306
294
|
end
|
307
295
|
end
|
308
296
|
|
309
|
-
describe "with
|
310
|
-
it "
|
311
|
-
|
312
|
-
|
313
|
-
|
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 "
|
318
|
-
|
307
|
+
it "returns nil on 404" do
|
308
|
+
response = mock_response('404', nil)
|
319
309
|
|
320
|
-
|
321
|
-
instance.expects(:name=)
|
322
|
-
@searcher.expects(:deserialize).with(@response).returns instance
|
310
|
+
connection.expects(:get).returns(response)
|
323
311
|
|
324
|
-
|
312
|
+
terminus.find(request).should == nil
|
325
313
|
end
|
326
314
|
|
327
|
-
it "
|
328
|
-
|
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
|
-
|
331
|
-
|
318
|
+
model.expects(:convert_from).with(response['content-type'], response.body).returns(
|
319
|
+
model.new('overwritten', 'decoded body')
|
320
|
+
)
|
332
321
|
|
333
|
-
|
322
|
+
terminus.find(request).should == model.new('foo', 'decoded body')
|
334
323
|
end
|
335
324
|
|
336
|
-
it "
|
337
|
-
|
338
|
-
|
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
|
-
|
342
|
-
|
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
|
-
|
345
|
-
@searcher.find(@request)
|
333
|
+
terminus.find(request).should == model.new('name', 'decoded body')
|
346
334
|
end
|
347
335
|
|
348
|
-
it "
|
349
|
-
|
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
|
-
|
352
|
-
|
339
|
+
terminus.model.expects(:supported_formats).returns %w{supported formats}
|
340
|
+
terminus.find(request)
|
353
341
|
end
|
354
342
|
|
355
|
-
it "
|
356
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
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
|
-
|
378
|
-
@request = Puppet::Indirector::Request.new(:foo, :head, "foo bar")
|
357
|
+
terminus.find(request).should == "myobject"
|
379
358
|
end
|
380
359
|
|
381
|
-
it "
|
382
|
-
|
383
|
-
|
384
|
-
|
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
|
-
|
388
|
-
|
389
|
-
|
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
|
-
|
379
|
+
before :each do
|
380
|
+
terminus.stubs(:network).returns(connection)
|
392
381
|
end
|
393
382
|
|
394
|
-
|
395
|
-
|
396
|
-
|
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
|
-
|
388
|
+
terminus.head(request).should == true
|
399
389
|
end
|
400
390
|
|
401
|
-
it "
|
402
|
-
|
403
|
-
|
404
|
-
|
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 "
|
409
|
-
|
410
|
-
|
411
|
-
|
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
|
-
|
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
|
-
|
420
|
-
|
421
|
-
|
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 "
|
425
|
-
|
426
|
-
|
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
|
-
|
422
|
+
terminus.search(request).should == []
|
429
423
|
end
|
430
424
|
|
431
|
-
it "
|
432
|
-
|
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
|
-
|
430
|
+
connection.expects(:get).with(anything, has_entry("Accept" => "supported, formats")).returns(mock_response(200, ''))
|
439
431
|
|
440
|
-
|
441
|
-
|
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
|
-
|
437
|
+
model.stubs(:convert_from_multiple).returns nil
|
446
438
|
|
447
|
-
|
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 "
|
457
|
-
|
458
|
-
|
459
|
-
|
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
|
-
|
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
|
-
|
466
|
-
|
467
|
-
|
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
|
-
|
463
|
+
request = delete_request('foo', :one => "two", :three => "four")
|
472
464
|
|
473
|
-
|
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
|
-
|
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
|
-
|
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 "
|
490
|
-
|
491
|
-
@searcher.destroy(@request)
|
492
|
-
end
|
474
|
+
it "returns nil on 404" do
|
475
|
+
response = mock_response('404', nil)
|
493
476
|
|
494
|
-
|
495
|
-
@connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
|
477
|
+
connection.expects(:delete).returns(response)
|
496
478
|
|
497
|
-
|
498
|
-
@searcher.destroy(@request)
|
479
|
+
terminus.destroy(request).should == nil
|
499
480
|
end
|
500
481
|
|
501
|
-
it "should
|
502
|
-
|
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
|
-
|
507
|
-
|
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 "
|
513
|
-
|
514
|
-
|
515
|
-
|
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
|
-
|
518
|
-
|
519
|
-
@request.instance = @instance
|
497
|
+
before :each do
|
498
|
+
terminus.stubs(:network).returns(connection)
|
520
499
|
end
|
521
500
|
|
522
|
-
|
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
|
529
|
-
|
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
|
-
|
506
|
+
terminus.save(request)
|
532
507
|
end
|
533
508
|
|
534
|
-
it "should
|
535
|
-
|
536
|
-
|
537
|
-
|
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
|
-
|
542
|
-
|
516
|
+
instance.expects(:render).returns "serial_instance"
|
517
|
+
connection.expects(:put).with(anything, "serial_instance", anything).returns response
|
543
518
|
|
544
|
-
|
519
|
+
terminus.save(request)
|
545
520
|
end
|
546
521
|
|
547
|
-
it "
|
548
|
-
|
549
|
-
@searcher.expects(:deserialize).with(@response).returns "myobject"
|
522
|
+
it "returns nil on 404" do
|
523
|
+
response = mock_response('404', nil)
|
550
524
|
|
551
|
-
|
525
|
+
connection.expects(:put).returns(response)
|
526
|
+
|
527
|
+
terminus.save(request).should == nil
|
552
528
|
end
|
553
529
|
|
554
|
-
it "
|
555
|
-
|
530
|
+
it "returns nil" do
|
531
|
+
connection.expects(:put).returns response
|
556
532
|
|
557
|
-
|
558
|
-
@searcher.save(@request)
|
533
|
+
terminus.save(request).should be_nil
|
559
534
|
end
|
560
535
|
|
561
|
-
it "should provide
|
562
|
-
|
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
|
-
|
565
|
-
|
566
|
-
|
539
|
+
instance.expects(:render).returns('')
|
540
|
+
model.expects(:supported_formats).returns %w{supported formats}
|
541
|
+
instance.expects(:mime).returns "supported"
|
567
542
|
|
568
|
-
|
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
|
574
|
-
|
575
|
-
|
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
|