diff options
Diffstat (limited to 'spec/functional')
-rw-r--r-- | spec/functional/knife/cookbook_delete_spec.rb | 162 | ||||
-rw-r--r-- | spec/functional/knife/exec_spec.rb | 62 | ||||
-rw-r--r-- | spec/functional/knife/ssh_spec.rb | 211 | ||||
-rw-r--r-- | spec/functional/resource/directory_spec.rb | 39 | ||||
-rw-r--r-- | spec/functional/resource/file_spec.rb | 69 | ||||
-rw-r--r-- | spec/functional/resource/link_spec.rb | 572 | ||||
-rw-r--r-- | spec/functional/resource/remote_directory_spec.rb | 116 | ||||
-rw-r--r-- | spec/functional/resource/remote_file_spec.rb | 58 | ||||
-rw-r--r-- | spec/functional/resource/template_spec.rb | 70 | ||||
-rw-r--r-- | spec/functional/run_lock_spec.rb | 90 | ||||
-rw-r--r-- | spec/functional/tiny_server_spec.rb | 77 |
11 files changed, 1526 insertions, 0 deletions
diff --git a/spec/functional/knife/cookbook_delete_spec.rb b/spec/functional/knife/cookbook_delete_spec.rb new file mode 100644 index 0000000000..54081263f0 --- /dev/null +++ b/spec/functional/knife/cookbook_delete_spec.rb @@ -0,0 +1,162 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, Inc. +# 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' +require 'tiny_server' + +describe Chef::Knife::CookbookDelete do + before(:all) do + @original_config = Chef::Config.hash_dup + + Thin::Logging.silent = true + + @server = TinyServer::Manager.new + @server.start + end + + before(:each) do + @knife = Chef::Knife::CookbookDelete.new + @api = TinyServer::API.instance + @api.clear + + Chef::Config[:node_name] = nil + Chef::Config[:client_key] = nil + Chef::Config[:chef_server_url] = 'http://localhost:9000' + end + + after(:all) do + Chef::Config.configuration = @original_config + @server.stop + end + + context "when the the cookbook doesn't exist" do + before do + @log_output = StringIO.new + + Chef::Log.logger = Logger.new(@log_output) + Chef::Log.level = :debug + + @knife.name_args = %w{no-such-cookbook} + @api.get("/cookbooks/no-such-cookbook", 404, {'error'=>'dear Tim, no. -Sent from my iPad'}.to_json) + end + + it "logs an error and exits" do + @knife.ui.stub!(:stderr).and_return(@log_output) + lambda {@knife.run}.should raise_error(SystemExit) + @log_output.string.should match(/Cannot find a cookbook named no-such-cookbook to delete/) + end + + end + + context "when there is only one version of a cookbook" do + before do + @knife.name_args = %w{obsolete-cookbook} + @cookbook_list = {'obsolete-cookbook' => { 'versions' => ['version' => '1.0.0']} } + @api.get("/cookbooks/obsolete-cookbook", 200, @cookbook_list.to_json) + end + + it "asks for confirmation, then deletes the cookbook" do + stdin, stdout = StringIO.new("y\n"), StringIO.new + @knife.ui.stub!(:stdin).and_return(stdin) + @knife.ui.stub!(:stdout).and_return(stdout) + + cb100_deleted = false + @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } + + @knife.run + + stdout.string.should match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/) + cb100_deleted.should be_true + end + + it "asks for confirmation before purging" do + @knife.config[:purge] = true + + stdin, stdout = StringIO.new("y\ny\n"), StringIO.new + @knife.ui.stub!(:stdin).and_return(stdin) + @knife.ui.stub!(:stdout).and_return(stdout) + + cb100_deleted = false + @api.delete("/cookbooks/obsolete-cookbook/1.0.0?purge=true", 200) { cb100_deleted = true; "[\"true\"]" } + + @knife.run + + stdout.string.should match(/#{Regexp.escape('Are you sure you want to purge files')}/) + stdout.string.should match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/) + cb100_deleted.should be_true + + end + + end + + context "when there are several versions of a cookbook" do + before do + @knife.name_args = %w{obsolete-cookbook} + versions = ['1.0.0', '1.1.0', '1.2.0'] + with_version = lambda { |version| { 'version' => version } } + @cookbook_list = {'obsolete-cookbook' => { 'versions' => versions.map(&with_version) } } + @api.get("/cookbooks/obsolete-cookbook", 200, @cookbook_list.to_json) + end + + it "deletes all versions of a cookbook when given the '-a' flag" do + @knife.config[:all] = true + @knife.config[:yes] = true + cb100_deleted = cb110_deleted = cb120_deleted = nil + @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } + @api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" } + @api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" } + @knife.run + + cb100_deleted.should be_true + cb110_deleted.should be_true + cb120_deleted.should be_true + end + + it "asks which version to delete and deletes that when not given the -a flag" do + cb100_deleted = cb110_deleted = cb120_deleted = nil + @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } + stdin, stdout = StringIO.new, StringIO.new + @knife.ui.stub!(:stdin).and_return(stdin) + @knife.ui.stub!(:stdout).and_return(stdout) + stdin << "1\n" + stdin.rewind + @knife.run + cb100_deleted.should be_true + stdout.string.should match(/Which version\(s\) do you want to delete\?/) + end + + it "deletes all versions of the cookbook when not given the -a flag and the user chooses to delete all" do + cb100_deleted = cb110_deleted = cb120_deleted = nil + @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } + @api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" } + @api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" } + + stdin, stdout = StringIO.new("4\n"), StringIO.new + @knife.ui.stub!(:stdin).and_return(stdin) + @knife.ui.stub!(:stdout).and_return(stdout) + + @knife.run + + cb100_deleted.should be_true + cb110_deleted.should be_true + cb120_deleted.should be_true + end + + end + +end diff --git a/spec/functional/knife/exec_spec.rb b/spec/functional/knife/exec_spec.rb new file mode 100644 index 0000000000..fa4a448fbb --- /dev/null +++ b/spec/functional/knife/exec_spec.rb @@ -0,0 +1,62 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, Inc. +# 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' +require 'tiny_server' + +describe Chef::Knife::Exec do + before(:all) do + @original_config = Chef::Config.hash_dup + + Thin::Logging.silent = false + + @server = TinyServer::Manager.new#(:debug => true) + @server.start + end + + before(:each) do + @knife = Chef::Knife::Exec.new + @api = TinyServer::API.instance + @api.clear + + Chef::Config[:node_name] = nil + Chef::Config[:client_key] = nil + Chef::Config[:chef_server_url] = 'http://localhost:9000' + + $output = StringIO.new + end + + after(:all) do + Chef::Config.configuration = @original_config + @server.stop + end + + pending "executes a script in the context of the chef-shell main context", :ruby_18_only + + it "executes a script in the context of the chef-shell main context", :ruby_19_only do + @node = Chef::Node.new + @node.name("ohai-world") + response = {"rows" => [@node],"start" => 0,"total" => 1} + @api.get(%r{^/search/node}, 200, response.to_json) + code = "$output.puts nodes.all.inspect" + @knife.config[:exec] = code + @knife.run + $output.string.should match(%r{node\[ohai-world\]}) + end + +end diff --git a/spec/functional/knife/ssh_spec.rb b/spec/functional/knife/ssh_spec.rb new file mode 100644 index 0000000000..8f87e53bf7 --- /dev/null +++ b/spec/functional/knife/ssh_spec.rb @@ -0,0 +1,211 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, Inc. +# 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' +require 'tiny_server' + +describe Chef::Knife::Ssh do + + before(:all) do + @original_config = Chef::Config.hash_dup + Chef::Knife::Ssh.load_deps + Thin::Logging.silent = true + @server = TinyServer::Manager.new + @server.start + end + + after(:all) do + Chef::Config.configuration = @original_config + @server.stop + end + + describe "identity file" do + context "when knife[:ssh_identity_file] is set" do + before do + setup_knife(['*:*', 'uptime']) + Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa" + end + + it "uses the ssh_identity_file" do + @knife.run + @knife.config[:identity_file].should == "~/.ssh/aws.rsa" + end + end + + context "when knife[:ssh_identity_file] is set and frozen" do + before do + setup_knife(['*:*', 'uptime']) + Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa".freeze + end + + it "uses the ssh_identity_file" do + @knife.run + @knife.config[:identity_file].should == "~/.ssh/aws.rsa" + end + end + + context "when -i is provided" do + before do + setup_knife(['-i ~/.ssh/aws.rsa', '*:*', 'uptime']) + Chef::Config[:knife][:ssh_identity_file] = nil + end + + it "should use the value on the command line" do + @knife.run + @knife.config[:identity_file].should == "~/.ssh/aws.rsa" + end + + it "should override what is set in knife.rb" do + Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/other.rsa" + @knife.run + @knife.config[:identity_file].should == "~/.ssh/aws.rsa" + end + end + + context "when knife[:ssh_identity_file] is not provided]" do + before do + setup_knife(['*:*', 'uptime']) + Chef::Config[:knife][:ssh_identity_file] = nil + end + + it "uses the default" do + @knife.run + @knife.config[:identity_file].should == nil + end + end + end + + describe "user" do + context "when knife[:ssh_user] is set" do + before do + setup_knife(['*:*', 'uptime']) + Chef::Config[:knife][:ssh_user] = "ubuntu" + end + + it "uses the ssh_user" do + @knife.run + @knife.config[:ssh_user].should == "ubuntu" + end + end + + context "when knife[:ssh_user] is set and frozen" do + before do + setup_knife(['*:*', 'uptime']) + Chef::Config[:knife][:ssh_user] = "ubuntu".freeze + end + + it "uses the ssh_user" do + @knife.run + @knife.config[:ssh_user].should == "ubuntu" + end + end + + context "when -x is provided" do + before do + setup_knife(['-x ubuntu', '*:*', 'uptime']) + Chef::Config[:knife][:ssh_user] = nil + end + + it "should use the value on the command line" do + @knife.run + @knife.config[:ssh_user].should == "ubuntu" + end + + it "should override what is set in knife.rb" do + Chef::Config[:knife][:ssh_user] = "root" + @knife.run + @knife.config[:ssh_user].should == "ubuntu" + end + end + + context "when knife[:ssh_user] is not provided]" do + before do + setup_knife(['*:*', 'uptime']) + Chef::Config[:knife][:ssh_user] = nil + end + + it "uses the default" do + @knife.run + @knife.config[:ssh_user].should == nil + end + end + end + + describe "attribute" do + context "when knife[:ssh_attribute] is set" do + before do + setup_knife(['*:*', 'uptime']) + Chef::Config[:knife][:ssh_attribute] = "ec2.public_hostname" + end + + it "uses the ssh_attribute" do + @knife.run + @knife.config[:attribute].should == "ec2.public_hostname" + end + end + + context "when knife[:ssh_attribute] is not provided]" do + before do + setup_knife(['*:*', 'uptime']) + Chef::Config[:knife][:ssh_attribute] = nil + end + + it "uses the default" do + @knife.run + @knife.config[:attribute].should == "fqdn" + end + end + + context "when -a ec2.public_ipv4 is provided" do + before do + setup_knife(['-a ec2.public_hostname', '*:*', 'uptime']) + Chef::Config[:knife][:ssh_attribute] = nil + end + + it "should use the value on the command line" do + @knife.run + @knife.config[:attribute].should == "ec2.public_hostname" + end + + it "should override what is set in knife.rb" do + # This is the setting imported from knife.rb + Chef::Config[:knife][:ssh_attribute] = "fqdn" + # Then we run knife with the -a flag, which sets the above variable + setup_knife(['-a ec2.public_hostname', '*:*', 'uptime']) + @knife.run + @knife.config[:attribute].should == "ec2.public_hostname" + end + end + end + + def setup_knife(params=[]) + @knife = Chef::Knife::Ssh.new(params) + @knife.stub!(:ssh_command).and_return { [] } + @api = TinyServer::API.instance + @api.clear + + Chef::Config[:node_name] = nil + Chef::Config[:client_key] = nil + Chef::Config[:chef_server_url] = 'http://localhost:9000' + + @api.get("/search/node?q=*:*&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000", 200) { + %({"total":1, "start":0, "rows":[{"name":"i-xxxxxxxx", "json_class":"Chef::Node", "automatic":{"fqdn":"the.fqdn", "ec2":{"public_hostname":"the_public_hostname"}},"recipes":[]}]}) + } + end + +end diff --git a/spec/functional/resource/directory_spec.rb b/spec/functional/resource/directory_spec.rb new file mode 100644 index 0000000000..5777a114fc --- /dev/null +++ b/spec/functional/resource/directory_spec.rb @@ -0,0 +1,39 @@ +# +# Author:: Seth Chisamore (<schisamo@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, Inc. +# 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::Resource::Directory do + include_context Chef::Resource::Directory + + let(:directory_base) { "directory_spec" } + + def create_resource + events = Chef::EventDispatch::Dispatcher.new + node = Chef::Node.new + run_context = Chef::RunContext.new(node, {}, events) + Chef::Resource::Directory.new(path, run_context) + end + + let!(:resource) do + create_resource + end + + it_behaves_like "a directory resource" + +end diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb new file mode 100644 index 0000000000..d587e5f93b --- /dev/null +++ b/spec/functional/resource/file_spec.rb @@ -0,0 +1,69 @@ +# +# Author:: Seth Chisamore (<schisamo@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, Inc. +# 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::Resource::File do + include_context Chef::Resource::File + + let(:file_base) { "file_spec" } + let(:expected_content) { "Don't fear the ruby." } + + def create_resource + events = Chef::EventDispatch::Dispatcher.new + node = Chef::Node.new + run_context = Chef::RunContext.new(node, {}, events) + resource = Chef::Resource::File.new(path, run_context) + resource.content(expected_content) + resource + end + + let!(:resource) do + create_resource + end + + it_behaves_like "a file resource" + + context "when the target file does not exist" do + it "it creates the file when the :touch action is run" do + resource.run_action(:touch) + File.should exist(path) + end + end + + context "when the target file has the correct content" do + before(:each) do + File.open(path, "w") { |f| f.print expected_content } + end + + it "updates the mtime/atime of the file when the :touch action is run" do + expected_mtime = File.stat(path).mtime + expected_atime = File.stat(path).atime + sleep 1 + resource.run_action(:touch) + File.stat(path).mtime.should > expected_mtime + File.stat(path).atime.should > expected_atime + end + + it "does not change the content when :touch action is run" do + expected_checksum = sha256_checksum(path) + resource.run_action(:touch) + sha256_checksum(path).should == expected_checksum + end + end +end diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb new file mode 100644 index 0000000000..b80dc72d49 --- /dev/null +++ b/spec/functional/resource/link_spec.rb @@ -0,0 +1,572 @@ +# +# Author:: John Keiser (<jkeiser@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, Inc. +# 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' + +if windows? + require 'chef/win32/file' #probably need this in spec_helper +end + +describe Chef::Resource::Link do + let(:file_base) { "file_spec" } + + let(:base_dir) do + if windows? + Chef::ReservedNames::Win32::File.get_long_path_name(Dir.tmpdir.gsub('/', '\\')) + else + Dir.tmpdir + end + end + + let(:to) do + File.join(base_dir, make_tmpname("to_spec", nil)) + end + let(:target_file) do + File.join(base_dir, make_tmpname("from_spec", nil)) + end + + after(:each) do + # TODO Windows fails to clean up some symlinks. + begin + FileUtils.rm_r(to) if File.exists?(to) + FileUtils.rm_r(target_file) if File.exists?(target_file) + FileUtils.rm_r(CHEF_SPEC_BACKUP_PATH) if File.exists?(CHEF_SPEC_BACKUP_PATH) + rescue + puts "Could not remove a file: #{$!}" + end + end + + def canonicalize(path) + windows? ? path.gsub('/', '\\') : path + end + + def symlink(a, b) + if windows? + Chef::ReservedNames::Win32::File.symlink(a, b) + else + File.symlink(a, b) + end + end + def symlink?(file) + if windows? + Chef::ReservedNames::Win32::File.symlink?(file) + else + File.symlink?(file) + end + end + def readlink(file) + if windows? + Chef::ReservedNames::Win32::File.readlink(file) + else + File.readlink(file) + end + end + def link(a, b) + if windows? + Chef::ReservedNames::Win32::File.link(a, b) + else + File.link(a, b) + end + end + + def create_resource + node = Chef::Node.new + events = Chef::EventDispatch::Dispatcher.new + cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) + cookbook_collection = Chef::CookbookCollection.new(Chef::CookbookLoader.new(cookbook_repo)) + run_context = Chef::RunContext.new(node, cookbook_collection, events) + resource = Chef::Resource::Link.new(target_file, run_context) + resource.to(to) + resource + end + + let!(:resource) do + create_resource + end + + shared_examples_for 'delete errors out' do + it 'delete errors out' do + lambda { resource.run_action(:delete) }.should raise_error(Chef::Exceptions::Link) + (File.exist?(target_file) || symlink?(target_file)).should be_true + end + end + + shared_context 'delete is noop' do + describe 'the :delete action' do + before(:each) do + @info = [] + Chef::Log.stub!(:info) { |msg| @info << msg } + resource.run_action(:delete) + end + + it 'leaves the file deleted' do + File.exist?(target_file).should be_false + symlink?(target_file).should be_false + end + it 'does not mark the resource updated' do + resource.should_not be_updated + end + it 'does not log that it deleted' do + @info.include?("link[#{target_file}] deleted").should be_false + end + end + end + + shared_context 'delete succeeds' do + describe 'the :delete action' do + before(:each) do + @info = [] + Chef::Log.stub!(:info) { |msg| @info << msg } + resource.run_action(:delete) + end + + it 'deletes the file' do + File.exist?(target_file).should be_false + symlink?(target_file).should be_false + end + it 'marks the resource updated' do + resource.should be_updated + end + it 'logs that it deleted' do + @info.include?("link[#{target_file}] deleted").should be_true + end + end + end + + shared_context 'create symbolic link succeeds' do + describe 'the :create action' do + before(:each) do + @info = [] + Chef::Log.stub!(:info) { |msg| @info << msg } + resource.run_action(:create) + end + + it 'links to the target file' do + symlink?(target_file).should be_true + readlink(target_file).should == canonicalize(to) + end + it 'marks the resource updated' do + resource.should be_updated + end + it 'logs that it created' do + @info.include?("link[#{target_file}] created").should be_true + end + end + end + + shared_context 'create symbolic link is noop' do + describe 'the :create action' do + before(:each) do + @info = [] + Chef::Log.stub!(:info) { |msg| @info << msg } + resource.run_action(:create) + end + + it 'leaves the file linked' do + symlink?(target_file).should be_true + readlink(target_file).should == canonicalize(to) + end + it 'does not mark the resource updated' do + resource.should_not be_updated + end + it 'does not log that it created' do + @info.include?("link[#{target_file}] created").should be_false + end + end + end + + shared_context 'create hard link succeeds' do + describe 'the :create action' do + before(:each) do + @info = [] + Chef::Log.stub!(:info) { |msg| @info << msg } + resource.run_action(:create) + end + it 'preserves the hard link' do + File.exists?(target_file).should be_true + symlink?(target_file).should be_false + # Writing to one hardlinked file should cause both + # to have the new value. + IO.read(to).should == IO.read(target_file) + File.open(to, "w") { |file| file.write('wowzers') } + IO.read(target_file).should == 'wowzers' + end + it 'marks the resource updated' do + resource.should be_updated + end + it 'logs that it created' do + @info.include?("link[#{target_file}] created").should be_true + end + end + end + + shared_context 'create hard link is noop' do + describe 'the :create action' do + before(:each) do + @info = [] + Chef::Log.stub!(:info) { |msg| @info << msg } + resource.run_action(:create) + end + it 'links to the target file' do + File.exists?(target_file).should be_true + symlink?(target_file).should be_false + # Writing to one hardlinked file should cause both + # to have the new value. + IO.read(to).should == IO.read(target_file) + File.open(to, "w") { |file| file.write('wowzers') } + IO.read(target_file).should == 'wowzers' + end + it 'does not mark the resource updated' do + resource.should_not be_updated + end + it 'does not log that it created' do + @info.include?("link[#{target_file}] created").should be_false + end + end + end + + context "is symbolic" do + + context 'when the link destination is a file' do + before(:each) do + File.open(to, "w") do |file| + file.write('woohoo') + end + end + context 'and the link does not yet exist' do + include_context 'create symbolic link succeeds' + include_context 'delete is noop' + end + context 'and the link already exists and is a symbolic link' do + context 'pointing at the target' do + before(:each) do + symlink(to, target_file) + symlink?(target_file).should be_true + readlink(target_file).should == canonicalize(to) + end + include_context 'create symbolic link is noop' + include_context 'delete succeeds' + it 'the :delete action does not delete the target file' do + resource.run_action(:delete) + File.exists?(to).should be_true + end + end + context 'pointing somewhere else' do + before(:each) do + @other_target = File.join(base_dir, make_tmpname('other_spec', nil)) + File.open(@other_target, 'w') { |file| file.write('eek') } + symlink(@other_target, target_file) + symlink?(target_file).should be_true + readlink(target_file).should == @other_target + end + after(:each) do + File.delete(@other_target) + end + include_context 'create symbolic link succeeds' + include_context 'delete succeeds' + it 'the :delete action does not delete the target file' do + resource.run_action(:delete) + File.exists?(to).should be_true + end + end + context 'pointing nowhere' do + before(:each) do + nonexistent = File.join(base_dir, make_tmpname('nonexistent_spec', nil)) + symlink(nonexistent, target_file) + symlink?(target_file).should be_true + readlink(target_file).should == nonexistent + end + include_context 'create symbolic link succeeds' + include_context 'delete succeeds' + end + end + context 'and the link already exists and is a hard link to the file' do + before(:each) do + link(to, target_file) + File.exists?(target_file).should be_true + symlink?(target_file).should be_false + end + include_context 'create symbolic link succeeds' + it_behaves_like 'delete errors out' + end + context 'and the link already exists and is a file' do + before(:each) do + File.open(target_file, 'w') { |file| file.write('eek') } + end + include_context 'create symbolic link succeeds' + it_behaves_like 'delete errors out' + end + context 'and the link already exists and is a directory' do + before(:each) do + Dir.mkdir(target_file) + end + it 'create errors out' do + if windows? + lambda { resource.run_action(:create) }.should raise_error(Errno::EACCES) + elsif os_x? or solaris? or freebsd? + lambda { resource.run_action(:create) }.should raise_error(Errno::EPERM) + else + lambda { resource.run_action(:create) }.should raise_error(Errno::EISDIR) + end + end + it_behaves_like 'delete errors out' + end + context 'and the link already exists and is not writeable to this user', :pending do + end + it_behaves_like 'a securable resource' do + let(:path) { target_file } + def allowed_acl(sid, expected_perms) + [ ACE.access_allowed(sid, expected_perms[:specific]) ] + end + def denied_acl(sid, expected_perms) + [ ACE.access_denied(sid, expected_perms[:specific]) ] + end + end + end + context 'when the link destination is a directory' do + before(:each) do + Dir.mkdir(to) + end + # On Windows, readlink fails to open the link. FILE_FLAG_OPEN_REPARSE_POINT + # might help, from http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx + context 'and the link does not yet exist' do + include_context 'create symbolic link succeeds' + include_context 'delete is noop' + end + end + context "when the link destination is a symbolic link" do + context 'to a file that exists' do + before(:each) do + @other_target = File.join(base_dir, make_tmpname("other_spec", nil)) + File.open(@other_target, "w") { |file| file.write("eek") } + symlink(@other_target, to) + symlink?(to).should be_true + readlink(to).should == @other_target + end + after(:each) do + File.delete(@other_target) + end + context 'and the link does not yet exist' do + include_context 'create symbolic link succeeds' + include_context 'delete is noop' + end + end + context 'to a file that does not exist' do + before(:each) do + @other_target = File.join(base_dir, make_tmpname("other_spec", nil)) + symlink(@other_target, to) + symlink?(to).should be_true + readlink(to).should == @other_target + end + context 'and the link does not yet exist' do + include_context 'create symbolic link succeeds' + include_context 'delete is noop' + end + end + end + context "when the link destination is not readable to this user", :pending do + end + context "when the link destination does not exist" do + include_context 'create symbolic link succeeds' + include_context 'delete is noop' + end + + { + '../' => 'with a relative link destination', + '' => 'with a bare filename for the link destination' + }.each do |prefix, desc| + context desc do + let(:to) { "#{prefix}#{File.basename(absolute_to)}" } + let(:absolute_to) { File.join(base_dir, make_tmpname("to_spec", nil)) } + before(:each) do + resource.to(to) + end + context 'when the link does not yet exist' do + include_context 'create symbolic link succeeds' + include_context 'delete is noop' + end + context 'when the link already exists and points at the target' do + before(:each) do + symlink(to, target_file) + symlink?(target_file).should be_true + readlink(target_file).should == canonicalize(to) + end + include_context 'create symbolic link is noop' + include_context 'delete succeeds' + end + context 'when the link already exists and points at the target with an absolute path' do + before(:each) do + symlink(absolute_to, target_file) + symlink?(target_file).should be_true + readlink(target_file).should == canonicalize(absolute_to) + end + include_context 'create symbolic link succeeds' + include_context 'delete succeeds' + end + end + end + end + + context "is a hard link" do + before(:each) do + resource.link_type(:hard) + end + + context "when the link destination is a file" do + before(:each) do + File.open(to, "w") do |file| + file.write('woohoo') + end + end + context "and the link does not yet exist" do + include_context 'create hard link succeeds' + include_context 'delete is noop' + end + context "and the link already exists and is a symbolic link pointing at the same file" do + before(:each) do + symlink(to, target_file) + symlink?(target_file).should be_true + readlink(target_file).should == to + end + include_context 'create hard link succeeds' + it_behaves_like 'delete errors out' + end + context 'and the link already exists and is a hard link to the file' do + before(:each) do + link(to, target_file) + File.exists?(target_file).should be_true + symlink?(target_file).should be_false + end + include_context 'create hard link is noop' + include_context 'delete succeeds' + it 'the :delete action does not delete the target file' do + resource.run_action(:delete) + File.exists?(to).should be_true + end + end + context "and the link already exists and is a file" do + before(:each) do + File.open(target_file, 'w') { |file| file.write('tomfoolery') } + end + include_context 'create hard link succeeds' + it_behaves_like 'delete errors out' + end + context "and the link already exists and is a directory" do + before(:each) do + Dir.mkdir(target_file) + end + it 'errors out' do + if windows? + lambda { resource.run_action(:create) }.should raise_error(Errno::EACCES) + elsif os_x? or solaris? or freebsd? + lambda { resource.run_action(:create) }.should raise_error(Errno::EPERM) + else + lambda { resource.run_action(:create) }.should raise_error(Errno::EISDIR) + end + end + it_behaves_like 'delete errors out' + end + context "and the link already exists and is not writeable to this user", :pending do + end + context "and specifies security attributes" do + before(:each) do + resource.owner(windows? ? 'Guest' : 'nobody') + end + it 'ignores them' do + resource.run_action(:create) + if windows? + Chef::ReservedNames::Win32::Security.get_named_security_info(target_file).owner.should_not == SID.Guest + else + File.lstat(target_file).uid.should_not == Etc.getpwnam('nobody').uid + end + end + end + end + context "when the link destination is a directory" do + before(:each) do + Dir.mkdir(to) + end + context 'and the link does not yet exist' do + it 'create errors out' do + lambda { resource.run_action(:create) }.should raise_error(windows? ? Chef::Exceptions::Win32APIError : Errno::EPERM) + end + include_context 'delete is noop' + end + end + context "when the link destination is a symbolic link" do + context 'to a real file' do + before(:each) do + @other_target = File.join(base_dir, make_tmpname("other_spec", nil)) + File.open(@other_target, "w") { |file| file.write("eek") } + symlink(@other_target, to) + symlink?(to).should be_true + readlink(to).should == @other_target + end + after(:each) do + File.delete(@other_target) + end + context 'and the link does not yet exist' do + it 'links to the target file' do + resource.run_action(:create) + File.exists?(target_file).should be_true + # OS X gets angry about this sort of link. Bug in OS X, IMO. + pending('OS X/FreeBSD symlink? and readlink working on hard links to symlinks', :if => (os_x? or freebsd?)) do + symlink?(target_file).should be_true + readlink(target_file).should == @other_target + end + end + include_context 'delete is noop' + end + end + context 'to a nonexistent file' do + before(:each) do + @other_target = File.join(base_dir, make_tmpname("other_spec", nil)) + symlink(@other_target, to) + symlink?(to).should be_true + readlink(to).should == @other_target + end + context 'and the link does not yet exist' do + it 'links to the target file' do + pending('OS X/FreeBSD fails to create hardlinks to broken symlinks', :if => (os_x? or freebsd?)) do + resource.run_action(:create) + # Windows and Unix have different definitions of exists? here, and that's OK. + if windows? + File.exists?(target_file).should be_true + else + File.exists?(target_file).should be_false + end + symlink?(target_file).should be_true + readlink(target_file).should == @other_target + end + end + include_context 'delete is noop' + end + end + end + context "when the link destination is not readable to this user", :pending do + end + context "when the link destination does not exist" do + context 'and the link does not yet exist' do + it 'create errors out' do + lambda { resource.run_action(:create) }.should raise_error(Errno::ENOENT) + end + include_context 'delete is noop' + end + end + end +end diff --git a/spec/functional/resource/remote_directory_spec.rb b/spec/functional/resource/remote_directory_spec.rb new file mode 100644 index 0000000000..c8319e37ba --- /dev/null +++ b/spec/functional/resource/remote_directory_spec.rb @@ -0,0 +1,116 @@ +# +# Author:: Seth Chisamore (<schisamo@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, Inc. +# 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::Resource::RemoteDirectory do + include_context Chef::Resource::Directory + + let(:directory_base) { "directory_spec" } + + def create_resource + cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) + Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, cookbook_repo) } + node = Chef::Node.new + cl = Chef::CookbookLoader.new(cookbook_repo) + cl.load_cookbooks + cookbook_collection = Chef::CookbookCollection.new(cl) + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, cookbook_collection, events) + + resource = Chef::Resource::RemoteDirectory.new(path, run_context) + resource.source "remotedir" + resource.cookbook('openldap') + resource + end + + let!(:resource) do + create_resource + end + + it_behaves_like "a directory resource" + + context "when creating the remote directory" do + it "transfers the directory with all contents" do + resource.run_action(:create) + File.should exist(File.join(path, 'remote_dir_file1.txt')) + File.should exist(File.join(path, 'remote_dir_file2.txt')) + File.should exist(File.join(path, 'remotesubdir', 'remote_subdir_file1.txt')) + File.should exist(File.join(path, 'remotesubdir', 'remote_subdir_file2.txt')) + File.should exist(File.join(path, 'remotesubdir', '.a_dotfile')) + File.should exist(File.join(path, '.a_dotdir', '.a_dotfile_in_a_dotdir')) + end + + context "with purging enabled" do + before(:each) do + resource.purge(true) + end + + it "removes existing files if purge is true" do + FileUtils.mkdir_p(File.join(path, 'remotesubdir')) + existing1 = File.join(path, 'marked_for_death.txt') + existing2 = File.join(path, 'remotesubdir', 'marked_for_death_again.txt') + FileUtils.touch(existing1) + FileUtils.touch(existing2) + + resource.run_action(:create) + File.should_not exist(existing1) + File.should_not exist(existing2) + end + + it "removes files in subdirectories before files above" do + FileUtils.mkdir_p(File.join(path, 'a', 'multiply', 'nested', 'directory')) + existing1 = File.join(path, 'a', 'foo.txt') + existing2 = File.join(path, 'a', 'multiply', 'bar.txt') + existing3 = File.join(path, 'a', 'multiply', 'nested', 'baz.txt') + existing4 = File.join(path, 'a', 'multiply', 'nested', 'directory', 'qux.txt') + FileUtils.touch(existing1) + FileUtils.touch(existing2) + FileUtils.touch(existing3) + FileUtils.touch(existing4) + + resource.run_action(:create) + File.should_not exist(existing1) + File.should_not exist(existing2) + File.should_not exist(existing3) + File.should_not exist(existing4) + end + end + + describe "with overwrite disabled" do + before(:each) do + resource.purge(false) + resource.overwrite(false) + end + + it "leaves modifications alone" do + FileUtils.mkdir_p(File.join(path, 'remotesubdir')) + modified_file = File.join(path, 'remote_dir_file1.txt') + modified_subdir_file = File.join(path, 'remotesubdir', 'remote_subdir_file1.txt') + File.open(modified_file, 'a') {|f| f.puts "santa is real"} + File.open(modified_subdir_file, 'a') {|f| f.puts "so is rudolph"} + modified_file_checksum = sha256_checksum(modified_file) + modified_subdir_file_checksum = sha256_checksum(modified_subdir_file) + + resource.run_action(:create) + sha256_checksum(modified_file).should == modified_file_checksum + sha256_checksum(modified_subdir_file).should == modified_subdir_file_checksum + end + end + end +end diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb new file mode 100644 index 0000000000..e695e8feae --- /dev/null +++ b/spec/functional/resource/remote_file_spec.rb @@ -0,0 +1,58 @@ +# +# Author:: Seth Chisamore (<schisamo@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, Inc. +# 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' +require 'tiny_server' + +describe Chef::Resource::RemoteFile do + include_context Chef::Resource::File + + let(:file_base) { "remote_file_spec" } + let(:source) { 'http://localhost:9000/nyan_cat.png' } + let(:expected_content) { IO.read(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png')) } + + def create_resource + node = Chef::Node.new + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + resource = Chef::Resource::RemoteFile.new(path, run_context) + resource.source(source) + resource + end + + let!(:resource) do + create_resource + end + + before(:all) do + Thin::Logging.silent = false + @server = TinyServer::Manager.new + @server.start + @api = TinyServer::API.instance + @api.clear + @api.get("/nyan_cat.png", 200) { + IO.read(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png')) + } + end + + after(:all) do + @server.stop + end + + it_behaves_like "a file resource" +end diff --git a/spec/functional/resource/template_spec.rb b/spec/functional/resource/template_spec.rb new file mode 100644 index 0000000000..0dfdb17324 --- /dev/null +++ b/spec/functional/resource/template_spec.rb @@ -0,0 +1,70 @@ +# +# Author:: Seth Chisamore (<schisamo@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, Inc. +# 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::Resource::Template do + + include_context Chef::Resource::File + + let(:file_base) { "template_spec" } + let(:expected_content) { "slappiness is a warm gun" } + + let(:node) do + node = Chef::Node.new + node.normal[:slappiness] = "a warm gun" + node + end + + def create_resource + cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) + Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, cookbook_repo) } + cl = Chef::CookbookLoader.new(cookbook_repo) + cl.load_cookbooks + cookbook_collection = Chef::CookbookCollection.new(cl) + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, cookbook_collection, events) + resource = Chef::Resource::Template.new(path, run_context) + resource.source('openldap_stuff.conf.erb') + resource.cookbook('openldap') + resource + end + + let!(:resource) do + create_resource + end + + it_behaves_like "a file resource" + + context "when the target file does not exist" do + it "creates the template with the rendered content using the variable attribute when the :create action is run" do + resource.source('openldap_variable_stuff.conf.erb') + resource.variables(:secret => "nutella") + resource.run_action(:create) + IO.read(path).should == "super secret is nutella" + end + + it "creates the template with the rendered content using a local erb file when the :create action is run" do + resource.source(File.expand_path(File.join(CHEF_SPEC_DATA,'cookbooks','openldap','templates','default','openldap_stuff.conf.erb'))) + resource.cookbook(nil) + resource.local(true) + resource.run_action(:create) + IO.read(path).should == expected_content + end + end +end diff --git a/spec/functional/run_lock_spec.rb b/spec/functional/run_lock_spec.rb new file mode 100644 index 0000000000..af9bd1aa1f --- /dev/null +++ b/spec/functional/run_lock_spec.rb @@ -0,0 +1,90 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, Inc. +# 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 File.expand_path('../../spec_helper', __FILE__) +require 'chef/client' + +describe Chef::RunLock do + + # This behavior is believed to work on windows, but the tests use UNIX APIs. + describe "when locking the chef-client run", :unix_only => true do + it "allows only one chef client run per lockfile" do + read, write = IO.pipe + run_lock = Chef::RunLock.new(:file_cache_path => "/var/chef/cache", :lockfile => "/tmp/chef-client-running.pid") + p1 = fork do + run_lock.acquire + write.puts 1 + #puts "[#{Time.new.to_i % 100}] p1 (#{Process.pid}) running with lock" + sleep 2 + write.puts 2 + #puts "[#{Time.new.to_i % 100}] p1 (#{Process.pid}) releasing lock" + run_lock.release + end + + sleep 0.5 + + p2 = fork do + run_lock.acquire + write.puts 3 + #puts "[#{Time.new.to_i % 100}] p2 (#{Process.pid}) running with lock" + run_lock.release + end + + Process.waitpid2(p1) + Process.waitpid2(p2) + + write.close + order = read.read + read.close + + order.should == "1\n2\n3\n" + end + + it "clears the lock if the process dies unexpectedly" do + read, write = IO.pipe + run_lock = Chef::RunLock.new(:file_cache_path => "/var/chef/cache", :lockfile => "/tmp/chef-client-running.pid") + p1 = fork do + run_lock.acquire + write.puts 1 + #puts "[#{Time.new.to_i % 100}] p1 (#{Process.pid}) running with lock" + sleep 1 + write.puts 2 + #puts "[#{Time.new.to_i % 100}] p1 (#{Process.pid}) releasing lock" + run_lock.release + end + + p2 = fork do + run_lock.acquire + write.puts 3 + #puts "[#{Time.new.to_i % 100}] p2 (#{Process.pid}) running with lock" + run_lock.release + end + Process.kill(:KILL, p1) + + Process.waitpid2(p1) + Process.waitpid2(p2) + + write.close + order = read.read + read.close + + order.should =~ /3\Z/ + end + end + +end + diff --git a/spec/functional/tiny_server_spec.rb b/spec/functional/tiny_server_spec.rb new file mode 100644 index 0000000000..0cfef4305f --- /dev/null +++ b/spec/functional/tiny_server_spec.rb @@ -0,0 +1,77 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, Inc. +# 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' +require 'tiny_server' + +describe TinyServer::API do + before do + @api = TinyServer::API.instance + @api.clear + end + + it "is a Singleton" do + lambda {TinyServer::API.new}.should raise_error + end + + it "clears the router" do + @api.get('/blargh', 200, "blargh") + @api.clear + @api.routes["GET"].should be_empty + end + + it "creates a route for a GET request" do + @api.get('/foo/bar', 200, 'hello foobar') + response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/foo/bar') + response.should == [200, {'Content-Type' => 'application/json'}, 'hello foobar'] + end + + it "creates a route for a request with a block" do + block_called = false + @api.get('/bar/baz', 200) { block_called = true; 'hello barbaz' } + response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/bar/baz') + response.should == [200, {'Content-Type' => 'application/json'}, 'hello barbaz'] + block_called.should be_true + end + + it "returns debugging info for 404s" do + response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/no_such_thing') + response[0].should == 404 + response[1].should == {'Content-Type' => 'application/json'} + response[2].should be_a_kind_of(String) + response_obj = Chef::JSONCompat.from_json(response[2]) + response_obj["message"].should == "no data matches the request for /no_such_thing" + response_obj["available_routes"].should == {"GET"=>[], "PUT"=>[], "POST"=>[], "DELETE"=>[]} + response_obj["request"].should == {"REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"/no_such_thing"} + end + +end + +describe TinyServer::Manager do + it "runs the server" do + @server = TinyServer::Manager.new + @server.start + + TinyServer::API.instance.get("/index", 200, "[\"hello\"]") + + rest = Chef::REST.new('http://localhost:9000', false, false) + rest.get_rest("index").should == ["hello"] + + @server.stop + end +end |