From d5c20dd81bda4aebee2c1ed426208841a6e659bc Mon Sep 17 00:00:00 2001 From: "Marc A. Paradise" Date: Fri, 26 Apr 2019 15:14:41 -0400 Subject: Add tests, and make connector more testable Signed-off-by: Marc A. Paradise --- lib/chef/knife/bootstrap/train_connector.rb | 8 +- spec/unit/knife/bootstrap/train_connector_spec.rb | 133 +++++++++++++++++++++- 2 files changed, 133 insertions(+), 8 deletions(-) diff --git a/lib/chef/knife/bootstrap/train_connector.rb b/lib/chef/knife/bootstrap/train_connector.rb index 40e0c565ee..06bc2ba900 100644 --- a/lib/chef/knife/bootstrap/train_connector.rb +++ b/lib/chef/knife/bootstrap/train_connector.rb @@ -36,7 +36,6 @@ class Chef MKTEMP_NIX_COMMAND = "bash -c 'd=$(mktemp -d ${TMPDIR:-/tmp}/chef_XXXXXX); echo $d'".freeze def initialize(host_url, default_transport, opts) - uri_opts = opts_from_uri(host_url) uri_opts[:backend] ||= @default_transport @transport_type = uri_opts[:backend] @@ -59,12 +58,7 @@ class Chef # Specifying sudo: false ensures that attempted operations # don't fail because the mock platform doesn't support sudo tc = TrainConnector.new(url, protocol, { sudo: false }.merge(opts)) - - # Don't pull in the platform-specific mixins automatically during connect - # Otherwise, it will raise since it can't resolve the OS without the mock. tc.connect! - # We need to provide this mock before invoking mix_in_target_platform, - # otherwise it will fail with an unknown OS (since we don't have a real connection). tc.backend.mock_os( family: family, name: name, @@ -72,7 +66,6 @@ class Chef arch: arch ) tc - end def connect! @@ -174,6 +167,7 @@ class Chef def train @train end + def backend @train.connection end diff --git a/spec/unit/knife/bootstrap/train_connector_spec.rb b/spec/unit/knife/bootstrap/train_connector_spec.rb index 872bf5481d..b15965645f 100644 --- a/spec/unit/knife/bootstrap/train_connector_spec.rb +++ b/spec/unit/knife/bootstrap/train_connector_spec.rb @@ -20,5 +20,136 @@ require "ostruct" require "chef/knife/bootstrap/train_connector" describe Chef::Knife::Bootstrap::TrainConnector do - # Tests in flight, will be pushed up + let(:protocol) { "mock" } + let(:family) { "unknown" } + let(:release) { "unknown" } # version + let(:name) { "unknown" } + let(:arch) { "x86_64" } + let(:host_url) { "mock://user1@example.com" } + let(:opts) { {} } + subject do + # Create a valid TargetHost with the backend stubbed out. + Chef::Knife::Bootstrap::TrainConnector.test_instance(host_url, + protocol: protocol, + family: family, + name: name, + release: release, + arch: arch, + opts: opts) + end + + context "connect!" do + end + + describe "platform helpers" do + context "on linux" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "reports that it is linux and unix, because that is how train classifies it" do + expect(subject.unix?).to eq true + expect(subject.linux?).to eq true + expect(subject.windows?).to eq false + end + end + context "on unix" do + let(:family) { "os" } + let(:name) { "mac_os_x" } + it "reports only a unix OS" do + expect(subject.unix?).to eq true + expect(subject.linux?).to eq false + expect(subject.windows?).to eq false + end + end + context "on windows" do + let(:family) { "windows" } + let(:name) { "windows" } + it "reports only a windows OS" do + expect(subject.unix?).to eq false + expect(subject.linux?).to eq false + expect(subject.windows?).to eq true + end + end + end + + describe "#connect!" do + it "establishes the connection to the remote host by waiting for it" do + expect(subject.backend).to receive(:wait_until_ready) + subject.connect! + end + end + + describe "#temp_dir" do + context "under windows" do + let(:family) { "windows" } + let(:name) { "windows" } + + it "uses the windows command to create the temp dir" do + expected_command = Chef::Knife::Bootstrap::TrainConnector::MKTEMP_WIN_COMMAND + expect(subject).to receive(:run_command!).with(expected_command) + .and_return double("result", stdout: "C:/a/path") + expect(subject.temp_dir).to eq "C:/a/path" + end + + end + context "under linux and unix-like" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "uses the *nix command to create the temp dir and sets ownership to logged-in user" do + expected_command = Chef::Knife::Bootstrap::TrainConnector::MKTEMP_NIX_COMMAND + expect(subject).to receive(:run_command!).with(expected_command) + .and_return double("result", stdout: "/a/path") + expect(subject).to receive(:run_command!).with("chown user1 '/a/path'") + expect(subject.temp_dir).to eq "/a/path" + end + + end + end + context "#upload_file_content!" do + it "creates a local file with expected content and uploads it" do + expect(subject).to receive(:upload_file!) do |local_path, remote_path| + expect(File.read(local_path)).to eq "test data" + expect(remote_path).to eq "/target/path" + end + subject.upload_file_content!("test data", "/target/path") + end + end + + context "del_file" do + context "on windows" do + let(:family) { "windows" } + let(:name) { "windows" } + it "deletes the file with a windows command" do + expect(subject).to receive(:run_command!) do |cmd, &_handler| + expect(cmd).to match(/Test-Path "deleteme\.txt".*/) + end + subject.del_file!("deleteme.txt") + end + end + context "on unix-like" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "deletes the file with a windows command" do + expect(subject).to receive(:run_command!) do |cmd, &_handler| + expect(cmd).to match(/rm -f "deleteme\.txt".*/) + end + subject.del_file!("deleteme.txt") + end + end + end + + context "#run_command!" do + it "raises a RemoteExecutionFailed when the remote execution failed" do + command_result = double("results", stdout: "", stderr: "failed", exit_status: 1) + expect(subject).to receive(:run_command).and_return command_result + + expect { subject.run_command!("test") }.to raise_error do |e| + expect(e.hostname).to eq subject.hostname + expect(e.class).to eq Chef::Knife::Bootstrap::RemoteExecutionFailed + expect(e.stderr).to eq "failed" + expect(e.stdout).to eq "" + expect(e.exit_status).to eq 1 + end + end + end + end -- cgit v1.2.1