chef 11.16.4-x86-mingw32 → 11.18.0-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -2
  3. data/lib/chef/api_client.rb +1 -1
  4. data/lib/chef/chef_fs/chef_fs_data_store.rb +3 -2
  5. data/lib/chef/chef_fs/command_line.rb +3 -2
  6. data/lib/chef/chef_fs/data_handler/group_data_handler.rb +5 -1
  7. data/lib/chef/chef_fs/file_system/acl_entry.rb +2 -1
  8. data/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb +2 -1
  9. data/lib/chef/chef_fs/file_system/rest_list_dir.rb +3 -2
  10. data/lib/chef/chef_fs/file_system/rest_list_entry.rb +5 -4
  11. data/lib/chef/config_fetcher.rb +1 -1
  12. data/lib/chef/cookbook/cookbook_version_loader.rb +4 -4
  13. data/lib/chef/cookbook/metadata.rb +1 -1
  14. data/lib/chef/cookbook_version.rb +2 -2
  15. data/lib/chef/data_bag.rb +1 -1
  16. data/lib/chef/data_bag_item.rb +1 -1
  17. data/lib/chef/encrypted_data_bag_item/decryptor.rb +3 -3
  18. data/lib/chef/environment.rb +1 -1
  19. data/lib/chef/exceptions.rb +19 -2
  20. data/lib/chef/json_compat.rb +64 -45
  21. data/lib/chef/knife/bootstrap.rb +2 -2
  22. data/lib/chef/knife/bootstrap/archlinux-gems.erb +2 -2
  23. data/lib/chef/knife/bootstrap/centos5-gems.erb +2 -2
  24. data/lib/chef/knife/bootstrap/chef-aix.erb +2 -2
  25. data/lib/chef/knife/bootstrap/chef-full.erb +2 -2
  26. data/lib/chef/knife/bootstrap/fedora13-gems.erb +2 -2
  27. data/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb +2 -2
  28. data/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb +2 -2
  29. data/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb +2 -2
  30. data/lib/chef/knife/cookbook_site_download.rb +1 -1
  31. data/lib/chef/knife/cookbook_site_install.rb +34 -10
  32. data/lib/chef/knife/cookbook_site_list.rb +1 -1
  33. data/lib/chef/knife/cookbook_site_search.rb +1 -1
  34. data/lib/chef/knife/cookbook_site_share.rb +2 -2
  35. data/lib/chef/knife/cookbook_site_show.rb +3 -3
  36. data/lib/chef/knife/cookbook_site_unshare.rb +1 -1
  37. data/lib/chef/knife/core/subcommand_loader.rb +24 -0
  38. data/lib/chef/knife/deps.rb +3 -2
  39. data/lib/chef/node.rb +1 -1
  40. data/lib/chef/provider/deploy/revision.rb +1 -1
  41. data/lib/chef/provider/dsc_script.rb +32 -5
  42. data/lib/chef/provider/env.rb +25 -10
  43. data/lib/chef/provider/remote_file/cache_control_data.rb +1 -1
  44. data/lib/chef/resource.rb +1 -1
  45. data/lib/chef/resource/dsc_script.rb +2 -16
  46. data/lib/chef/resource_collection.rb +1 -1
  47. data/lib/chef/resource_reporter.rb +3 -3
  48. data/lib/chef/role.rb +1 -1
  49. data/lib/chef/run_list.rb +1 -1
  50. data/lib/chef/user.rb +1 -1
  51. data/lib/chef/util/dsc/local_configuration_manager.rb +15 -11
  52. data/lib/chef/util/powershell/cmdlet_result.rb +2 -2
  53. data/lib/chef/version.rb +1 -2
  54. data/spec/data/bootstrap/test-hints.erb +1 -1
  55. data/spec/data/bootstrap/test.erb +1 -1
  56. data/spec/functional/knife/cookbook_delete_spec.rb +3 -3
  57. data/spec/functional/knife/exec_spec.rb +1 -1
  58. data/spec/functional/resource/dsc_script_spec.rb +92 -47
  59. data/spec/functional/resource/env_spec.rb +3 -4
  60. data/spec/functional/util/powershell/cmdlet_spec.rb +1 -2
  61. data/spec/integration/knife/chef_fs_data_store_spec.rb +1 -1
  62. data/spec/integration/knife/chef_repo_path_spec.rb +6 -1
  63. data/spec/integration/knife/chef_repository_file_system_spec.rb +1 -1
  64. data/spec/integration/knife/chefignore_spec.rb +1 -1
  65. data/spec/integration/knife/common_options_spec.rb +1 -1
  66. data/spec/integration/knife/cookbook_api_ipv6_spec.rb +1 -1
  67. data/spec/integration/knife/delete_spec.rb +1 -1
  68. data/spec/integration/knife/deps_spec.rb +1 -1
  69. data/spec/integration/knife/diff_spec.rb +3 -3
  70. data/spec/integration/knife/download_spec.rb +3 -3
  71. data/spec/integration/knife/list_spec.rb +1 -1
  72. data/spec/integration/knife/raw_spec.rb +11 -1
  73. data/spec/integration/knife/redirection_spec.rb +1 -1
  74. data/spec/integration/knife/serve_spec.rb +1 -1
  75. data/spec/integration/knife/show_spec.rb +1 -1
  76. data/spec/integration/knife/upload_spec.rb +9 -9
  77. data/spec/spec_helper.rb +6 -0
  78. data/spec/support/shared/integration/integration_helper.rb +1 -2
  79. data/spec/support/shared/shared_examples.rb +10 -0
  80. data/spec/tiny_server.rb +2 -1
  81. data/spec/unit/api_client_spec.rb +3 -3
  82. data/spec/unit/chef_fs/data_handler/group_handler_spec.rb +63 -0
  83. data/spec/unit/config_fetcher_spec.rb +1 -1
  84. data/spec/unit/cookbook/metadata_spec.rb +7 -3
  85. data/spec/unit/cookbook_loader_spec.rb +1 -1
  86. data/spec/unit/cookbook_version_spec.rb +4 -0
  87. data/spec/unit/data_bag_item_spec.rb +5 -1
  88. data/spec/unit/data_bag_spec.rb +5 -1
  89. data/spec/unit/deprecation_spec.rb +1 -1
  90. data/spec/unit/encrypted_data_bag_item_spec.rb +14 -7
  91. data/spec/unit/environment_spec.rb +7 -3
  92. data/spec/unit/exceptions_spec.rb +6 -0
  93. data/spec/unit/json_compat_spec.rb +58 -17
  94. data/spec/unit/knife/cookbook_metadata_from_file_spec.rb +0 -1
  95. data/spec/unit/knife/cookbook_site_download_spec.rb +2 -1
  96. data/spec/unit/knife/cookbook_site_install_spec.rb +161 -116
  97. data/spec/unit/knife/cookbook_site_share_spec.rb +6 -6
  98. data/spec/unit/knife/core/bootstrap_context_spec.rb +2 -2
  99. data/spec/unit/knife/core/subcommand_loader_spec.rb +66 -1
  100. data/spec/unit/knife/data_bag_from_file_spec.rb +1 -2
  101. data/spec/unit/node_spec.rb +4 -0
  102. data/spec/unit/provider/dsc_script_spec.rb +134 -105
  103. data/spec/unit/provider/env/windows_spec.rb +2 -2
  104. data/spec/unit/provider/env_spec.rb +76 -11
  105. data/spec/unit/provider/remote_file/cache_control_data_spec.rb +1 -1
  106. data/spec/unit/resource/dsc_script_spec.rb +0 -29
  107. data/spec/unit/resource_collection_spec.rb +5 -1
  108. data/spec/unit/resource_reporter_spec.rb +3 -3
  109. data/spec/unit/resource_spec.rb +5 -1
  110. data/spec/unit/role_spec.rb +4 -0
  111. data/spec/unit/run_list_spec.rb +5 -1
  112. data/spec/unit/user_spec.rb +5 -1
  113. data/spec/unit/util/dsc/local_configuration_manager_spec.rb +15 -10
  114. metadata +11 -9
@@ -0,0 +1,63 @@
1
+ #
2
+ # Author:: Ryan Cragun (<ryan@getchef.com>)
3
+ # Copyright:: Copyright (c) 2014 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+ require 'lib/chef/chef_fs/data_handler/group_data_handler'
21
+
22
+ class TestEntry < Mash
23
+ attr_accessor :name, :org
24
+
25
+ def initialize(name, org)
26
+ @name = name
27
+ @org = org
28
+ end
29
+ end
30
+
31
+ describe Chef::ChefFS::DataHandler::GroupDataHandler do
32
+ describe '#normalize_for_post' do
33
+ let(:entry) do
34
+ TestEntry.new('workers.json', 'hive')
35
+ end
36
+
37
+ let(:group) do
38
+ { 'name' => 'worker_bees',
39
+ 'clients' => %w(honey sting),
40
+ 'users' => %w(fizz buzz),
41
+ 'actors' => %w(honey)
42
+ }
43
+ end
44
+
45
+ let(:normalized) do
46
+ { 'actors' =>
47
+ { 'users' => %w(fizz buzz),
48
+ 'clients'=> %w(honey sting),
49
+ 'groups'=> []
50
+ },
51
+ 'groupname' => 'workers',
52
+ 'name' => 'worker_bees',
53
+ 'orgname' => 'hive'
54
+ }
55
+ end
56
+
57
+ let(:handler) { described_class.new }
58
+
59
+ it 'normalizes the users, clients and groups into actors' do
60
+ expect(handler.normalize_for_post(group, entry)).to eq(normalized)
61
+ end
62
+ end
63
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'chef/config_fetcher'
3
3
  describe Chef::ConfigFetcher do
4
- let(:valid_json) { {:a=>"b"}.to_json }
4
+ let(:valid_json) { Chef::JSONCompat.to_json({:a=>"b"}) }
5
5
  let(:invalid_json) { %q[{"syntax-error": "missing quote}] }
6
6
  let(:http) { double("Chef::HTTP::Simple") }
7
7
 
@@ -582,7 +582,7 @@ describe Chef::Cookbook::Metadata do
582
582
  @meta.version "1.2.3"
583
583
  end
584
584
 
585
- describe "serialize" do
585
+ describe "#to_json" do
586
586
  before(:each) do
587
587
  @serial = Chef::JSONCompat.from_json(@meta.to_json)
588
588
  end
@@ -613,11 +613,15 @@ describe Chef::Cookbook::Metadata do
613
613
  @serial[t].should == @meta.send(t.to_sym)
614
614
  end
615
615
  end
616
+
617
+ it "should produce the same output from to_json and Chef::JSONCompat" do
618
+ expect(@meta.to_json).to eq(Chef::JSONCompat.to_json(@meta))
619
+ end
616
620
  end
617
621
 
618
- describe "deserialize" do
622
+ describe "::from_json" do
619
623
  before(:each) do
620
- @deserial = Chef::Cookbook::Metadata.from_json(@meta.to_json)
624
+ @deserial = Chef::Cookbook::Metadata.from_json(Chef::JSONCompat.to_json(@meta))
621
625
  end
622
626
 
623
627
  it "should deserialize to a Chef::Cookbook::Metadata object" do
@@ -181,7 +181,7 @@ describe Chef::CookbookLoader do
181
181
  aa.to_hash["metadata"].recipes.keys.should include("openldap")
182
182
  expected_desc = "Main Open LDAP configuration"
183
183
  aa.to_hash["metadata"].recipes["openldap"].should == expected_desc
184
- raw = aa.to_hash["metadata"].recipes.to_json
184
+ raw = Chef::JSONCompat.to_json(aa.to_hash["metadata"].recipes)
185
185
  search_str = "\"openldap\":\""
186
186
  key_idx = raw.index(search_str)
187
187
  key_idx.should be > 0
@@ -420,6 +420,10 @@ describe Chef::CookbookVersion do
420
420
  end
421
421
  end
422
422
 
423
+ include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
424
+ let(:subject) { Chef::CookbookVersion.new("tatft", '/tmp/blah') }
425
+ end
426
+
423
427
  end
424
428
 
425
429
  end
@@ -166,7 +166,7 @@ describe Chef::DataBagItem do
166
166
  before(:each) do
167
167
  @data_bag_item.data_bag('mars_volta')
168
168
  @data_bag_item.raw_data = { "id" => "octahedron", "snooze" => { "finally" => :world_will }}
169
- @deserial = Chef::JSONCompat.from_json(@data_bag_item.to_json)
169
+ @deserial = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@data_bag_item))
170
170
  end
171
171
 
172
172
  it "should deserialize to a Chef::DataBagItem object" do
@@ -184,6 +184,10 @@ describe Chef::DataBagItem do
184
184
  it "should have a matching 'snooze' key" do
185
185
  @deserial["snooze"].should == { "finally" => "world_will" }
186
186
  end
187
+
188
+ include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
189
+ let(:subject) { @data_bag_item }
190
+ end
187
191
  end
188
192
 
189
193
  describe "when converting to a string" do
@@ -58,7 +58,7 @@ describe Chef::DataBag do
58
58
  describe "deserialize" do
59
59
  before(:each) do
60
60
  @data_bag.name('mars_volta')
61
- @deserial = Chef::JSONCompat.from_json(@data_bag.to_json)
61
+ @deserial = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@data_bag))
62
62
  end
63
63
 
64
64
  it "should deserialize to a Chef::DataBag object" do
@@ -73,6 +73,10 @@ describe Chef::DataBag do
73
73
  end
74
74
  end
75
75
 
76
+ include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
77
+ let(:subject) { @data_bag }
78
+ end
79
+
76
80
  end
77
81
 
78
82
  describe "when saving" do
@@ -46,7 +46,7 @@ describe Chef::Deprecation do
46
46
  end
47
47
 
48
48
  method_snapshot_file = File.join(CHEF_SPEC_DATA, "file-providers-method-snapshot-chef-11-4.json")
49
- method_snapshot = JSON.parse(File.open(method_snapshot_file).read())
49
+ method_snapshot = Chef::JSONCompat.parse(File.open(method_snapshot_file).read())
50
50
 
51
51
  method_snapshot.each do |class_name, old_methods|
52
52
  class_object = class_from_string(class_name)
@@ -100,6 +100,17 @@ describe Chef::EncryptedDataBagItem::Decryptor do
100
100
  let(:plaintext_data) { {"foo" => "bar"} }
101
101
  let(:encryption_key) { "passwd" }
102
102
  let(:decryption_key) { encryption_key }
103
+ let(:json_wrapped_data) { Chef::JSONCompat.to_json({"json_wrapper" => plaintext_data}) }
104
+
105
+ shared_examples "decryption examples" do
106
+ it "decrypts the encrypted value" do
107
+ decryptor.decrypted_data.should eq(json_wrapped_data)
108
+ end
109
+
110
+ it "unwraps the encrypted data and returns it" do
111
+ decryptor.for_decrypted_item.should eq plaintext_data
112
+ end
113
+ end
103
114
 
104
115
  context "when decrypting a version 2 (JSON+aes-256-cbc+hmac-sha256+random iv) encrypted value" do
105
116
  let(:encrypted_value) do
@@ -112,6 +123,8 @@ describe Chef::EncryptedDataBagItem::Decryptor do
112
123
  Base64.encode64(raw_hmac)
113
124
  end
114
125
 
126
+ include_examples "decryption examples"
127
+
115
128
  it "rejects the data if the hmac is wrong" do
116
129
  encrypted_value["hmac"] = bogus_hmac
117
130
  lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure)
@@ -134,13 +147,7 @@ describe Chef::EncryptedDataBagItem::Decryptor do
134
147
  decryptor.should be_a_kind_of Chef::EncryptedDataBagItem::Decryptor::Version1Decryptor
135
148
  end
136
149
 
137
- it "decrypts the encrypted value" do
138
- decryptor.decrypted_data.should eq({"json_wrapper" => plaintext_data}.to_json)
139
- end
140
-
141
- it "unwraps the encrypted data and returns it" do
142
- decryptor.for_decrypted_item.should eq plaintext_data
143
- end
150
+ include_examples "decryption examples"
144
151
 
145
152
  describe "and the decryption step returns invalid data" do
146
153
  it "raises a decryption failure error" do
@@ -196,7 +196,7 @@ describe Chef::Environment do
196
196
 
197
197
  %w{name description cookbook_versions}.each do |t|
198
198
  it "should include '#{t}'" do
199
- @json.should =~ /"#{t}":#{Regexp.escape(@environment.send(t.to_sym).to_json)}/
199
+ @json.should =~ /"#{t}":#{Regexp.escape(Chef::JSONCompat.to_json(@environment.send(t.to_sym)))}/
200
200
  end
201
201
  end
202
202
 
@@ -207,6 +207,10 @@ describe Chef::Environment do
207
207
  it "should include 'chef_type'" do
208
208
  @json.should =~ /"chef_type":"environment"/
209
209
  end
210
+
211
+ include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
212
+ let(:subject) { @environment }
213
+ end
210
214
  end
211
215
 
212
216
  describe "from_json" do
@@ -222,7 +226,7 @@ describe Chef::Environment do
222
226
  "json_class" => "Chef::Environment",
223
227
  "chef_type" => "environment"
224
228
  }
225
- @environment = Chef::JSONCompat.from_json(@data.to_json)
229
+ @environment = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@data))
226
230
  end
227
231
 
228
232
  it "should return a Chef::Environment" do
@@ -420,7 +424,7 @@ describe Chef::Environment do
420
424
  "description" => "desc",
421
425
  "chef_type" => "environment"
422
426
  }
423
- IO.should_receive(:read).with(File.join(Chef::Config[:environment_path], 'foo.json')).and_return(JSON.dump(environment_hash))
427
+ IO.should_receive(:read).with(File.join(Chef::Config[:environment_path], 'foo.json')).and_return(Chef::JSONCompat.to_json(environment_hash))
424
428
  environment = Chef::Environment.load('foo')
425
429
 
426
430
  environment.should be_a_kind_of(Chef::Environment)
@@ -74,5 +74,11 @@ describe Chef::Exceptions do
74
74
  it "should have an exception class of #{exception} which inherits from #{expected_super_class}" do
75
75
  lambda{ raise exception }.should raise_error(expected_super_class)
76
76
  end
77
+
78
+ if exception.methods.include?(:to_json)
79
+ include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
80
+ let(:subject) { exception }
81
+ end
82
+ end
77
83
  end
78
84
  end
@@ -21,49 +21,90 @@ require 'chef/json_compat'
21
21
 
22
22
  describe Chef::JSONCompat do
23
23
 
24
- describe "with JSON containing an existing class" do
25
- let(:json){'{"json_class": "Chef::Role"}'}
24
+ describe "#from_json with JSON containing an existing class" do
25
+ let(:json) { '{"json_class": "Chef::Role"}' }
26
+
26
27
  it "returns an instance of the class instead of a Hash" do
27
- Chef::JSONCompat.from_json(json).class.should eq Chef::Role
28
+ expect(Chef::JSONCompat.from_json(json).class).to eq Chef::Role
29
+ end
30
+ end
31
+
32
+ describe "#from_json with JSON containing comments" do
33
+ let(:json) { %Q{{\n/* comment */\n// comment 2\n"json_class": "Chef::Role"}} }
34
+
35
+ it "returns an instance of the class instead of a Hash" do
36
+ expect(Chef::JSONCompat.from_json(json).class).to eq Chef::Role
37
+ end
38
+ end
39
+
40
+ describe "#parse with JSON containing comments" do
41
+ let(:json) { %Q{{\n/* comment */\n// comment 2\n"json_class": "Chef::Role"}} }
42
+
43
+ it "returns a Hash" do
44
+ expect(Chef::JSONCompat.parse(json).class).to eq Hash
28
45
  end
29
46
  end
30
47
 
31
48
  describe 'with JSON containing "Chef::Sandbox" as a json_class value' do
32
49
  require 'chef/sandbox' # Only needed for this test
33
- let(:json){'{"json_class": "Chef::Sandbox", "arbitrary": "data"}'}
50
+
51
+ let(:json) { '{"json_class": "Chef::Sandbox", "arbitrary": "data"}' }
52
+
34
53
  it "returns a Hash, because Chef::Sandbox is a dummy class" do
35
- Chef::JSONCompat.from_json(json).should eq({"json_class" => "Chef::Sandbox", "arbitrary" => "data"})
54
+ expect(Chef::JSONCompat.from_json(json)).to eq({"json_class" => "Chef::Sandbox", "arbitrary" => "data"})
36
55
  end
37
56
  end
38
57
 
39
- describe "with a file with 300 or less nested entries" do
40
- before(:all) do
41
- @json = IO.read(File.join(CHEF_SPEC_DATA, 'big_json.json'))
42
- @hash = Chef::JSONCompat.from_json(@json)
58
+ describe "when pretty printing an object that defines #to_json" do
59
+ class Foo
60
+ def to_json(*a)
61
+ Chef::JSONCompat.to_json({'bar' => {'baz' => 5678}}, *a)
62
+ end
43
63
  end
44
64
 
65
+ it "should work" do
66
+ f = Foo.new
67
+ expect(Chef::JSONCompat.to_json_pretty(f)).to eql("{\n \"bar\": {\n \"baz\": 5678\n }\n}\n")
68
+ end
69
+
70
+ include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
71
+ let(:subject) { Foo.new }
72
+ end
73
+ end
74
+
75
+ describe "with a file with 300 or less nested entries" do
76
+ let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'big_json.json')) }
77
+ let(:hash) { Chef::JSONCompat.from_json(json) }
78
+
45
79
  describe "when a big json file is loaded" do
46
80
  it "should create a Hash from the file" do
47
- @hash.should be_kind_of(Hash)
81
+ expect(hash).to be_kind_of(Hash)
48
82
  end
83
+
49
84
  it "should has 'test' as a 300th nested value" do
50
- @hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key'].should == 'test'
85
+ expect(hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']).to eq('test')
51
86
  end
52
87
  end
53
88
  end
89
+
54
90
  describe "with a file with more than 300 nested entries" do
55
- before(:all) do
56
- @json = IO.read(File.join(CHEF_SPEC_DATA, 'big_json_plus_one.json'))
57
- @hash = Chef::JSONCompat.from_json(@json, {:max_nesting => 301})
58
- end
91
+ let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'big_json_plus_one.json')) }
92
+ let(:hash) { Chef::JSONCompat.from_json(json, {:max_nesting => 301}) }
59
93
 
60
94
  describe "when a big json file is loaded" do
61
95
  it "should create a Hash from the file" do
62
- @hash.should be_kind_of(Hash)
96
+ expect(hash).to be_kind_of(Hash)
63
97
  end
98
+
64
99
  it "should has 'test' as a 301st nested value" do
65
- @hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key'].should == 'test'
100
+ expect(hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']).to eq('test')
66
101
  end
67
102
  end
68
103
  end
104
+
105
+ it "should define .to_json on all classes" do
106
+ class SomeClass; end
107
+
108
+ expect(SomeClass.new.respond_to?(:to_json)).to eq(true)
109
+ end
69
110
  end
@@ -27,7 +27,6 @@ describe Chef::Knife::CookbookMetadataFromFile do
27
27
  @tgt = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.json"))
28
28
  @knife = Chef::Knife::CookbookMetadataFromFile.new
29
29
  @knife.name_args = [ @src ]
30
- @knife.stub(:to_json_pretty).and_return(true)
31
30
  @md = Chef::Cookbook::Metadata.new
32
31
  Chef::Cookbook::Metadata.stub(:new).and_return(@md)
33
32
  $stdout.stub(:write)
@@ -26,7 +26,8 @@ describe Chef::Knife::CookbookSiteDownload do
26
26
  @knife.name_args = ['apache2']
27
27
  @noauth_rest = double('no auth rest')
28
28
  @stdout = StringIO.new
29
- @cookbook_api_url = 'http://cookbooks.opscode.com/api/v1/cookbooks'
29
+ @stderr = StringIO.new
30
+ @cookbook_api_url = 'https://supermarket.getchef.com/api/v1/cookbooks'
30
31
  @version = '1.0.2'
31
32
  @version_us = @version.gsub '.', '_'
32
33
  @current_data = { 'deprecated' => false,
@@ -19,132 +19,177 @@
19
19
  require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
20
20
 
21
21
  describe Chef::Knife::CookbookSiteInstall do
22
+ let(:knife) { Chef::Knife::CookbookSiteInstall.new }
23
+ let(:stdout) { StringIO.new }
24
+ let(:stderr) { StringIO.new }
25
+ let(:downloader) { Hash.new }
26
+ let(:repo) { double(:sanity_check => true, :reset_to_default_state => true,
27
+ :prepare_to_import => true, :finalize_updates_to => true,
28
+ :merge_updates_from => true) }
29
+ let(:install_path) { if Chef::Platform.windows?
30
+ 'C:/tmp/chef'
31
+ else
32
+ '/var/tmp/chef'
33
+ end }
34
+
22
35
  before(:each) do
23
36
  require 'chef/knife/core/cookbook_scm_repo'
24
- @stdout = StringIO.new
25
- @knife = Chef::Knife::CookbookSiteInstall.new
26
- @knife.ui.stub(:stdout).and_return(@stdout)
27
- @knife.config = {}
28
- if Chef::Platform.windows?
29
- @install_path = 'C:/tmp/chef'
30
- else
31
- @install_path = '/var/tmp/chef'
32
- end
33
- @knife.config[:cookbook_path] = [ @install_path ]
34
-
35
- @stdout = StringIO.new
36
- @stderr = StringIO.new
37
- @knife.stub(:stderr).and_return(@stdout)
38
- @knife.stub(:stdout).and_return(@stdout)
39
-
40
- #Assume all external commands would have succeed. :(
41
- File.stub(:unlink)
42
- File.stub(:rmtree)
43
- @knife.stub(:shell_out!).and_return(true)
44
-
45
- #CookbookSiteDownload Stup
46
- @downloader = {}
47
- @knife.stub(:download_cookbook_to).and_return(@downloader)
48
- @downloader.stub(:version).and_return do
49
- if @knife.name_args.size == 2
50
- @knife.name_args[1]
37
+
38
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
39
+ knife.config = {}
40
+ knife.config[:cookbook_path] = [ install_path ]
41
+
42
+ allow(knife).to receive(:stderr).and_return(stderr)
43
+ allow(knife).to receive(:stdout).and_return(stdout)
44
+
45
+ # Assume all external commands would have succeed. :(
46
+ allow(File).to receive(:unlink)
47
+ allow(File).to receive(:rmtree)
48
+ allow(knife).to receive(:shell_out!).and_return(true)
49
+
50
+ # CookbookSiteDownload Stup
51
+ allow(knife).to receive(:download_cookbook_to).and_return(downloader)
52
+ allow(downloader).to receive(:version) do
53
+ if knife.name_args.size == 2
54
+ knife.name_args[1]
51
55
  else
52
56
  "0.3.0"
53
57
  end
54
58
  end
55
59
 
56
- #Stubs for CookbookSCMRepo
57
- @repo = double(:sanity_check => true, :reset_to_default_state => true,
58
- :prepare_to_import => true, :finalize_updates_to => true,
59
- :merge_updates_from => true)
60
- Chef::Knife::CookbookSCMRepo.stub(:new).and_return(@repo)
60
+ # Stubs for CookbookSCMRepo
61
+ allow(Chef::Knife::CookbookSCMRepo).to receive(:new).and_return(repo)
61
62
  end
62
63
 
63
-
64
64
  describe "run" do
65
- it "should return an error if a cookbook name is not provided" do
66
- @knife.name_args = []
67
- @knife.ui.should_receive(:error).with("Please specify a cookbook to download and install.")
68
- lambda { @knife.run }.should raise_error(SystemExit)
69
- end
70
-
71
- it "should return an error if more than two arguments are given" do
72
- @knife.name_args = ["foo", "bar", "baz"]
73
- @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.")
74
- lambda { @knife.run }.should raise_error(SystemExit)
75
- end
76
-
77
- it "should return an error if the second argument is not a version" do
78
- @knife.name_args = ["getting-started", "1pass"]
79
- @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.")
80
- lambda { @knife.run }.should raise_error(SystemExit)
81
- end
82
-
83
- it "should return an error if the second argument is a four-digit version" do
84
- @knife.name_args = ["getting-started", "0.0.0.1"]
85
- @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.")
86
- lambda { @knife.run }.should raise_error(SystemExit)
87
- end
88
-
89
- it "should return an error if the second argument is a one-digit version" do
90
- @knife.name_args = ["getting-started", "1"]
91
- @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.")
92
- lambda { @knife.run }.should raise_error(SystemExit)
93
- end
94
-
95
- it "should install the specified version if second argument is a three-digit version" do
96
- @knife.name_args = ["getting-started", "0.1.0"]
97
- @knife.config[:no_deps] = true
98
- upstream_file = File.join(@install_path, "getting-started.tar.gz")
99
- @knife.should_receive(:download_cookbook_to).with(upstream_file)
100
- @knife.should_receive(:extract_cookbook).with(upstream_file, "0.1.0")
101
- @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started"))
102
- @repo.should_receive(:merge_updates_from).with("getting-started", "0.1.0")
103
- @knife.run
104
- end
105
-
106
- it "should install the specified version if second argument is a two-digit version" do
107
- @knife.name_args = ["getting-started", "0.1"]
108
- @knife.config[:no_deps] = true
109
- upstream_file = File.join(@install_path, "getting-started.tar.gz")
110
- @knife.should_receive(:download_cookbook_to).with(upstream_file)
111
- @knife.should_receive(:extract_cookbook).with(upstream_file, "0.1")
112
- @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started"))
113
- @repo.should_receive(:merge_updates_from).with("getting-started", "0.1")
114
- @knife.run
115
- end
116
-
117
- it "should install the latest version if only a cookbook name is given" do
118
- @knife.name_args = ["getting-started"]
119
- @knife.config[:no_deps] = true
120
- upstream_file = File.join(@install_path, "getting-started.tar.gz")
121
- @knife.should_receive(:download_cookbook_to).with(upstream_file)
122
- @knife.should_receive(:extract_cookbook).with(upstream_file, "0.3.0")
123
- @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started"))
124
- @repo.should_receive(:merge_updates_from).with("getting-started", "0.3.0")
125
- @knife.run
126
- end
127
-
128
- it "should not create/reset git branches if use_current_branch is set" do
129
- @knife.name_args = ["getting-started"]
130
- @knife.config[:use_current_branch] = true
131
- @knife.config[:no_deps] = true
132
- upstream_file = File.join(@install_path, "getting-started.tar.gz")
133
- @repo.should_not_receive(:prepare_to_import)
134
- @repo.should_not_receive(:reset_to_default_state)
135
- @knife.run
136
- end
137
-
138
- it "should not raise an error if cookbook_path is a string" do
139
- @knife.config[:cookbook_path] = @install_path
140
- @knife.config[:no_deps] = true
141
- @knife.name_args = ["getting-started"]
142
- upstream_file = File.join(@install_path, "getting-started.tar.gz")
143
- @knife.should_receive(:download_cookbook_to).with(upstream_file)
144
- @knife.should_receive(:extract_cookbook).with(upstream_file, "0.3.0")
145
- @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started"))
146
- @repo.should_receive(:merge_updates_from).with("getting-started", "0.3.0")
147
- lambda { @knife.run }.should_not raise_error
65
+ it "raises an error if a cookbook name is not provided" do
66
+ knife.name_args = []
67
+ expect(knife.ui).to receive(:error).with("Please specify a cookbook to download and install.")
68
+ expect { knife.run }.to raise_error(SystemExit)
69
+ end
70
+
71
+ it "raises an error if more than two arguments are given" do
72
+ knife.name_args = ["foo", "bar", "baz"]
73
+ expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
74
+ expect { knife.run }.to raise_error(SystemExit)
75
+ end
76
+
77
+ it "raises an error if the second argument is not a version" do
78
+ knife.name_args = ["getting-started", "1pass"]
79
+ expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
80
+ expect { knife.run }.to raise_error(SystemExit)
81
+ end
82
+
83
+ it "raises an error if the second argument is a four-digit version" do
84
+ knife.name_args = ["getting-started", "0.0.0.1"]
85
+ expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
86
+ expect { knife.run }.to raise_error(SystemExit)
87
+ end
88
+
89
+ it "raises an error if the second argument is a one-digit version" do
90
+ knife.name_args = ["getting-started", "1"]
91
+ expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
92
+ expect { knife.run }.to raise_error(SystemExit)
93
+ end
94
+
95
+ it "installs the specified version if second argument is a three-digit version" do
96
+ knife.name_args = ["getting-started", "0.1.0"]
97
+ knife.config[:no_deps] = true
98
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
99
+ expect(knife).to receive(:download_cookbook_to).with(upstream_file)
100
+ expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1.0")
101
+ expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
102
+ expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1.0")
103
+ knife.run
104
+ end
105
+
106
+ it "installs the specified version if second argument is a two-digit version" do
107
+ knife.name_args = ["getting-started", "0.1"]
108
+ knife.config[:no_deps] = true
109
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
110
+ expect(knife).to receive(:download_cookbook_to).with(upstream_file)
111
+ expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1")
112
+ expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
113
+ expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1")
114
+ knife.run
115
+ end
116
+
117
+ it "installs the latest version if only a cookbook name is given" do
118
+ knife.name_args = ["getting-started"]
119
+ knife.config[:no_deps] = true
120
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
121
+ expect(knife).to receive(:download_cookbook_to).with(upstream_file)
122
+ expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0")
123
+ expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
124
+ expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0")
125
+ knife.run
126
+ end
127
+
128
+ it "does not create/reset git branches if use_current_branch is set" do
129
+ knife.name_args = ["getting-started"]
130
+ knife.config[:use_current_branch] = true
131
+ knife.config[:no_deps] = true
132
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
133
+ expect(repo).not_to receive(:prepare_to_import)
134
+ expect(repo).not_to receive(:reset_to_default_state)
135
+ knife.run
148
136
  end
137
+
138
+ it "does not raise an error if cookbook_path is a string" do
139
+ knife.config[:cookbook_path] = install_path
140
+ knife.config[:no_deps] = true
141
+ knife.name_args = ["getting-started"]
142
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
143
+ expect(knife).to receive(:download_cookbook_to).with(upstream_file)
144
+ expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0")
145
+ expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
146
+ expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0")
147
+ expect { knife.run }.not_to raise_error
148
+ end
149
+ end # end of run
150
+
151
+ let(:metadata) { Chef::Cookbook::Metadata.new }
152
+ let(:rb_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.rb") }
153
+ let(:json_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.json") }
154
+
155
+ describe "preferred_metadata" do
156
+ before do
157
+ allow(Chef::Cookbook::Metadata).to receive(:new).and_return(metadata)
158
+ allow(File).to receive(:exist?).and_return(false)
159
+ knife.instance_variable_set(:@cookbook_name, "post-punk-kitchen")
160
+ knife.instance_variable_set(:@install_path, install_path)
161
+ end
162
+
163
+ it "returns a populated Metadata object if metadata.rb exists" do
164
+ allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true)
165
+ expect(metadata).to receive(:from_file).with(rb_metadata_path)
166
+ knife.preferred_metadata
167
+ end
168
+
169
+ it "returns a populated Metadata object if metadata.json exists" do
170
+ allow(File).to receive(:exist?).with(json_metadata_path).and_return(true)
171
+ #expect(IO).to receive(:read).with(json_metadata_path)
172
+ allow(IO).to receive(:read)
173
+ expect(metadata).to receive(:from_json)
174
+ knife.preferred_metadata
175
+ end
176
+
177
+ it "prefers metadata.rb over metadata.json" do
178
+ allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true)
179
+ allow(File).to receive(:exist?).with(json_metadata_path).and_return(true)
180
+ allow(IO).to receive(:read)
181
+ expect(metadata).to receive(:from_file).with(rb_metadata_path)
182
+ expect(metadata).not_to receive(:from_json)
183
+ knife.preferred_metadata
184
+ end
185
+
186
+ it "rasies an error if it finds no metadata file" do
187
+ expect { knife.preferred_metadata }.to raise_error { |error|
188
+ expect(error).to be_a(Chef::Exceptions::MetadataNotFound)
189
+ expect(error.cookbook_name).to eq("post-punk-kitchen")
190
+ expect(error.install_path).to eq(install_path)
191
+ }
192
+ end
193
+
149
194
  end
150
195
  end