require 'spec_helper'
require 'messages/security_group_create_message'

module VCAP::CloudController
  RSpec.describe SecurityGroupCreateMessage do
    subject { SecurityGroupCreateMessage.new(params) }

    describe 'validating parameters' do
      context 'when valid params are given' do
        let(:params) do
          {
            'name' => 'some-name',
            'globally_enabled' => {
              'running' => true,
              'staging' => false
            },
            'relationships' => {
              'staging_spaces' => {
                'data' => [{
                  'guid' => 'some-space-guid'
                }]
              },
              'running_spaces' => {
                'data' => [{
                  'guid' => 'some-space-guid'
                }]
              }
            }
          }
        end

        it 'is valid' do
          expect(subject).to be_valid
        end
      end

      context 'when no params are given' do
        let(:params) {}

        it 'is not valid' do
          expect(subject).not_to be_valid
          expect(subject.errors[:name]).to contain_exactly "can't be blank", 'must be a string'
        end
      end

      context 'when unexpected keys are requested' do
        let(:params) { { unexpected: 'meow', name: 'the-name' } }

        it 'is not valid' do
          expect(subject).not_to be_valid
          expect(subject.errors.full_messages[0]).to include("Unknown field(s): 'unexpected'")
        end
      end

      describe 'name' do
        context 'when it is non-alphanumeric' do
          let(:params) { { 'name' => 'thë-name' } }

          it { is_expected.to be_valid }
        end

        context 'when it contains hyphens' do
          let(:params) { { 'name' => 'a-z' } }

          it { is_expected.to be_valid }
        end

        context 'when it contains capital ascii' do
          let(:params) { { 'name' => 'AZ' } }

          it { is_expected.to be_valid }
        end

        context 'when it is at max length' do
          let(:params) { { 'name' => 'B' * SecurityGroupCreateMessage::MAX_SECURITY_GROUP_NAME_LENGTH } }

          it { is_expected.to be_valid }
        end

        context 'when it is too long' do
          let(:params) { { 'name' => 'B' * (SecurityGroupCreateMessage::MAX_SECURITY_GROUP_NAME_LENGTH + 1) } }

          it 'is not valid' do
            expect(subject).not_to be_valid
            expect(subject.errors[:name]).to contain_exactly('is too long (maximum is 250 characters)')
          end
        end

        context 'when it is blank' do
          let(:params) { { 'name' => '' } }

          it 'is not valid' do
            expect(subject).not_to be_valid
            expect(subject.errors[:name]).to include("can't be blank")
          end
        end

        context 'when it is not a string' do
          let(:params) { { name: true } }

          it { is_expected.not_to be_valid }
        end
      end

      describe 'rules' do
        let(:rules) { [] }

        let(:params) do
          {
            name: 'basic',
            rules: rules
          }
        end

        context 'when no rules are passed in' do
          let(:params) do
            { name: 'no_rules' }
          end

          it 'is valid' do
            expect(subject).to be_valid
          end
        end

        context 'when rules are valid' do
          let(:rules) do
            [
              {
                protocol: 'tcp',
                destination: '10.10.10.0/24',
                ports: '443,80,8080'
              },
              {
                protocol: 'icmp',
                destination: '10.10.10.0/24',
                type: 8,
                code: 0,
                description: 'Allow ping requests to private services'
              }
            ]
          end

          it 'is valid' do
            expect(subject).to be_valid
          end
        end

        context 'when comma-delimited destinations are enabled' do
          before do
            TestConfig.config[:security_groups][:enable_comma_delimited_destinations] = true
          end

          context 'when rules are valid' do
            let(:rules) do
              [
                {
                  protocol: 'tcp',
                  destination: '10.10.10.0/24,1.0.0.0-1.0.0.200',
                  ports: '443,80,8080'
                },
                {
                  protocol: 'icmp',
                  destination: '10.10.10.0/24,1.1.1.1',
                  type: 8,
                  code: 0
                }
              ]
            end

            it 'is valid' do
              expect(subject).to be_valid
            end
          end
        end

        context 'when rules are invalid' do
          let(:rules) do
            [
              {
                protocol: 'blah'
              },
              {
                'not-a-field': true
              }
            ]
          end

          it 'is invalid' do
            expect(subject).not_to be_valid
          end
        end

        context 'when the rule contains leading zeros' do
          context 'in a CIDR' do
            let(:rules) do
              [
                {
                  protocol: 'tcp',
                  destination: '010.000.0.0/24',
                  ports: '443,80,8080'
                }
              ]
            end

            it 'is invalid' do
              expect(subject).not_to be_valid
            end
          end

          context 'in a range' do
            let(:rules) do
              [
                {
                  protocol: 'tcp',
                  destination: '1.0.0.000-1.0.0.200',
                  ports: '443,80,8080'
                }
              ]
            end

            it 'is invalid' do
              expect(subject).not_to be_valid
            end
          end

          context 'in an IP' do
            let(:rules) do
              [
                {
                  protocol: 'tcp',
                  destination: '010.000.000.053',
                  ports: '443,80,8080'
                }
              ]
            end

            it 'is invalid' do
              expect(subject).not_to be_valid
            end
          end
        end
      end

      describe 'globally_enabled' do
        let(:globally_enabled) { {} }

        let(:params) do
          {
            'name' => 'basic',
            'globally_enabled' => globally_enabled
          }
        end

        context 'when no configuration is supplied' do
          context 'when value is not a hash' do
            let(:globally_enabled) { 'bad' }

            it 'is not valid' do
              expect(subject).not_to be_valid
              expect(subject.errors[:globally_enabled]).to eq(['must be an object'])
            end
          end

          context 'when the nested keys are invalid' do
            let(:globally_enabled) { { 'bad' => 'key' } }

            it 'is not valid' do
              expect(subject).not_to be_valid
              expect(subject.errors[:globally_enabled]).to eq(["only allows keys 'running' or 'staging'"])
            end
          end

          context 'when the values provided to running/staging is not a boolean' do
            let(:globally_enabled) do
              {
                'running' => 'value',
                'staging' => 'value'
              }
            end

            it 'is not valid' do
              expect(subject).not_to be_valid
              expect(subject.errors[:globally_enabled]).to eq(['values must be booleans'])
            end
          end
        end
      end

      describe 'relationships' do
        let(:params) do
          {
            'name' => 'basic',
            'relationships' => relationships
          }
        end

        context 'given no relationships' do
          let(:params) do
            {
              name: 'kris'
            }
          end

          it { is_expected.to be_valid }
        end

        context 'given a malformed staging space guid' do
          let(:params) do
            {
              name: 'rob',
              relationships: {
                staging_spaces: {
                  data: [{
                    guid: 150_000
                  }]
                }
              }
            }
          end

          it { is_expected.not_to be_valid }
        end

        context 'given unexpected staging spaces relationship data (not one-to-many relationship)' do
          let(:params) do
            {
              name: 'kim',
              relationships: {
                staging_spaces: {
                  data: { guid: 'skims' }
                }
              }
            }
          end

          it { is_expected.not_to be_valid }
        end

        context 'given unexpected running spaces relationship data (not one-to-many relationship)' do
          let(:params) do
            {
              name: 'kim',
              relationships: {
                running_spaces: {
                  data: { guid: 'skims' }
                }
              }
            }
          end

          it { is_expected.not_to be_valid }
        end

        context 'given a malformed running space guid' do
          let(:params) do
            {
              name: 'rob',
              relationships: {
                running_spaces: {
                  data: [
                    { guid: 150_000 }
                  ]
                }
              }
            }
          end

          it { is_expected.not_to be_valid }
        end
      end
    end

    describe '#running' do
      let(:params) do
        {
          name: 'some-name',
          globally_enabled: {
            running: true,
            staging: false
          }
        }
      end

      it 'returns the value provided for the running key' do
        expect(subject.running).to be true
      end
    end

    describe '#staging' do
      let(:params) do
        {
          name: 'some-name',
          globally_enabled: {
            running: true,
            staging: false
          }
        }
      end

      it 'returns the value provided for the staging key' do
        expect(subject.staging).to be false
      end
    end

    describe '#staging_space_guids' do
      let(:params) do
        {
          name: 'some-name',
          relationships: {
            staging_spaces: {
              data: [
                { guid: 'space-guid' }
              ]
            }
          }
        }
      end

      it 'returns the value provided for the staging key' do
        expect(subject.staging_space_guids).to eq ['space-guid']
      end
    end

    describe '#running_space_guids' do
      let(:params) do
        {
          name: 'some-name',
          relationships: {
            running_spaces: {
              data: [
                { guid: 'space-guid' }
              ]
            }
          }
        }
      end

      it 'returns the value provided for the staging key' do
        expect(subject.running_space_guids).to eq ['space-guid']
      end
    end
  end
end
