#
# Author:: Jay Mundrawala <jmundrawala@getchef.com>
# Copyright:: Copyright (c) 2014 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 'chef'
require 'chef/util/dsc/configuration_generator'

describe Chef::Util::DSC::ConfigurationGenerator do
  let(:conf_man) do
    node = Chef::Node.new
    Chef::Util::DSC::ConfigurationGenerator.new(node, 'tmp')
  end

  describe '#validate_configuration_name!' do
    it 'should not raise an error if a name contains all upper case letters' do
      conf_man.send(:validate_configuration_name!, "HELLO")
    end

    it 'should not raise an error if the name contains all lower case letters' do
      conf_man.send(:validate_configuration_name!, "hello")
    end

    it 'should not raise an error if no special characters are used except _' do
      conf_man.send(:validate_configuration_name!, "hello_world")
    end

    %w{! @ # $ % ^ & * & * ( ) - = + \{ \} . ? < > \\ /}.each do |sym|
      it "raises an Argument error if it configuration name contains #{sym}" do
        expect {
          conf_man.send(:validate_configuration_name!, "Hello#{sym}")
        }.to raise_error(ArgumentError)
      end
    end
  end

  describe "#get_merged_configuration_flags" do
    context 'when strings are used as switches' do
      it 'should merge the hash if there are no restricted switches' do
        merged = conf_man.send(:get_merged_configuration_flags!, {'flag' => 'a'}, 'hello')
        expect(merged).to include(:flag)
        expect(merged[:flag]).to eql('a')
        expect(merged).to include(:outputpath)
      end

      it 'should raise an ArgumentError if you try to override outputpath' do
        expect {
          conf_man.send(:get_merged_configuration_flags!, {'outputpath' => 'a'}, 'hello')
        }.to raise_error(ArgumentError)
      end

      it 'should be case insensitive for switches that are not allowed' do
        expect {
          conf_man.send(:get_merged_configuration_flags!, {'OutputPath' => 'a'}, 'hello')
        }.to raise_error(ArgumentError)
      end

      it 'should be case insensitive to switches that are allowed' do
        merged = conf_man.send(:get_merged_configuration_flags!, {'FLAG' => 'a'}, 'hello')
        expect(merged).to include(:flag)
      end
    end

    context 'when symbols are used as switches' do
      it 'should merge the hash if there are no restricted switches' do
        merged = conf_man.send(:get_merged_configuration_flags!, {:flag => 'a'}, 'hello')
        expect(merged).to include(:flag)
        expect(merged[:flag]).to eql('a')
        expect(merged).to include(:outputpath)
      end

      it 'should raise an ArgumentError if you try to override outputpath' do
        expect {
          conf_man.send(:get_merged_configuration_flags!, {:outputpath => 'a'}, 'hello')
        }.to raise_error(ArgumentError)
      end

      it 'should be case insensitive for switches that are not allowed' do
        expect {
          conf_man.send(:get_merged_configuration_flags!, {:OutputPath => 'a'}, 'hello')
        }.to raise_error(ArgumentError)
      end

      it 'should be case insensitive to switches that are allowed' do
        merged = conf_man.send(:get_merged_configuration_flags!, {:FLAG => 'a'}, 'hello')
        expect(merged).to include(:flag)
      end
    end

    context 'when there are no flags' do
      it 'should supply an output path if configuration_flags is an empty hash' do
        merged = conf_man.send(:get_merged_configuration_flags!, {}, 'hello')
        expect(merged).to include(:outputpath)
        expect(merged.length).to eql(1)
      end

      it 'should supply an output path if configuration_flags is an empty hash' do
        merged = conf_man.send(:get_merged_configuration_flags!, nil, 'hello')
        expect(merged).to include(:outputpath)
        expect(merged.length).to eql(1)
      end
    end

    # What should happen if configuration flags contains duplicates?
    # flagA => 'a', flaga => 'a'
    # or
    # flagA => 'a', flaga => 'b'
    #
  end

  describe '#write_document_generation_script' do
    let(:file_like_object) { double("file like object") }

    it "should write the input to a file" do
      allow(File).to receive(:open).and_yield(file_like_object)
      allow(File).to receive(:join) do |a, b|
        [a,b].join("++")
      end
      allow(file_like_object).to receive(:write)
      conf_man.send(:write_document_generation_script, 'file', 'hello', {})
      expect(file_like_object).to have_received(:write)
    end
  end

  describe "#find_configuration_document" do
    it "should find the mof file" do
      # These tests seem way too implementation specific. Unfortunately, File and Dir
      # need to be mocked because they are OS specific
      allow(File).to receive(:join) do |a, b|
        [a,b].join("++")
      end

      allow(Dir).to receive(:entries).with("tmp++hello") {['f1', 'f2', 'hello.mof', 'f3']}
      expect(conf_man.send(:find_configuration_document, 'hello')).to eql('tmp++hello++hello.mof')
    end

    it "should return nil if the mof file is not found" do
      allow(File).to receive(:join) do |a, b|
        [a,b].join("++")
      end
      allow(Dir).to receive(:entries).with("tmp++hello") {['f1', 'f2', 'f3']}
      expect(conf_man.send(:find_configuration_document, 'hello')).to be_nil
    end
  end

  describe "#configuration_code" do
    it "should build dsc" do
      dsc = conf_man.send(:configuration_code, 'archive{}', 'hello', {})
      found_configuration = false
      dsc.split(';').each do |command|
        if command.downcase =~ /\s*configuration\s+'hello'\s*\{\s*node\s+'localhost'\s*\{\s*archive\s*\{\s*\}\s*\}\s*\}\s*/
          found_configuration = true
        end
      end
      expect(found_configuration).to be_truthy
    end
    context "with imports" do
      it "should import all resources when a module has an empty list" do
        dsc = conf_man.send(:configuration_code, 'archive{}', 'hello', {'FooModule' => []})
        expect(dsc).to match(/Import-DscResource -ModuleName FooModule\s*\n/)
      end

      it "should import all resources when a module has a list with *" do
        dsc = conf_man.send(:configuration_code, 'archive{}', 'hello', {'FooModule' => ['FooResource', '*', 'BarResource']})
        expect(dsc).to match(/Import-DscResource -ModuleName FooModule\s*\n/)
      end

      it "should import specific resources when a module has list without * that is not empty" do
        dsc = conf_man.send(:configuration_code, 'archive{}', 'hello', {'FooModule' => ['FooResource', 'BarResource']})
        expect(dsc).to match(/Import-DscResource -ModuleName FooModule -Name FooResource,BarResource/)
      end

      it "should import multiple modules with multiple import statements" do
        dsc = conf_man.send(:configuration_code, 'archive{}', 'hello', {'FooModule' => ['FooResource', 'BarResource'], 'BazModule' => []})
        expect(dsc).to match(/Import-DscResource -ModuleName FooModule -Name FooResource,BarResource/)
        expect(dsc).to match(/Import-DscResource -ModuleName BazModule\s*\n/)
      end 
    end
  end
end
