require 'spec_helper'

module Bosh::Director
  module Addon
    describe Filter do
      subject(:addon_include) { Filter.parse(filter_hash, type, addon_level) }
      let(:instance_group) { instance_double(DeploymentPlan::InstanceGroup) }
      let(:addon_level) { RUNTIME_LEVEL }

      shared_examples :common_filter_checks do
        describe 'when the filter has a job with an empty job name' do
          let(:filter_hash) { { 'jobs' => [{ 'name' => '', 'release' => 'release_name' }] } }

          it 'raises' do
            expect do
              addon_include.applies?('anything', [], instance_group)
            end.to raise_error(
              AddonIncompleteFilterJobSection,
              'Job {"name"=>"", "release"=>"release_name"} in runtime '\
              "config's #{type} section must have both name and release.",
            )
          end
        end

        describe 'when the filter has a job with a missing release name' do
          let(:filter_hash) { { 'jobs' => [{ 'name' => 'job-name', 'release' => '' }] } }

          it 'raises' do
            expect do
              addon_include.applies?('anything', [], instance_group)
            end.to raise_error(
              AddonIncompleteFilterJobSection,
              'Job {"name"=>"job-name", "release"=>""} in runtime '\
              "config's #{type} section must have both name and release.",
            )
          end
        end

        describe 'when the filter has a stemcell property' do
          describe 'when the stemcell property has an os missing a name' do
            let(:filter_hash) { { 'stemcell' => [{ 'os' => '' }] } }

            it 'raises' do
              expect do
                addon_include.applies?('anything', [], instance_group)
              end.to raise_error AddonIncompleteFilterStemcellSection,
                                 "Stemcell {\"os\"=>\"\"} in runtime config's #{type} section must have an os name."
            end
          end

          describe 'when the stemcell os matches the instance group stemcell os' do
            let(:filter_hash) { { 'stemcell' => [{ 'os' => 'my_os' }] } }

            it 'applies' do
              allow(instance_group).to receive(:has_os?).with('my_os').and_return(true)
              expect(addon_include.applies?('anything', [], instance_group)).to be(true)
            end
          end

          describe 'when the stemcell os does not match the instance group stemcell os' do
            let(:filter_hash) { { 'stemcell' => [{ 'os' => 'my_os' }] } }

            it 'does not apply' do
              allow(instance_group).to receive(:has_os?).with('my_os').and_return(false)
              expect(addon_include.applies?('anything', [], instance_group)).to be(false)
            end
          end
        end

        describe 'when the filter spec has only a deployments section' do
          let(:filter_hash) { { 'deployments' => %w[deployment_1 deployment_2] } }

          describe 'when the deployment name matches one from the include spec' do
            it 'applies' do
              expect(addon_include.applies?('deployment_1', [], nil)).to be(true)
            end
          end

          describe 'when the deployment name does not match one from the filter spec' do
            it 'does not apply' do
              expect(addon_include.applies?('deployment_blarg', [], nil)).to be(false)
            end
          end
        end

        describe 'when the filter spec has only a teams section' do
          let(:filter_hash) { { 'teams' => %w[team_1 team_2] } }

          describe 'when one of the teams matches one from the included spec' do
            it 'applies' do
              expect(addon_include.applies?('anything', ['team_1'], nil)).to be(true)
            end
          end

          describe 'when none of the teams match the filter spec' do
            let(:filter_hash) { { 'teams' => %w[team_1 team_2] } }
            it 'does not apply' do
              expect(addon_include.applies?('anything', ['team_5'], nil)).to be(false)
            end
          end
        end

        describe 'when the filter spec has only a networks section' do
          let(:filter_hash) { { 'networks' => %w[net_1 net_2] } }

          describe 'when the network name matches one from the include spec' do
            it 'applies' do
              allow(instance_group).to receive(:network_present?).with('net_1').and_return(true)
              expect(addon_include.applies?('anything', [], instance_group)).to be(true)
            end
          end

          describe 'when the network name does not match any from the filter spec' do
            it 'does not apply' do
              allow(instance_group).to receive(:network_present?).with('net_1').and_return(false)
              allow(instance_group).to receive(:network_present?).with('net_2').and_return(false)
              expect(addon_include.applies?('anything', [], instance_group)).to be(false)
            end
          end
        end

        describe 'when the filter spec has only a jobs section' do
          let(:filter_hash) { { 'jobs' => [{ 'name' => 'job_name', 'release' => 'release_name' }] } }

          describe 'when the instance group contains a matching job from the include spec' do
            it 'applies' do
              allow(instance_group).to receive(:has_job?).with('job_name', 'release_name').and_return(true)
              expect(addon_include.applies?('', [], instance_group)).to be(true)
            end
          end

          describe 'when the instance group does not contain a matching job from the filter spec' do
            it 'does not apply' do
              allow(instance_group).to receive(:has_job?).with('job_name', 'release_name').and_return(false)
              expect(addon_include.applies?('', [], instance_group)).to be(false)
            end
          end

          describe 'when there are multiple jobs on the filter spec' do
            let(:filter_hash) do
              {
                'jobs' => [
                  { 'name' => 'job_name', 'release' => 'release_name' },
                  { 'name' => 'job_name_2', 'release' => 'release_name_2' },
                ],
              }
            end

            describe 'when the instance group has all of the jobs' do
              it 'applies' do
                allow(instance_group).to receive(:has_job?).with('job_name', 'release_name').and_return(true)
                allow(instance_group).to receive(:has_job?).with('job_name_2', 'release_name_2').and_return(true)
                expect(addon_include.applies?('', [], instance_group)).to be(true)
              end
            end

            describe 'when the instance group has some of the jobs' do
              it 'applies' do
                allow(instance_group).to receive(:has_job?).with('job_name', 'release_name').and_return(true)
                allow(instance_group).to receive(:has_job?).with('job_name_2', 'release_name_2').and_return(false)
                expect(addon_include.applies?('', [], instance_group)).to be(true)
              end
            end

            describe 'when the instance group does not have any of the jobs' do
              it 'does not apply' do
                allow(instance_group).to receive(:has_job?).with('job_name', 'release_name').and_return(false)
                allow(instance_group).to receive(:has_job?).with('job_name_2', 'release_name_2').and_return(false)
                expect(addon_include.applies?('', [], instance_group)).to be(false)
              end
            end
          end

          describe 'when the filter spec has both deployment section and jobs section' do
            let(:filter_hash) do
              {
                'deployments' => %w[deployment_1 deployment_2],
                'jobs' => [
                  { 'name' => 'job_name', 'release' => 'release_name' },
                  { 'name' => 'job_name_2', 'release' => 'release_name_2' },
                ],
              }
            end

            describe 'when the deployment name matches and the instance group contains the job' do
              it 'applies' do
                expect(instance_group).to receive(:has_job?).with('job_name', 'release_name').and_return(true)
                expect(addon_include.applies?('deployment_2', [], instance_group)).to be(true)
              end
            end

            describe 'when the deployment name does not match and the instance group contains job' do
              it 'does not apply' do
                allow(instance_group).to receive(:has_job?).with('job_name', 'release_name').and_return(true)
                expect(addon_include.applies?('deployment_3', [], instance_group)).to be(false)
              end
            end

            describe 'when the deployment name matches and the instance group does not contain all of the jobs' do
              it 'does not apply' do
                expect(instance_group).to receive(:has_job?).with('job_name', 'release_name').and_return(false)
                expect(instance_group).to receive(:has_job?).with('job_name_2', 'release_name_2').and_return(false)
                expect(addon_include.applies?('deployment_1', [], instance_group)).to be(false)
              end
            end
          end
        end

        context 'when there is a deployment filter' do
          let(:filter_hash) { { 'deployments' => %w[deployment_1 deployment_2] } }
          context 'when addon is in deployment manifest' do
            let(:addon_level) { DEPLOYMENT_LEVEL }

            it 'raises' do
              expect do
                addon_include
              end.to raise_error AddonDeploymentFilterNotAllowed,
                                 'Deployment filter is not allowed for deployment level addons.'
            end
          end

          context 'when addon is not in deployment manifest' do
            let(:addon_level) { RUNTIME_LEVEL }

            it 'raises' do
              expect do
                addon_include
              end.not_to raise_error
            end
          end
        end
      end

      describe 'include' do
        let(:type) { :include }

        describe 'applies?' do
          describe 'when the include hash is nil' do
            let(:filter_hash) { nil }

            it 'applies' do
              expect(addon_include.applies?(nil, [], nil)).to be(true)
              expect(addon_include.applies?('anything', [], instance_group)).to be(true)
            end
          end

          context 'when the addon is in the deployment manifest' do
            context 'when the team filter is specified' do
              let(:addon_level) { DEPLOYMENT_LEVEL }
              let(:filter_hash) { { 'teams' => ['team_5'] } }

              it 'does not consider' do
                expect(addon_include.applies?('anything', ['team_3'], instance_group)).to be(true)
              end
            end
          end

          context 'when the azs filter is specified' do
            let(:filter_hash) { { 'azs' => ['z5'] } }

            context 'in the deployment manifest' do
              let(:addon_level) { DEPLOYMENT_LEVEL }

              it 'applies' do
                allow(instance_group).to receive(:has_availability_zone?).with('z5').and_return(true)
                expect(addon_include.applies?('anything', [], instance_group)).to be(true)
              end
            end

            context 'in the runtime config' do
              let(:addon_level) { RUNTIME_LEVEL }

              it 'applies' do
                allow(instance_group).to receive(:has_availability_zone?).with('z5').and_return(true)
                expect(addon_include.applies?('anything', [], instance_group)).to be(true)
              end
            end
          end

          it_behaves_like :common_filter_checks
        end
      end

      describe 'exclude' do
        let(:type) { :exclude }
        let(:addon_exclude) { addon_include }
        describe 'applies?' do
          describe 'when the exclude hash is nil' do
            let(:filter_hash) { nil }

            it 'does not apply' do
              expect(addon_exclude.applies?(nil, [], nil)).to be(false)
              expect(addon_exclude.applies?('anything', [], instance_group)).to be(false)
            end
          end

          context 'when the addon is in the deployment manifest' do
            context 'when the team filter is specified' do
              let(:addon_level) { DEPLOYMENT_LEVEL }
              let(:filter_hash) { { 'teams' => ['team_3'] } }

              it 'does not consider' do
                expect(addon_exclude.applies?('anything', ['team_3'], instance_group)).to be(false)
              end
            end
          end

          context 'when the azs filter is specified' do
            let(:filter_hash) { { 'azs' => ['z1'] } }

            context 'in the deployment manifest' do
              let(:addon_level) { DEPLOYMENT_LEVEL }

              it 'does not apply' do
                allow(instance_group).to receive(:has_availability_zone?).with('z1').and_return(false)
                expect(addon_exclude.applies?('anything', [], instance_group)).to be(false)
              end
            end

            context 'in the runtime config' do
              let(:addon_level) { RUNTIME_LEVEL }

              it 'does not apply' do
                allow(instance_group).to receive(:has_availability_zone?).with('z1').and_return(false)
                expect(addon_exclude.applies?('anything', [], instance_group)).to be(false)
              end
            end
          end

          it_behaves_like :common_filter_checks
        end
      end
    end
  end
end
