require 'spec_helper'
require 'messages/list_message'

class VCAP::CloudController::ListMessage
  register_allowed_keys []
end

module VCAP::CloudController
  RSpec.describe ListMessage do
    describe 'page' do
      it 'is invalid if page is a string' do
        message = ListMessage.from_params({ page: 'a string' }, [])
        expect(message).not_to be_valid
        expect(message.errors[:page]).to include('must be a positive integer')
      end

      it 'is invalid if page is 0' do
        message = ListMessage.from_params({ page: 0 }, [])
        expect(message).not_to be_valid
        expect(message.errors[:page]).to include('must be a positive integer')
      end

      it 'is invalid if page is negative' do
        message = ListMessage.from_params({ page: -1 }, [])
        expect(message).not_to be_valid
        expect(message.errors[:page]).to include('must be a positive integer')
      end

      it 'is valid if page is nil' do
        message = ListMessage.from_params({}, [])
        expect(message).to be_valid
      end
    end

    describe 'per_page' do
      it 'is invalid if per_page is a string' do
        message = ListMessage.from_params({ per_page: 'a string' }, [])
        expect(message).not_to be_valid
        expect(message.errors[:per_page]).to include('must be a positive integer')
      end

      it 'is invalid if per_page is 0' do
        message = ListMessage.from_params({ per_page: 0 }, [])
        expect(message).not_to be_valid
        expect(message.errors[:per_page]).to include('must be a positive integer')
      end

      it 'is invalid if per_page is negative' do
        message = ListMessage.from_params({ per_page: -1 }, [])
        expect(message).not_to be_valid
        expect(message.errors[:per_page]).to include('must be a positive integer')
      end

      it 'is valid if per_page is nil' do
        message = ListMessage.from_params({ per_page: nil }, [])
        expect(message).to be_valid
      end

      it 'is valid if it is between 1 and 5000' do
        invalid_message = ListMessage.from_params({ per_page: 5001 }, [])
        message = ListMessage.from_params({ per_page: 5000 }, [])

        expect(message).to be_valid
        expect(invalid_message).not_to be_valid
      end
    end

    describe 'maximum_result with max_total_results not configured' do
      it 'is valid if page and per_page is nil' do
        message = ListMessage.from_params({ page: nil, per_page: nil }, [])
        expect(message).to be_valid
      end

      it 'is valid when using default page' do
        message = ListMessage.from_params({ page: nil, per_page: 5000 }, [])
        expect(message).to be_valid
      end

      it 'is valid when using default per_page' do
        message = ListMessage.from_params({ page: 20, per_page: nil }, [])
        expect(message).to be_valid
      end

      it 'is invalid when page * default per_page exceeds DB limits' do
        message = ListMessage.from_params({ page: 184_467_440_737_095_517, per_page: nil }, [])
        expect(message).not_to be_valid
        expect(message.errors[:maximum_result]).to include('(page * per_page) must be less than 9223372036854775807')
      end

      it 'is invalid when page * per_page exceeds DB limits' do
        message = ListMessage.from_params({ page: 9_223_372_036_854_775_807, per_page: 2 }, [])
        expect(message).not_to be_valid
        expect(message.errors[:maximum_result]).to include('(page * per_page) must be less than 9223372036854775807')
      end
    end

    describe 'maximum_result with max_total_results set to 10_000' do
      before { TestConfig.override(renderer: { max_total_results: 10_000 }) }

      it 'is valid if page and per_page is nil' do
        message = ListMessage.from_params({ page: nil, per_page: nil }, [])
        expect(message).to be_valid
      end

      it 'is valid when using default page' do
        message = ListMessage.from_params({ page: nil, per_page: 5000 }, [])
        expect(message).to be_valid
      end

      it 'is valid when using default per_page' do
        message = ListMessage.from_params({ page: 20, per_page: nil }, [])
        expect(message).to be_valid
      end

      it 'is invalid when page * default per_page exceeds DB limits' do
        message = ListMessage.from_params({ page: 100_001, per_page: nil }, [])
        expect(message).not_to be_valid
        expect(message.errors[:maximum_result]).to include('(page * per_page) must be less than 10000')
      end

      it 'is invalid when page * per_page exceeds DB limits' do
        message = ListMessage.from_params({ page: 5001, per_page: 2 }, [])
        expect(message).not_to be_valid
        expect(message.errors[:maximum_result]).to include('(page * per_page) must be less than 10000')
      end
    end

    describe 'order validations' do
      context 'when order_by is present' do
        it 'validates when order_by is `created_at`' do
          message = ListMessage.from_params({ order_by: 'created_at' }, [])
          expect(message).to be_valid
        end

        it 'validates when order_by is `+created_at`' do
          message = ListMessage.from_params({ order_by: '+created_at' }, [])
          expect(message).to be_valid
        end

        it 'validates when order_by is `-updated_at`' do
          message = ListMessage.from_params({ order_by: '-updated_at' }, [])
          expect(message).to be_valid
        end

        it 'does not validate when order_by is `something_else`' do
          message = ListMessage.from_params({ order_by: 'something_else' }, [])
          expect(message).not_to be_valid
        end

        it 'does not validate when order_by is `*created_at`' do
          message = ListMessage.from_params({ order_by: '*created_at' }, [])
          expect(message).not_to be_valid
        end

        it 'does not validate when order_by is `12312`' do
          message = ListMessage.from_params({ order_by: '12312' }, [])
          expect(message).not_to be_valid
        end
      end

      context 'when order_by is not present' do
        it 'only validates order_by' do
          expect(ListMessage.from_params({}, [])).to be_valid
        end
      end
    end

    describe 'label_selector parsing' do
      let(:list_message_klass) do
        Class.new(VCAP::CloudController::ListMessage) do
          register_allowed_keys [:label_selector]

          def self.from_params(params)
            super(params, [])
          end
        end
      end

      context 'invalid operators' do
        it 'parses incorrect "in" operations as nil requirement' do
          message = list_message_klass.from_params('label_selector' => 'foo inn (bar,baz)')

          expect(message.requirements).to contain_exactly(nil)
        end

        it 'parses incorrect "notin" operations as nil requirement' do
          message = list_message_klass.from_params('label_selector' => 'foo notinn (bar,baz)')

          expect(message.requirements).to contain_exactly(nil)
        end

        it 'parses incorrect set operations as nil requirement' do
          message = list_message_klass.from_params('label_selector' => 'foo == (bar,baz)')

          expect(message.requirements).to contain_exactly(nil)
        end

        it 'parses multiple incorrect operations as nil requirements' do
          message = list_message_klass.from_params('label_selector' => 'foo == (bar,baz),foo narp doggie,bar inn (bat)')

          expect(message.requirements).to contain_exactly(nil, nil, nil)
        end
      end

      context 'set operations' do
        it 'parses correct in operation' do
          message = list_message_klass.from_params('label_selector' => 'example.com/foo in (bar,baz)')

          expect(message.requirements.first.key).to eq('example.com/foo')
          expect(message.requirements.first.operator).to eq(:in)
          expect(message.requirements.first.values).to contain_exactly('bar', 'baz')
        end

        it 'parses correct notin operation' do
          message = list_message_klass.from_params('label_selector' => 'example.com/foo notin (bar,baz)')

          expect(message.requirements.first.key).to eq('example.com/foo')
          expect(message.requirements.first.operator).to eq(:notin)
          expect(message.requirements.first.values).to contain_exactly('bar', 'baz')
        end
      end

      context 'equality operation' do
        it 'parses correct = operation' do
          message = list_message_klass.from_params('label_selector' => 'example.com/foo=bar')

          expect(message.requirements.first.key).to eq('example.com/foo')
          expect(message.requirements.first.operator).to eq(:equal)
          expect(message.requirements.first.values).to contain_exactly('bar')
        end

        it 'parses correct == operation' do
          message = list_message_klass.from_params('label_selector' => 'example.com/foo==bar')

          expect(message.requirements.first.key).to eq('example.com/foo')
          expect(message.requirements.first.operator).to eq(:equal)
          expect(message.requirements.first.values).to contain_exactly('bar')
        end

        it 'parses correct != operation' do
          message = list_message_klass.from_params('label_selector' => 'example.com/foo!=bar')

          expect(message.requirements.first.key).to eq('example.com/foo')
          expect(message.requirements.first.operator).to eq(:not_equal)
          expect(message.requirements.first.values).to contain_exactly('bar')
        end
      end

      context 'existence operations' do
        it 'parses correct existence operation' do
          message = list_message_klass.from_params('label_selector' => 'example.com/foo')

          expect(message.requirements.first.key).to eq('example.com/foo')
          expect(message.requirements.first.operator).to eq(:exists)
          expect(message.requirements.first.values).to be_empty
        end

        it 'parses correct non-existence operation' do
          message = list_message_klass.from_params('label_selector' => '!example.com/foo')

          expect(message.requirements.first.key).to eq('example.com/foo')
          expect(message.requirements.first.operator).to eq(:not_exists)
          expect(message.requirements.first.values).to be_empty
        end
      end

      context 'multiple operations' do
        it 'parses multiple operations' do
          message = list_message_klass.from_params('label_selector' => 'example.com/foo,bar!=baz,spork in (fork,spoon)')

          expect(message.requirements.first.key).to eq('example.com/foo')
          expect(message.requirements.first.operator).to eq(:exists)
          expect(message.requirements.first.values).to be_empty

          expect(message.requirements.second.key).to eq('bar')
          expect(message.requirements.second.operator).to eq(:not_equal)
          expect(message.requirements.second.values).to contain_exactly('baz')

          expect(message.requirements.third.key).to eq('spork')
          expect(message.requirements.third.operator).to eq(:in)
          expect(message.requirements.third.values).to contain_exactly('fork', 'spoon')
        end
      end

      context 'input form' do
        it 'handles ruby strings' do
          message = list_message_klass.from_params('label_selector' => 'example.com/foo==bar')
          expect(message.requirements.first.key).to eq('example.com/foo')
        end

        it 'handles ruby symbols' do
          message = list_message_klass.from_params(label_selector: 'example.com/foo==bar')
          expect(message.requirements.first.key).to eq('example.com/foo')
        end
      end
    end

    describe 'timestamp validations' do
      context 'validates the created_ats filter' do
        it 'delegates to the TimestampValidator' do
          message = ListMessage.from_params({ 'created_ats' => 47 }, [])
          expect(message).not_to be_valid
          expect(message.errors[:created_ats]).to include("has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'")
        end

        it 'validates guids are in array format' do
          message = ListMessage.from_params({ guids: 47 }, [])
          expect(message).not_to be_valid
          expect(message.errors[:guids]).to include('must be an array')
        end
      end

      context 'validates the updated_ats filter' do
        it 'delegates to the TimestampValidator' do
          message = ListMessage.from_params({ 'updated_ats' => { gte: '2020-06-30T23:49:04Z' } }, [])
          expect(message).to be_valid
        end
      end

      context 'validates the updated_ats filter' do
        it 'delegates to the TimestampValidator' do
          message = ListMessage.from_params({ 'updated_ats' => 47 }, [])
          expect(message).not_to be_valid
          expect(message.errors[:updated_ats]).to include("has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'")
        end
      end
    end

    context 'validates the guids filter' do
      it 'validates guids are in array format' do
        message = ListMessage.from_params({ 'guids' => { guid: 47 } }, [])
        expect(message).not_to be_valid
        expect(message.errors[:guids]).to include('must be an array')
      end
    end
  end
end
