berkshelf 1.4.6 → 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.md +1 -5
  3. data/CONTRIBUTING.md +3 -1
  4. data/Gemfile +11 -1
  5. data/README.md +16 -0
  6. data/Thorfile +3 -1
  7. data/berkshelf.gemspec +26 -38
  8. data/features/apply_command.feature +32 -0
  9. data/features/configure_command.feature +31 -0
  10. data/features/contingent_command.feature +5 -5
  11. data/features/default_locations.feature +2 -2
  12. data/features/groups_install.feature +19 -20
  13. data/features/info_command.feature +13 -13
  14. data/features/install_command.feature +86 -83
  15. data/features/json_formatter.feature +60 -23
  16. data/features/list_command.feature +5 -11
  17. data/features/lockfile.feature +286 -6
  18. data/features/open_command.feature +8 -4
  19. data/features/outdated_command.feature +8 -15
  20. data/features/package_command.feature +39 -0
  21. data/features/show_command.feature +8 -9
  22. data/features/step_definitions/chef_server_steps.rb +20 -2
  23. data/features/step_definitions/cli_steps.rb +10 -2
  24. data/features/step_definitions/configure_cli_steps.rb +7 -0
  25. data/features/step_definitions/filesystem_steps.rb +19 -14
  26. data/features/step_definitions/json_steps.rb +22 -5
  27. data/features/step_definitions/utility_steps.rb +13 -1
  28. data/features/support/env.rb +10 -23
  29. data/features/update_command.feature +105 -24
  30. data/features/upload_command.feature +0 -14
  31. data/features/vendor_install.feature +3 -3
  32. data/generator_files/Vagrantfile.erb +11 -11
  33. data/lib/berkshelf.rb +6 -5
  34. data/lib/berkshelf/berksfile.rb +267 -99
  35. data/lib/berkshelf/cli.rb +70 -34
  36. data/lib/berkshelf/cli_commands/test_command.rb +11 -0
  37. data/lib/berkshelf/community_rest.rb +1 -1
  38. data/lib/berkshelf/config.rb +19 -2
  39. data/lib/berkshelf/cookbook_source.rb +41 -12
  40. data/lib/berkshelf/cookbook_store.rb +8 -4
  41. data/lib/berkshelf/errors.rb +61 -29
  42. data/lib/berkshelf/formatters.rb +13 -19
  43. data/lib/berkshelf/formatters/human_readable.rb +8 -0
  44. data/lib/berkshelf/formatters/json.rb +12 -1
  45. data/lib/berkshelf/formatters/null.rb +23 -0
  46. data/lib/berkshelf/init_generator.rb +22 -11
  47. data/lib/berkshelf/location.rb +8 -10
  48. data/lib/berkshelf/locations/chef_api_location.rb +4 -5
  49. data/lib/berkshelf/locations/git_location.rb +14 -12
  50. data/lib/berkshelf/locations/path_location.rb +16 -1
  51. data/lib/berkshelf/locations/site_location.rb +1 -3
  52. data/lib/berkshelf/lockfile.rb +230 -33
  53. data/lib/berkshelf/resolver.rb +2 -1
  54. data/lib/berkshelf/ui.rb +4 -8
  55. data/lib/berkshelf/version.rb +1 -1
  56. data/lib/thor/monkies/shell.rb +2 -5
  57. data/spec/fixtures/cassettes/Berkshelf_Resolver/{ClassMethods/_initialize → _initialize}/adds_the_dependencies_of_the_source_as_sources.yml +0 -0
  58. data/spec/fixtures/cookbooks/example_cookbook/.gitignore +2 -0
  59. data/spec/fixtures/cookbooks/example_cookbook/.kitchen.yml +26 -0
  60. data/spec/fixtures/cookbooks/example_cookbook/Berksfile.lock +5 -0
  61. data/spec/fixtures/lockfiles/default.lock +11 -0
  62. data/spec/{config/knife.rb → knife.rb.sample} +2 -2
  63. data/spec/spec_helper.rb +15 -3
  64. data/spec/support/chef_api.rb +19 -5
  65. data/spec/support/chef_server.rb +4 -3
  66. data/spec/support/knife.rb +18 -0
  67. data/spec/unit/berkshelf/berksfile_spec.rb +332 -235
  68. data/spec/unit/berkshelf/cached_cookbook_spec.rb +40 -42
  69. data/spec/unit/berkshelf/chef/cookbook/chefignore_spec.rb +11 -15
  70. data/spec/unit/berkshelf/community_rest_spec.rb +16 -14
  71. data/spec/unit/berkshelf/config_spec.rb +36 -20
  72. data/spec/unit/berkshelf/cookbook_generator_spec.rb +6 -10
  73. data/spec/unit/berkshelf/cookbook_source_spec.rb +244 -183
  74. data/spec/unit/berkshelf/cookbook_store_spec.rb +72 -76
  75. data/spec/unit/berkshelf/core_ext/file_utils_spec.rb +2 -2
  76. data/spec/unit/berkshelf/downloader_spec.rb +137 -157
  77. data/spec/unit/berkshelf/errors_spec.rb +1 -1
  78. data/spec/unit/berkshelf/formatters/null_spec.rb +17 -0
  79. data/spec/unit/berkshelf/formatters_spec.rb +83 -90
  80. data/spec/unit/berkshelf/git_spec.rb +219 -207
  81. data/spec/unit/berkshelf/init_generator_spec.rb +73 -73
  82. data/spec/unit/berkshelf/location_spec.rb +143 -162
  83. data/spec/unit/berkshelf/locations/chef_api_location_spec.rb +94 -89
  84. data/spec/unit/berkshelf/locations/git_location_spec.rb +75 -59
  85. data/spec/unit/berkshelf/locations/path_location_spec.rb +46 -30
  86. data/spec/unit/berkshelf/locations/site_location_spec.rb +4 -4
  87. data/spec/unit/berkshelf/lockfile_spec.rb +185 -1
  88. data/spec/unit/berkshelf/logger_spec.rb +6 -24
  89. data/spec/unit/berkshelf/mixin/logging_spec.rb +6 -8
  90. data/spec/unit/berkshelf/resolver_spec.rb +36 -38
  91. data/spec/unit/berkshelf/ui_spec.rb +9 -10
  92. data/spec/unit/berkshelf_spec.rb +41 -40
  93. data/spec/unit/chef/config_spec.rb +9 -11
  94. metadata +55 -203
  95. data/spec/config/berkshelf.pem +0 -27
  96. data/spec/config/validator.pem +0 -27
@@ -5,6 +5,7 @@ module Berkshelf
5
5
 
6
6
  # @return [Berkshelf::Berksfile]
7
7
  attr_reader :berksfile
8
+
8
9
  # @return [Solve::Graph]
9
10
  attr_reader :graph
10
11
 
@@ -14,7 +15,7 @@ module Berkshelf
14
15
  # @option options [Array<CookbookSource>, CookbookSource] sources
15
16
  def initialize(berksfile, options = {})
16
17
  @berksfile = berksfile
17
- @downloader = @berksfile.downloader
18
+ @downloader = berksfile.downloader
18
19
  @graph = Solve::Graph.new
19
20
  @sources = Hash.new
20
21
 
@@ -10,16 +10,12 @@ module Berkshelf
10
10
  @mute = false
11
11
  end
12
12
 
13
- def say(message = '', color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
13
+ def say(message = "", color = nil, force_new_line = nil)
14
14
  return if quiet?
15
15
 
16
- super(message, color, force_new_line)
17
- end
18
-
19
- # @see {say}
20
- def info(message = '', color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
21
- say(message, color, force_new_line)
16
+ super(message, color)
22
17
  end
18
+ alias_method :info, :say
23
19
 
24
20
  def say_status(status, message, log_status = true)
25
21
  return if quiet?
@@ -29,7 +25,7 @@ module Berkshelf
29
25
 
30
26
  def warn(message, color = :yellow)
31
27
  return if quiet?
32
-
28
+
33
29
  say(message, color)
34
30
  end
35
31
 
@@ -1,3 +1,3 @@
1
1
  module Berkshelf
2
- VERSION = "1.4.6"
2
+ VERSION = "2.0.0.beta"
3
3
  end
@@ -1,8 +1,5 @@
1
1
  require 'berkshelf/ui'
2
2
 
3
3
  # @author Seth Vargo <sethvargo@gmail.com>
4
- class ::Thor::Shell::Color
5
- # Include the Berkshelf UI methods - this is used by both
6
- # Vagrant and Berkshelf UI
7
- include ::Berkshelf::UI
8
- end
4
+ # Include the Berkshelf UI methods - this is used by both Vagrant and Berkshelf
5
+ Thor::Base.shell.send(:include, Berkshelf::UI)
@@ -0,0 +1,2 @@
1
+ .kitchen/
2
+ .kitchen.local.yml
@@ -0,0 +1,26 @@
1
+ ---
2
+ driver_plugin: vagrant
3
+
4
+ platforms:
5
+ - name: ubuntu-12.04
6
+ driver_config:
7
+ box: canonical-ubuntu-12.04
8
+ box_url: http://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box
9
+ require_chef_omnibus: true
10
+ - name: ubuntu-10.04
11
+ driver_config:
12
+ box: opscode-ubuntu-10.04
13
+ box_url: http://opscode-vm.s3.amazonaws.com/vagrant/opscode_ubuntu-10.04_chef-11.2.0.box
14
+ - name: centos-6.3
15
+ driver_config:
16
+ box: opscode-centos-6.3
17
+ box_url: http://opscode-vm.s3.amazonaws.com/vagrant/opscode_centos-6.3_chef-11.2.0.box
18
+ - name: centos-5.8
19
+ driver_config:
20
+ box: opscode-centos-5.8
21
+ box_url: http://opscode-vm.s3.amazonaws.com/vagrant/opscode_centos-5.8_chef-11.2.0.box
22
+
23
+ suites:
24
+ - name: default
25
+ run_list: []
26
+ attributes: {}
@@ -0,0 +1,5 @@
1
+ {
2
+ "sha": null,
3
+ "sources": {
4
+ }
5
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "sha":"6b76225554cc1f7c0aea0f8b3f10c6743aeba67e",
3
+ "sources":{
4
+ "build-essential":{
5
+ "locked_version":"1.1.2"
6
+ },
7
+ "chef-client":{
8
+ "locked_version":"2.1.4"
9
+ }
10
+ }
11
+ }
@@ -4,8 +4,8 @@ log_level :info
4
4
  log_location STDOUT
5
5
  node_name "berkshelf"
6
6
  client_key "#{current_dir}/berkshelf.pem"
7
- validation_client_name "validator"
8
- validation_key "#{current_dir}/validator.pem"
7
+ validation_client_name "chef-validator"
8
+ validation_key "#{current_dir}/chef-validator.pem"
9
9
  chef_server_url "http://localhost:4000"
10
10
  cache_type 'BasicFile'
11
11
  cache_options( :path => "#{ENV['HOME']}/.chef/checksums" )
@@ -15,7 +15,7 @@ Spork.prefork do
15
15
 
16
16
  APP_ROOT = File.expand_path('../../', __FILE__)
17
17
  ENV["BERKSHELF_PATH"] = File.join(APP_ROOT, "spec", "tmp", "berkshelf")
18
- ENV["BERKSHELF_CHEF_CONFIG"] = File.join(APP_ROOT, "spec", "config", "knife.rb")
18
+ ENV["BERKSHELF_CHEF_CONFIG"] = File.join(APP_ROOT, "spec", "knife.rb")
19
19
 
20
20
  Dir[File.join(APP_ROOT, "spec/support/**/*.rb")].each {|f| require f}
21
21
 
@@ -24,30 +24,40 @@ Spork.prefork do
24
24
  config.cassette_library_dir = 'spec/fixtures/cassettes'
25
25
  config.hook_into :webmock
26
26
  config.default_cassette_options = { record: :new_episodes }
27
+ config.ignore_localhost = true
27
28
  end
28
29
 
29
30
  RSpec.configure do |config|
30
31
  config.include Berkshelf::RSpec::FileSystemMatchers
31
32
  config.include JsonSpec::Helpers
32
33
  config.include Berkshelf::RSpec::ChefAPI
34
+ config.include Berkshelf::RSpec::ChefServer
35
+
36
+ # Disallow should syntax
37
+ config.expect_with :rspec do |c|
38
+ c.syntax = :expect
39
+ end
33
40
 
34
41
  config.mock_with :rspec
35
42
  config.treat_symbols_as_metadata_keys_with_true_values = true
36
43
  config.filter_run focus: true
37
44
  config.run_all_when_everything_filtered = true
38
45
 
39
- config.before(:suite) do
46
+ config.before(:all) do
40
47
  Berkshelf::RSpec::ChefServer.start
41
48
  WebMock.disable_net_connect!(allow_localhost: true, net_http_connect_on_start: true)
42
49
  end
43
50
 
44
- config.after(:suite) do
51
+ config.after(:all) do
45
52
  Berkshelf::RSpec::ChefServer.stop
46
53
  end
47
54
 
48
55
  config.before(:each) do
56
+ Celluloid.shutdown
57
+ Celluloid.boot
49
58
  clean_tmp_path
50
59
  Berkshelf.cookbook_store = Berkshelf::CookbookStore.new(tmp_path.join("downloader_tmp"))
60
+ Berkshelf.set_format(:null)
51
61
  Berkshelf.ui.mute!
52
62
  end
53
63
 
@@ -164,6 +174,8 @@ Spork.prefork do
164
174
  def run(cmd)
165
175
  `#{cmd}`
166
176
  end
177
+
178
+ Berkshelf::RSpec::Knife.load_knife_config(File.join(APP_ROOT, 'spec/knife.rb'))
167
179
  end
168
180
 
169
181
  Spork.each_run do
@@ -24,8 +24,7 @@ module Berkshelf
24
24
  else
25
25
  ridley.cookbook.delete(name, version)
26
26
  end
27
- rescue Ridley::Errors::HTTPNotFound,
28
- Ridley::Errors::ResourceNotFound
27
+ rescue Ridley::Errors::HTTPNotFound
29
28
  true
30
29
  end
31
30
 
@@ -37,8 +36,7 @@ module Berkshelf
37
36
  else
38
37
  !versions.find { |ver| ver == version }.nil?
39
38
  end
40
- rescue Ridley::Errors::HTTPNotFound,
41
- Ridley::Errors::ResourceNotFound
39
+ rescue Ridley::Errors::HTTPNotFound
42
40
  false
43
41
  end
44
42
 
@@ -89,10 +87,26 @@ EOF
89
87
  File.open(cookbook_path.join("metadata.rb"), 'w+') do |f|
90
88
  f.write metadata
91
89
  end
92
-
90
+
93
91
  cookbook_path
94
92
  end
95
93
 
94
+ def create_environment(environment_name)
95
+ ridley.environment.create(name: environment_name)
96
+ end
97
+
98
+ def delete_environment(environment_name)
99
+ ridley.environment.delete(environment_name)
100
+ end
101
+
102
+ def environment(environment_name)
103
+ ridley.environment.find(environment_name)
104
+ end
105
+
106
+ def environment_exists?(environment_name)
107
+ !environment(environment_name).nil?
108
+ end
109
+
96
110
  private
97
111
 
98
112
  def ridley
@@ -14,14 +14,15 @@ module Berkshelf::RSpec
14
14
  end
15
15
 
16
16
  def server
17
- @server ||= ChefZero::Server.new(port: PORT, generate_real_keys: false)
17
+ @server ||= ChefZero::Server.new(port: PORT, signals: false, log_requests: true)
18
18
  end
19
19
 
20
20
  def server_url
21
- (@server && @server.url) || "http://localhost:#{PORT}"
21
+ "http://localhost:#{PORT}"
22
22
  end
23
23
 
24
24
  def start
25
+ Thin::Logging.silent = true
25
26
  server.start_background
26
27
  server.on_response do |request, response|
27
28
  request_log << [ request, response ]
@@ -32,7 +33,7 @@ module Berkshelf::RSpec
32
33
  end
33
34
 
34
35
  def stop
35
- @server.stop if running?
36
+ @server.stop if @server
36
37
  end
37
38
 
38
39
  def running?
@@ -0,0 +1,18 @@
1
+ require 'berkshelf/chef'
2
+
3
+ module Berkshelf
4
+ module RSpec
5
+ module Knife
6
+ class << self
7
+ def load_knife_config(path)
8
+ if File.exist?(path)
9
+ Berkshelf::Chef::Config.from_file(path)
10
+ ENV["CHEF_CONFIG"] = path
11
+ else
12
+ raise "Cannot continue; '#{path}' must exist and have testing credentials." unless ENV['CI']
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,174 +1,162 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Berkshelf::Berksfile do
4
- let(:content) do
5
- <<-EOF
6
- cookbook 'ntp', '<= 1.0.0'
7
- cookbook 'mysql'
8
- cookbook 'nginx', '< 0.101.2'
9
- cookbook 'ssh_known_hosts2', :git => 'https://github.com/erikh/chef-ssh_known_hosts2.git'
10
- EOF
11
- end
4
+ describe '.from_file' do
5
+ let(:cookbook_file) { fixtures_path.join('lockfile_spec', 'with_lock', 'Berksfile') }
12
6
 
13
- describe "ClassMethods" do
14
- subject { described_class }
7
+ it 'reads a Berksfile and returns an instance Berksfile' do
8
+ expect(Berkshelf::Berksfile.from_file(cookbook_file)).to be_a(Berkshelf::Berksfile)
9
+ end
15
10
 
16
- describe "::from_file" do
17
- let(:cookbook_file) { fixtures_path.join('lockfile_spec', 'with_lock', 'Berksfile') }
11
+ context 'when Berksfile does not exist at given path' do
12
+ let(:bad_path) { tmp_path.join('thisdoesnotexist') }
18
13
 
19
- it "reads a Berksfile and returns an instance Berksfile" do
20
- subject.from_file(cookbook_file).should be_a(Berkshelf::Berksfile)
14
+ it 'raises BerksfileNotFound' do
15
+ expect {
16
+ Berkshelf::Berksfile.from_file(bad_path)
17
+ }.to raise_error(Berkshelf::BerksfileNotFound)
21
18
  end
19
+ end
20
+ end
22
21
 
23
- context "when Berksfile does not exist at given path" do
24
- let(:bad_path) { tmp_path.join("thisdoesnotexist") }
22
+ describe '.vendor' do
23
+ let(:cached_cookbooks) { [] }
24
+ let(:tmpdir) { Dir.mktmpdir(nil, tmp_path) }
25
25
 
26
- it "raises BerksfileNotFound" do
27
- lambda {
28
- subject.from_file(bad_path)
29
- }.should raise_error(Berkshelf::BerksfileNotFound)
30
- end
31
- end
26
+ it 'returns the expanded filepath of the vendor directory' do
27
+ expect(Berkshelf::Berksfile.vendor(cached_cookbooks, tmpdir)).to eql(tmpdir)
32
28
  end
33
29
 
34
- describe "::vendor" do
35
- let(:cached_cookbooks) { [] }
36
- let(:tmpdir) { Dir.mktmpdir(nil, tmp_path) }
37
-
38
- it "returns the expanded filepath of the vendor directory" do
39
- subject.vendor(cached_cookbooks, tmpdir).should eql(tmpdir)
30
+ context 'with a chefignore' do
31
+ before do
32
+ File.stub(:exists?).and_return(true)
33
+ Berkshelf::Chef::Cookbook::Chefignore.any_instance.stub(:remove_ignores_from).and_return(['metadata.rb'])
40
34
  end
41
35
 
42
- context "with a chefignore" do
43
- it "finds a chefignore file" do
44
- Berkshelf::Chef::Cookbook::Chefignore.should_receive(:find_relative_to).and_return(File.expand_path('chefignore'))
45
- subject.vendor(cached_cookbooks, tmpdir)
46
- end
36
+ it 'finds a chefignore file' do
37
+ Berkshelf::Chef::Cookbook::Chefignore.should_receive(:new).with(File.expand_path('chefignore'))
38
+ Berkshelf::Berksfile.vendor(cached_cookbooks, tmpdir)
39
+ end
47
40
 
48
- it "ignores files specified by a chefignore if a chefignore is present" do
49
- Berkshelf::Chef::Cookbook::Chefignore.should_receive(:find_relative_to).and_return(File.expand_path('chefignore'))
50
- Berkshelf::Chef::Cookbook::Chefignore.any_instance.stub(:remove_ignores_from).and_return(['metadata.rb'])
51
- subject.vendor(cached_cookbooks, tmpdir)
52
- end
41
+ it 'removes files in chefignore' do
42
+ cached_cookbooks = [ Berkshelf::CachedCookbook.from_path(fixtures_path.join('cookbooks/example_cookbook')) ]
43
+ FileUtils.should_receive(:cp_r).with(['metadata.rb'], anything()).exactly(1).times
44
+ FileUtils.should_receive(:cp_r).with(anything(), anything(), anything()).once
45
+ Berkshelf::Berksfile.vendor(cached_cookbooks, tmpdir)
53
46
  end
54
47
  end
55
48
  end
56
49
 
57
- let(:source_one) { double('source_one', name: "nginx") }
58
- let(:source_two) { double('source_two', name: "mysql") }
59
50
 
60
- subject { described_class.new(tmp_path.join("Berksfile")) }
61
51
 
62
- describe "#cookbook" do
63
- let(:name) { "artifact" }
52
+ let(:source_one) { double('source_one', name: 'nginx') }
53
+ let(:source_two) { double('source_two', name: 'mysql') }
54
+
55
+ subject do
56
+ berksfile_path = tmp_path.join('Berksfile').to_s
57
+ FileUtils.touch(berksfile_path)
58
+ Berkshelf::Berksfile.new(berksfile_path)
59
+ end
60
+
61
+ describe '#cookbook' do
62
+ let(:name) { 'artifact' }
64
63
  let(:constraint) { double('constraint') }
65
64
  let(:default_options) { { group: [] } }
66
65
 
67
- it "sends the add_source message with the name, constraint, and options to the instance of the includer" do
66
+ it 'sends the add_source message with the name, constraint, and options to the instance of the includer' do
68
67
  subject.should_receive(:add_source).with(name, constraint, default_options)
69
-
70
- subject.cookbook name, constraint, default_options
68
+ subject.cookbook(name, constraint, default_options)
71
69
  end
72
70
 
73
- it "merges the default options into specified options" do
74
- subject.should_receive(:add_source).with(name, constraint, path: "/Users/reset", group: [])
75
-
76
- subject.cookbook name, constraint, path: "/Users/reset"
71
+ it 'merges the default options into specified options' do
72
+ subject.should_receive(:add_source).with(name, constraint, path: '/Users/reset', group: [])
73
+ subject.cookbook(name, constraint, path: '/Users/reset')
77
74
  end
78
75
 
79
- it "converts a single specified group option into an array of groups" do
76
+ it 'converts a single specified group option into an array of groups' do
80
77
  subject.should_receive(:add_source).with(name, constraint, group: [:production])
81
-
82
- subject.cookbook name, constraint, group: :production
78
+ subject.cookbook(name, constraint, group: :production)
83
79
  end
84
80
 
85
- context "when no constraint specified" do
86
- it "sends the add_source message with a nil value for constraint" do
81
+ context 'when no constraint specified' do
82
+ it 'sends the add_source message with a nil value for constraint' do
87
83
  subject.should_receive(:add_source).with(name, nil, default_options)
88
-
89
- subject.cookbook name, default_options
84
+ subject.cookbook(name, default_options)
90
85
  end
91
86
  end
92
87
 
93
- context "when no options specified" do
94
- it "sends the add_source message with an empty Hash for the value of options" do
88
+ context 'when no options specified' do
89
+ it 'sends the add_source message with an empty Hash for the value of options' do
95
90
  subject.should_receive(:add_source).with(name, constraint, default_options)
96
-
97
- subject.cookbook name, constraint
91
+ subject.cookbook(name, constraint)
98
92
  end
99
93
  end
100
94
  end
101
95
 
102
96
  describe '#group' do
103
- let(:name) { "artifact" }
104
- let(:group) { "production" }
97
+ let(:name) { 'artifact' }
98
+ let(:group) { 'production' }
105
99
 
106
- it "sends the add_source message with an array of groups determined by the parameter passed to the group block" do
100
+ it 'sends the add_source message with an array of groups determined by the parameter passed to the group block' do
107
101
  subject.should_receive(:add_source).with(name, nil, group: [group])
108
102
 
109
- subject.group group do
110
- subject.cookbook name
103
+ subject.group(group) do
104
+ subject.cookbook(name)
111
105
  end
112
106
  end
113
107
  end
114
108
 
115
- describe "#metadata" do
116
- let(:cb_path) { fixtures_path.join('cookbooks/example_cookbook') }
117
- subject { described_class.new(cb_path.join("Berksfile")) }
109
+ describe '#metadata' do
110
+ let(:path) { fixtures_path.join('cookbooks/example_cookbook') }
111
+ subject { Berkshelf::Berksfile.new(path.join('Berksfile')) }
118
112
 
119
- before(:each) { Dir.chdir(cb_path) }
120
-
121
- it "sends the add_source message with an explicit version constraint and the path to the cookbook" do
122
- subject.should_receive(:add_source).with("example_cookbook", "= 0.5.0", path: cb_path.to_s)
113
+ before { Dir.chdir(path) }
123
114
 
115
+ it 'sends the add_source message with an explicit version constraint and the path to the cookbook' do
116
+ subject.should_receive(:add_source).with('example_cookbook', '= 0.5.0', path: path.to_s)
124
117
  subject.metadata
125
118
  end
126
119
  end
127
120
 
128
- describe "#site" do
129
- let(:uri) { "http://opscode/v1" }
121
+ describe '#site' do
122
+ let(:uri) { 'http://opscode/v1' }
130
123
 
131
- it "sends the add_location to the instance of the implementing class with a SiteLocation" do
124
+ it 'sends the add_location to the instance of the implementing class with a SiteLocation' do
132
125
  subject.should_receive(:add_location).with(:site, uri)
133
-
134
126
  subject.site(uri)
135
127
  end
136
128
 
137
- context "given the symbol :opscode" do
138
- it "sends an add_location message with the default Opscode Community API as the first parameter" do
129
+ context 'given the symbol :opscode' do
130
+ it 'sends an add_location message with the default Opscode Community API as the first parameter' do
139
131
  subject.should_receive(:add_location).with(:site, :opscode)
140
-
141
132
  subject.site(:opscode)
142
133
  end
143
134
  end
144
135
  end
145
136
 
146
- describe "#chef_api" do
147
- let(:uri) { "http://chef:8080/" }
137
+ describe '#chef_api' do
138
+ let(:uri) { 'http://chef:8080/' }
148
139
 
149
- it "sends and add_location message with the type :chef_api and the given URI" do
140
+ it 'sends and add_location message with the type :chef_api and the given URI' do
150
141
  subject.should_receive(:add_location).with(:chef_api, uri, {})
151
-
152
142
  subject.chef_api(uri)
153
143
  end
154
144
 
155
- it "also sends any options passed" do
156
- options = { node_name: "reset", client_key: "/Users/reset/.chef/reset.pem" }
145
+ it 'also sends any options passed' do
146
+ options = { node_name: 'reset', client_key: '/Users/reset/.chef/reset.pem' }
157
147
  subject.should_receive(:add_location).with(:chef_api, uri, options)
158
-
159
148
  subject.chef_api(uri, options)
160
149
  end
161
150
 
162
- context "given the symbol :config" do
163
- it "sends an add_location message with the the type :chef_api and the URI :config" do
151
+ context 'given the symbol :config' do
152
+ it 'sends an add_location message with the the type :chef_api and the URI :config' do
164
153
  subject.should_receive(:add_location).with(:chef_api, :config, {})
165
-
166
154
  subject.chef_api(:config)
167
155
  end
168
156
  end
169
157
  end
170
158
 
171
- describe "#sources" do
159
+ describe '#sources' do
172
160
  let(:groups) do
173
161
  [
174
162
  :nautilus,
@@ -176,146 +164,146 @@ EOF
176
164
  ]
177
165
  end
178
166
 
179
- it "returns all CookbookSources added to the instance of Berksfile" do
167
+ it 'returns all CookbookSources added to the instance of Berksfile' do
180
168
  subject.add_source(source_one.name)
181
169
  subject.add_source(source_two.name)
182
170
 
183
- subject.sources.should have(2).items
184
- subject.should have_source(source_one.name)
185
- subject.should have_source(source_two.name)
171
+ expect(subject.sources).to have(2).items
172
+ expect(subject).to have_source(source_one.name)
173
+ expect(subject).to have_source(source_two.name)
186
174
  end
187
175
 
188
- context "given the option :except" do
189
- before(:each) do
176
+ context 'given the option :except' do
177
+ before do
190
178
  source_one.stub(:groups) { [:default, :skarner] }
191
179
  source_two.stub(:groups) { [:default, :nautilus] }
192
180
  end
193
181
 
194
- it "returns all of the sources except the ones in the given groups" do
182
+ it 'returns all of the sources except the ones in the given groups' do
195
183
  subject.add_source(source_one.name, nil, group: [:default, :skarner])
196
184
  subject.add_source(source_two.name, nil, group: [:default, :nautilus])
197
185
  filtered = subject.sources(except: :nautilus)
198
186
 
199
- filtered.should have(1).item
200
- filtered.first.name.should eql(source_one.name)
187
+ expect(filtered).to have(1).item
188
+ expect(filtered.first.name).to eq(source_one.name)
201
189
  end
202
190
  end
203
191
 
204
- context "given the option :only" do
205
- before(:each) do
192
+ context 'given the option :only' do
193
+ before do
206
194
  source_one.stub(:groups) { [:default, :skarner] }
207
195
  source_two.stub(:groups) { [:default, :nautilus] }
208
196
  end
209
197
 
210
- it "returns only the sources in the givne groups" do
198
+ it 'returns only the sources in the givne groups' do
211
199
  subject.add_source(source_one.name, nil, group: [:default, :skarner])
212
200
  subject.add_source(source_two.name, nil, group: [:default, :nautilus])
213
201
  filtered = subject.sources(only: :nautilus)
214
202
 
215
- filtered.should have(1).item
216
- filtered.first.name.should eql(source_two.name)
203
+ expect(filtered).to have(1).item
204
+ expect(filtered.first.name).to eq(source_two.name)
217
205
  end
218
206
  end
219
207
 
220
- context "when a value for :only and :except is given" do
221
- it "raises an ArgumentError" do
222
- lambda {
208
+ context 'when a value for :only and :except is given' do
209
+ it 'raises an ArgumentError' do
210
+ expect {
223
211
  subject.sources(only: [:default], except: [:other])
224
- }.should raise_error(Berkshelf::ArgumentError, "Cannot specify both :except and :only")
212
+ }.to raise_error(Berkshelf::ArgumentError, "Cannot specify both :except and :only")
225
213
  end
226
214
  end
227
215
  end
228
216
 
229
- describe "#groups" do
230
- before(:each) do
217
+ describe '#groups' do
218
+ before do
231
219
  subject.stub(:sources) { [source_one, source_two] }
232
220
  source_one.stub(:groups) { [:nautilus, :skarner] }
233
221
  source_two.stub(:groups) { [:nautilus, :riven] }
234
222
  end
235
223
 
236
- it "returns a hash containing keys for every group a source is a member of" do
237
- subject.groups.keys.should have(3).items
238
- subject.groups.should have_key(:nautilus)
239
- subject.groups.should have_key(:skarner)
240
- subject.groups.should have_key(:riven)
224
+ it 'returns a hash containing keys for every group a source is a member of' do
225
+ expect(subject.groups.keys).to have(3).items
226
+ expect(subject.groups).to have_key(:nautilus)
227
+ expect(subject.groups).to have_key(:skarner)
228
+ expect(subject.groups).to have_key(:riven)
241
229
  end
242
230
 
243
- it "returns an Array of CookbookSources who are members of the group for value" do
244
- subject.groups[:nautilus].should have(2).items
245
- subject.groups[:riven].should have(1).item
231
+ it 'returns an Array of CookbookSources who are members of the group for value' do
232
+ expect(subject.groups[:nautilus]).to have(2).items
233
+ expect(subject.groups[:riven]).to have(1).item
246
234
  end
247
235
  end
248
236
 
249
- describe "#resolve" do
237
+ describe '#resolve' do
250
238
  let(:resolver) { double('resolver') }
251
- before(:each) { Berkshelf::Resolver.stub(:new) { resolver } }
239
+ let(:sources) { [source_one, source_two] }
240
+ let(:cached) { [double('cached_one'), double('cached_two')] }
241
+
242
+ before do
243
+ Berkshelf::Resolver.stub(:new).and_return(resolver)
244
+ end
245
+
246
+ it 'resolves the Berksfile' do
247
+ resolver.should_receive(:resolve).and_return(cached)
248
+ resolver.should_receive(:sources).and_return(sources)
252
249
 
253
- it "resolves the Berksfile" do
254
- resolver.should_receive(:resolve).and_return([double('cached_cookbook_one'), double('cached_cookbook_two')])
255
- solution = subject.resolve
256
- solution.should have(2).items
250
+ expect(subject.resolve).to eq({ solution: cached, sources: sources })
257
251
  end
258
252
  end
259
253
 
260
- describe "#install" do
254
+ describe '#install' do
261
255
  let(:resolver) { double('resolver') }
262
- before(:each) { Berkshelf::Resolver.stub(:new) { resolver } }
256
+ let(:lockfile) { double('lockfile') }
263
257
 
264
- context "when a lockfile is not present" do
265
- before(:each) do
266
- subject.should_receive(:lockfile_present?).and_return(false)
267
- resolver.should_receive(:sources).and_return([])
268
- end
258
+ let(:cached_cookbooks) { [double('cached_one'), double('cached_two')] }
259
+ let(:sources) { [source_one, source_two] }
269
260
 
270
- let(:cached_cookbooks) do
271
- [
272
- double('cached_one'),
273
- double('cached_two')
274
- ]
275
- end
261
+ before do
262
+ Berkshelf::Resolver.stub(:new).and_return(resolver)
263
+ Berkshelf::Lockfile.stub(:new).and_return(lockfile)
276
264
 
277
- it "returns the result from sending the message resolve to resolver" do
278
- resolver.should_receive(:resolve).and_return(cached_cookbooks)
265
+ subject.stub(:sha).and_return('abc123')
279
266
 
280
- subject.install.should eql(cached_cookbooks)
267
+ lockfile.stub(:sources).and_return([])
268
+ lockfile.stub(:sha).and_return('xyz456')
269
+
270
+ resolver.stub(:sources).and_return([])
271
+ lockfile.stub(:update)
272
+ end
273
+
274
+ context 'when a lockfile is not present' do
275
+ it 'returns the result from sending the message resolve to resolver' do
276
+ resolver.should_receive(:resolve).and_return(cached_cookbooks)
277
+ expect(subject.install).to eql(cached_cookbooks)
281
278
  end
282
279
 
283
- it "sets a value for self.cached_cookbooks equivalent to the return value" do
280
+ it 'sets a value for self.cached_cookbooks equivalent to the return value' do
284
281
  resolver.should_receive(:resolve).and_return(cached_cookbooks)
285
282
  subject.install
286
283
 
287
- subject.cached_cookbooks.should eql(cached_cookbooks)
284
+ expect(subject.cached_cookbooks).to eql(cached_cookbooks)
288
285
  end
289
286
 
290
- it "creates a new resolver and finds a solution by calling resolve on the resolver" do
287
+ it 'creates a new resolver and finds a solution by calling resolve on the resolver' do
291
288
  resolver.should_receive(:resolve)
292
-
293
289
  subject.install
294
290
  end
295
291
 
296
- it "writes a lockfile with the resolvers sources" do
292
+ it 'writes a lockfile with the resolvers sources' do
297
293
  resolver.should_receive(:resolve)
298
- subject.should_receive(:write_lockfile).with([])
294
+ lockfile.should_receive(:update).with([], sha: 'abc123')
299
295
 
300
296
  subject.install
301
297
  end
302
298
  end
303
299
 
304
- context "when a lockfile is present" do
305
- before(:each) { subject.should_receive(:lockfile_present?).and_return(true) }
306
-
307
- it "does not write a new lock file" do
300
+ context 'when a value for :path is given' do
301
+ before do
308
302
  resolver.should_receive(:resolve)
309
- subject.should_not_receive(:write_lockfile)
310
-
311
- subject.install
303
+ resolver.should_receive(:sources).and_return([])
312
304
  end
313
- end
314
-
315
- context "when a value for :path is given" do
316
- before(:each) { resolver.should_receive(:resolve) }
317
305
 
318
- it "sends the message 'vendor' to Berksfile with the value for :path" do
306
+ it 'sends the message :vendor to Berksfile with the value for :path' do
319
307
  path = double('path')
320
308
  subject.class.should_receive(:vendor).with(subject.cached_cookbooks, path)
321
309
 
@@ -323,99 +311,113 @@ EOF
323
311
  end
324
312
  end
325
313
 
326
- context "when a value for :except is given" do
327
- before(:each) { resolver.should_receive(:resolve) }
328
-
329
- it "filters the sources and gives the results to the Resolver initializer" do
330
- filtered = double('sources')
331
- subject.should_receive(:sources).with(except: [:skip_me]).and_return(filtered)
332
- Berkshelf::Resolver.should_receive(:new).with(anything, sources: filtered)
314
+ context 'when a value for :except is given' do
315
+ before do
316
+ resolver.should_receive(:resolve)
317
+ resolver.should_receive(:sources).and_return([])
318
+ subject.stub(:sources).and_return(sources)
319
+ subject.stub(:apply_lockfile).and_return(sources)
320
+ end
333
321
 
322
+ it 'filters the sources and gives the results to the Resolver initializer' do
323
+ subject.should_receive(:sources).with(except: [:skip_me]).and_return(sources)
334
324
  subject.install(except: [:skip_me])
335
325
  end
336
326
  end
337
327
 
338
- context "when a value for :only is given" do
339
- before(:each) { resolver.should_receive(:resolve) }
340
-
341
- it "filters the sources and gives the results to the Resolver initializer" do
342
- filtered = double('sources')
343
- subject.should_receive(:sources).with(only: [:skip_me]).and_return(filtered)
328
+ context 'when a value for :only is given' do
329
+ before do
330
+ resolver.should_receive(:resolve)
331
+ resolver.should_receive(:sources).and_return([])
332
+ subject.stub(:sources).and_return(sources)
333
+ subject.stub(:apply_lockfile).and_return(sources)
334
+ end
344
335
 
336
+ it 'filters the sources and gives the results to the Resolver initializer' do
337
+ subject.should_receive(:sources).with(only: [:skip_me]).and_return(sources)
345
338
  subject.install(only: [:skip_me])
346
339
  end
347
340
  end
348
341
  end
349
342
 
350
- describe "#load" do
351
- it "reads the content of a Berksfile and adds the sources to the Shelf" do
343
+ describe '#load' do
344
+ let(:content) do
345
+ <<-EOF.strip
346
+ cookbook 'ntp', '<= 1.0.0'
347
+ cookbook 'mysql'
348
+ cookbook 'nginx', '< 0.101.2'
349
+ cookbook 'ssh_known_hosts2', :git => 'https://github.com/erikh/chef-ssh_known_hosts2.git'
350
+ EOF
351
+ end
352
+
353
+ it 'reads the content of a Berksfile and adds the sources to the Shelf' do
352
354
  subject.load(content)
353
355
 
354
- ['ntp', 'mysql', 'nginx', 'ssh_known_hosts2'].each do |name|
355
- subject.should have_source(name)
356
+ %w(ntp mysql nginx ssh_known_hosts2).each do |name|
357
+ expect(subject).to have_source(name)
356
358
  end
357
359
  end
358
360
 
359
- it "returns an instance of Berksfile" do
360
- subject.load(content).should be_a(described_class)
361
+ it 'returns an instance of Berksfile' do
362
+ expect(subject.load(content)).to be_a(Berkshelf::Berksfile)
361
363
  end
362
364
  end
363
365
 
364
- describe "#add_source" do
365
- let(:name) { "cookbook_one" }
366
- let(:constraint) { "= 1.2.0" }
367
- let(:location) { { site: "http://site" } }
366
+ describe '#add_source' do
367
+ let(:name) { 'cookbook_one' }
368
+ let(:constraint) { '= 1.2.0' }
369
+ let(:location) { { site: 'http://site' } }
368
370
 
369
371
  before(:each) do
370
372
  subject.add_source(name, constraint, location)
371
373
  end
372
374
 
373
- it "adds new cookbook source to the list of sources" do
374
- subject.sources.should have(1).source
375
+ it 'adds new cookbook source to the list of sources' do
376
+ expect(subject.sources).to have(1).source
375
377
  end
376
378
 
377
379
  it "adds a cookbook source with a 'name' of the given name" do
378
- subject.sources.first.name.should eql(name)
380
+ expect(subject.sources.first.name).to eq(name)
379
381
  end
380
382
 
381
383
  it "adds a cookbook source with a 'version_constraint' of the given constraint" do
382
- subject.sources.first.version_constraint.to_s.should eql(constraint)
384
+ expect(subject.sources.first.version_constraint.to_s).to eq(constraint)
383
385
  end
384
386
 
385
- it "raises DuplicateSourceDefined if multiple sources of the same name are found" do
386
- lambda {
387
+ it 'raises DuplicateSourceDefined if multiple sources of the same name are found' do
388
+ expect {
387
389
  subject.add_source(name)
388
- }.should raise_error(Berkshelf::DuplicateSourceDefined)
390
+ }.to raise_error(Berkshelf::DuplicateSourceDefined)
389
391
  end
390
392
  end
391
393
 
392
- describe "#add_location" do
394
+ describe '#add_location' do
393
395
  let(:type) { :site }
394
396
  let(:value) { double('value') }
395
397
  let(:options) { double('options') }
396
398
 
397
- it "delegates 'add_location' to the downloader" do
399
+ it 'delegates :add_location to the downloader' do
398
400
  subject.downloader.should_receive(:add_location).with(type, value, options)
399
-
400
401
  subject.add_location(type, value, options)
401
402
  end
402
403
  end
403
404
 
404
- describe "#upload" do
405
+ describe '#upload' do
405
406
  let(:upload) { subject.upload(options) }
406
407
  let(:options) { Hash.new }
407
408
  let(:ssl) { double('ssl', verify: true) }
408
409
  let(:chef) {
409
410
  double('chef',
410
- node_name: "fake-client",
411
- client_key: "client-key",
412
- chef_server_url: "http://configured-chef-server/")
411
+ node_name: 'fake-client',
412
+ client_key: 'client-key',
413
+ chef_server_url: 'http://configured-chef-server/'
414
+ )
413
415
  }
414
416
  let(:berkshelf_config) { double('berks', ssl: ssl, chef: chef) }
415
417
  let(:default_ridley_options) {
416
418
  {
417
- client_name: "fake-client",
418
- client_key: "client-key",
419
+ client_name: 'fake-client',
420
+ client_key: 'client-key',
419
421
  ssl: {
420
422
  verify: true
421
423
  }
@@ -424,82 +426,177 @@ EOF
424
426
 
425
427
  before do
426
428
  Berkshelf::Config.stub(:instance).and_return(berkshelf_config)
427
- subject.stub(:resolve).and_return([])
429
+ subject.stub(:resolve).and_return(solution: [], sources: [])
428
430
  end
429
431
 
430
- context "when there is no :server_url" do
431
- let(:chef) {
432
+ context 'when there is no :server_url' do
433
+ let(:chef) do
432
434
  double('chef',
433
- node_name: "fake-client",
434
- client_key: "client-key",
435
- chef_server_url: nil)
436
- }
437
- let(:message) { "Missing required attribute in your Berkshelf configuration: chef.server_url" }
435
+ node_name: 'fake-client',
436
+ client_key: 'client-key',
437
+ chef_server_url: nil
438
+ )
439
+ end
440
+ let(:message) { 'Missing required attribute in your Berkshelf configuration: chef.server_url' }
438
441
 
439
- it "raises an error" do
442
+ it 'raises an error' do
440
443
  expect {
441
444
  upload
442
- }.to raise_error(Berkshelf::UploadFailure, message)
445
+ }.to raise_error(Berkshelf::ChefConnectionError, message)
443
446
  end
444
447
  end
445
448
 
446
- context "when there is no :client_name" do
447
- let(:chef) {
449
+ context 'when there is no :client_name' do
450
+ let(:chef) do
448
451
  double('chef',
449
452
  node_name: nil,
450
- client_key: "client-key",
451
- chef_server_url: "http://configured-chef-server/")
452
- }
453
- let(:message) { "Missing required attribute in your Berkshelf configuration: chef.node_name" }
453
+ client_key: 'client-key',
454
+ chef_server_url: 'http://configured-chef-server/'
455
+ )
456
+ end
457
+ let(:message) { 'Missing required attribute in your Berkshelf configuration: chef.node_name' }
454
458
 
455
- it "raises an error" do
459
+ it 'raises an error' do
456
460
  expect {
457
461
  upload
458
- }.to raise_error(Berkshelf::UploadFailure, message)
462
+ }.to raise_error(Berkshelf::ChefConnectionError, message)
459
463
  end
460
464
  end
461
465
 
462
- context "when there is no :client_key" do
463
- let(:chef) {
466
+ context 'when there is no :client_key' do
467
+ let(:chef) do
464
468
  double('chef',
465
- node_name: "fake-client",
469
+ node_name: 'fake-client',
466
470
  client_key: nil,
467
- chef_server_url: "http://configured-chef-server/")
468
- }
469
- let(:message) { "Missing required attribute in your Berkshelf configuration: chef.client_key" }
471
+ chef_server_url: 'http://configured-chef-server/'
472
+ )
473
+ end
474
+ let(:message) { 'Missing required attribute in your Berkshelf configuration: chef.client_key' }
470
475
 
471
- it "raises an error" do
476
+ it 'raises an error' do
472
477
  expect {
473
478
  upload
474
- }.to raise_error(Berkshelf::UploadFailure, message)
479
+ }.to raise_error(Berkshelf::ChefConnectionError, message)
475
480
  end
476
481
  end
477
482
 
478
- context "when a Chef Server url is not passed as an option" do
483
+ context 'when a Chef Server url is not passed as an option' do
479
484
  let(:ridley_options) do
480
- {server_url: "http://configured-chef-server/"}.merge(default_ridley_options)
485
+ { server_url: 'http://configured-chef-server/' }.merge(default_ridley_options)
481
486
  end
482
487
 
483
- it "uses Berkshelf::Config configured server_url" do
488
+ it 'uses Berkshelf::Config configured server_url' do
484
489
  Ridley.should_receive(:new).with(ridley_options)
485
490
  upload
486
491
  end
487
492
  end
488
493
 
489
- context "when a Chef Server url is passed as an option" do
490
- let(:options) {
494
+ context 'when a Chef Server url is passed as an option' do
495
+ let(:options) do
491
496
  {
492
- server_url: "http://fake-chef-server.com/"
497
+ server_url: 'http://fake-chef-server.com/'
493
498
  }
494
- }
495
- let(:ridley_options) {
496
- {server_url: "http://fake-chef-server.com/"}.merge(default_ridley_options)
497
- }
499
+ end
500
+ let(:ridley_options) do
501
+ { server_url: 'http://fake-chef-server.com/'}.merge(default_ridley_options)
502
+ end
498
503
 
499
- it "uses the passed in :server_url" do
504
+ it 'uses the passed in :server_url' do
500
505
  Ridley.should_receive(:new).with(ridley_options)
501
506
  upload
502
507
  end
503
508
  end
504
509
  end
510
+
511
+ describe '#apply' do
512
+ let(:env_name) { 'berkshelf-test' }
513
+ let(:server_url) { Berkshelf::RSpec::ChefServer.server_url }
514
+ let(:client_name) { 'reset' }
515
+ let(:client_key) { fixtures_path.join('reset.pem').to_s }
516
+ let(:ridley) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) }
517
+
518
+ before do
519
+ subject.stub(:ridley_connection).and_return(ridley)
520
+ subject.add_source('nginx', '>= 0.1.2', chef_api: server_url, node_name: client_name, client_key: client_key)
521
+ subject.stub(install: nil)
522
+ end
523
+
524
+ context 'when the chef environment exists' do
525
+ let(:sources) do
526
+ [
527
+ double(name: 'nginx', locked_version: '1.2.3'),
528
+ double(name: 'artifact', locked_version: '1.4.0')
529
+ ]
530
+ end
531
+
532
+ before do
533
+ chef_environment('berkshelf')
534
+ subject.lockfile.stub(:sources).and_return(sources)
535
+ end
536
+
537
+ it 'installs the Berksfile' do
538
+ subject.should_receive(:install)
539
+ subject.apply('berkshelf')
540
+ end
541
+
542
+ it 'applys the locked_versions of the Lockfile sources to the given Chef environment' do
543
+ subject.apply('berkshelf')
544
+
545
+ environment = ::JSON.parse(chef_server.data['environments']['berkshelf'])
546
+ expect(environment['cookbook_versions']).to have(2).items
547
+ expect(environment['cookbook_versions']['nginx']).to eq('1.2.3')
548
+ expect(environment['cookbook_versions']['artifact']).to eq('1.4.0')
549
+ end
550
+ end
551
+
552
+ context 'when the environment does not exist' do
553
+ it 'raises an EnvironmentNotFound error' do
554
+ expect {
555
+ subject.apply(env_name)
556
+ }.to raise_error(Berkshelf::EnvironmentNotFound)
557
+ end
558
+ end
559
+
560
+ context 'when Ridley throw an exception' do
561
+ before { ridley.stub_chain(:environment, :find).and_raise(Ridley::Errors::RidleyError) }
562
+
563
+ it 'raises a ChefConnectionError' do
564
+ expect {
565
+ subject.apply(env_name)
566
+ }.to raise_error(Berkshelf::ChefConnectionError)
567
+ end
568
+ end
569
+ end
570
+
571
+ describe '#package' do
572
+ context 'when the source does not exist' do
573
+ it 'raises a CookbookNotFound exception' do
574
+ expect {
575
+ subject.package('non-existent', output: '/tmp')
576
+ }.to raise_error(Berkshelf::CookbookNotFound)
577
+ end
578
+ end
579
+
580
+ context 'when the source exists' do
581
+ let(:source) { double('source') }
582
+ let(:cached) { double('cached', path: '/foo/bar', cookbook_name: 'cookbook') }
583
+ let(:options) { { output: '/tmp' } }
584
+
585
+ before do
586
+ FileUtils.stub(:cp_r)
587
+ FileUtils.stub(:mkdir_p)
588
+ subject.stub(:find).with('non-existent').and_return(source)
589
+ subject.stub(:resolve).with(source, options).and_return({ solution: [cached], sources: [source] })
590
+ end
591
+
592
+ it 'resolves the sources' do
593
+ subject.should_receive(:resolve).with(source, options)
594
+ subject.package('non-existent', options)
595
+ end
596
+
597
+ it 'returns the output path' do
598
+ expect(subject.package('non-existent', options)).to eq('/tmp/non-existent.tar.gz')
599
+ end
600
+ end
601
+ end
505
602
  end