# # Copyright:: Copyright (c) 2015 Chef Software 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 "chef-dk/policyfile_services/undelete" describe ChefDK::PolicyfileServices::Undelete do let(:chef_config) { double("Chef::Config") } let(:ui) { TestHelpers::TestUI.new } let(:policy_name) { nil } let(:policy_group) { nil } let(:show_orphans) { false } let(:summary_diff) { false } let(:undo_dir) { tempdir } let(:undo_record_id) { nil } subject(:undelete_service) do described_class.new(config: chef_config, ui: ui, undo_record_id: undo_record_id) end describe "listing undo operations" do before do allow(undelete_service.undo_stack).to receive(:undo_dir).and_return(undo_dir) end after do clear_tempdir end context "when the undo dir doesn't exist" do let(:undo_dir) { File.join(tempdir, "this", "isnt", "here") } it "prints a message saying there aren't any things to undo to stderr" do undelete_service.list expect(ui.output).to eq("Nothing to undo.\n") end end context "when the undo dir exists, but it empty" do it "prints a message saying there aren't any things to undo to stderr" do undelete_service.list expect(ui.output).to eq("Nothing to undo.\n") end end context "when the undo dir exists and there are undo records in it" do let(:policy_revision) do { "name" => "appserver", "revision_id" => "1111111111111111111111111111111111111111111111111111111111111111", } end let(:undo_record1) do ChefDK::Policyfile::UndoRecord.new.tap do |undo_record| undo_record.description = "delete-policy-group example1" undo_record.add_policy_group("example1") undo_record.add_policy_revision("appserver", "example1", policy_revision) end end let(:undo_record2) do ChefDK::Policyfile::UndoRecord.new.tap do |undo_record| undo_record.description = "delete-policy-group example2" undo_record.add_policy_group("example2") undo_record.add_policy_revision("appserver", "example2", policy_revision) end end let(:undo_record3) do ChefDK::Policyfile::UndoRecord.new.tap do |undo_record| undo_record.description = "delete-policy-group example3" undo_record.add_policy_group("example3") undo_record.add_policy_revision("appserver", "example3", policy_revision) end end # `Time.new` is stubbed later on, need to force it to be evaluated before # then. let!(:start_time) { Time.new } def next_time @increment ||= 0 @increment += 1 start_time + @increment end let(:times) { [] } before do allow(Time).to receive(:new) do t = next_time times << t t end undo_stack = ChefDK::Policyfile::UndoStack.new allow(undo_stack).to receive(:undo_dir).and_return(undo_dir) undo_stack.push(undo_record1).push(undo_record2).push(undo_record3) end it "prints the items in reverse chronological order" do undelete_service.list timestamps = times.map { |t| t.utc.strftime("%Y%m%d%H%M%S") } expected_output = <<~OUTPUT #{timestamps[2]}: delete-policy-group example3 #{timestamps[1]}: delete-policy-group example2 #{timestamps[0]}: delete-policy-group example1 OUTPUT expect(ui.output).to eq(expected_output) end end end describe "undoing a policy group delete" do let(:policy_revision) do { "name" => "appserver", "revision_id" => "1111111111111111111111111111111111111111111111111111111111111111", } end let(:undo_record1) do ChefDK::Policyfile::UndoRecord.new.tap do |undo_record| undo_record.description = "delete-policy-group example1" undo_record.add_policy_group("example1") undo_record.add_policy_revision("appserver", "example1", policy_revision) end end let(:undo_stack) do instance_double(ChefDK::Policyfile::UndoStack).tap do |s| allow(s).to receive(:pop).and_yield(undo_record1) end end let(:http_client) { instance_double(Chef::ServerAPI) } before do allow(undelete_service).to receive(:http_client).and_return(http_client) allow(undelete_service).to receive(:undo_stack).and_return(undo_stack) end describe "when an error occurs posting data to the server" do let(:response) do Net::HTTPResponse.send(:response_class, "500").new("1.0", "500", "Internal Server Error").tap do |r| r.instance_variable_set(:@body, "oops") end end let(:http_exception) do begin response.error! rescue => e e end end before do expect(http_client).to receive(:put) .with("/policy_groups/example1/policies/appserver", policy_revision) .and_raise(http_exception) end it "raises an error" do expect { undelete_service.run }.to raise_error(ChefDK::UndeleteError) end end context "when the undelete is successful" do before do expect(http_client).to receive(:put) .with("/policy_groups/example1/policies/appserver", policy_revision) end it "uploads all policies to the server" do undelete_service.run expect(ui.output).to eq("Restored policy 'appserver'\nRestored policy group 'example1'\n") end end context "when given a specific undo record id to undo" do let(:undo_record_id) { "20150827172127" } context "and the id doesn't exist" do before do expect(undo_stack).to receive(:has_id?).with(undo_record_id).and_return(false) end it "prints an error message that the id doesn't exist" do undelete_service.run expect(ui.output).to eq("No undo record with id '#{undo_record_id}' exists\n") end end context "and the id exists" do before do expect(undo_stack).to receive(:has_id?).with(undo_record_id).and_return(true) expect(undo_stack).to receive(:delete).with(undo_record_id).and_yield(undo_record1) expect(http_client).to receive(:put) .with("/policy_groups/example1/policies/appserver", policy_revision) end it "uploads all policies to the server" do undelete_service.run expect(ui.output).to eq("Restored policy 'appserver'\nRestored policy group 'example1'\n") end end end end describe "undoing a policy delete" do let(:policy_revision) do { "name" => "appserver", "revision_id" => "1111111111111111111111111111111111111111111111111111111111111111", } end let(:undo_record1) do ChefDK::Policyfile::UndoRecord.new.tap do |undo_record| undo_record.description = "delete-policy-group example1" undo_record.add_policy_revision("appserver", nil, policy_revision) end end let(:undo_stack) do instance_double(ChefDK::Policyfile::UndoStack).tap do |s| allow(s).to receive(:pop).and_yield(undo_record1) end end let(:http_client) { instance_double(Chef::ServerAPI) } before do allow(undelete_service).to receive(:http_client).and_return(http_client) allow(undelete_service).to receive(:undo_stack).and_return(undo_stack) end context "when the revision to create doesn't exist" do before do expect(http_client).to receive(:post) .with("/policies/appserver/revisions", policy_revision) end it "uploads all policies to the server" do undelete_service.run expect(ui.output).to eq("Restored policy 'appserver'\n") end end end end