chef-apply 0.1.17 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -7
  3. data/Gemfile.lock +176 -84
  4. data/chef-apply.gemspec +2 -2
  5. data/lib/chef_apply/version.rb +1 -1
  6. data/spec/fixtures/custom_config.toml +2 -0
  7. data/spec/integration/chef-run_spec.rb +41 -0
  8. data/spec/integration/fixtures/chef_help.out +69 -0
  9. data/spec/integration/fixtures/chef_version.out +1 -0
  10. data/spec/integration/spec_helper.rb +55 -0
  11. data/spec/spec_helper.rb +114 -0
  12. data/spec/support/matchers/output_to_terminal.rb +36 -0
  13. data/spec/unit/action/base_spec.rb +89 -0
  14. data/spec/unit/action/converge_target_spec.rb +292 -0
  15. data/spec/unit/action/generate_local_policy_spec.rb +114 -0
  16. data/spec/unit/action/generate_temp_cookbook_spec.rb +75 -0
  17. data/spec/unit/action/install_chef/base_spec.rb +234 -0
  18. data/spec/unit/action/install_chef_spec.rb +69 -0
  19. data/spec/unit/cli/options_spec.rb +75 -0
  20. data/spec/unit/cli/validation_spec.rb +78 -0
  21. data/spec/unit/cli_spec.rb +440 -0
  22. data/spec/unit/config_spec.rb +70 -0
  23. data/spec/unit/errors/ccr_failure_mapper_spec.rb +103 -0
  24. data/spec/unit/file_fetcher_spec.rb +40 -0
  25. data/spec/unit/fixtures/multi-error.out +2 -0
  26. data/spec/unit/log_spec.rb +37 -0
  27. data/spec/unit/recipe_lookup_spec.rb +122 -0
  28. data/spec/unit/startup_spec.rb +283 -0
  29. data/spec/unit/target_host_spec.rb +231 -0
  30. data/spec/unit/target_resolver_spec.rb +380 -0
  31. data/spec/unit/telemeter/sender_spec.rb +140 -0
  32. data/spec/unit/telemeter_spec.rb +191 -0
  33. data/spec/unit/temp_cookbook_spec.rb +199 -0
  34. data/spec/unit/ui/error_printer_spec.rb +173 -0
  35. data/spec/unit/ui/terminal_spec.rb +109 -0
  36. data/spec/unit/version_spec.rb +31 -0
  37. data/warning.txt +3 -0
  38. metadata +34 -2
@@ -0,0 +1,70 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "spec_helper"
19
+ require "chef_apply/config"
20
+
21
+ RSpec.describe ChefApply::Config do
22
+ subject(:config) do
23
+ ChefApply::Config
24
+ end
25
+
26
+ before(:each) do
27
+ ChefApply::Config.reset
28
+ end
29
+
30
+ it "raises an error when trying to specify non-existing config location" do
31
+ expect { config.custom_location("/does/not/exist") }.to raise_error(RuntimeError, /No config file/)
32
+ end
33
+
34
+ it "should use default location by default" do
35
+ expect(config.using_default_location?).to eq(true)
36
+ end
37
+
38
+ context "when there is a custom config" do
39
+ let(:custom_config) { File.expand_path("../../fixtures/custom_config.toml", __FILE__) }
40
+
41
+ it "successfully loads the config" do
42
+ config.custom_location(custom_config)
43
+ expect(config.using_default_location?).to eq(false)
44
+ expect(config.exist?).to eq(true)
45
+ config.load
46
+ expect(config.telemetry.dev).to eq(true)
47
+ end
48
+ end
49
+ describe "#load" do
50
+ before do
51
+ expect(subject).to receive(:exist?).and_return(exists)
52
+ end
53
+
54
+ context "when the config file exists" do
55
+ let(:exists) { true }
56
+ it "loads the file" do
57
+ expect(subject).to receive(:from_file)
58
+ subject.load
59
+ end
60
+ end
61
+
62
+ context "when the config file does not exist" do
63
+ let(:exists) { false }
64
+ it "does not try to load the file" do
65
+ expect(subject).to_not receive(:from_file)
66
+ subject.load
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,103 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "spec_helper"
19
+ require "chef_apply/errors/ccr_failure_mapper"
20
+
21
+ RSpec.describe ChefApply::Errors::CCRFailureMapper do
22
+ let(:cause_line) { nil }
23
+ let(:resource) { "apt_package" }
24
+ let(:params) do
25
+ { resource: resource, resource_name: "a-test-thing",
26
+ stderr: "an error", stdout: "other output" }
27
+ end
28
+ subject { ChefApply::Errors::CCRFailureMapper.new(cause_line, params) }
29
+
30
+ describe "#exception_args_from_cause" do
31
+ context "when resource properties have valid names but invalid values" do
32
+ context "and the property is 'action'" do
33
+ let(:cause_line) { "Chef::Exceptions::ValidationFailed: Option action must be equal to one of: nothing, install, upgrade, remove, purge, reconfig, lock, unlock! You passed :marve." }
34
+ it "returns a correct CHEFCCR003" do
35
+ expect(subject.exception_args_from_cause).to eq(
36
+ ["CHEFCCR003", "marve",
37
+ "nothing, install, upgrade, remove, purge, reconfig, lock, unlock"]
38
+ )
39
+ end
40
+ end
41
+
42
+ context "and the property is something else" do
43
+ context "and details are available" do
44
+ let(:cause_line) { "Chef::Exceptions::ValidationFailed: Option force must be a kind of [TrueClass, FalseClass]! You passed \"purle\"." }
45
+ it "returns a correct CHEFCCR004 when details are available" do
46
+ expect(subject.exception_args_from_cause).to eq(
47
+ ["CHEFCCR004",
48
+ "Option force must be a kind of [TrueClass, FalseClass]! You passed \"purle\"."])
49
+ end
50
+ end
51
+ context "And less detail is available" do
52
+ let(:cause_line) { "Chef::Exceptions::User: linux_user[marc] ((chef-client cookbook)::(chef-client recipe) line 1) had an error: Chef::Exceptions::User: Couldn't lookup integer GID for group name blah" }
53
+ it "returns a correct CHEFCCR002" do
54
+ expect(subject.exception_args_from_cause).to eq(
55
+ ["CHEFCCR002", "Couldn't lookup integer GID for group name blah"])
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ context "when resource is not a known Chef resource" do
62
+ let(:cause_line) { "NoMethodError: undefined method `useraaa' for cookbook: (chef-client cookbook), recipe: (chef-client recipe) :Chef::Recipe" }
63
+ let(:resource) { "useraaa" }
64
+ it "returns a correct CHEFCCR005" do
65
+ expect(subject.exception_args_from_cause).to eq(["CHEFCCR005", resource])
66
+ end
67
+ end
68
+
69
+ context "when a resource property does not exist for the given resource" do
70
+ let(:cause_line) { "NoMethodError: undefined method `badresourceprop' for Chef::Resource::User::LinuxUser" }
71
+ it "returns a correct CHEFCCR006 " do
72
+ expect(subject.exception_args_from_cause).to eq(
73
+ ["CHEFCCR006", "badresourceprop", "Chef::Resource::User::LinuxUser"])
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "#raise_mapped_exception!" do
79
+ context "when no cause is provided" do
80
+ let(:cause_line) { nil }
81
+ it "raises a RemoteChefRunFailedToResolveError" do
82
+ expect { subject.raise_mapped_exception! }.to raise_error(ChefApply::Errors::CCRFailureMapper::RemoteChefRunFailedToResolveError)
83
+
84
+ end
85
+ end
86
+
87
+ context "when a cause is provided" do
88
+ context "but can't resolve it" do
89
+ let(:cause_line) { "unparseable mess" }
90
+ it "raises a RemoteChefClientRunFailedUnknownReason" do
91
+ expect { subject.raise_mapped_exception! }.to raise_error(ChefApply::Errors::CCRFailureMapper::RemoteChefClientRunFailedUnknownReason)
92
+ end
93
+ end
94
+
95
+ context "and can resolve the cause" do
96
+ let(:cause_line) { "NoMethodError: undefined method `badresourceprop' for Chef::Resource::User::LinuxUser" }
97
+ it "raises a RemoteChefClientRunFailed" do
98
+ expect { subject.raise_mapped_exception! }.to raise_error(ChefApply::Errors::CCRFailureMapper::RemoteChefClientRunFailed)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "chef_apply/file_fetcher"
19
+ require "spec_helper"
20
+
21
+ RSpec.describe ChefApply::FileFetcher do
22
+ let(:expected_local_location) { File.join(ChefApply::Config.cache.path, "example.txt") }
23
+ subject { ChefApply::FileFetcher }
24
+ describe ".fetch" do
25
+ it "returns the local path when the file is cached" do
26
+ allow(FileUtils).to receive(:mkdir)
27
+ expect(File).to receive(:exist?).with(expected_local_location).and_return(true)
28
+ result = subject.fetch("https://example.com/example.txt")
29
+ expect(result).to eq expected_local_location
30
+ end
31
+
32
+ it "returns the local path when the file is fetched" do
33
+ allow(FileUtils).to receive(:mkdir)
34
+ expect(File).to receive(:exist?).with(expected_local_location).and_return(false)
35
+ expect(subject).to receive(:download_file)
36
+ result = subject.fetch("https://example.com/example.txt")
37
+ expect(result).to eq expected_local_location
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,2 @@
1
+ Host: host1 Error: CHEFUPL005: Uploading policy bundle to target failed.
2
+ Host: host2 : An unexpected error has occurred: Hello World
@@ -0,0 +1,37 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "spec_helper"
19
+ require "chef_apply/log"
20
+
21
+ RSpec.describe ChefApply::Log do
22
+ Log = ChefApply::Log
23
+ let(:output) { StringIO.new }
24
+
25
+ before do
26
+ Log.setup output, :debug
27
+ end
28
+
29
+ after do
30
+ Log.setup "/dev/null", :error
31
+ end
32
+
33
+ it "correctly logs to stdout" do
34
+ Log.debug("test")
35
+ expect(output.string).to match(/DEBUG: test$/)
36
+ end
37
+ end
@@ -0,0 +1,122 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "spec_helper"
19
+ require "chef_apply/recipe_lookup"
20
+ require "chef/exceptions"
21
+ require "chef/cookbook/cookbook_version_loader"
22
+ require "chef/cookbook_version"
23
+ require "chef/cookbook_loader"
24
+
25
+ RSpec.describe ChefApply::RecipeLookup do
26
+ let(:repo_path) { "repo_path" }
27
+ subject(:rp) { ChefApply::RecipeLookup.new([repo_path]) }
28
+ VL = Chef::Cookbook::CookbookVersionLoader
29
+ let(:version_loader) { instance_double(VL) }
30
+ let(:cookbook_version) { instance_double(Chef::CookbookVersion, root_dir: "dir", name: "name") }
31
+ let(:cookbook_loader) { instance_double(Chef::CookbookLoader, load_cookbooks_without_shadow_warning: nil) }
32
+
33
+ describe "#split" do
34
+ it "splits a customer provided specifier into a cookbook part and possible recipe part" do
35
+ expect(rp.split("/some/path")).to eq(%w{/some/path})
36
+ expect(rp.split("cookbook::recipe")).to eq(%w{cookbook recipe})
37
+ end
38
+ end
39
+
40
+ describe "#load_cookbook" do
41
+ context "when a directory is provided" do
42
+ let(:recipe_specifier) { "/some/directory" }
43
+ let(:default_recipe) { File.join(recipe_specifier, "default.rb") }
44
+ let(:recipes_by_name) { { "default" => default_recipe } }
45
+ before do
46
+ expect(File).to receive(:directory?).with(recipe_specifier).and_return(true)
47
+ expect(VL).to receive(:new).with(recipe_specifier).and_return(version_loader)
48
+ end
49
+
50
+ it "loads the cookbook and returns the path to the default recipe" do
51
+ expect(version_loader).to receive(:load!)
52
+ expect(version_loader).to receive(:cookbook_version).and_return(cookbook_version)
53
+ expect(rp.load_cookbook(recipe_specifier)).to eq(cookbook_version)
54
+ end
55
+
56
+ context "the directory is not a cookbook" do
57
+ it "raise an InvalidCookbook error" do
58
+ expect(version_loader).to receive(:load!).and_raise(Chef::Exceptions::CookbookNotFoundInRepo.new)
59
+ expect { rp.load_cookbook(recipe_specifier) }.to raise_error(ChefApply::RecipeLookup::InvalidCookbook)
60
+ end
61
+ end
62
+ end
63
+
64
+ context "when a cookbook name is provided" do
65
+ let(:recipe_specifier) { "cb" }
66
+ before do
67
+ expect(File).to receive(:directory?).with(recipe_specifier).and_return(false)
68
+ expect(Chef::CookbookLoader).to receive(:new).and_return(cookbook_loader)
69
+ end
70
+
71
+ context "and a cookbook in the cookbook repository exists with that name" do
72
+ it "returns the default cookbook" do
73
+ expect(cookbook_loader).to receive(:[]).with(recipe_specifier).and_return(cookbook_version)
74
+ expect(rp.load_cookbook(recipe_specifier)).to eq(cookbook_version)
75
+ end
76
+ end
77
+
78
+ context "and a cookbook exists but it is invalid" do
79
+ it "raises an InvalidCookbook error" do
80
+ expect(cookbook_loader).to receive(:[]).with(recipe_specifier).and_raise(Chef::Exceptions::CookbookNotFoundInRepo.new())
81
+ expect(File).to receive(:directory?).with(File.join(repo_path, recipe_specifier)).and_return(true)
82
+ expect { rp.load_cookbook(recipe_specifier) }.to raise_error(ChefApply::RecipeLookup::InvalidCookbook)
83
+ end
84
+ end
85
+
86
+ context "and a cookbook does not exist" do
87
+ it "raises an CookbookNotFound error" do
88
+ expect(cookbook_loader).to receive(:[]).with(recipe_specifier).and_raise(Chef::Exceptions::CookbookNotFoundInRepo.new())
89
+ expect(File).to receive(:directory?).with(File.join(repo_path, recipe_specifier)).and_return(false)
90
+ expect { rp.load_cookbook(recipe_specifier) }.to raise_error(ChefApply::RecipeLookup::CookbookNotFound)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "#find_recipe" do
97
+ let(:recipe) { double("recipe") }
98
+
99
+ context "no recipe is specified" do
100
+ it "finds a default recipe" do
101
+ expect(cookbook_version).to receive(:recipe_filenames_by_name).and_return({ "default" => recipe })
102
+ expect(rp.find_recipe(cookbook_version)).to eq(recipe)
103
+ end
104
+ it "when there is no default recipe it raises a NoDefaultRecipe error" do
105
+ expect(cookbook_version).to receive(:recipe_filenames_by_name).and_return({})
106
+ expect { rp.find_recipe(cookbook_version) }.to raise_error(ChefApply::RecipeLookup::NoDefaultRecipe)
107
+ end
108
+ end
109
+
110
+ context "a recipe is specified" do
111
+ let(:desired_recipe) { "a_recipe" }
112
+ it "finds the specified recipe" do
113
+ expect(cookbook_version).to receive(:recipe_filenames_by_name).and_return({ desired_recipe => recipe })
114
+ expect(rp.find_recipe(cookbook_version, desired_recipe)).to eq(recipe)
115
+ end
116
+ it "when there is no recipe with that name it raises a RecipeNotFound error" do
117
+ expect(cookbook_version).to receive(:recipe_filenames_by_name).and_return({})
118
+ expect { rp.find_recipe(cookbook_version, desired_recipe) }.to raise_error(ChefApply::RecipeLookup::RecipeNotFound)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,283 @@
1
+ require "chef_apply/startup"
2
+ require "chef_apply/text"
3
+ require "chef_apply/ui/terminal"
4
+
5
+ RSpec.describe ChefApply::Startup do
6
+ let(:argv) { [] }
7
+ let(:telemetry) { ChefApply::Telemeter.instance }
8
+ subject do
9
+ ChefApply::Startup.new(argv)
10
+ end
11
+ before do
12
+ allow(ChefApply::UI::Terminal).to receive(:init)
13
+ end
14
+
15
+ after do
16
+ ChefApply::Config.reset
17
+ end
18
+
19
+ describe "#initalize" do
20
+ it "initializes the terminal" do
21
+ expect_any_instance_of(ChefApply::Startup).to receive(:init_terminal)
22
+ ChefApply::Startup.new([])
23
+ end
24
+ end
25
+
26
+ describe "#run" do
27
+ it "performs ordered startup tasks and invokes the CLI" do
28
+ ordered_messages = [:first_run_tasks,
29
+ :setup_workstation_user_directories,
30
+ :setup_error_handling,
31
+ :load_config,
32
+ :setup_logging,
33
+ :start_telemeter_upload,
34
+ :start_chef_apply]
35
+ ordered_messages.each do |msg|
36
+ expect(subject).to receive(msg).ordered
37
+ end
38
+ subject.run()
39
+ end
40
+ context "when errors happen" do
41
+ let(:error) { nil }
42
+ let(:error_text) { ChefApply::Text.cli.error }
43
+ before do
44
+ # Force the error to happen in first_run_tasks, since it's always... well, first.
45
+ expect(subject).to receive(:first_run_tasks).and_raise(error)
46
+ end
47
+
48
+ context "when an UnknownConfigOptionError is raised" do
49
+ let(:bad_path) { "bad/path" }
50
+ let(:bad_option) { "bad_option" }
51
+
52
+ context "and it matches the expected text form" do
53
+ let(:error) { Mixlib::Config::UnknownConfigOptionError.new("unsupported config value #{bad_option}.") }
54
+ it "shows the correct error" do
55
+ expected_text = error_text.invalid_config_key(bad_option, ChefApply::Config.location)
56
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
57
+ subject.run
58
+ end
59
+ end
60
+
61
+ context "and it does not match the expeted text form" do
62
+ let(:msg) { "something bad happened" }
63
+ let(:error) { Mixlib::Config::UnknownConfigOptionError.new(msg) }
64
+ it "shows the correct error" do
65
+ expected_text = error_text.unknown_config_error(msg, ChefApply::Config.location)
66
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
67
+ subject.run
68
+ end
69
+ end
70
+ end
71
+
72
+ context "when a ConfigPathInvalid is raised" do
73
+ let(:bad_path) { "bad/path" }
74
+ let(:error) { ChefApply::Startup::ConfigPathInvalid.new(bad_path) }
75
+
76
+ it "shows the correct error" do
77
+ expected_text = error_text.bad_config_file(bad_path)
78
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
79
+ subject.run
80
+ end
81
+ end
82
+
83
+ context "when a ConfigPathNotProvided is raised" do
84
+ let(:error) { ChefApply::Startup::ConfigPathNotProvided.new }
85
+
86
+ it "shows the correct error" do
87
+ expected_text = error_text.missing_config_path
88
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
89
+ subject.run
90
+ end
91
+ end
92
+
93
+ context "when a Tomlrb::ParserError is raised" do
94
+ let(:msg) { "Parse failed." }
95
+ let(:error) { Tomlrb::ParseError.new(msg) }
96
+
97
+ it "shows the correct error" do
98
+ expected_text = error_text.unknown_config_error(msg, ChefApply::Config.location)
99
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
100
+ subject.run
101
+ end
102
+ end
103
+ end
104
+ end
105
+ describe "#init_terminal" do
106
+ it "initializees the terminal for stdout" do
107
+ expect(ChefApply::UI::Terminal).to receive(:init).with($stdout)
108
+ subject.init_terminal
109
+ end
110
+ end
111
+ describe "#first_run_tasks" do
112
+ let(:first_run_complete) { true }
113
+ before do
114
+ allow(Dir).to receive(:exist?).with(ChefApply::Config::WS_BASE_PATH).and_return first_run_complete
115
+ end
116
+
117
+ context "when first run has already occurred" do
118
+ let(:first_run_complete) { true }
119
+ it "returns without taking action" do
120
+ expect(subject).to_not receive(:create_default_config)
121
+ expect(subject).to_not receive(:setup_telemetry)
122
+ subject.first_run_tasks
123
+ end
124
+ end
125
+ context "when first run has not already occurred" do
126
+ let(:first_run_complete) { false }
127
+ it "Performs required first-run tasks" do
128
+ expect(subject).to receive(:create_default_config)
129
+ expect(subject).to receive(:setup_telemetry)
130
+ subject.first_run_tasks
131
+ end
132
+ end
133
+ end
134
+
135
+ describe "#create_default_config" do
136
+ it "touches the configuration file to create it and notifies that it has done so" do
137
+ expected_config_path = ChefApply::Config.default_location
138
+ expected_message = ChefApply::Text.cli.creating_config(expected_config_path)
139
+ expect(ChefApply::UI::Terminal).to receive(:output).
140
+ with(expected_message)
141
+ expect(ChefApply::UI::Terminal).to receive(:output).
142
+ with("")
143
+ expect(FileUtils).to receive(:touch).
144
+ with(expected_config_path)
145
+ subject.create_default_config
146
+
147
+ end
148
+ end
149
+
150
+ describe "#setup_telemetry" do
151
+ let(:mock_guid) { "1234" }
152
+ it "sets up a telemetry installation id and notifies the operator that telemetry is enabled" do
153
+ expect(SecureRandom).to receive(:uuid).and_return(mock_guid)
154
+ expect(File).to receive(:write).
155
+ with(ChefApply::Config.telemetry_installation_identifier_file, mock_guid)
156
+ subject.setup_telemetry
157
+ end
158
+ end
159
+
160
+ describe "#start_telemeter_upload" do
161
+ it "launches telemetry uploads" do
162
+ expect(ChefApply::Telemeter::Sender).to receive(:start_upload_thread)
163
+ subject.start_telemeter_upload
164
+ end
165
+ end
166
+
167
+ describe "setup_workstation_user_directories" do
168
+ it "creates the required chef-workstation directories in HOME" do
169
+ expect(FileUtils).to receive(:mkdir_p).with(ChefApply::Config::WS_BASE_PATH)
170
+ expect(FileUtils).to receive(:mkdir_p).with(ChefApply::Config.base_log_directory)
171
+ expect(FileUtils).to receive(:mkdir_p).with(ChefApply::Config.telemetry_path)
172
+ subject.setup_workstation_user_directories
173
+ end
174
+ end
175
+
176
+ describe "#custom_config_path" do
177
+ context "when a custom config path is not provided as an option" do
178
+ let(:args) { [] }
179
+ it "returns nil" do
180
+ expect(subject.custom_config_path).to be_nil
181
+ end
182
+ end
183
+
184
+ context "when a --config-path is provided" do
185
+ context "but the actual path parameter is not provided" do
186
+ let(:argv) { %w{--config-path} }
187
+ it "raises ConfigPathNotProvided" do
188
+ expect { subject.custom_config_path }.to raise_error(ChefApply::Startup::ConfigPathNotProvided)
189
+ end
190
+ end
191
+
192
+ context "and the path is provided" do
193
+ let(:path) { "/mock/file.toml" }
194
+ let(:argv) { ["--config-path", path] }
195
+
196
+ context "but the path is not a file" do
197
+ before do
198
+ allow(File).to receive(:file?).with(path).and_return false
199
+ end
200
+ it "raises an error ConfigPathInvalid" do
201
+ expect { subject.custom_config_path }.to raise_error(ChefApply::Startup::ConfigPathInvalid)
202
+ end
203
+ end
204
+
205
+ context "and the path exists and is a valid file" do
206
+ before do
207
+ allow(File).to receive(:file?).with(path).and_return true
208
+ end
209
+
210
+ context "but it is not readable" do
211
+ before do
212
+ allow(File).to receive(:readable?).with(path).and_return false
213
+ end
214
+ it "raises an error ConfigPathInvalid" do
215
+ expect { subject.custom_config_path }.to raise_error(ChefApply::Startup::ConfigPathInvalid)
216
+ end
217
+ end
218
+
219
+ context "and it is readable" do
220
+ before do
221
+ allow(File).to receive(:readable?).with(path).and_return true
222
+ end
223
+ it "returns the custom path" do
224
+ expect(subject.custom_config_path).to eq path
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ describe "#load_config" do
233
+ context "when a custom configuraton path is provided" do
234
+ let(:config_path) { nil }
235
+ it "loads the config at the custom path" do
236
+ expect(subject).to receive(:custom_config_path).and_return config_path
237
+ expect(ChefApply::Config).to receive(:custom_location).with config_path
238
+ expect(ChefApply::Config).to receive(:load)
239
+ subject.load_config
240
+ end
241
+ let(:config_path) { "/tmp/workstation-mock-config.toml" }
242
+ end
243
+
244
+ context "when no custom configuration path is provided" do
245
+ let(:config_path) { nil }
246
+ it "loads it at the default configuration path" do
247
+ expect(subject).to receive(:custom_config_path).and_return config_path
248
+ expect(ChefApply::Config).not_to receive(:custom_location)
249
+ expect(ChefApply::Config).to receive(:load)
250
+ subject.load_config
251
+ end
252
+ end
253
+
254
+ end
255
+
256
+ describe "#setup_logging" do
257
+ let(:log_path) { "/tmp/logs" }
258
+ let(:log_level) { :debug }
259
+ before do
260
+ ChefApply::Config.log.location = log_path
261
+ ChefApply::Config.log.level = log_level
262
+ end
263
+
264
+ it "sets up the logging for ChefApply and Chef" do
265
+ expect(ChefApply::Log).to receive(:setup).
266
+ with(log_path, log_level)
267
+ expect(Chef::Log).to receive(:init).
268
+ with(ChefApply::Log)
269
+ subject.setup_logging
270
+ expect(ChefConfig.logger).to eq(ChefApply::Log)
271
+ end
272
+ end
273
+
274
+ describe "#start_chef_apply" do
275
+ let(:argv) { %w{some arguments} }
276
+ it "runs ChefApply::CLI and passes along arguments it received" do
277
+ run_double = instance_double(ChefApply::CLI)
278
+ expect(ChefApply::CLI).to receive(:new).with(argv).and_return(run_double)
279
+ expect(run_double).to receive(:run)
280
+ subject.start_chef_apply
281
+ end
282
+ end
283
+ end