require "spec_helper"
require "mvcli/form"
require "mvcli/decoding"
require "ipaddr"

describe "A form for creating a load balancer" do
  use_natural_assertions
  Given(:definition) do
    Class.new(MVCLI::Form) do
      input :name, String, default: -> {naming.generate 'l', 'b'}

      input :port, Integer, default: 80, decode: ->(s) {Integer s}

      input :protocol, String, default: 'HTTP', decode: :upcase

      input :virtual_ips, [String], default: ['PUBLIC'], decode: :upcase

      input :nodes, [Node], required: true do
        input :address, IPAddr, required: true, decode: ->(s) {IPAddr.new s}
        input :port, Integer, default: 80, decode: ->(s) {Integer s}
        input :type, String, default: 'PRIMARY', decode: :upcase
        input :condition, String, default: 'ENABLED', decode: :upcase

        validates(:port, "port must be between 0 and 65,535") {|port| port >= 0 && port <= 65535}
        validates(:type, "invalid type") {|type| ['PRIMARY', 'SECONDARY'].member? type}
        validates(:condition, "invalid condition") {|c| ['ENABLED', 'DISABLED'].member? c}
      end
      validates(:virtual_ips, "invalid virtual IP") {|ip| ['PUBLIC', 'SERVICENET'].member? ip}
    end
  end
  Given(:form) do
    definition.new(params).tap do |f|
      f.stub(:decoders) {MVCLI::Decoding}
      f.stub(:naming) {double(:NameGenerator, generate: 'random-name')}
    end
  end
  context "with invalid array properties" do
    Given(:params) { ({virtual_ip: ['PERBLIC'], node: ['10.0.0.1']}) }
    Then { not form.valid? }
    And {form.violations["virtual_ips[0]"] == ["invalid virtual IP"]}

  end
  context "with no nodes provided" do
    Given(:params) {({node: []})}
    Then {!form.valid?}
    And {form.violations[:nodes] == ["cannot be empty"]}

  end
  context "with invalid node inputs" do
    Given(:params) do
      ({
         node: ['10.0.0.1:-500', 'xxx:80']
       })
    end
    Then {!form.valid?}
    context "the first violation" do
      Given(:violations) {form.validation[:nodes].first.violations}
      Then {violations[:port] == ["port must be between 0 and 65,535"]}
    end
    context "the second error" do
      Given(:errors) {form.validation[:nodes].last.errors}
      Then { not errors[:address].empty? }
    end
  end
  context "with partially specified, valid inputs" do
    Given(:params) do
      ({node: ['10.0.0.1:80']})
    end
    Then {form.name == 'random-name'}
    And {form.port == 80}
    And {form.protocol == 'HTTP'}
    And {form.virtual_ips == ['PUBLIC']}
    context "the default form node" do
      Given(:node) {form.nodes.first}
      Then {node.address == IPAddr.new('10.0.0.1')}
      And {node.port == 80}
    end
  end
  context "with fully specified, valid inputs" do
    Given(:params) {
      ({
         name: 'foo',
         port: '80',
         protocol: 'http',
         virtual_ip: ['public', 'servicenet'],
         node: [
                 '10.0.0.1:80:primary:enabled',
                 '10.0.0.2:80:secondary:disabled'
                ]
       })
    }
    Then {form.valid?}
    And {form.name == 'foo'}
    And {form.port == 80}
    And {form.protocol == 'HTTP'}
    And {form.nodes.length == 2}

    Given(:attributes) {form.value.to_hash.reject {|k,v| k == "nodes"}}
    Then {attributes == {"name" => "foo", "port" => 80, "protocol" => "HTTP", "virtual_ips" => ["PUBLIC", "SERVICENET"]}}

    Given (:nodeattrs) { form.attributes[:nodes]}
    Then { nodeattrs.first == {"address" => IPAddr.new('10.0.0.1'), "port" =>  80, "condition" => "ENABLED", "type" =>  "PRIMARY"} }

    context ". On the first node" do
      Given(:node) {form.nodes.first}
      Then {node.address == IPAddr.new('10.0.0.1')}
      And {node.port == 80}
      And {node.condition == 'ENABLED'}
      And {node.type == 'PRIMARY'}
    end
    context ". On the second node" do
      Given(:node) {form.nodes.last}
      Then {node.address == IPAddr.new('10.0.0.2')}
      And {node.port == 80}
      And {node.condition == 'DISABLED'}
      And {node.type == 'SECONDARY'}
    end
  end
end

class Node
  include MVCLI::Validatable
  attr_accessor :address, :port, :protocol, :condition, :type
  validates(:port, "port must be between 0 and 65,535") {|port| port >= 0 && port <= 65535}

  def initialize(attrs)
    @address, @port, @protocal, @condition, @type = attrs.values_at(:address, :port, :protocol, :condition, :type)
  end

end
