#
# Author:: Tyler Ball (<tball@chef.io>)
# Copyright:: Copyright 2014-2016, 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 "rspec/core/sandbox"
require "chef/audit/runner"
require "chef/audit/audit_event_proxy"
require "chef/audit/rspec_formatter"
require "rspec/support/spec/in_sub_process"
require "rspec/support/spec/stderr_splitter"

describe Chef::Audit::Runner do
  include RSpec::Support::InSubProcess

  let(:events) { double("events") }
  let(:run_context) { instance_double(Chef::RunContext, :events => events) }
  let(:runner) { Chef::Audit::Runner.new(run_context) }

  around(:each) do |ex|
    RSpec::Core::Sandbox.sandboxed { ex.run }
  end

  context "when we run in audit mode" do
    let(:paths) { [ "/opt/chef/lib/chef/", 'C:\windows/here/lib/chef/' , "/opt/chef/extra/folders/lib/chef/"] }
    it "excludes the current path from backtrace" do
      paths.each do |path|
        expect(runner.exclusion_pattern).to match(path)
      end
    end
  end

  describe "#initialize" do
    it "correctly sets the run_context during initialization" do
      expect(runner.instance_variable_get(:@run_context)).to eq(run_context)
    end
  end

  context "during #run" do

    describe "#setup" do
      let(:log_location) { File.join(Dir.tmpdir, "audit_log") }
      let(:color) { false }

      before do
        Chef::Config[:log_location] = log_location
        Chef::Config[:color] = color
      end

      it "sets all the config values" do
        # This runs the Serverspec includes - we don't want these hanging around in all subsequent tests so
        # we run this in a forked process.  Keeps Serverspec files from getting loaded into main process.
        in_sub_process do
          runner.send(:setup)

          expect(RSpec.configuration.output_stream).to eq(Chef::Audit::Logger)
          expect(RSpec.configuration.error_stream).to eq(Chef::Audit::Logger)

          expect(RSpec.configuration.formatters.size).to eq(2)
          expect(RSpec.configuration.formatters).to include(instance_of(Chef::Audit::AuditEventProxy))
          expect(RSpec.configuration.formatters).to include(instance_of(Chef::Audit::RspecFormatter))
          expect(Chef::Audit::AuditEventProxy.class_variable_get(:@@events)).to eq(run_context.events)

          expect(RSpec.configuration.expectation_frameworks).to eq([RSpec::Matchers])
          expect(RSpec::Matchers.configuration.syntax).to eq([:expect])

          expect(RSpec.configuration.color).to eq(color)
          expect(RSpec.configuration.expose_dsl_globally?).to eq(false)
          expect(RSpec.configuration.backtrace_exclusion_patterns).to include(runner.exclusion_pattern)

          expect(Specinfra.configuration.backend).to eq(:exec)
        end
      end
    end

    describe "#register_control_groups" do
      let(:audits) { [] }
      let(:run_context) { instance_double(Chef::RunContext, :audits => audits) }

      it "adds the control group aliases" do
        runner.send(:register_control_groups)

        expect(RSpec::Core::DSL.example_group_aliases).to include(:__control_group__)
        expect(RSpec::Core::DSL.example_group_aliases).to include(:control)
      end

      context "audits exist" do
        let(:audits) { { "audit_name" => group } }
        let(:group) { Struct.new(:args, :block).new(["group_name"], nil) }

        it "sends the audits to the world" do
          runner.send(:register_control_groups)

          expect(RSpec.world.example_groups.size).to eq(1)
          # For whatever reason, `kind_of` is not working
          # expect(RSpec.world.example_groups).to include(kind_of(RSpec::Core::ExampleGroup)) => FAIL
          g = RSpec.world.example_groups[0]
          expect(g.ancestors).to include(RSpec::Core::ExampleGroup)
          expect(g.description).to eq("group_name")
        end
      end
    end

    describe "#do_run" do
      let(:rspec_runner) { instance_double(RSpec::Core::Runner) }

      it "executes the runner" do
        expect(RSpec::Core::Runner).to receive(:new).with(nil).and_return(rspec_runner)
        expect(rspec_runner).to receive(:run_specs).with([])

        runner.send(:do_run)
      end
    end
  end

  describe "counters" do
    it "correctly calculates failed?" do
      expect(runner.failed?).to eq(false)
    end

    it "correctly calculates num_failed" do
      expect(runner.num_failed).to eq(0)
    end

    it "correctly calculates num_total" do
      expect(runner.num_total).to eq(0)
    end
  end

end
