puppet 3.2.1 → 3.2.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- 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 +8 -0
- data/lib/puppet/indirector/rest.rb +80 -52
- 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 +1 -1
- 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 +40 -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 -15
- data/lib/puppet/vendor.rb +55 -0
- data/lib/puppet/vendor/load_safe_yaml.rb +1 -0
- data/lib/puppet/vendor/require_vendored.rb +5 -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/vendor/safe_yaml_patches.rb +9 -0
- data/lib/puppet/version.rb +1 -1
- data/spec/lib/puppet_spec/matchers.rb +8 -0
- data/spec/unit/application/facts_spec.rb +1 -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 +307 -334
- 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 +14 -0
- data/spec/unit/resource_spec.rb +5 -35
- data/spec/unit/run_spec.rb +25 -6
- data/spec/unit/status_spec.rb +6 -0
- metadata +2566 -2521
@@ -0,0 +1,18 @@
|
|
1
|
+
HERE = File.dirname(__FILE__) unless defined?(HERE)
|
2
|
+
ROOT = File.join(HERE, "..") unless defined?(ROOT)
|
3
|
+
|
4
|
+
$LOAD_PATH << File.join(ROOT, "lib")
|
5
|
+
$LOAD_PATH << File.join(HERE, "support")
|
6
|
+
|
7
|
+
require "yaml"
|
8
|
+
if ENV["YAMLER"] && defined?(YAML::ENGINE)
|
9
|
+
YAML::ENGINE.yamler = ENV["YAMLER"]
|
10
|
+
puts "Running specs in Ruby #{RUBY_VERSION} with '#{YAML::ENGINE.yamler}' YAML engine."
|
11
|
+
end
|
12
|
+
|
13
|
+
require "safe_yaml"
|
14
|
+
require "ostruct"
|
15
|
+
require "hashie"
|
16
|
+
require "heredoc_unindent"
|
17
|
+
|
18
|
+
require File.join(HERE, "resolver_specs")
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class ExploitableBackDoor
|
2
|
+
def exploited?
|
3
|
+
@exploited_through_setter || @exploited_through_init_with || @exploited_through_ivars
|
4
|
+
end
|
5
|
+
|
6
|
+
def exploited_through_setter?
|
7
|
+
@exploited_through_setter
|
8
|
+
end
|
9
|
+
|
10
|
+
def exploited_through_init_with?
|
11
|
+
@exploited_through_init_with
|
12
|
+
end
|
13
|
+
|
14
|
+
def exploited_through_ivars?
|
15
|
+
self.instance_variables.any?
|
16
|
+
end
|
17
|
+
|
18
|
+
def init_with(command)
|
19
|
+
# Note: this is how bad this COULD be.
|
20
|
+
# system("#{command}")
|
21
|
+
@exploited_through_init_with = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def []=(command, arguments)
|
25
|
+
# Note: this is how bad this COULD be.
|
26
|
+
# system("#{command} #{arguments}")
|
27
|
+
@exploited_through_setter = true
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,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
@@ -95,3 +95,11 @@ end
|
|
95
95
|
def have_printed(what)
|
96
96
|
HavePrintedMatcher.new(what)
|
97
97
|
end
|
98
|
+
|
99
|
+
RSpec::Matchers.define :equal_attributes_of do |expected|
|
100
|
+
match do |actual|
|
101
|
+
actual.instance_variables.all? do |attr|
|
102
|
+
actual.instance_variable_get(attr) == expected.instance_variable_get(attr)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -17,6 +17,7 @@ describe Puppet::Application::Facts do
|
|
17
17
|
|
18
18
|
it "should return facts if a key is given to find" do
|
19
19
|
Puppet::Node::Facts.indirection.reset_terminus_class
|
20
|
+
Puppet::Node::Facts.indirection.expects(:find).returns(Puppet::Node::Facts.new('whatever', {}))
|
20
21
|
subject.command_line.stubs(:args).returns %w{find whatever --render-as yaml}
|
21
22
|
|
22
23
|
expect {
|
@@ -33,14 +33,6 @@ describe Puppet::FileServing::Metadata do
|
|
33
33
|
describe "when serializing" do
|
34
34
|
let(:metadata) { Puppet::FileServing::Metadata.new(foobar) }
|
35
35
|
|
36
|
-
it "should perform pson serialization by calling to_pson on it's pson_data_hash" do
|
37
|
-
pdh = mock "data hash"
|
38
|
-
pdh_as_pson = mock "data as pson"
|
39
|
-
metadata.expects(:to_pson_data_hash).returns pdh
|
40
|
-
pdh.expects(:to_pson).returns pdh_as_pson
|
41
|
-
metadata.to_pson.should == pdh_as_pson
|
42
|
-
end
|
43
|
-
|
44
36
|
it "should serialize as FileMetadata" do
|
45
37
|
metadata.to_pson_data_hash['document_type'].should == "FileMetadata"
|
46
38
|
end
|
@@ -49,48 +41,48 @@ describe Puppet::FileServing::Metadata do
|
|
49
41
|
metadata.to_pson_data_hash['data'].keys.sort.should == %w{ path relative_path links owner group mode checksum type destination }.sort
|
50
42
|
end
|
51
43
|
|
52
|
-
it "should pass the path in the hash
|
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
|
@@ -22,4 +22,45 @@ describe Puppet::Transaction::Report::Rest do
|
|
22
22
|
it "should use the :report SRV service" do
|
23
23
|
Puppet::Transaction::Report::Rest.srv_service.should == :report
|
24
24
|
end
|
25
|
+
|
26
|
+
let(:model) { Puppet::Transaction::Report }
|
27
|
+
let(:terminus_class) { Puppet::Transaction::Report::Rest }
|
28
|
+
let(:terminus) { model.indirection.terminus(:rest) }
|
29
|
+
let(:indirection) { model.indirection }
|
30
|
+
|
31
|
+
before(:each) do
|
32
|
+
Puppet::Transaction::Report.indirection.terminus_class = :rest
|
33
|
+
end
|
34
|
+
|
35
|
+
def mock_response(code, body, content_type='text/plain', encoding=nil)
|
36
|
+
obj = stub('http 200 ok', :code => code.to_s, :body => body)
|
37
|
+
obj.stubs(:[]).with('content-type').returns(content_type)
|
38
|
+
obj.stubs(:[]).with('content-encoding').returns(encoding)
|
39
|
+
obj
|
40
|
+
end
|
41
|
+
|
42
|
+
def save_request(key, instance, options={})
|
43
|
+
Puppet::Indirector::Request.new(:report, :find, key, instance, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#save" do
|
47
|
+
let(:http_method) { :put }
|
48
|
+
let(:response) { mock_response(200, 'body') }
|
49
|
+
let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) }
|
50
|
+
let(:instance) { model.new('the thing', 'some contents') }
|
51
|
+
let(:request) { save_request(instance.name, instance) }
|
52
|
+
|
53
|
+
before :each do
|
54
|
+
terminus.stubs(:network).returns(connection)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "deserializes the response as an array of report processor names" do
|
58
|
+
processors = ["store", "http"]
|
59
|
+
body = YAML.dump(processors)
|
60
|
+
response = mock_response('200', body, 'text/yaml')
|
61
|
+
connection.expects(:put).returns response
|
62
|
+
|
63
|
+
terminus.save(request).should == ["store", "http"]
|
64
|
+
end
|
65
|
+
end
|
25
66
|
end
|
@@ -1,57 +1,140 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
2
|
require 'spec_helper'
|
3
|
+
require 'puppet/indirector'
|
4
|
+
require 'puppet/indirector/errors'
|
3
5
|
require 'puppet/indirector/rest'
|
4
6
|
|
5
|
-
|
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, nil, options)
|
122
|
+
end
|
123
|
+
|
124
|
+
def head_request(key, options={})
|
125
|
+
Puppet::Indirector::Request.new(:test_model, :head, key, nil, options)
|
126
|
+
end
|
127
|
+
|
128
|
+
def search_request(key, options={})
|
129
|
+
Puppet::Indirector::Request.new(:test_model, :search, key, nil, options)
|
46
130
|
end
|
47
131
|
|
48
|
-
|
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, nil, options)
|
134
|
+
end
|
52
135
|
|
53
|
-
|
54
|
-
|
136
|
+
def save_request(key, instance, options={})
|
137
|
+
Puppet::Indirector::Request.new(:test_model, :save, key, instance, options)
|
55
138
|
end
|
56
139
|
|
57
140
|
it "should include the v1 REST API module" do
|
@@ -59,35 +142,35 @@ describe Puppet::Indirector::REST do
|
|
59
142
|
end
|
60
143
|
|
61
144
|
it "should have a method for specifying what setting a subclass should use to retrieve its server" do
|
62
|
-
|
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
|
-
|
149
|
+
terminus_class.expects(:server_setting).returns :inventory_server
|
67
150
|
Puppet[:inventory_server] = "myserver"
|
68
|
-
|
151
|
+
terminus_class.server.should == "myserver"
|
69
152
|
end
|
70
153
|
|
71
154
|
it "should default to :server for the server setting" do
|
72
|
-
|
155
|
+
terminus_class.expects(:server_setting).returns nil
|
73
156
|
Puppet[:server] = "myserver"
|
74
|
-
|
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
|
-
|
165
|
+
terminus_class.expects(:port_setting).returns :ca_port
|
83
166
|
Puppet[:ca_port] = "321"
|
84
|
-
|
167
|
+
terminus_class.port.should == 321
|
85
168
|
end
|
86
169
|
|
87
170
|
it "should default to :port for the port setting" do
|
88
|
-
|
171
|
+
terminus_class.expects(:port_setting).returns nil
|
89
172
|
Puppet[:masterport] = "543"
|
90
|
-
|
173
|
+
terminus_class.port.should == 543
|
91
174
|
end
|
92
175
|
|
93
176
|
|
@@ -95,140 +178,45 @@ describe Puppet::Indirector::REST do
|
|
95
178
|
Puppet::Indirector::REST.srv_service.should == :puppet
|
96
179
|
end
|
97
180
|
|
98
|
-
describe "when deserializing responses" do
|
99
|
-
it "should return nil if the response code is 404" do
|
100
|
-
response = mock 'response'
|
101
|
-
response.expects(:code).returns "404"
|
102
|
-
|
103
|
-
@searcher.deserialize(response).should be_nil
|
104
|
-
end
|
105
|
-
|
106
|
-
[300,400,403,405,500,501,502,503,504].each { |rc|
|
107
|
-
describe "when the response code is #{rc}" do
|
108
|
-
before :each do
|
109
|
-
@model.expects(:convert_from).never
|
110
|
-
|
111
|
-
@response = mock 'response'
|
112
|
-
@response.stubs(:code).returns rc.to_s
|
113
|
-
@response.stubs(:[]).with('content-encoding').returns nil
|
114
|
-
@response.stubs(:message).returns "There was a problem (header)"
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should fail" do
|
118
|
-
@response.stubs(:body).returns nil
|
119
|
-
lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError)
|
120
|
-
end
|
121
|
-
|
122
|
-
it "should take the error message from the body, if present" do
|
123
|
-
@response.stubs(:body).returns "There was a problem (body)"
|
124
|
-
lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (body)")
|
125
|
-
end
|
126
|
-
|
127
|
-
it "should take the error message from the response header if the body is empty" do
|
128
|
-
@response.stubs(:body).returns ""
|
129
|
-
lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
|
130
|
-
end
|
131
|
-
|
132
|
-
it "should take the error message from the response header if the body is absent" do
|
133
|
-
@response.stubs(:body).returns nil
|
134
|
-
lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
|
135
|
-
end
|
136
|
-
|
137
|
-
describe "and with http compression" do
|
138
|
-
it "should uncompress the body" do
|
139
|
-
@response.stubs(:body).returns("compressed body")
|
140
|
-
@searcher.expects(:uncompress_body).with(@response).returns("uncompressed")
|
141
|
-
lambda { @searcher.deserialize(@response) }.should raise_error { |e| e.message =~ /uncompressed/ }
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
}
|
146
|
-
|
147
|
-
it "should return the results of converting from the format specified by the content-type header if the response code is in the 200s" do
|
148
|
-
@model.expects(:convert_from).with("myformat", "mydata").returns "myobject"
|
149
|
-
|
150
|
-
response = mock 'response'
|
151
|
-
response.stubs(:[]).with("content-type").returns "myformat"
|
152
|
-
response.stubs(:[]).with("content-encoding").returns nil
|
153
|
-
response.stubs(:body).returns "mydata"
|
154
|
-
response.stubs(:code).returns "200"
|
155
|
-
|
156
|
-
@searcher.deserialize(response).should == "myobject"
|
157
|
-
end
|
158
|
-
|
159
|
-
it "should convert and return multiple instances if the return code is in the 200s and 'multiple' is specified" do
|
160
|
-
@model.expects(:convert_from_multiple).with("myformat", "mydata").returns "myobjects"
|
161
|
-
|
162
|
-
response = mock 'response'
|
163
|
-
response.stubs(:[]).with("content-type").returns "myformat"
|
164
|
-
response.stubs(:[]).with("content-encoding").returns nil
|
165
|
-
response.stubs(:body).returns "mydata"
|
166
|
-
response.stubs(:code).returns "200"
|
167
|
-
|
168
|
-
@searcher.deserialize(response, true).should == "myobjects"
|
169
|
-
end
|
170
|
-
|
171
|
-
it "should strip the content-type header to keep only the mime-type" do
|
172
|
-
@model.expects(:convert_from).with("text/plain", "mydata").returns "myobject"
|
173
|
-
|
174
|
-
response = mock 'response'
|
175
|
-
response.stubs(:[]).with("content-type").returns "text/plain; charset=utf-8"
|
176
|
-
response.stubs(:[]).with("content-encoding").returns nil
|
177
|
-
response.stubs(:body).returns "mydata"
|
178
|
-
response.stubs(:code).returns "200"
|
179
|
-
|
180
|
-
@searcher.deserialize(response)
|
181
|
-
end
|
182
|
-
|
183
|
-
it "should uncompress the body" do
|
184
|
-
@model.expects(:convert_from).with("myformat", "uncompressed mydata").returns "myobject"
|
185
|
-
|
186
|
-
response = mock 'response'
|
187
|
-
response.stubs(:[]).with("content-type").returns "myformat"
|
188
|
-
response.stubs(:body).returns "compressed mydata"
|
189
|
-
response.stubs(:code).returns "200"
|
190
|
-
|
191
|
-
@searcher.expects(:uncompress_body).with(response).returns("uncompressed mydata")
|
192
|
-
|
193
|
-
@searcher.deserialize(response).should == "myobject"
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
181
|
describe "when creating an HTTP client" do
|
198
182
|
it "should use the class's server and port if the indirection request provides neither" do
|
199
183
|
@request = stub 'request', :key => "foo", :server => nil, :port => nil
|
200
|
-
|
201
|
-
|
184
|
+
terminus.class.expects(:port).returns 321
|
185
|
+
terminus.class.expects(:server).returns "myserver"
|
202
186
|
Puppet::Network::HTTP::Connection.expects(:new).with("myserver", 321).returns "myconn"
|
203
|
-
|
187
|
+
terminus.network(@request).should == "myconn"
|
204
188
|
end
|
205
189
|
|
206
190
|
it "should use the server from the indirection request if one is present" do
|
207
191
|
@request = stub 'request', :key => "foo", :server => "myserver", :port => nil
|
208
|
-
|
192
|
+
terminus.class.stubs(:port).returns 321
|
209
193
|
Puppet::Network::HTTP::Connection.expects(:new).with("myserver", 321).returns "myconn"
|
210
|
-
|
194
|
+
terminus.network(@request).should == "myconn"
|
211
195
|
end
|
212
196
|
|
213
197
|
it "should use the port from the indirection request if one is present" do
|
214
198
|
@request = stub 'request', :key => "foo", :server => nil, :port => 321
|
215
|
-
|
199
|
+
terminus.class.stubs(:server).returns "myserver"
|
216
200
|
Puppet::Network::HTTP::Connection.expects(:new).with("myserver", 321).returns "myconn"
|
217
|
-
|
201
|
+
terminus.network(@request).should == "myconn"
|
218
202
|
end
|
219
203
|
end
|
220
204
|
|
221
|
-
describe "
|
222
|
-
|
223
|
-
|
224
|
-
|
205
|
+
describe "#find" do
|
206
|
+
let(:http_method) { :get }
|
207
|
+
let(:response) { mock_response(200, 'body') }
|
208
|
+
let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) }
|
209
|
+
let(:request) { find_request('foo') }
|
225
210
|
|
226
|
-
|
227
|
-
|
211
|
+
before :each do
|
212
|
+
terminus.stubs(:network).returns(connection)
|
228
213
|
end
|
229
214
|
|
230
|
-
|
231
|
-
|
215
|
+
it_behaves_like 'a REST terminus method', :find
|
216
|
+
it_behaves_like 'a deserializing terminus method', :find
|
217
|
+
|
218
|
+
describe "with a long set of parameters" do
|
219
|
+
it "calls post on the connection with the query params in the body" do
|
232
220
|
params = {}
|
233
221
|
'aa'.upto('zz') do |s|
|
234
222
|
params[s] = 'foo'
|
@@ -239,283 +227,270 @@ describe Puppet::Indirector::REST do
|
|
239
227
|
# to avoid a failure.
|
240
228
|
params.delete('ip')
|
241
229
|
|
242
|
-
|
230
|
+
request = find_request('whoa', params)
|
243
231
|
|
244
|
-
|
245
|
-
|
246
|
-
end.returns(
|
232
|
+
connection.expects(:post).with do |uri, body|
|
233
|
+
body.split("&").sort == params.map {|key,value| "#{key}=#{value}"}.sort
|
234
|
+
end.returns(mock_response(200, 'body'))
|
247
235
|
|
248
|
-
|
236
|
+
terminus.find(request)
|
249
237
|
end
|
250
238
|
end
|
251
239
|
|
252
|
-
describe "with
|
253
|
-
it "
|
254
|
-
|
255
|
-
|
256
|
-
|
240
|
+
describe "with no parameters" do
|
241
|
+
it "calls get on the connection" do
|
242
|
+
request = find_request('foo bar')
|
243
|
+
|
244
|
+
connection.expects(:get).with('/production/test_model/foo%20bar?', anything).returns(mock_response('200', 'response body'))
|
245
|
+
|
246
|
+
terminus.find(request).should == model.new('foo bar', 'response body')
|
257
247
|
end
|
258
248
|
end
|
259
249
|
|
260
|
-
it "
|
261
|
-
|
250
|
+
it "returns nil on 404" do
|
251
|
+
response = mock_response('404', nil)
|
262
252
|
|
263
|
-
|
264
|
-
instance.expects(:name=)
|
265
|
-
@searcher.expects(:deserialize).with(@response).returns instance
|
253
|
+
connection.expects(:get).returns(response)
|
266
254
|
|
267
|
-
|
255
|
+
terminus.find(request).should == nil
|
268
256
|
end
|
269
257
|
|
270
|
-
it "
|
271
|
-
|
258
|
+
it "asks the model to deserialize the response body and sets the name on the resulting object to the find key" do
|
259
|
+
connection.expects(:get).returns response
|
272
260
|
|
273
|
-
|
274
|
-
|
261
|
+
model.expects(:convert_from).with(response['content-type'], response.body).returns(
|
262
|
+
model.new('overwritten', 'decoded body')
|
263
|
+
)
|
275
264
|
|
276
|
-
|
265
|
+
terminus.find(request).should == model.new('foo', 'decoded body')
|
277
266
|
end
|
278
267
|
|
279
|
-
it "
|
280
|
-
|
281
|
-
|
282
|
-
end
|
268
|
+
it "doesn't require the model to support name=" do
|
269
|
+
connection.expects(:get).returns response
|
270
|
+
instance = model.new('name', 'decoded body')
|
283
271
|
|
284
|
-
|
285
|
-
|
272
|
+
model.expects(:convert_from).with(response['content-type'], response.body).returns(instance)
|
273
|
+
instance.expects(:respond_to?).with(:name=).returns(false)
|
274
|
+
instance.expects(:name=).never
|
286
275
|
|
287
|
-
|
288
|
-
@searcher.find(@request)
|
276
|
+
terminus.find(request).should == model.new('name', 'decoded body')
|
289
277
|
end
|
290
278
|
|
291
|
-
it "
|
292
|
-
|
279
|
+
it "provides an Accept header containing the list of supported formats joined with commas" do
|
280
|
+
connection.expects(:get).with(anything, has_entry("Accept" => "supported, formats")).returns(response)
|
293
281
|
|
294
|
-
|
295
|
-
|
282
|
+
terminus.model.expects(:supported_formats).returns %w{supported formats}
|
283
|
+
terminus.find(request)
|
296
284
|
end
|
297
285
|
|
298
|
-
it "
|
299
|
-
|
300
|
-
@searcher.find(@request).should equal(@instance)
|
301
|
-
end
|
286
|
+
it "adds an Accept-Encoding header" do
|
287
|
+
terminus.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"})
|
302
288
|
|
303
|
-
|
304
|
-
@searcher.expects(:deserialize).with(@response).returns @instance
|
305
|
-
@instance.expects(:name=).with "foo bar"
|
306
|
-
@searcher.find(@request)
|
307
|
-
end
|
289
|
+
connection.expects(:get).with(anything, has_entry("accept-encoding" => "gzip")).returns(response)
|
308
290
|
|
309
|
-
|
310
|
-
@searcher.expects(:deserialize).raises(ArgumentError)
|
311
|
-
lambda { @searcher.find(@request) }.should raise_error(ArgumentError)
|
291
|
+
terminus.find(request)
|
312
292
|
end
|
313
|
-
end
|
314
293
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
294
|
+
it "uses only the mime-type from the content-type header when asking the model to deserialize" do
|
295
|
+
response = mock_response('200', 'mydata', "text/plain; charset=utf-8")
|
296
|
+
connection.expects(:get).returns(response)
|
297
|
+
|
298
|
+
model.expects(:convert_from).with("text/plain", "mydata").returns "myobject"
|
319
299
|
|
320
|
-
|
321
|
-
@request = Puppet::Indirector::Request.new(:foo, :head, "foo bar", nil)
|
300
|
+
terminus.find(request).should == "myobject"
|
322
301
|
end
|
323
302
|
|
324
|
-
it "
|
325
|
-
|
326
|
-
|
327
|
-
|
303
|
+
it "decompresses the body before passing it to the model for deserialization" do
|
304
|
+
uncompressed_body = "Why hello there"
|
305
|
+
compressed_body = Zlib::Deflate.deflate(uncompressed_body)
|
306
|
+
|
307
|
+
response = mock_response('200', compressed_body, 'text/plain', 'deflate')
|
308
|
+
connection.expects(:get).returns(response)
|
309
|
+
|
310
|
+
model.expects(:convert_from).with("text/plain", uncompressed_body).returns "myobject"
|
311
|
+
|
312
|
+
terminus.find(request).should == "myobject"
|
328
313
|
end
|
314
|
+
end
|
329
315
|
|
330
|
-
|
331
|
-
|
332
|
-
|
316
|
+
describe "#head" do
|
317
|
+
let(:http_method) { :head }
|
318
|
+
let(:response) { mock_response(200, nil) }
|
319
|
+
let(:connection) { stub('mock http connection', :head => response, :verify_callback= => nil) }
|
320
|
+
let(:request) { head_request('foo') }
|
333
321
|
|
334
|
-
|
322
|
+
before :each do
|
323
|
+
terminus.stubs(:network).returns(connection)
|
335
324
|
end
|
336
325
|
|
337
|
-
|
338
|
-
|
339
|
-
|
326
|
+
it_behaves_like 'a REST terminus method', :head
|
327
|
+
|
328
|
+
it "returns true if there was a successful http response" do
|
329
|
+
connection.expects(:head).returns mock_response('200', nil)
|
340
330
|
|
341
|
-
|
331
|
+
terminus.head(request).should == true
|
342
332
|
end
|
343
333
|
|
344
|
-
it "
|
345
|
-
|
346
|
-
|
347
|
-
|
334
|
+
it "returns false on a 404 response" do
|
335
|
+
connection.expects(:head).returns mock_response('404', nil)
|
336
|
+
|
337
|
+
terminus.head(request).should == false
|
348
338
|
end
|
349
339
|
end
|
350
340
|
|
351
|
-
describe "
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
@model.stubs(:convert_from_multiple)
|
341
|
+
describe "#search" do
|
342
|
+
let(:http_method) { :get }
|
343
|
+
let(:response) { mock_response(200, 'data1,data2,data3') }
|
344
|
+
let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) }
|
345
|
+
let(:request) { search_request('foo') }
|
357
346
|
|
358
|
-
|
347
|
+
before :each do
|
348
|
+
terminus.stubs(:network).returns(connection)
|
359
349
|
end
|
360
350
|
|
351
|
+
it_behaves_like 'a REST terminus method', :search
|
352
|
+
it_behaves_like 'a deserializing terminus method', :search
|
353
|
+
|
361
354
|
it "should call the GET http method on a network connection" do
|
362
|
-
|
363
|
-
|
364
|
-
|
355
|
+
connection.expects(:get).with('/production/test_models/foo', has_key('Accept')).returns mock_response(200, 'data3, data4')
|
356
|
+
|
357
|
+
terminus.search(request)
|
365
358
|
end
|
366
359
|
|
367
|
-
it "
|
368
|
-
|
369
|
-
|
360
|
+
it "returns an empty list on 404" do
|
361
|
+
response = mock_response('404', nil)
|
362
|
+
|
363
|
+
connection.expects(:get).returns(response)
|
370
364
|
|
371
|
-
|
365
|
+
terminus.search(request).should == []
|
372
366
|
end
|
373
367
|
|
374
|
-
it "
|
375
|
-
|
376
|
-
@connection.expects(:get).with { |path, args| path == "/mys/uri" }.returns(@response)
|
377
|
-
@searcher.search(@request)
|
368
|
+
it "asks the model to deserialize the response body into multiple instances" do
|
369
|
+
terminus.search(request).should == [model.new('', 'data1'), model.new('', 'data2'), model.new('', 'data3')]
|
378
370
|
end
|
379
371
|
|
380
372
|
it "should provide an Accept header containing the list of supported formats joined with commas" do
|
381
|
-
|
373
|
+
connection.expects(:get).with(anything, has_entry("Accept" => "supported, formats")).returns(mock_response(200, ''))
|
382
374
|
|
383
|
-
|
384
|
-
|
375
|
+
terminus.model.expects(:supported_formats).returns %w{supported formats}
|
376
|
+
terminus.search(request)
|
385
377
|
end
|
386
378
|
|
387
379
|
it "should return an empty array if serialization returns nil" do
|
388
|
-
|
380
|
+
model.stubs(:convert_from_multiple).returns nil
|
389
381
|
|
390
|
-
|
391
|
-
end
|
392
|
-
|
393
|
-
it "should generate an error when result data deserializes fails" do
|
394
|
-
@searcher.expects(:deserialize).raises(ArgumentError)
|
395
|
-
lambda { @searcher.search(@request) }.should raise_error(ArgumentError)
|
382
|
+
terminus.search(request).should == []
|
396
383
|
end
|
397
384
|
end
|
398
385
|
|
399
|
-
describe "
|
400
|
-
|
401
|
-
|
402
|
-
|
386
|
+
describe "#destroy" do
|
387
|
+
let(:http_method) { :delete }
|
388
|
+
let(:response) { mock_response(200, 'body') }
|
389
|
+
let(:connection) { stub('mock http connection', :delete => response, :verify_callback= => nil) }
|
390
|
+
let(:request) { delete_request('foo') }
|
403
391
|
|
404
|
-
|
392
|
+
before :each do
|
393
|
+
terminus.stubs(:network).returns(connection)
|
405
394
|
end
|
406
395
|
|
396
|
+
it_behaves_like 'a REST terminus method', :destroy
|
397
|
+
it_behaves_like 'a deserializing terminus method', :destroy
|
398
|
+
|
407
399
|
it "should call the DELETE http method on a network connection" do
|
408
|
-
|
409
|
-
|
410
|
-
|
400
|
+
connection.expects(:delete).with('/production/test_model/foo', has_key('Accept')).returns(response)
|
401
|
+
|
402
|
+
terminus.destroy(request)
|
411
403
|
end
|
412
404
|
|
413
405
|
it "should fail if any options are provided, since DELETE apparently does not support query options" do
|
414
|
-
|
406
|
+
request = delete_request('foo', :one => "two", :three => "four")
|
415
407
|
|
416
|
-
|
408
|
+
expect { terminus.destroy(request) }.to raise_error(ArgumentError)
|
417
409
|
end
|
418
410
|
|
419
411
|
it "should deserialize and return the http response" do
|
420
|
-
|
421
|
-
@searcher.expects(:deserialize).with(@response).returns "myobject"
|
422
|
-
|
423
|
-
@searcher.destroy(@request).should == 'myobject'
|
424
|
-
end
|
412
|
+
connection.expects(:delete).returns response
|
425
413
|
|
426
|
-
|
427
|
-
@searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
|
428
|
-
@connection.expects(:delete).with { |path, args| path == "/my/uri" }.returns(@response)
|
429
|
-
@searcher.destroy(@request)
|
414
|
+
terminus.destroy(request).should == model.new('', 'body')
|
430
415
|
end
|
431
416
|
|
432
|
-
it "
|
433
|
-
|
434
|
-
@searcher.destroy(@request)
|
435
|
-
end
|
417
|
+
it "returns nil on 404" do
|
418
|
+
response = mock_response('404', nil)
|
436
419
|
|
437
|
-
|
438
|
-
@connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
|
420
|
+
connection.expects(:delete).returns(response)
|
439
421
|
|
440
|
-
|
441
|
-
@searcher.destroy(@request)
|
422
|
+
terminus.destroy(request).should == nil
|
442
423
|
end
|
443
424
|
|
444
|
-
it "should
|
445
|
-
|
446
|
-
@searcher.destroy(@request).should equal(@instance)
|
447
|
-
end
|
425
|
+
it "should provide an Accept header containing the list of supported formats joined with commas" do
|
426
|
+
connection.expects(:delete).with(anything, has_entry("Accept" => "supported, formats")).returns(response)
|
448
427
|
|
449
|
-
|
450
|
-
|
451
|
-
lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
|
428
|
+
terminus.model.expects(:supported_formats).returns %w{supported formats}
|
429
|
+
terminus.destroy(request)
|
452
430
|
end
|
453
431
|
end
|
454
432
|
|
455
|
-
describe "
|
456
|
-
|
457
|
-
|
458
|
-
|
433
|
+
describe "#save" do
|
434
|
+
let(:http_method) { :put }
|
435
|
+
let(:response) { mock_response(200, 'body') }
|
436
|
+
let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) }
|
437
|
+
let(:instance) { model.new('the thing', 'some contents') }
|
438
|
+
let(:request) { save_request(instance.name, instance) }
|
459
439
|
|
460
|
-
|
461
|
-
|
462
|
-
@request.instance = @instance
|
440
|
+
before :each do
|
441
|
+
terminus.stubs(:network).returns(connection)
|
463
442
|
end
|
464
443
|
|
465
|
-
|
466
|
-
@searcher.expects(:network).returns @connection
|
467
|
-
@connection.expects(:put).returns @response
|
468
|
-
@searcher.save(@request)
|
469
|
-
end
|
444
|
+
it_behaves_like 'a REST terminus method', :save
|
470
445
|
|
471
|
-
it "should
|
472
|
-
|
446
|
+
it "should call the PUT http method on a network connection" do
|
447
|
+
connection.expects(:put).with('/production/test_model/the%20thing', anything, has_key("Content-Type")).returns response
|
473
448
|
|
474
|
-
|
449
|
+
terminus.save(request)
|
475
450
|
end
|
476
451
|
|
477
|
-
it "should
|
478
|
-
|
479
|
-
|
480
|
-
|
452
|
+
it "should fail if any options are provided, since PUT apparently does not support query options" do
|
453
|
+
request = save_request(instance.name, instance, :one => "two", :three => "four")
|
454
|
+
|
455
|
+
expect { terminus.save(request) }.to raise_error(ArgumentError)
|
481
456
|
end
|
482
457
|
|
483
458
|
it "should serialize the instance using the default format and pass the result as the body of the request" do
|
484
|
-
|
485
|
-
|
459
|
+
instance.expects(:render).returns "serial_instance"
|
460
|
+
connection.expects(:put).with(anything, "serial_instance", anything).returns response
|
486
461
|
|
487
|
-
|
462
|
+
terminus.save(request)
|
488
463
|
end
|
489
464
|
|
490
|
-
it "
|
491
|
-
|
492
|
-
@searcher.expects(:deserialize).with(@response).returns "myobject"
|
465
|
+
it "returns nil on 404" do
|
466
|
+
response = mock_response('404', nil)
|
493
467
|
|
494
|
-
|
468
|
+
connection.expects(:put).returns(response)
|
469
|
+
|
470
|
+
terminus.save(request).should == nil
|
495
471
|
end
|
496
472
|
|
497
|
-
it "
|
498
|
-
|
473
|
+
it "returns nil" do
|
474
|
+
connection.expects(:put).returns response
|
499
475
|
|
500
|
-
|
501
|
-
@searcher.save(@request)
|
476
|
+
terminus.save(request).should be_nil
|
502
477
|
end
|
503
478
|
|
504
|
-
it "should provide
|
505
|
-
|
479
|
+
it "should provide an Accept header containing the list of supported formats joined with commas" do
|
480
|
+
connection.expects(:put).with(anything, anything, has_entry("Accept" => "supported, formats")).returns(response)
|
506
481
|
|
507
|
-
|
508
|
-
|
509
|
-
|
482
|
+
instance.expects(:render).returns('')
|
483
|
+
model.expects(:supported_formats).returns %w{supported formats}
|
484
|
+
instance.expects(:mime).returns "supported"
|
510
485
|
|
511
|
-
|
512
|
-
@searcher.expects(:deserialize).with(@response).returns @instance
|
513
|
-
@searcher.save(@request).should equal(@instance)
|
486
|
+
terminus.save(request)
|
514
487
|
end
|
515
488
|
|
516
|
-
it "should
|
517
|
-
|
518
|
-
|
489
|
+
it "should provide a Content-Type header containing the mime-type of the sent object" do
|
490
|
+
instance.expects(:mime).returns "mime"
|
491
|
+
connection.expects(:put).with(anything, anything, has_entry('Content-Type' => "mime")).returns(response)
|
492
|
+
|
493
|
+
terminus.save(request)
|
519
494
|
end
|
520
495
|
end
|
521
496
|
|
@@ -529,13 +504,11 @@ describe Puppet::Indirector::REST do
|
|
529
504
|
].each do |method|
|
530
505
|
it "##{method} passes the SRV service, and fall-back server & port to the request's do_request method" do
|
531
506
|
request = Puppet::Indirector::Request.new(:indirection, method, 'key', nil)
|
532
|
-
stub_response =
|
533
|
-
stub_response.stubs(:code).returns('200')
|
534
|
-
@searcher.stubs(:deserialize)
|
507
|
+
stub_response = mock_response('200', 'body')
|
535
508
|
|
536
|
-
request.expects(:do_request).with(
|
509
|
+
request.expects(:do_request).with(terminus.class.srv_service, terminus.class.server, terminus.class.port).returns(stub_response)
|
537
510
|
|
538
|
-
|
511
|
+
terminus.send(method, request)
|
539
512
|
end
|
540
513
|
end
|
541
514
|
end
|