require 'spec_helper'
require 'messages/process_update_message'
require 'messages/metadata_base_message'

module VCAP::CloudController
  RSpec.describe ProcessUpdateMessage do
    describe '#requested?' do
      it 'returns true if the key was requested, false otherwise' do
        message = ProcessUpdateMessage.new({ requested: 'thing' })

        expect(message).to be_requested(:requested)
        expect(message).not_to be_requested(:notrequested)
      end

      it 'handles nested health check keys' do
        message = ProcessUpdateMessage.new({ requested: 'thing' })
        expect(message).not_to be_requested(:health_check_type)
        expect(message).not_to be_requested(:health_check_timeout)

        message = ProcessUpdateMessage.new({ health_check: { type: 'type', data: { timeout: 4, invocation_timeout: 7, interval: 8 } } })
        expect(message).to be_requested(:health_check_type)
        expect(message).to be_requested(:health_check_timeout)
        expect(message).to be_requested(:health_check_invocation_timeout)
        expect(message).to be_requested(:health_check_interval)
      end

      it 'handles nested readiness health check keys' do
        message = ProcessUpdateMessage.new({ requested: 'thing' })
        expect(message).not_to be_requested(:readiness_health_check_type)
        expect(message).not_to be_requested(:readiness_health_check_timeout)

        message = ProcessUpdateMessage.new({ readiness_health_check: { type: 'type', data: { invocation_timeout: 7, interval: 9 } } })
        expect(message).to be_requested(:readiness_health_check_type)
        expect(message).to be_requested(:readiness_health_check_invocation_timeout)
        expect(message).to be_requested(:readiness_health_check_interval)
      end
    end

    describe '#audit_hash' do
      it 'excludes nested health check keys' do
        message = ProcessUpdateMessage.new(
          {
            health_check: { type: 'type', data: { timeout: 4, endpoint: 'something', invocation_timeout: 7, interval: 8 } },
            readiness_health_check: { type: 'http', data: { endpoint: '/meow', invocation_timeout: 8, interval: 9 } }
          }
        )
        expect(message.audit_hash).to eq(
          {
            'health_check' => { 'type' => 'type', 'data' => { 'timeout' => 4, 'endpoint' => 'something', 'invocation_timeout' => 7, 'interval' => 8 } },
            'readiness_health_check' => { 'type' => 'http', 'data' => { 'endpoint' => '/meow', 'invocation_timeout' => 8, 'interval' => 9 } }
          }
        )
      end
    end

    describe 'validations' do
      context 'when unexpected keys are requested' do
        let(:params) { { unexpected: 'foo', extra: 'bar', ports: [8181] } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors.full_messages[0]).to include("Unknown field(s): 'unexpected', 'extra', 'ports'")
        end
      end

      context 'when command is not a string' do
        let(:params) { { command: 32.77 } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:command]).to include('must be a string')
        end
      end

      context 'when command is nil' do
        let(:params) { { command: nil } }

        it 'is valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).to be_valid
        end
      end

      context 'when command is too long' do
        let(:params) { { command: 'a' * 5098 } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:command]).to include('must be between 1 and 4096 characters')
        end
      end

      context 'when command is empty' do
        let(:params) { { command: '' } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:command]).to include('must be between 1 and 4096 characters')
        end
      end

      context 'when user is not a string' do
        let(:params) { { user: 32.77 } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:user]).to include('must be a string')
        end
      end

      context 'when user is nil' do
        let(:params) { { user: nil } }

        it 'is valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).to be_valid
        end
      end

      context 'when user is too long' do
        let(:params) { { user: 'a' * 256 } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:user]).to include('must be between 1 and 255 characters')
        end
      end

      context 'when user is empty' do
        let(:params) { { user: '' } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:user]).to include('must be between 1 and 255 characters')
        end
      end

      context 'when user is just right' do
        let(:params) { { user: 'vcap' } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).to be_valid
        end
      end

      context 'when endpoint is too long' do
        let(:params) { { health_check: { type: 'http', data: { endpoint: 'a' * 256 } } } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:health_check_endpoint]).to include('is too long (maximum is 255 characters)')
        end
      end

      context 'when health_check type is http' do
        let(:params) { { health_check: { type: 'http' } } }

        it 'is valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).to be_valid
        end
      end

      context 'when health_check type is process' do
        let(:params) { { health_check: { type: 'process' } } }

        it 'is valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).to be_valid
        end
      end

      context 'when health_check type is port' do
        let(:params) { { health_check: { type: 'port' } } }

        it 'is valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).to be_valid
        end
      end

      context 'when health_check type is invalid' do
        let(:params) { { health_check: { type: 'invalid' } } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:health_check_type]).to include('must be "port", "process", or "http"')
        end
      end

      context 'when health_check type is nil' do
        let(:params) { { health_check: { type: nil } } }

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:health_check_type]).to include('must be "port", "process", or "http"')
        end
      end

      context 'when health_check timeout is not an integer' do
        let(:params) do
          {
            health_check: {
              type: 'port',
              data: {
                timeout: 0.2
              }
            }
          }
        end

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:health_check_timeout]).to include('must be an integer')
        end
      end

      context 'when health_check timeout is less than one' do
        let(:params) do
          {
            health_check: {
              type: 'port',
              data: {
                timeout: 0
              }
            }
          }
        end

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:health_check_timeout]).to include('must be greater than or equal to 1')
        end
      end

      context 'when health_check timeout is > the the max value allowed in the database' do
        let(:params) do
          {
            health_check: {
              type: 'port',
              data: {
                timeout: MetadataBaseMessage::MAX_DB_INT + 1
              }
            }
          }
        end

        it 'is not valid' do
          message = ProcessUpdateMessage.new(params)

          expect(message).not_to be_valid
          expect(message.errors[:health_check_timeout]).to include('must be less than or equal to 2147483647')
        end
      end

      describe 'health_check_invocation_timeout' do
        context 'when health_check invocation timeout is not an integer' do
          let(:params) do
            {
              health_check: {
                type: 'http',
                data: {
                  invocation_timeout: 0.2
                }
              }
            }
          end

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:health_check_invocation_timeout]).to include('must be an integer')
          end
        end

        context 'when health_check invocation timeout is less than one' do
          let(:params) do
            {
              health_check: {
                type: 'http',
                data: {
                  invocation_timeout: 0
                }
              }
            }
          end

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:health_check_invocation_timeout]).to include('must be greater than or equal to 1')
          end
        end

        context 'when health_check invocation timeout is > the the max value allowed in the database' do
          let(:params) do
            {
              health_check: {
                type: 'port',
                data: {
                  invocation_timeout: MetadataBaseMessage::MAX_DB_INT + 1
                }
              }
            }
          end

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:health_check_invocation_timeout]).to include('must be less than or equal to 2147483647')
          end
        end
      end

      describe 'health_check_interval' do
        context 'when health_check interval is not an integer' do
          let(:params) do
            {
              health_check: {
                type: 'http',
                data: {
                  interval: 0.2
                }
              }
            }
          end

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:health_check_interval]).to include('must be an integer')
          end
        end

        context 'when health_check interval is less than one' do
          let(:params) do
            {
              health_check: {
                type: 'http',
                data: {
                  interval: 0
                }
              }
            }
          end

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:health_check_interval]).to include('must be greater than or equal to 1')
          end
        end

        context 'when health_check interval is > the the max value allowed in the database' do
          let(:params) do
            {
              health_check: {
                type: 'port',
                data: {
                  interval: MetadataBaseMessage::MAX_DB_INT + 1
                }
              }
            }
          end

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:health_check_interval]).to include('must be less than or equal to 2147483647')
          end
        end
      end

      context 'when health_check timeout and endpoint are not requested' do
        let(:params) do
          {
            health_check: {
              type: 'port'
            }
          }
        end

        it 'is valid' do
          message = ProcessUpdateMessage.new(params)
          expect(message).to be_valid
        end
      end

      context 'when health_check endpoint is requested' do
        let(:endpoint) { '/healthcheck' }
        let(:params) do
          {
            health_check: {
              type: 'port',
              data: {
                endpoint: endpoint.to_s
              }
            }
          }
        end

        it 'is valid' do
          message = ProcessUpdateMessage.new(params)
          expect(message).to be_valid
        end

        context 'when endpoint is not a valid URI path' do
          let(:endpoint) { "some words that aren't a uri" }

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)
            expect(message).not_to be_valid
            expect(message.errors[:health_check_endpoint]).to include('must be a valid URI path')
          end
        end
      end

      context 'readiness health check properties' do
        context 'when readiness_health_check_type is http' do
          let(:params) { { readiness_health_check: { type: 'http' } } }

          it 'is valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).to be_valid
          end
        end

        context 'when readiness_health_check_type is process' do
          let(:params) { { readiness_health_check: { type: 'process' } } }

          it 'is valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).to be_valid
          end
        end

        context 'when readiness_health_check_type is port' do
          let(:params) { { readiness_health_check: { type: 'port' } } }

          it 'is valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).to be_valid
          end
        end

        context 'when readiness_health_check_type is invalid' do
          let(:params) { { readiness_health_check: { type: 'invalid' } } }

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:readiness_health_check_type]).to include('must be "port", "process", or "http"')
          end
        end

        context 'when readiness_health_check_type is nil' do
          let(:params) { { readiness_health_check: { type: nil } } }

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:readiness_health_check_type]).to include('must be "port", "process", or "http"')
          end
        end

        context 'when readiness_health_check_invocation_timeout is not an integer' do
          let(:params) do
            {
              readiness_health_check: {
                type: 'http',
                data: {
                  invocation_timeout: 0.2
                }
              }
            }
          end

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:readiness_health_check_invocation_timeout]).to include('must be an integer')
          end
        end

        context 'when readiness_health_check_invocation_timeout is less than one' do
          let(:params) do
            {
              readiness_health_check: {
                type: 'http',
                data: {
                  invocation_timeout: 0
                }
              }
            }
          end

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:readiness_health_check_invocation_timeout]).to include('must be greater than or equal to 1')
          end
        end

        context 'when readiness_health_check_invocation_timeout is > the the max value allowed in the database' do
          let(:params) do
            {
              readiness_health_check: {
                type: 'port',
                data: {
                  invocation_timeout: MetadataBaseMessage::MAX_DB_INT + 1
                }
              }
            }
          end

          it 'is not valid' do
            message = ProcessUpdateMessage.new(params)

            expect(message).not_to be_valid
            expect(message.errors[:readiness_health_check_invocation_timeout]).to include('must be less than or equal to 2147483647')
          end
        end

        describe 'readiness_health_check_interval' do
          context 'not an integer' do
            let(:params) do
              {
                readiness_health_check: {
                  type: 'http',
                  data: {
                    interval: 0.2
                  }
                }
              }
            end

            it 'is not valid' do
              message = ProcessUpdateMessage.new(params)

              expect(message).not_to be_valid
              expect(message.errors[:readiness_health_check_interval]).to include('must be an integer')
            end
          end

          context 'less than one' do
            let(:params) do
              {
                readiness_health_check: {
                  type: 'http',
                  data: {
                    interval: 0
                  }
                }
              }
            end

            it 'is not valid' do
              message = ProcessUpdateMessage.new(params)

              expect(message).not_to be_valid
              expect(message.errors[:readiness_health_check_interval]).to include('must be greater than or equal to 1')
            end
          end

          context 'interval is > the the max value allowed in the database' do
            let(:params) do
              {
                readiness_health_check: {
                  type: 'port',
                  data: {
                    interval: MetadataBaseMessage::MAX_DB_INT + 1
                  }
                }
              }
            end

            it 'is not valid' do
              message = ProcessUpdateMessage.new(params)

              expect(message).not_to be_valid
              expect(message.errors[:readiness_health_check_interval]).to include('must be less than or equal to 2147483647')
            end
          end
        end

        context 'when readiness_health_check_timeout and endpoint are not requested' do
          let(:params) do
            {
              readiness_health_check: {
                type: 'http'
              }
            }
          end

          it 'is valid' do
            message = ProcessUpdateMessage.new(params)
            expect(message).to be_valid
          end
        end

        context 'when readiness_health_check_endpoint is requested' do
          let(:endpoint) { '/healthcheck' }
          let(:params) do
            {
              readiness_health_check: {
                type: 'http',
                data: {
                  endpoint: endpoint.to_s
                }
              }
            }
          end

          it 'is valid' do
            message = ProcessUpdateMessage.new(params)
            expect(message).to be_valid
          end

          context 'when endpoint is not a valid URI path' do
            let(:endpoint) { "some words that aren't a uri" }

            it 'is not valid' do
              message = ProcessUpdateMessage.new(params)
              expect(message).not_to be_valid
              expect(message.errors[:readiness_health_check_endpoint]).to include('must be a valid URI path')
            end
          end
        end
      end

      context 'when there is metadata' do
        let(:body) do
          {
            metadata: {
              labels: {
                potato: 'mashed'
              },
              annotations: {
                cheese: 'bono'
              }
            }
          }
        end

        describe 'validations' do
          it 'validates that there are not excess fields' do
            body['bogus'] = 'field'
            message = ProcessUpdateMessage.new(body)

            expect(message).not_to be_valid
            expect(message.errors.full_messages).to include("Unknown field(s): 'bogus'")
          end

          it 'validates metadata' do
            message = ProcessUpdateMessage.new(body)

            expect(message).to be_valid
          end

          it 'complains about bogus metadata fields' do
            newbody = body.merge({ metadata: { choppers: 3 } })
            message = ProcessUpdateMessage.new(newbody)

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