chefspec 3.4.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -19
  3. data/CHANGELOG.md +32 -1
  4. data/CONTRIBUTING.md +1 -1
  5. data/README.md +37 -9
  6. data/chefspec.gemspec +2 -3
  7. data/examples/render_file/spec/default_spec.rb +32 -17
  8. data/examples/server/recipes/render_with_cached.rb +3 -0
  9. data/examples/server/spec/client_spec.rb +1 -1
  10. data/examples/server/spec/data_bag_spec.rb +1 -1
  11. data/examples/server/spec/environment_spec.rb +1 -1
  12. data/examples/server/spec/node_spec.rb +1 -1
  13. data/examples/server/spec/render_with_cached_spec.rb +16 -0
  14. data/examples/server/spec/role_spec.rb +1 -1
  15. data/features/server.feature +10 -12
  16. data/features/support/executor.rb +2 -0
  17. data/gemfiles/{chef-11.0.0.gemfile → chef-11.12.0.gemfile} +1 -1
  18. data/gemfiles/chef-master.gemfile +1 -1
  19. data/lib/chefspec.rb +1 -0
  20. data/lib/chefspec/api/erl_call.rb +5 -5
  21. data/lib/chefspec/api/execute.rb +5 -5
  22. data/lib/chefspec/api/powershell_script.rb +5 -5
  23. data/lib/chefspec/api/ruby_block.rb +2 -2
  24. data/lib/chefspec/api/script.rb +13 -2
  25. data/lib/chefspec/cacher.rb +2 -1
  26. data/lib/chefspec/coverage.rb +10 -2
  27. data/lib/chefspec/expect_exception.rb +0 -2
  28. data/lib/chefspec/extensions/chef/lwrp_base.rb +5 -1
  29. data/lib/chefspec/extensions/chef/resource/freebsd_package.rb +19 -0
  30. data/lib/chefspec/librarian.rb +5 -2
  31. data/lib/chefspec/macros.rb +3 -3
  32. data/lib/chefspec/matchers/do_nothing_matcher.rb +4 -2
  33. data/lib/chefspec/matchers/include_recipe_matcher.rb +2 -2
  34. data/lib/chefspec/matchers/link_to_matcher.rb +11 -5
  35. data/lib/chefspec/matchers/notifications_matcher.rb +2 -2
  36. data/lib/chefspec/matchers/render_file_matcher.rb +9 -3
  37. data/lib/chefspec/matchers/resource_matcher.rb +2 -2
  38. data/lib/chefspec/matchers/state_attrs_matcher.rb +2 -2
  39. data/lib/chefspec/matchers/subscribes_matcher.rb +4 -4
  40. data/lib/chefspec/renderer.rb +1 -1
  41. data/lib/chefspec/rspec.rb +1 -0
  42. data/lib/chefspec/runner.rb +2 -1
  43. data/lib/chefspec/server.rb +90 -19
  44. data/lib/chefspec/version.rb +1 -1
  45. data/spec/unit/cacher_spec.rb +1 -1
  46. data/spec/unit/expect_exception_spec.rb +6 -6
  47. data/spec/unit/extensions/lwrp_base_spec.rb +9 -1
  48. data/spec/unit/macros_spec.rb +6 -6
  49. data/spec/unit/matchers/include_recipe_matcher_spec.rb +6 -6
  50. data/spec/unit/matchers/link_to_matcher_spec.rb +31 -15
  51. data/spec/unit/matchers/notifications_matcher_spec.rb +4 -4
  52. data/spec/unit/matchers/render_file_matcher_spec.rb +19 -10
  53. data/spec/unit/matchers/state_attrs_matcher_spec.rb +28 -24
  54. data/spec/unit/matchers/subscribes_matcher_spec.rb +7 -7
  55. data/spec/unit/renderer_spec.rb +7 -7
  56. data/spec/unit/runner_spec.rb +39 -9
  57. metadata +10 -25
  58. data/gemfiles/chef-11.2.0.gemfile +0 -5
  59. data/gemfiles/chef-11.4.4.gemfile +0 -5
  60. data/gemfiles/chef-11.6.0.gemfile +0 -5
  61. data/gemfiles/chef-11.8.0.gemfile +0 -5
@@ -15,19 +15,19 @@ module ChefSpec::API
15
15
  # The Examples section demonstrates the different ways to test a
16
16
  # +powershell_script+ resource with ChefSpec.
17
17
  #
18
- # @example Assert that a +powershell_script+ was runed
18
+ # @example Assert that a +powershell_script+ was run
19
19
  # expect(chef_run).to run_powershell_script('/tmp')
20
20
  #
21
- # @example Assert that a +powershell_script+ was runed with predicate matchers
21
+ # @example Assert that a +powershell_script+ was run with predicate matchers
22
22
  # expect(chef_run).to run_powershell_script('/tmp').with_user('svargo')
23
23
  #
24
- # @example Assert that a +powershell_script+ was runed with attributes
24
+ # @example Assert that a +powershell_script+ was run with attributes
25
25
  # expect(chef_run).to run_powershell_script('/tmp').with(user: 'svargo')
26
26
  #
27
- # @example Assert that a +powershell_script+ was runed using a regex
27
+ # @example Assert that a +powershell_script+ was run using a regex
28
28
  # expect(chef_run).to run_powershell_script('/tmp').with(user: /sva(.+)/)
29
29
  #
30
- # @example Assert that a +powershell_script+ was _not_ runed
30
+ # @example Assert that a +powershell_script+ was _not_ run
31
31
  # expect(chef_run).to_not run_powershell_script('/tmp')
32
32
  #
33
33
  #
@@ -18,10 +18,10 @@ module ChefSpec::API
18
18
  # The Examples section demonstrates the different ways to test a
19
19
  # +ruby_block+ resource with ChefSpec.
20
20
  #
21
- # @example Assert that a +ruby_block+ was runed
21
+ # @example Assert that a +ruby_block+ was run
22
22
  # expect(chef_run).to run_ruby_block('do_something')
23
23
  #
24
- # @example Assert that a +ruby_block+ was _not_ runed
24
+ # @example Assert that a +ruby_block+ was _not_ run
25
25
  # expect(chef_run).to_not run_ruby_block('do_something')
26
26
  #
27
27
  #
@@ -1,8 +1,6 @@
1
1
  module ChefSpec::API
2
2
  # @since 1.0.0
3
3
  module ScriptMatchers
4
- ChefSpec::Runner.define_runner_method :script
5
-
6
4
  #
7
5
  # Assert that a +bash+ resource exists in the Chef run with the
8
6
  # action +:run+. Given a Chef Recipe that runs "command" using
@@ -40,6 +38,8 @@ module ChefSpec::API
40
38
  ChefSpec::Matchers::ResourceMatcher.new(:bash, :run, resource_name)
41
39
  end
42
40
 
41
+ ChefSpec::Runner.define_runner_method :bash
42
+
43
43
  #
44
44
  # Assert that a +csh+ resource exists in the Chef run with the
45
45
  # action +:run+. Given a Chef Recipe that runs "command" using
@@ -77,6 +77,8 @@ module ChefSpec::API
77
77
  ChefSpec::Matchers::ResourceMatcher.new(:csh, :run, resource_name)
78
78
  end
79
79
 
80
+ ChefSpec::Runner.define_runner_method :csh
81
+
80
82
  #
81
83
  # Assert that a +perl+ resource exists in the Chef run with the
82
84
  # action +:run+. Given a Chef Recipe that runs "command" using
@@ -114,6 +116,8 @@ module ChefSpec::API
114
116
  ChefSpec::Matchers::ResourceMatcher.new(:perl, :run, resource_name)
115
117
  end
116
118
 
119
+ ChefSpec::Runner.define_runner_method :perl
120
+
117
121
  #
118
122
  # Assert that a +python+ resource exists in the Chef run with the
119
123
  # action +:run+. Given a Chef Recipe that runs "command" using
@@ -151,6 +155,8 @@ module ChefSpec::API
151
155
  ChefSpec::Matchers::ResourceMatcher.new(:python, :run, resource_name)
152
156
  end
153
157
 
158
+ ChefSpec::Runner.define_runner_method :python
159
+
154
160
  #
155
161
  # Assert that a +ruby+ resource exists in the Chef run with the
156
162
  # action +:run+. Given a Chef Recipe that runs "command" using
@@ -188,6 +194,8 @@ module ChefSpec::API
188
194
  ChefSpec::Matchers::ResourceMatcher.new(:ruby, :run, resource_name)
189
195
  end
190
196
 
197
+ ChefSpec::Runner.define_runner_method :ruby
198
+
191
199
  #
192
200
  # Assert that a +script+ resource exists in the Chef run with the
193
201
  # action +:run+. Given a Chef Recipe that runs "command" using
@@ -224,5 +232,8 @@ module ChefSpec::API
224
232
  def run_script(resource_name)
225
233
  ChefSpec::Matchers::ResourceMatcher.new(:script, :run, resource_name)
226
234
  end
235
+
236
+
237
+ ChefSpec::Runner.define_runner_method :script
227
238
  end
228
239
  end
@@ -34,7 +34,8 @@ module ChefSpec
34
34
  FINALIZER = lambda { |id| @@cache.delete(id) }
35
35
 
36
36
  def cached(name, &block)
37
- location = ancestors.first.metadata[:example_group][:location]
37
+ location = ancestors.first.metadata[:location]
38
+ location ||= ancestors.first.metadata[:parent_example_group][:location]
38
39
 
39
40
  define_method(name) do
40
41
  key = [location, name.to_s].join('.')
@@ -161,11 +161,19 @@ module ChefSpec
161
161
  end
162
162
 
163
163
  def source_file
164
- @source_file ||= shortname(@resource.source_line.split(':').first)
164
+ @source_file ||= if @resource.source_line
165
+ shortname(@resource.source_line.split(':').first)
166
+ else
167
+ 'Unknown'
168
+ end
165
169
  end
166
170
 
167
171
  def source_line
168
- @source_line ||= @resource.source_line.split(':', 2).last.to_i
172
+ @source_line ||= if @resource.source_line
173
+ @resource.source_line.split(':', 2).last.to_i
174
+ else
175
+ 'Unknown'
176
+ end
169
177
  end
170
178
 
171
179
  def touch!
@@ -1,5 +1,3 @@
1
- require 'rspec/matchers/built_in/raise_error'
2
-
3
1
  class RSpec::Matchers::BuiltIn::RaiseError
4
2
  class << self
5
3
  attr_accessor :last_run
@@ -39,7 +39,11 @@ class Chef
39
39
  [self, superclass].each do |resource_holder|
40
40
  look_in_parents = false
41
41
  if resource_holder.const_defined?(class_name, look_in_parents)
42
- resource_holder.send(:remove_const, class_name)
42
+ old_class = resource_holder.send(:remove_const, class_name)
43
+
44
+ if resource_holder.respond_to?(:resource_classes)
45
+ resource_holder.resource_classes.delete(old_class)
46
+ end
43
47
  end
44
48
  end
45
49
  end
@@ -0,0 +1,19 @@
1
+ require 'chef/resource/freebsd_package'
2
+
3
+ class Chef
4
+ class Resource
5
+ class FreebsdPackage < Chef::Resource::Package
6
+ #
7
+ # Chef decided it was a good idea to just shellout inside of a resource.
8
+ # Not only is that a horrible fucking idea, but I got flak when I asked
9
+ # to change it. So we are just going to monkey patch the fucking thing so
10
+ # it does not shell out.
11
+ #
12
+ # @return [false]
13
+ #
14
+ def supports_pkgng?
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
@@ -25,7 +25,7 @@ module ChefSpec
25
25
  #
26
26
  def setup!
27
27
  env = ::Librarian::Chef::Environment.new(project_path: Dir.pwd)
28
- env.config_db.local['path'] = @tmpdir
28
+ @originalpath, env.config_db.local['path'] = env.config_db.local['path'], @tmpdir
29
29
  ::Librarian::Action::Resolve.new(env).run
30
30
  ::Librarian::Action::Install.new(env).run
31
31
 
@@ -33,9 +33,12 @@ module ChefSpec
33
33
  end
34
34
 
35
35
  #
36
- # Remove the temporary directory.
36
+ # Remove the temporary directory and restore the librarian-chef cookbook path.
37
37
  #
38
38
  def teardown!
39
+ env = ::Librarian::Chef::Environment.new(project_path: Dir.pwd)
40
+ env.config_db.local['path'] = @originalpath
41
+
39
42
  FileUtils.rm_rf(@tmpdir) if File.exists?(@tmpdir)
40
43
  end
41
44
  end
@@ -44,11 +44,11 @@ module ChefSpec
44
44
  scope = self.is_a?(Class) ? self : self.class
45
45
 
46
46
  metahash = scope.metadata
47
- while metahash.has_key?(:example_group)
48
- metahash = metahash[:example_group]
47
+ while metahash.has_key?(:parent_example_group)
48
+ metahash = metahash[:parent_example_group]
49
49
  end
50
50
 
51
- metahash[:description_args].first.to_s
51
+ metahash[:description].to_s
52
52
  end
53
53
 
54
54
  #
@@ -4,6 +4,8 @@ module ChefSpec::Matchers
4
4
  @resource = resource
5
5
 
6
6
  if @resource
7
+ ChefSpec::Coverage.cover!(@resource)
8
+
7
9
  actions = @resource.performed_actions
8
10
  actions.empty? || actions == [:nothing]
9
11
  else
@@ -15,7 +17,7 @@ module ChefSpec::Matchers
15
17
  'do nothing'
16
18
  end
17
19
 
18
- def failure_message_for_should
20
+ def failure_message
19
21
  if @resource
20
22
  message = %|expected #{@resource} to do nothing, but the following |
21
23
  message << %|actions were performed:|
@@ -35,7 +37,7 @@ module ChefSpec::Matchers
35
37
  end
36
38
  end
37
39
 
38
- def failure_message_for_should_not
40
+ def failure_message_when_negated
39
41
  if @resource
40
42
  message = %|expected #{@resource} to do something, but no actions |
41
43
  message << %|were performed.|
@@ -13,11 +13,11 @@ module ChefSpec::Matchers
13
13
  %Q{include recipe "#{@recipe_name}"}
14
14
  end
15
15
 
16
- def failure_message_for_should
16
+ def failure_message
17
17
  %Q{expected #{loaded_recipes.inspect} to include "#{@recipe_name}"}
18
18
  end
19
19
 
20
- def failure_message_for_should_not
20
+ def failure_message_when_negated
21
21
  %Q{expected "#{@recipe_name}" to not be included}
22
22
  end
23
23
 
@@ -7,16 +7,22 @@ module ChefSpec::Matchers
7
7
  def matches?(link)
8
8
  @link = link
9
9
 
10
- @link.is_a?(Chef::Resource::Link) &&
11
- @link.performed_action?(:create) &&
12
- @path === @link.to
10
+ if @link
11
+ ChefSpec::Coverage.cover!(@link)
12
+
13
+ @link.is_a?(Chef::Resource::Link) &&
14
+ @link.performed_action?(:create) &&
15
+ @path === @link.to
16
+ else
17
+ false
18
+ end
13
19
  end
14
20
 
15
21
  def description
16
22
  %Q{link to "#{@path}"}
17
23
  end
18
24
 
19
- def failure_message_for_should
25
+ def failure_message
20
26
  if @link.nil?
21
27
  %Q{expected "link[#{@path}]" with action :create to be in Chef run}
22
28
  else
@@ -24,7 +30,7 @@ module ChefSpec::Matchers
24
30
  end
25
31
  end
26
32
 
27
- def failure_message_for_should_not
33
+ def failure_message_when_negated
28
34
  %Q{expected "#{@link}" to not link to "#{@path}"}
29
35
  end
30
36
  end
@@ -51,7 +51,7 @@ module ChefSpec::Matchers
51
51
  message
52
52
  end
53
53
 
54
- def failure_message_for_should
54
+ def failure_message
55
55
  if @resource
56
56
  message = %Q{expected "#{@resource}" to notify "#{@expected_resource_type}[#{@expected_resource_name}]"}
57
57
  message << " with action :#{@action}" if @action
@@ -77,7 +77,7 @@ module ChefSpec::Matchers
77
77
  end
78
78
  end
79
79
 
80
- def failure_message_for_should_not
80
+ def failure_message_when_negated
81
81
  if @resource
82
82
  message = %Q{expected "#{@resource}" to not notify "#{@expected_resource_type}[#{@expected_resource_name}]"}
83
83
  message << ", but it did."
@@ -6,7 +6,13 @@ module ChefSpec::Matchers
6
6
 
7
7
  def matches?(runner)
8
8
  @runner = runner
9
- resource && has_create_action? && matches_content?
9
+
10
+ if resource
11
+ ChefSpec::Coverage.cover!(resource)
12
+ has_create_action? && matches_content?
13
+ else
14
+ false
15
+ end
10
16
  end
11
17
 
12
18
  def with_content(expected_content)
@@ -26,7 +32,7 @@ module ChefSpec::Matchers
26
32
  message
27
33
  end
28
34
 
29
- def failure_message_for_should
35
+ def failure_message
30
36
  message = %Q{expected Chef run to render "#{@path}"}
31
37
  if @expected_content
32
38
  message << " matching:"
@@ -41,7 +47,7 @@ module ChefSpec::Matchers
41
47
  message
42
48
  end
43
49
 
44
- def failure_message_for_should_not
50
+ def failure_message_when_negated
45
51
  message = %Q{expected file "#{@path}"}
46
52
  if @expected_content
47
53
  message << " matching:"
@@ -50,7 +50,7 @@ module ChefSpec::Matchers
50
50
  end
51
51
  end
52
52
 
53
- def failure_message_for_should
53
+ def failure_message
54
54
  if resource
55
55
  if resource.performed_action?(@expected_action)
56
56
  if unmatched_parameters.empty?
@@ -79,7 +79,7 @@ module ChefSpec::Matchers
79
79
  end
80
80
  end
81
81
 
82
- def failure_message_for_should_not
82
+ def failure_message_when_negated
83
83
  if resource
84
84
  message = %Q{expected "#{resource.to_s}" actions #{resource.performed_actions.inspect} to not exist}
85
85
  else
@@ -18,7 +18,7 @@ module ChefSpec::Matchers
18
18
  %Q{have state attributes #{@expected_attrs.inspect}}
19
19
  end
20
20
 
21
- def failure_message_for_should
21
+ def failure_message
22
22
  if @resource
23
23
  "expected #{state_attrs.inspect} to equal #{@expected_attrs.inspect}"
24
24
  else
@@ -32,7 +32,7 @@ module ChefSpec::Matchers
32
32
  end
33
33
  end
34
34
 
35
- def failure_message_for_should_not
35
+ def failure_message_when_negated
36
36
  if @resource
37
37
  "expected #{state_attrs.inspect} to not equal " \
38
38
  "#{@expected_attrs.inspect}"
@@ -52,12 +52,12 @@ module ChefSpec::Matchers
52
52
  @instance.description
53
53
  end
54
54
 
55
- def failure_message_for_should
56
- @instance.failure_message_for_should
55
+ def failure_message
56
+ @instance.failure_message
57
57
  end
58
58
 
59
- def failure_message_for_should_not
60
- @instance.failure_message_for_should_not
59
+ def failure_message_when_negated
60
+ @instance.failure_message_when_negated
61
61
  end
62
62
  end
63
63
  end
@@ -108,7 +108,7 @@ module ChefSpec
108
108
  def content_from_cookbook_file(chef_run, cookbook_file)
109
109
  cookbook_name = cookbook_file.cookbook || cookbook_file.cookbook_name
110
110
  cookbook = cookbook_collection(chef_run.node)[cookbook_name]
111
- File.read(cookbook.preferred_filename_on_disk_location(chef_run.node, :files, cookbook_file.source, cookbook_file.path))
111
+ File.read(cookbook.preferred_filename_on_disk_location(chef_run.node, :files, cookbook_file.source))
112
112
  end
113
113
 
114
114
  # The cookbook collection for the current Chef run context. Handles
@@ -10,6 +10,7 @@ RSpec.configure do |config|
10
10
  end
11
11
 
12
12
  config.add_setting :cookbook_path
13
+ config.add_setting :organization, default: 'chefspec'
13
14
  config.add_setting :role_path
14
15
  config.add_setting :log_level, default: :warn
15
16
  config.add_setting :path
@@ -80,6 +80,7 @@ module ChefSpec
80
80
  Chef::Config[:cache_type] = 'Memory'
81
81
  Chef::Config[:client_key] = nil
82
82
  Chef::Config[:cookbook_path] = Array(options[:cookbook_path])
83
+ Chef::Config[:no_lazy_load] = true
83
84
  Chef::Config[:role_path] = Array(options[:role_path])
84
85
  Chef::Config[:force_logger] = true
85
86
  Chef::Config[:solo] = true
@@ -270,7 +271,7 @@ module ChefSpec
270
271
  calling_spec = kaller.find { |line| line =~ /\/spec/ }
271
272
  raise Error::CookbookPathNotFound if calling_spec.nil?
272
273
 
273
- bits = calling_spec.split(':', 2).first.split(File::SEPARATOR)
274
+ bits = calling_spec.split(/:[0-9]/, 2).first.split(File::SEPARATOR)
274
275
  spec_dir = bits.index('spec') || 0
275
276
 
276
277
  File.expand_path(File.join(bits.slice(0, spec_dir), '..'))
@@ -61,6 +61,13 @@ module ChefSpec
61
61
  instance.send(m, *args, &block)
62
62
  end
63
63
 
64
+ #
65
+ # RSpec 3 checks +respond_to?+ for some odd reason.
66
+ #
67
+ def self.respond_to_missing?(m, include_private = false)
68
+ instance.respond_to?(m, include_private) || super
69
+ end
70
+
64
71
  #
65
72
  # @macro entity
66
73
  # @method create_$1(name, data = {})
@@ -105,11 +112,11 @@ module ChefSpec
105
112
  # Convert it to JSON
106
113
  data = JSON.fast_generate(data)
107
114
 
108
- @server.load_data('#{key}' => { name => data })
115
+ load_data(name, '#{key}', data)
109
116
  end
110
117
 
111
118
  def #{method}(name)
112
- data = @server.data_store.get(['#{key}', name])
119
+ data = get('#{key}', name)
113
120
  json = JSON.parse(data)
114
121
 
115
122
  if #{klass}.respond_to?(:json_create)
@@ -122,37 +129,45 @@ module ChefSpec
122
129
  end
123
130
 
124
131
  def #{key}
125
- @server.data_store.list(['#{key}'])
132
+ get('#{key}')
126
133
  end
127
134
 
128
135
  def has_#{method}?(name)
129
- !@server.data_store.get(['#{key}', name]).nil?
136
+ !get('#{key}', name).nil?
130
137
  rescue ChefZero::DataStore::DataNotFoundError
131
138
  false
132
139
  end
133
140
  EOH
134
141
  end
135
142
 
143
+ entity :client, Chef::Client, 'clients'
144
+ entity :data_bag, Chef::DataBag, 'data'
145
+ entity :environment, Chef::Environment, 'environments'
146
+ entity :node, Chef::Node, 'nodes'
147
+ entity :role, Chef::Role, 'roles'
148
+
136
149
  include Singleton
137
150
 
138
151
  attr_reader :server
139
152
 
140
153
  #
141
154
  # Create a new instance of the +ChefSpec::Server+ singleton. This method
142
- # also starts the Chef Zero server in the background.
155
+ # also starts the Chef Zero server in the background under the organization
156
+ # "chefspec".
143
157
  #
144
158
  def initialize
145
159
  @server = ChefZero::Server.new(
160
+ # Set the log level from RSpec, defaulting to warn
146
161
  log_level: RSpec.configuration.log_level || :warn,
162
+
163
+ # Set a random port so ChefSpec may be run in multiple jobs
164
+ port: port,
165
+
166
+ # Disable the "old" way - this is actually +multi_tenant: true+
167
+ single_org: false,
147
168
  )
148
169
  end
149
170
 
150
- entity :client, Chef::Client, 'clients'
151
- entity :data_bag, Chef::DataBag, 'data'
152
- entity :environment, Chef::Environment, 'environments'
153
- entity :node, Chef::Node, 'nodes'
154
- entity :role, Chef::Role, 'roles'
155
-
156
171
  #
157
172
  # Create a new data_bag on the Chef Server. This overrides the method
158
173
  # created by {entity}
@@ -163,7 +178,7 @@ module ChefSpec
163
178
  # the data to load into the data bag
164
179
  #
165
180
  def create_data_bag(name, data = {})
166
- @server.load_data('data' => { name => data })
181
+ load_data(name, 'data', data)
167
182
  end
168
183
 
169
184
  #
@@ -197,7 +212,7 @@ module ChefSpec
197
212
  data = JSON.fast_generate(data)
198
213
  end
199
214
 
200
- @server.load_data('nodes' => { name => data })
215
+ load_data(name, 'nodes', data)
201
216
  end
202
217
 
203
218
  #
@@ -217,6 +232,15 @@ module ChefSpec
217
232
  end
218
233
  end
219
234
 
235
+ #
236
+ # The URL where ChefZero is listening (including the organization).
237
+ #
238
+ # @return [String]
239
+ #
240
+ def chef_server_url
241
+ @chef_server_url ||= File.join(@server.url, 'organizations', organization)
242
+ end
243
+
220
244
  #
221
245
  # Start the Chef Zero server in the background, updating the +Chef::Config+
222
246
  # with the proper +chef_server_url+.
@@ -224,7 +248,7 @@ module ChefSpec
224
248
  def start!
225
249
  unless @server.running?
226
250
  @server.start_background
227
- Chef::Config[:chef_server_url] = @server.url
251
+ Chef::Config[:chef_server_url] = chef_server_url
228
252
  end
229
253
  end
230
254
 
@@ -233,6 +257,7 @@ module ChefSpec
233
257
  #
234
258
  def reset!
235
259
  @server.clear_data
260
+ @server.data_store.create_dir(['organizations'], organization)
236
261
  end
237
262
 
238
263
  #
@@ -246,6 +271,15 @@ module ChefSpec
246
271
 
247
272
  private
248
273
 
274
+ #
275
+ # The name of the Chef organization.
276
+ #
277
+ # @return [String]
278
+ #
279
+ def organization
280
+ RSpec.configuration.organization
281
+ end
282
+
249
283
  #
250
284
  # The directory where any cache information (such as private keys) should
251
285
  # be stored. This cache is destroyed at the end of the run.
@@ -260,16 +294,52 @@ module ChefSpec
260
294
  #
261
295
  # Shortcut method for loading data into Chef Zero.
262
296
  #
263
- # @param [String, Symbol] key
264
- # the key to load
265
297
  # @param [String] name
266
298
  # the name or id of the item to load
299
+ # @param [String, Symbol] key
300
+ # the key to load
267
301
  # @param [Hash] data
268
302
  # the data for the object, which will be converted to JSON and uploaded
269
303
  # to the server
270
304
  #
271
- def load_data(key, name, data = {})
272
- @server.load_data(key.to_s => { name => JSON.fast_generate(data) })
305
+ def load_data(name, key, data = {})
306
+ @server.load_data({ key => { name => data } }, organization)
307
+ end
308
+
309
+ #
310
+ # Get the path to an item in the data store.
311
+ #
312
+ def get(*args)
313
+ if args.size == 1
314
+ @server.data_store.list(with_organization(*args))
315
+ else
316
+ @server.data_store.get(with_organization(*args))
317
+ end
318
+ end
319
+
320
+ #
321
+ # Prefix the given args with the organization. This is used by the data
322
+ # store to fetch elements.
323
+ #
324
+ # @return [Array<String>]
325
+ #
326
+ def with_organization(*args)
327
+ ['organizations', organization, *args]
328
+ end
329
+
330
+ #
331
+ # A randomly assigned, open port for run the Chef Zero server.
332
+ #
333
+ # @return [Fixnum]
334
+ #
335
+ def port
336
+ return @port if @port
337
+
338
+ @server = TCPServer.new('127.0.0.1', 0)
339
+ @port = @server.addr[1].to_i
340
+ @server.close
341
+
342
+ return @port
273
343
  end
274
344
  end
275
345
  end
@@ -277,7 +347,8 @@ end
277
347
  ChefSpec::Server.start!
278
348
 
279
349
  RSpec.configure do |config|
280
- config.after(:each) { ChefSpec::Server.reset! }
350
+ config.before(:each) { ChefSpec::Server.reset! }
351
+ config.after(:each) { ChefSpec::Server.reset! }
281
352
  end
282
353
 
283
354
  at_exit { ChefSpec::Server.stop! }