# # Author:: Jesse Campbell () # Copyright:: Copyright (c) 2013 Jesse Campbell # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require 'spec_helper' describe Chef::Provider::RemoteFile::FTP do let(:enclosing_directory) { canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates"))) } let(:resource_path) { canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt"))) } let(:new_resource) do r = Chef::Resource::RemoteFile.new("remote file ftp backend test (new resource)") r.ftp_active_mode(false) r.path(resource_path) r end let(:current_resource) do Chef::Resource::RemoteFile.new("remote file ftp backend test (current resource)'") end let(:ftp) do ftp = mock(Net::FTP, { }) ftp.stub!(:connect) ftp.stub!(:login) ftp.stub!(:voidcmd) ftp.stub!(:mtime).and_return(Time.now) ftp.stub!(:getbinaryfile) ftp.stub!(:close) ftp.stub!(:passive=) ftp end let(:tempfile_path) { "/tmp/somedir/remote-file-ftp-backend-spec-test" } let(:tempfile) do t = StringIO.new t.stub(:path).and_return(tempfile_path) t end let(:uri) { URI.parse("ftp://opscode.com/seattle.txt") } before(:each) do Net::FTP.stub!(:new).with().and_return(ftp) Tempfile.stub!(:new).and_return(tempfile) end describe "when first created" do it "throws an argument exception when no path is given" do uri.path = "" lambda { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.should raise_error(ArgumentError) end it "throws an argument exception when only a / is given" do uri.path = "/" lambda { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.should raise_error(ArgumentError) end it "throws an argument exception when no filename is given" do uri.path = "/the/whole/path/" lambda { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.should raise_error(ArgumentError) end it "throws an argument exception when the typecode is invalid" do uri.typecode = "d" lambda { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.should raise_error(ArgumentError) end it "does not use passive mode when new_resource sets ftp_active_mode to true" do new_resource.ftp_active_mode(true) fetcher = Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) fetcher.use_passive_mode?.should be_false end it "uses passive mode when new_resource sets ftp_active_mode to false" do new_resource.ftp_active_mode(false) fetcher = Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) fetcher.use_passive_mode?.should be_true end end describe "when fetching the object" do let(:cache_control_data) { Chef::Provider::RemoteFile::CacheControlData.new(uri) } let(:current_resource_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" } subject(:fetcher) { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) } before do current_resource.checksum(current_resource_checksum) #Chef::Provider::RemoteFile::CacheControlData.should_receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data) end it "should connect to the host from the uri on the default port 21" do ftp.should_receive(:connect).with("opscode.com", 21) fetcher.fetch end it "should set passive true when ftp_active_mode is false" do new_resource.ftp_active_mode(false) ftp.should_receive(:passive=).with(true) fetcher.fetch end it "should set passive false when ftp_active_mode is false" do new_resource.ftp_active_mode(true) ftp.should_receive(:passive=).with(false) fetcher.fetch end it "should use anonymous ftp when no userinfo is provided" do ftp.should_receive(:login).with("anonymous", nil) fetcher.fetch end context "and the URI specifies an alternate port" do let(:uri) { URI.parse("ftp://opscode.com:8021/seattle.txt") } it "should connect on an alternate port when one is provided" do uri = URI.parse("ftp://opscode.com:8021/seattle.txt") ftp.should_receive(:connect).with("opscode.com", 8021) fetcher.fetch end end context "and the URI contains a username and password" do let(:uri) { URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt") } it "should use authenticated ftp when userinfo is provided" do ftp.should_receive(:login).with("the_user", "the_password") fetcher.fetch end end context "and the uri sets the typecode to ascii" do let(:uri) { URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt;type=a") } it "fetches the file with ascii typecode set" do ftp.should_receive(:voidcmd).with("TYPE A").once fetcher.fetch end end context "and the uri sets the typecode to image" do let(:uri) { URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt;type=i") } it "should accept image for the typecode" do ftp.should_receive(:voidcmd).with("TYPE I").once fetcher.fetch end end context "and the uri specifies a nested path" do let(:uri) { URI.parse("ftp://opscode.com/the/whole/path/seattle.txt") } it "should fetch the file from the correct path" do ftp.should_receive(:voidcmd).with("CWD the").once ftp.should_receive(:voidcmd).with("CWD whole").once ftp.should_receive(:voidcmd).with("CWD path").once ftp.should_receive(:getbinaryfile).with("seattle.txt", tempfile.path) fetcher.fetch end end context "when not using last modified based conditional fetching" do before do new_resource.use_last_modified(false) end it "should return a tempfile in the result" do result = fetcher.fetch result.should equal(tempfile) end end context "and proxying is enabled" do before do Chef::Config[:ftp_proxy] = "socks5://socks.example.com:5000" Chef::Config[:ftp_proxy_user] = "bill" Chef::Config[:ftp_proxy_pass] = "ted" end it "fetches the file via the proxy" do current_socks_server = ENV["SOCKS_SERVER"] ENV.should_receive(:[]=).with("SOCKS_SERVER", "socks5://bill:ted@socks.example.com:5000").ordered ENV.should_receive(:[]=).with("SOCKS_SERVER", current_socks_server).ordered result = fetcher.fetch result.should equal(tempfile) end end end end