#
# 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 "chef/cookbook/syntax_check"

describe Chef::Cookbook::SyntaxCheck do
  before do
    Chef::Platform.stub(:windows?) { false }
  end

  let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'cookbooks', 'openldap') }
  let(:syntax_check) { Chef::Cookbook::SyntaxCheck.new(cookbook_path) }

  let(:open_ldap_cookbook_files) do
    %w{ attributes/default.rb
        attributes/smokey.rb
        definitions/client.rb
        definitions/server.rb
        libraries/openldap.rb
        libraries/openldap/version.rb
        metadata.rb
        recipes/default.rb
        recipes/gigantor.rb
        recipes/one.rb
        recipes/return.rb }.map{ |f| File.join(cookbook_path, f) }
  end

  before do
    Chef::Log.logger = Logger.new(StringIO.new)
    Chef::Log.level = :warn # suppress "Syntax OK" messages

    @attr_files = %w{default.rb smokey.rb}.map { |f| File.join(cookbook_path, 'attributes', f) }
    @libr_files = %w{openldap.rb openldap/version.rb}.map { |f| File.join(cookbook_path, 'libraries', f) }
    @defn_files = %w{client.rb server.rb}.map { |f| File.join(cookbook_path, 'definitions', f)}
    @recipes = %w{default.rb gigantor.rb one.rb return.rb}.map { |f| File.join(cookbook_path, 'recipes', f) }
    @ruby_files = @attr_files + @libr_files + @defn_files + @recipes + [File.join(cookbook_path, "metadata.rb")]
    basenames = %w{ helpers_via_partial_test.erb
                    helper_test.erb
                    openldap_stuff.conf.erb
                    openldap_variable_stuff.conf.erb
                    test.erb
                    some_windows_line_endings.erb
                    all_windows_line_endings.erb
                    no_windows_line_endings.erb }
    @template_files = basenames.map { |f| File.join(cookbook_path, 'templates', 'default', f)}
  end

  it "creates a syntax checker given the cookbook name when Chef::Config.cookbook_path is set" do
    Chef::Config[:cookbook_path] = File.dirname(cookbook_path)
    syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:openldap)
    syntax_check.cookbook_path.should == cookbook_path
    syntax_check.ruby_files.sort.should == open_ldap_cookbook_files.sort
  end

  it "creates a syntax checker given the cookbook name and cookbook_path" do
    syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:openldap, File.join(CHEF_SPEC_DATA, 'cookbooks'))
    syntax_check.cookbook_path.should == cookbook_path
    syntax_check.ruby_files.sort.should == open_ldap_cookbook_files.sort
  end

  context "when using a standalone cookbook" do
    let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'standalone_cookbook') }

    it "creates a syntax checker given the cookbook name and cookbook_path for a standalone cookbook" do
      syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:standalone_cookbook, CHEF_SPEC_DATA)
      syntax_check.cookbook_path.should == cookbook_path
      syntax_check.ruby_files.should == [File.join(cookbook_path, 'recipes/default.rb')]
    end
  end

  describe "when first created" do
    it "has the path to the cookbook to syntax check" do
      syntax_check.cookbook_path.should == cookbook_path
    end

    it "lists the ruby files in the cookbook" do
      syntax_check.ruby_files.sort.should == @ruby_files.sort
    end

    it "lists the erb templates in the cookbook" do
      syntax_check.template_files.sort.should == @template_files.sort
    end

  end

  describe "when validating cookbooks" do
    let(:cache_path) { Dir.mktmpdir }

    before do
      Chef::Config[:syntax_check_cache_path] = cache_path
    end

    after do
      FileUtils.rm_rf(cache_path) if File.exist?(cache_path)
    end

    describe "and the files have not been syntax checked previously" do
      it "shows that all ruby files require a syntax check" do
        syntax_check.untested_ruby_files.sort.should == @ruby_files.sort
      end

      it "shows that all template files require a syntax check" do
        syntax_check.untested_template_files.sort.should == @template_files.sort
      end

      it "removes a ruby file from the list of untested files after it is marked as validated" do
        recipe = File.join(cookbook_path, 'recipes', 'default.rb')
        syntax_check.validated(recipe)
        syntax_check.untested_ruby_files.should_not include(recipe)
      end

      it "removes a template file from the list of untested files after it is marked as validated" do
        template = File.join(cookbook_path, 'templates', 'default', 'test.erb')
        syntax_check.validated(template)
        syntax_check.untested_template_files.should_not include(template)
      end

      it "validates all ruby files" do
        syntax_check.validate_ruby_files.should be_true
        syntax_check.untested_ruby_files.should be_empty
      end

      it "validates all templates" do
        syntax_check.validate_templates.should be_true
        syntax_check.untested_template_files.should be_empty
      end

      describe "and a file has a syntax error" do
        before do
          cookbook_path = File.join(CHEF_SPEC_DATA, 'cookbooks', 'borken')
          syntax_check.cookbook_path.replace(cookbook_path)
        end

        it "it indicates that a ruby file has a syntax error" do
          syntax_check.validate_ruby_files.should be_false
        end

        it "does not remove the invalid file from the list of untested files" do
          syntax_check.untested_ruby_files.should include(File.join(cookbook_path, 'recipes', 'default.rb'))
          syntax_check.validate_ruby_files
          syntax_check.untested_ruby_files.should include(File.join(cookbook_path, 'recipes', 'default.rb'))
        end

        it "indicates that a template file has a syntax error" do
          syntax_check.validate_templates.should be_false
        end

        it "does not remove the invalid template from the list of untested templates" do
          syntax_check.untested_template_files.should include(File.join(cookbook_path, 'templates', 'default', 'borken.erb'))
          lambda {syntax_check.validate_templates}.should_not change(syntax_check, :untested_template_files)
        end

      end

      describe "and an ignored file has a syntax error" do
        before do
          cookbook_path = File.join(CHEF_SPEC_DATA, 'cookbooks', 'ignorken')
          Chef::Config[:cookbook_path] = File.dirname(cookbook_path)
          syntax_check.cookbook_path.replace(cookbook_path)
          @ruby_files = [File.join(cookbook_path, 'metadata.rb'), File.join(cookbook_path, 'recipes/default.rb')]
        end

        it "shows that ignored ruby files do not require a syntax check" do
          syntax_check.untested_ruby_files.sort.should == @ruby_files.sort
        end

        it "does not indicate that a ruby file has a syntax error" do
          syntax_check.validate_ruby_files.should be_true
          syntax_check.untested_ruby_files.should be_empty
        end

      end

    end

    describe "and the files have been syntax checked previously" do
      before do
        syntax_check.untested_ruby_files.each { |f| syntax_check.validated(f) }
        syntax_check.untested_template_files.each { |f| syntax_check.validated(f) }
      end

      it "does not syntax check ruby files" do
        syntax_check.should_not_receive(:shell_out)
        syntax_check.validate_ruby_files.should be_true
      end

      it "does not syntax check templates" do
        syntax_check.should_not_receive(:shell_out)
        syntax_check.validate_templates.should be_true
      end
    end
  end
end
