require "set"

require File.expand_path("../../../../base", __FILE__)

describe Vagrant::Config::V2::Root do
  include_context "unit"

  it "should provide access to config objects" do
    foo_class = Class.new
    map       = { foo: foo_class }

    instance  = described_class.new(map)
    foo       = instance.foo
    expect(foo).to be_kind_of(foo_class)
    expect(instance.foo).to eql(foo)
  end

  it "record a missing key call if invalid key used" do
    instance = described_class.new({})
    expect { instance.foo }.to_not raise_error
    expect(instance.__internal_state["missing_key_calls"].include?("foo")).to be
  end

  it "returns a dummy config for a missing key" do
    instance = described_class.new({})
    expect { instance.foo.foo = "bar" }.to_not raise_error
  end

  it "can be created with initial state" do
    instance = described_class.new({}, { foo: "bar" })
    expect(instance.foo).to eq("bar")
  end

  it "should return internal state" do
    map      = { "foo" => Object, "bar" => Object }
    instance = described_class.new(map)
    expect(instance.__internal_state).to eq({
      "config_map"        => map,
      "keys"              => {},
      "missing_key_calls" => Set.new
    })
  end

  describe "#finalize!" do
    it "should call #finalize!" do
      foo_class = Class.new(Vagrant.plugin("2", "config")) do
        attr_accessor :foo

        def finalize!
          @foo = "SET"
        end
      end

      map = { foo: foo_class }
      instance = described_class.new(map)
      instance.finalize!

      expect(instance.foo.foo).to eq("SET")
    end

    it "should call #_finalize!" do
      klass = Class.new(Vagrant.plugin("2", "config"))

      expect_any_instance_of(klass).to receive(:finalize!)
      expect_any_instance_of(klass).to receive(:_finalize!)

      map = { foo: klass }
      instance = described_class.new(map)
      instance.finalize!
    end
  end

  describe "validation" do
    let(:instance) do
      map = { foo: Object, bar: Object }
      described_class.new(map)
    end

    it "should return nil if valid" do
      expect(instance.validate({})).to eq({})
    end

    it "should return errors if invalid" do
      errors = { "foo" => ["errors!"] }
      env    = { "errors" => errors }
      foo    = instance.foo
      def foo.validate(env)
        env["errors"]
      end

      expect(instance.validate(env)).to eq(errors)
    end

    context "with vms and ignoring provider validations" do
      let(:instance) do
        map = { vm: Object, bar: Object }
        described_class.new(map)
      end

      it "should pass along the ignore_provider flag for ignoring validations" do
        errors = { "vm" => ["errors!"] }
        env    = { "errors" => errors }
        vm     = instance.vm
        def vm.validate(env, param)
          env["errors"]
        end

        expect(instance.validate({}, true)).to eq({})
      end
    end

    it "should merge errors via array concat if matching keys" do
      errors = { "foo" => ["errors!"] }
      env    = { "errors" => errors }
      foo    = instance.foo
      bar    = instance.bar
      def foo.validate(env)
        env["errors"]
      end

      def bar.validate(env)
        env["errors"].merge({ "bar" => ["bar"] })
      end

      expected_errors = {
        "foo" => ["errors!", "errors!"],
        "bar" => ["bar"]
      }

      expect(instance.validate(env)).to eq(expected_errors)
    end

    it "shouldn't count empty keys" do
      errors = { "foo" => [] }
      env    = { "errors" => errors }
      foo    = instance.foo
      def foo.validate(env)
        env["errors"]
      end

      expect(instance.validate(env)).to eq({})
    end
  end
end
