require 'spec_helper'
require 'securerandom'

RSpec.describe JenkinsfileRunner::Commands::Init do
  before(:example, :filesystem) do
    @filesystem_dir = Pathname("/tmp/#{SecureRandom.uuid}").tap(&:mkpath)
  end

  after(:example, :filesystem) do
    @filesystem_dir.rmtree if @filesystem_dir.exist?
  end

  subject(:init) { described_class.new(argv) }
  let(:argv) { [] }

  it { is_expected.to respond_to(:run) }

  context 'with known arguments' do
    let(:argv) do
      [
        '--jfr-version', '1.2.3',
        '--jenkins-home', '/var/jenkins_home',
        '--jenkins-war', '/usr/share/jenkins/jenkins.war',
        '--jenkins-plugins', '/app/jenkins_home/plugins/plugins.txt',
        '--agent-type', 'docker',
        '--build-output', 'path/to/output',
      ]
    end

    it 'parses configuration' do
      expect(subject.configuration)
        .to have_attributes(
          jfr_version: '1.2.3',
          jenkins_home: '/var/jenkins_home',
          jenkins_war: '/usr/share/jenkins/jenkins.war',
          jenkins_plugins: '/app/jenkins_home/plugins/plugins.txt',
          agent_type: :docker,
          build_output: 'path/to/output')
    end
  end

  context 'with unknown arguments' do
    let(:argv) do
      ['--errors', 'no']
    end

    it 'raises an exception' do
      expect { subject}.to raise_error(OptionParser::InvalidOption)
    end
  end

  describe '#run' do
    describe 'output directory', :filesystem do
      subject(:create_output_directory) { init.send(:create_output_directory) }

      let(:path) { build_dir }
      let(:argv) { [ '--build-output', path.to_s ] }

      it 'creates the output directory' do
        expect { create_output_directory }.to change { path.exist? }
      end
    end

    describe '/app/bin/jenkinsfile-runner', :filesystem do
      let(:path) { create_dir }
      let(:destination_file) { path.join('jenkinsfile-runner') }
      let(:argv) { [ '--build-output', path.to_s ] }

      subject(:create_jenkinsfile_runner_executable) do
        init.send(:create_jenkinsfile_runner_executable)
      end

      it 'creates jenkinsfile-runner file' do
        expect { create_jenkinsfile_runner_executable }.to change { destination_file.exist? }
      end

      context 'with default jenkins plugins settings' do
        it 'uses the default path' do
          create_jenkinsfile_runner_executable

          expect(destination_file.read).to include('--plugins /app/jenkins_home/plugins/')
        end
      end

      context 'with custom jenkins plugins file', :filesystem do
        let(:input_path) { create_dir }
        let(:plugins_path) { create_file('file.txt', dir: input_path) }
        let(:argv) { [ '--build-output', path.to_s, '--jenkins-plugins', plugins_path.to_s ] }

        it 'uses the custom file' do
          create_jenkinsfile_runner_executable

          expect(destination_file.read).to include('--plugins /app/jenkins_plugins/file.txt')
        end
      end

      context 'with custom jenkins plugins directory' do
        let(:argv) { [ '--build-output', path.to_s, '--jenkins-plugins', '/tmp/plugins_dir' ] }

        it 'uses the custom directory' do
          create_jenkinsfile_runner_executable

          expect(destination_file.read).to include('--plugins /app/jenkins_plugins')
        end
      end
    end

    describe '/app/jenkins_home', :filesystem do
      subject(:copy_jenkins_home_directory) { init.send(:copy_jenkins_home_directory) }

      let(:home_path) { create_dir }
      let(:out_path) { create_dir }
      let(:argv) { [ '--jenkins-home', home_path.to_s, '--build-output', out_path.to_s ] }

      it 'copies the home directory' do
        expect { copy_jenkins_home_directory }.to change { out_path.join('jenkins_home').exist? }
      end
    end

    describe '/app/bin/jenkins.war', :filesystem do
      subject(:copy_jenkins_war_file) { init.send(:copy_jenkins_war_file) }

      let(:home_path) { create_dir }
      let(:out_path) { create_dir }
      let(:war_path) { create_file('jenkins.war', dir: home_path) }
      let(:argv) { [ '--jenkins-war', war_path.to_s, '--build-output', out_path.to_s ] }

      it 'copies the home directory' do
        expect { copy_jenkins_war_file }.to change { out_path.join('jenkins.war').exist? }
      end
    end

    describe 'custom jenkins plugins', :filesystem do
      subject(:copy_jenkins_plugins_file) { init.send(:copy_jenkins_plugins_file) }

      let(:home_path) { create_dir }
      let(:out_path) { create_dir }
      let(:argv) { [ '--jenkins-plugins', plugins_path.to_s, '--build-output', out_path.to_s ] }

      context 'with plugins file' do
        let(:plugins_path) { create_file('plugins.txt', dir: home_path) }

        it 'copies the plugins file' do
          expect { copy_jenkins_plugins_file }.to change { out_path.join('jenkins_plugins', 'plugins.txt').exist? }
        end
      end

      context 'with plugins directory' do
        let(:plugins_path) { home_path.join('plugins_dir').tap(&:mkpath) }

        before do
          3.times { |index| create_file("plugin_#{index}.txt", dir: plugins_path) }
        end

        it 'copies the plugins directory' do
          expect { copy_jenkins_plugins_file }.to change { out_path.join('jenkins_plugins').exist? }
        end

        it 'copies the plugins files', :aggregate_failures do
          copy_jenkins_plugins_file

          3.times do |index|
            expect(out_path.join('jenkins_plugins', "plugin_#{index}.txt")).to be_exist
          end
        end
      end
    end

    describe 'Dockerfile', :filesystem do
      let(:path) { create_dir }
      let(:dockerfile_path) { path.join('Dockerfile') }
      let(:argv) { [ '--build-output', path.to_s, '--jfr-version', '1.2.3' ] }

      subject(:create_docker_file) do
        init.send(:create_docker_file)
      end

      it 'creates Dockerfile' do
        expect { create_docker_file }.to change { dockerfile_path.exist? }
      end

      context 'with --agent-type docker' do
        let(:argv) { ['--build-output', path.to_s, '--agent-type', 'docker', '--jfr-version', '1.2.3'] }

        it 'creates jenkinsfile-runner file' do
          create_docker_file
          expect(dockerfile_path.read).to include('apt install -y docker-ce-cli')
        end
      end

      context 'with --jfr-version 1.2.3' do
        let(:argv) { ['--build-output', path.to_s, '--jfr-version', '1.2.3'] }

        it 'creates jenkinsfile-runner file' do
          create_docker_file
          expect(dockerfile_path.read).to include('jenkinsfile-runner/1.2.3/jenkinsfile-runner-1.2.3.jar')
        end
      end
    end

    describe 'ignore files', :filesystem do
      let(:out_path) { create_dir }
      let(:argv) { ['--build-output', out_path.to_s] }

      subject(:create_ignore_files) do
        init.send(:create_ignore_files)
      end

      it 'creates a dockerignore file' do
        expect { create_ignore_files }.to change { out_path.join('.dockerignore').exist? }
      end

      it 'creates a gitignore file' do
        expect { create_ignore_files }.to change { out_path.join('.gitignore').exist? }
      end
    end
  end

  def build_dir(name = SecureRandom.uuid)
    @filesystem_dir.join(name)
  end

  def create_dir(name = SecureRandom.uuid)
    build_dir(name).tap(&:mkpath)
  end

  def create_file(name, body: "", dir:)
    dir.join(name).tap { |path| path.write(body) }
  end
end
