vaguerent/test/unit/vagrant/machine_test.rb
Chris Roberts 602d42bbc8 Add and update tests for insecure private keys
Updates existing test coverage to use insecure private key collection
and adds testing for behavior changes within the communicator and the
keypair utility.
2023-06-26 15:47:32 -07:00

1022 lines
30 KiB
Ruby

require "pathname"
require "tmpdir"
require File.expand_path("../../base", __FILE__)
require "vagrant/util/platform"
describe Vagrant::Machine do
include_context "unit"
let(:name) { "foo" }
let(:provider) { new_provider_mock }
let(:provider_cls) do
obj = double("provider_cls")
allow(obj).to receive(:new).and_return(provider)
obj
end
let(:provider_config) { Object.new }
let(:provider_name) { :test }
let(:provider_options) { {} }
let(:base) { false }
let(:box) do
double("box").tap do |b|
allow(b).to receive(:name).and_return("foo")
allow(b).to receive(:provider).and_return(:dummy)
allow(b).to receive(:version).and_return("1.0")
end
end
let(:config) { env.vagrantfile.config }
let(:data_dir) { Pathname.new(Dir.mktmpdir("vagrant-machine-data-dir")) }
let(:env) do
# We need to create a Vagrantfile so that this test environment
# has a proper root path
test_env.vagrantfile("")
# Create the Vagrant::Environment instance
test_env.create_vagrant_env
end
let(:test_env) { isolated_environment }
let(:instance) { new_instance }
after do
FileUtils.rm_rf(data_dir) if data_dir
end
subject { instance }
def new_provider_mock
double("provider").tap do |obj|
allow(obj).to receive(:_initialize)
.with(provider_name, anything).and_return(nil)
allow(obj).to receive(:machine_id_changed).and_return(nil)
allow(obj).to receive(:state).and_return(Vagrant::MachineState.new(
:created, "", ""))
end
end
# Returns a new instance with the test data
def new_instance
described_class.new(name, provider_name, provider_cls, provider_config,
provider_options, config, data_dir, box,
env, env.vagrantfile, base)
end
describe "initialization" do
it "should set the ID to nil if the state is not created" do
subject.id = "foo"
allow(provider).to receive(:state).and_return(Vagrant::MachineState.new(
Vagrant::MachineState::NOT_CREATED_ID, "short", "long"))
subject = new_instance
expect(subject.state.id).to eq(Vagrant::MachineState::NOT_CREATED_ID)
expect(subject.id).to be_nil
end
context "setting up triggers" do
before do
expect(provider).to receive(:_initialize) do |*args|
machine = args.last
@trigger_instance = machine.instance_variable_get(:@triggers)
true
end
end
it "should initialize the trigger object" do
subject = new_instance
expect(subject.instance_variable_get(:@triggers))
.to be_a(Vagrant::Plugin::V2::Trigger)
expect(subject.instance_variable_get(:@triggers))
.to eq(@trigger_instance)
end
end
describe "as a base" do
let(:base) { true}
it "should not insert key" do
subject = new_instance
expect(subject.config.ssh.insert_key).to be(false)
end
end
describe "communicator loading" do
it "doesn't eager load SSH" do
config.vm.communicator = :ssh
klass = Vagrant.plugin("2").manager.communicators[:ssh]
expect(klass).to_not receive(:new)
subject
end
it "eager loads WinRM" do
config.vm.communicator = :winrm
klass = Vagrant.plugin("2").manager.communicators[:winrm]
instance = double("instance")
expect(klass).to receive(:new).and_return(instance)
subject
end
end
describe "provider initialization" do
# This is a helper that generates a test for provider initialization.
# This is a separate helper method because it takes a block that can
# be used to have additional tests on the received machine.
#
# @yield [machine] Yields the machine that the provider initialization
# method received so you can run additional tests on it.
def provider_init_test(instance=nil)
received_machine = nil
if !instance
instance = new_provider_mock
end
provider_cls = double("provider_cls")
expect(provider_cls).to receive(:new) { |machine|
# Store this for later so we can verify that it is the
# one we expected to receive.
received_machine = machine
# Sanity check
expect(machine).to be
# Yield our machine if we want to do additional tests
yield machine if block_given?
true
}.and_return(instance)
# Initialize a new machine and verify that we properly receive
# the machine we expect.
instance = described_class.new(name, provider_name, provider_cls, provider_config,
provider_options, config, data_dir, box,
env, env.vagrantfile)
expect(received_machine).to eql(instance)
end
it "should initialize with the machine object" do
# Just run the blank test
provider_init_test
end
it "should have the machine name setup" do
provider_init_test do |machine|
expect(machine.name).to eq(name)
end
end
it "should have the machine configuration" do
provider_init_test do |machine|
expect(machine.config).to eql(config)
end
end
it "should have the box" do
provider_init_test do |machine|
expect(machine.box).to eql(box)
end
end
it "should have the environment" do
provider_init_test do |machine|
expect(machine.env).to eql(env)
end
end
it "should have the vagrantfile" do
provider_init_test do |machine|
expect(machine.vagrantfile).to equal(env.vagrantfile)
end
end
it "should have access to the ID" do
# Stub this because #id= calls it.
allow(provider).to receive(:machine_id_changed)
# Set the ID on the previous instance so that it is persisted
instance.id = "foo"
provider_init_test do |machine|
expect(machine.id).to eq("foo")
end
end
it "should NOT have access to the provider" do
provider_init_test do |machine|
expect(machine.provider).to be_nil
end
end
it "should initialize the capabilities" do
instance = new_provider_mock
expect(instance).to receive(:_initialize).with(any_args) { |p, m|
expect(p).to eq(provider_name)
expect(m.name).to eq(name)
true
}
provider_init_test(instance)
end
end
end
describe "attributes" do
describe '#name' do
subject { super().name }
it { should eq(name) }
end
describe '#config' do
subject { super().config }
it { should eql(config) }
end
describe '#box' do
subject { super().box }
it { should eql(box) }
end
describe '#env' do
subject { super().env }
it { should eql(env) }
end
describe '#provider' do
subject { super().provider }
it { should eql(provider) }
end
describe '#provider_config' do
subject { super().provider_config }
it { should eql(provider_config) }
end
describe '#provider_options' do
subject { super().provider_options }
it { should eq(provider_options) }
end
end
describe "#action" do
before do
allow(Vagrant::Plugin::Manager.instance).to receive(:installed_plugins).and_return({})
end
it "should be able to run an action that exists" do
action_name = :destroy
called = false
callable = lambda { |_env| called = true }
expect(provider).to receive(:action).with(action_name).and_return(callable)
instance.action(action_name)
expect(called).to be
end
it "should provide the machine in the environment" do
action_name = :destroy
machine = nil
callable = lambda { |env| machine = env[:machine] }
allow(provider).to receive(:action).with(action_name).and_return(callable)
instance.action(action_name)
expect(machine).to eql(instance)
end
it "should pass any extra options to the environment" do
action_name = :destroy
foo = nil
callable = lambda { |env| foo = env[:foo] }
allow(provider).to receive(:action).with(action_name).and_return(callable)
instance.action(action_name, foo: :bar)
expect(foo).to eq(:bar)
end
it "should pass any extra options to the environment as strings" do
action_name = :destroy
foo = nil
callable = lambda { |env| foo = env["foo"] }
allow(provider).to receive(:action).with(action_name).and_return(callable)
instance.action(action_name, "foo" => :bar)
expect(foo).to eq(:bar)
end
it "should return the environment as a result" do
action_name = :destroy
callable = lambda { |env| env[:result] = "FOO" }
allow(provider).to receive(:action).with(action_name).and_return(callable)
result = instance.action(action_name)
expect(result[:result]).to eq("FOO")
end
it "should raise an exception if the action is not implemented" do
action_name = :destroy
allow(provider).to receive(:action).with(action_name).and_return(nil)
expect { instance.action(action_name) }.
to raise_error(Vagrant::Errors::UnimplementedProviderAction)
end
it 'should not warn if the machines cwd has not changed' do
initial_action_name = :destroy
second_action_name = :reload
callable = lambda { |_env| }
allow(provider).to receive(:action).with(initial_action_name).and_return(callable)
allow(provider).to receive(:action).with(second_action_name).and_return(callable)
allow(subject.ui).to receive(:warn)
instance.action(initial_action_name)
expect(subject.ui).to_not have_received(:warn)
instance.action(second_action_name)
expect(subject.ui).to_not have_received(:warn)
end
it 'should warn if the machine was last run under a different directory' do
action_name = :destroy
callable = lambda { |_env| }
original_cwd = env.cwd.to_s
allow(provider).to receive(:action).with(action_name).and_return(callable)
allow(subject.ui).to receive(:warn)
instance.action(action_name)
expect(subject.ui).to_not have_received(:warn)
# Whenever the machine is run on a different directory, the user is warned
allow(env).to receive(:root_path).and_return('/a/new/path')
instance.action(action_name)
expect(subject.ui).to have_received(:warn) do |warn_msg|
expect(warn_msg).to include(original_cwd)
expect(warn_msg).to include('/a/new/path')
end
end
it 'should not warn if dirs are same but different cases' do
action_name = :destroy
callable = lambda { |_env| }
original_cwd = env.cwd.to_s
allow(provider).to receive(:action).with(action_name).and_return(callable)
allow(subject.ui).to receive(:warn)
instance.action(action_name)
expect(subject.ui).to_not have_received(:warn)
# In cygwin or other windows shell, it might have a path like
# c:/path and C:/path
# which are the same.
allow(env).to receive(:root_path).and_return(original_cwd.upcase)
expect(subject.ui).to_not have_received(:warn)
instance.action(action_name)
end
context "if in a subdir" do
let (:data_dir) { env.cwd }
it 'should not warn if vagrant is run in subdirectory' do
action_name = :destroy
callable = lambda { |_env| }
original_cwd = env.cwd.to_s
allow(provider).to receive(:action).with(action_name).and_return(callable)
allow(subject.ui).to receive(:warn)
instance.action(action_name)
expect(subject.ui).to_not have_received(:warn)
# mock out cwd to be subdir and ensure no warn is printed
allow(env).to receive(:cwd).and_return("#{original_cwd}/a/new/path")
instance.action(action_name)
expect(subject.ui).to_not have_received(:warn)
end
end
end
describe "#action_raw" do
let(:callable) {lambda { |e|
e[:called] = true
@env = e
}}
before do
@env = {}
end
it "should run the callable with the proper env" do
subject.action_raw(:foo, callable)
expect(@env[:called]).to be(true)
expect(@env[:action_name]).to eq(:machine_action_foo)
expect(@env[:machine]).to equal(subject)
expect(@env[:machine_action]).to eq(:foo)
expect(@env[:ui]).to equal(subject.ui)
end
it "should return the environment as a result" do
result = subject.action_raw(:foo, callable)
expect(result).to equal(@env)
end
it "should merge in any extra env" do
subject.action_raw(:bar, callable, foo: :bar)
expect(@env[:called]).to be(true)
expect(@env[:foo]).to eq(:bar)
end
end
describe "#communicate" do
it "should return the SSH communicator by default" do
expect(subject.communicate).
to be_kind_of(VagrantPlugins::CommunicatorSSH::Communicator)
end
it "should return the specified communicator if given" do
subject.config.vm.communicator = :winrm
expect(subject.communicate).
to be_kind_of(VagrantPlugins::CommunicatorWinRM::Communicator)
end
it "should memoize the result" do
obj = subject.communicate
expect(subject.communicate).to equal(obj)
end
it "raises an exception if an invalid communicator is given" do
subject.config.vm.communicator = :foo
expect { subject.communicate }.
to raise_error(Vagrant::Errors::CommunicatorNotFound)
end
end
describe "guest implementation" do
let(:communicator) do
result = double("communicator")
allow(result).to receive(:ready?).and_return(true)
allow(result).to receive(:test).and_return(false)
result
end
before(:each) do
test_guest = Class.new(Vagrant.plugin("2", :guest)) do
def detect?(machine)
true
end
end
register_plugin do |p|
p.guest(:test) { test_guest }
end
allow(instance).to receive(:communicate).and_return(communicator)
end
it "should raise an exception if communication is not ready" do
expect(communicator).to receive(:ready?).and_return(false)
expect { instance.guest }.
to raise_error(Vagrant::Errors::MachineGuestNotReady)
end
it "should return the configured guest" do
result = instance.guest
expect(result).to be_kind_of(Vagrant::Guest)
expect(result).to be_ready
expect(result.capability_host_chain[0][0]).to eql(:test)
end
end
describe "setting the ID" do
before(:each) do
allow(provider).to receive(:machine_id_changed)
end
it "should not have an ID by default" do
expect(instance.id).to be_nil
end
it "should set an ID" do
instance.id = "bar"
expect(instance.id).to eq("bar")
end
it "should notify the machine that the ID changed" do
expect(provider).to receive(:machine_id_changed).once
instance.id = "bar"
end
it "should persist the ID" do
instance.id = "foo"
expect(new_instance.id).to eq("foo")
end
it "should delete the ID" do
instance.id = "foo"
second = new_instance
expect(second.id).to eq("foo")
second.id = nil
expect(second.id).to be_nil
third = new_instance
expect(third.id).to be_nil
end
it "should set the UID that created the machine" do
instance.id = "foo"
second = new_instance
expect(second.uid).to eq(Process.uid.to_s)
end
it "should delete the UID when the id is nil" do
instance.id = "foo"
instance.id = nil
second = new_instance
expect(second.uid).to be_nil
end
end
describe "#index_uuid" do
before(:each) do
allow(provider).to receive(:machine_id_changed)
end
it "should not have an index UUID by default" do
expect(subject.index_uuid).to be_nil
end
it "is set one when setting an ID" do
# Stub the message we want
allow(provider).to receive(:state).and_return(Vagrant::MachineState.new(
:preparing, "preparing", "preparing"))
# Setup the box information
box = double("box")
allow(box).to receive(:name).and_return("foo")
allow(box).to receive(:provider).and_return(:bar)
allow(box).to receive(:version).and_return("1.2.3")
subject.box = box
subject.id = "foo"
uuid = subject.index_uuid
expect(uuid).to_not be_nil
expect(new_instance.index_uuid).to eq(uuid)
# Test the entry itself
entry = env.machine_index.get(uuid)
expect(entry.name).to eq(subject.name)
expect(entry.provider).to eq(subject.provider_name.to_s)
expect(entry.state).to eq("preparing")
expect(entry.vagrantfile_path).to eq(env.root_path)
expect(entry.vagrantfile_name).to eq(env.vagrantfile_name)
expect(entry.extra_data["box"]).to eq({
"name" => box.name,
"provider" => box.provider.to_s,
"version" => box.version,
})
env.machine_index.release(entry)
end
it "deletes the UUID when setting to nil" do
subject.id = "foo"
uuid = subject.index_uuid
subject.id = nil
expect(subject.index_uuid).to be_nil
expect(env.machine_index.get(uuid)).to be_nil
end
end
describe "#reload" do
before do
allow(provider).to receive(:machine_id_changed)
subject.id = "foo"
end
it "should read the ID" do
expect(provider).to_not receive(:machine_id_changed)
subject.reload
expect(subject.id).to eq("foo")
end
it "should read the updated ID" do
new_instance.id = "bar"
expect(provider).to receive(:machine_id_changed)
subject.reload
expect(subject.id).to eq("bar")
end
end
describe "#ssh_info" do
describe "with the provider returning nil" do
it "should return nil if the provider returns nil" do
expect(provider).to receive(:ssh_info).and_return(nil)
expect(instance.ssh_info).to be_nil
end
end
describe "with the provider returning data" do
let(:provider_ssh_info) { {} }
let(:ssh_klass) { Vagrant::Util::SSH }
before(:each) do
allow(provider).to receive(:ssh_info).and_return(provider_ssh_info)
# Stub the check_key_permissions method so that even if we test incorrectly,
# no side effects actually happen.
allow(ssh_klass).to receive(:check_key_permissions)
end
[:host, :port, :username].each do |type|
it "should return the provider data if not configured in Vagrantfile" do
provider_ssh_info[type] = "foo"
instance.config.ssh.send("#{type}=", nil)
expect(instance.ssh_info[type]).to eq("foo")
end
it "should return the Vagrantfile value if provider data not given" do
provider_ssh_info[type] = nil
instance.config.ssh.send("#{type}=", "bar")
expect(instance.ssh_info[type]).to eq("bar")
end
it "should use the default if no override and no provider" do
provider_ssh_info[type] = nil
instance.config.ssh.send("#{type}=", nil)
instance.config.ssh.default.send("#{type}=", "foo")
expect(instance.ssh_info[type]).to eq("foo")
end
it "should use the override if set even with a provider" do
provider_ssh_info[type] = "baz"
instance.config.ssh.send("#{type}=", "bar")
instance.config.ssh.default.send("#{type}=", "foo")
expect(instance.ssh_info[type]).to eq("bar")
end
end
it "should set the configured forward agent settings" do
provider_ssh_info[:forward_agent] = true
instance.config.ssh.forward_agent = false
expect(instance.ssh_info[:forward_agent]).to eq(false)
end
it "should set the configured forward X11 settings" do
provider_ssh_info[:forward_x11] = true
instance.config.ssh.forward_x11 = false
expect(instance.ssh_info[:forward_x11]).to eq(false)
end
it "should return the provider private key if given" do
provider_ssh_info[:private_key_path] = "/foo"
expect(instance.ssh_info[:private_key_path]).to eq([File.expand_path("/foo", env.root_path)])
end
it "should return the configured SSH key path if set" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = "/bar"
expect(instance.ssh_info[:private_key_path]).to eq([File.expand_path("/bar", env.root_path)])
end
it "should return the array of SSH keys if set" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = ["/foo", "/bar"]
expect(instance.ssh_info[:private_key_path]).to eq([
File.expand_path("/foo", env.root_path),
File.expand_path("/bar", env.root_path),
])
end
it "should check and try to fix the permissions of the default private key file" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = nil
instance.env.default_private_key_paths.each do |key_path|
expect(ssh_klass).to receive(:check_key_permissions).once.with(Pathname.new(key_path.to_s))
end
instance.ssh_info
end
it "should check and try to fix the permissions of given private key files" do
provider_ssh_info[:private_key_path] = nil
# Use __FILE__ to provide an existing file
instance.config.ssh.private_key_path = [File.expand_path(__FILE__), File.expand_path(__FILE__)]
expect(ssh_klass).to receive(:check_key_permissions).twice.with(Pathname.new(File.expand_path(__FILE__)))
instance.ssh_info
end
it "should not check the permissions of a private key file that does not exist" do
provider_ssh_info[:private_key_path] = "/foo"
expect(ssh_klass).to_not receive(:check_key_permissions)
instance.ssh_info
end
context "expanding path relative to the root path" do
it "should with the provider key path" do
provider_ssh_info[:private_key_path] = "~/foo"
expect(instance.ssh_info[:private_key_path]).to eq(
[File.expand_path("~/foo", env.root_path)]
)
end
it "should with the config private key path" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = "~/bar"
expect(instance.ssh_info[:private_key_path]).to eq(
[File.expand_path("~/bar", env.root_path)]
)
end
end
it "should return the default private key path if provider and config doesn't have one" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = nil
expect(instance.ssh_info[:private_key_path]).to eq(
instance.env.default_private_key_paths
)
end
it "should return the default private key path with keys_only = false" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = nil
instance.config.ssh.keys_only = false
expect(instance.ssh_info[:private_key_path]).to eq(
instance.env.default_private_key_paths
)
end
it "should not set any default private keys if a password is specified" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = nil
instance.config.ssh.password = ""
expect(instance.ssh_info[:private_key_path]).to be_empty
expect(instance.ssh_info[:password]).to eql("")
end
it "should return the private key in the data dir above all else" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = nil
instance.config.ssh.password = ""
instance.data_dir.join("private_key").open("w+") do |f|
f.write("hey")
end
expect(instance.ssh_info[:private_key_path]).to eql(
[instance.data_dir.join("private_key").to_s])
expect(instance.ssh_info[:password]).to eql("")
end
it "should return the private key in the Vagrantfile if the data dir exists" do
path = "/foo"
path = "C:/foo" if Vagrant::Util::Platform.windows?
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = path
instance.data_dir.join("private_key").open("w+") do |f|
f.write("hey")
end
expect(instance.ssh_info[:private_key_path]).to eql([path])
end
it "should return the remote_user when set" do
instance.config.ssh.remote_user = "remote-user"
expect(instance.ssh_info[:remote_user]).to eq("remote-user")
end
it "should return the config when set" do
instance.config.ssh.config = "/path/to/ssh_config"
expect(instance.ssh_info[:config]).to eq("/path/to/ssh_config")
end
it "should return the default connect_timeout" do
expect(instance.ssh_info[:connect_timeout]).
to eq(VagrantPlugins::Kernel_V2::SSHConnectConfig::DEFAULT_SSH_CONNECT_TIMEOUT)
end
it "should return the connect_timeout when set" do
instance.config.ssh.connect_timeout = 2
expect(instance.ssh_info[:connect_timeout]).to eq(2)
end
context "with no data dir" do
let(:base) { true }
let(:data_dir) { nil }
it "returns nil as the private key path" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = nil
instance.config.ssh.password = ""
expect(instance.ssh_info[:private_key_path]).to be_empty
expect(instance.ssh_info[:password]).to eql("")
end
end
context "with custom ssh_info" do
it "keys_only should be default" do
expect(instance.ssh_info[:keys_only]).to be(true)
end
it "verify_host_key should be default" do
expect(instance.ssh_info[:verify_host_key]).to be(:never)
end
it "extra_args should be nil" do
expect(instance.ssh_info[:extra_args]).to be(nil)
end
it "extra_args should be set" do
instance.config.ssh.extra_args = ["-L", "127.1.2.7:8008:127.1.2.7:8008"]
expect(instance.ssh_info[:extra_args]).to eq(["-L", "127.1.2.7:8008:127.1.2.7:8008"])
end
it "extra_args should be set as an array" do
instance.config.ssh.extra_args = "-6"
expect(instance.ssh_info[:extra_args]).to eq("-6")
end
it "keys_only should be overridden" do
instance.config.ssh.keys_only = false
expect(instance.ssh_info[:keys_only]).to be(false)
end
it "verify_host_key should be overridden" do
instance.config.ssh.verify_host_key = true
expect(instance.ssh_info[:verify_host_key]).to be(true)
end
end
end
end
describe "#state" do
it "should query state from the provider" do
state = Vagrant::MachineState.new(:id, "short", "long")
allow(provider).to receive(:state).and_return(state)
expect(instance.state.id).to eq(:id)
end
it "should raise an exception if a MachineState is not returned" do
expect(provider).to receive(:state).and_return(:old_school)
expect { instance.state }.
to raise_error(Vagrant::Errors::MachineStateInvalid)
end
it "should save the state with the index" do
allow(provider).to receive(:machine_id_changed)
subject.id = "foo"
state = Vagrant::MachineState.new(:id, "short", "long")
expect(provider).to receive(:state).and_return(state)
subject.state
entry = env.machine_index.get(subject.index_uuid)
expect(entry).to_not be_nil
expect(entry.state).to eq("short")
env.machine_index.release(entry)
end
end
describe "#recover_machine" do
it "does not recover a machine already in the index" do
subject.id = "foo"
expected_entry = env.machine_index.get(subject.index_uuid)
env.machine_index.release(expected_entry)
entry = subject.recover_machine(:running)
expect(entry.id).to eq(expected_entry.id)
# Ensure entry is not locked
env.machine_index.get(subject.index_uuid)
end
it "recovers a machine" do
instance = new_instance
instance.id = "foo"
entry = instance.recover_machine(:running)
expect(entry.id).to eq(instance.index_uuid)
# Ensure entry is not locked
env.machine_index.get(entry.id)
env.machine_index.release(entry)
query_entry = env.machine_index.get(instance.index_uuid)
expect(entry.id).to eq(query_entry.id)
end
end
describe "#with_ui" do
it "temporarily changes the UI" do
ui = Object.new
changed_ui = nil
subject.with_ui(ui) do
changed_ui = subject.ui
end
expect(changed_ui).to equal(ui)
expect(subject.ui).to_not equal(ui)
end
end
describe "#reload" do
context "when ID is unset and id file does not exist" do
it "should remain nil" do
expect(subject.id).to be_nil
instance.reload
expect(subject.id).to be_nil
end
end
context "when id file is set" do
let(:id_content) { "test-machine-id" }
before do
id_file = subject.data_dir.join("id")
File.write(id_file.to_s, id_content)
end
it "should update the machine id" do
expect(subject.id).to be_nil
instance.reload
expect(subject.id).to eq(id_content)
end
it "should notify of the id change when provider is set" do
expect(provider).to receive(:machine_id_changed)
instance.reload
end
context "when id file content includes whitespace" do
let(:id_content) { " test-machine-id\n" }
it "should remove all whitespace" do
instance.reload
expect(instance.id).to eq("test-machine-id")
end
end
context "when id file content is all whitespace" do
let(:id_content) { "\0\0\0\0\0\0" }
it "should not update the id" do
expect(instance.id).to be_nil
instance.reload
expect(instance.id).to be_nil
end
end
context "when id is already set to value in id file" do
it "should not notify of id change" do
instance.instance_variable_set(:@id, id_content)
expect(provider).not_to receive(:machine_id_changed)
instance.reload
expect(instance.id).to eq(id_content)
end
end
end
end
end