# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

require "ostruct"

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

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

  before(:each) do
    # Force the V2 loader to believe that we are in V2
    stub_const("Vagrant::Config::CURRENT_VERSION", "2")
  end

  describe "empty" do
    it "returns an empty configuration object" do
      result = described_class.init
      expect(result).to be_kind_of(Vagrant::Config::V2::Root)
    end
  end

  describe "finalizing" do
    it "should call `#finalize` on the configuration object" do
      # Register a plugin for our test
      register_plugin("2") do |plugin|
        plugin.config "foo" do
          Class.new(Vagrant.plugin("2", "config")) do
            attr_accessor :bar

            def finalize!
              @bar = "finalized"
            end
          end
        end
      end

      # Create the proc we're testing
      config_proc = Proc.new do |config|
        config.foo.bar = "value"
      end

      # Test that it works properly
      config = described_class.load(config_proc)
      expect(config.foo.bar).to eq("value")

      # Finalize it
      described_class.finalize(config)
      expect(config.foo.bar).to eq("finalized")
    end
  end

  describe "loading" do
    it "should configure with all plugin config keys loaded" do
      # Register a plugin for our test
      register_plugin("2") do |plugin|
        plugin.config("foo") { OpenStruct }
      end

      # Create the proc we're testing
      config_proc = Proc.new do |config|
        config.foo.bar = "value"
      end

      # Test that it works properly
      config = described_class.load(config_proc)
      expect(config.foo.bar).to eq("value")
    end
  end

  describe "merging" do
    it "should merge available configuration keys" do
      old = Vagrant::Config::V2::Root.new({ foo: Object })
      new = Vagrant::Config::V2::Root.new({ bar: Object })
      result = described_class.merge(old, new)
      expect(result.foo).to be_kind_of(Object)
      expect(result.bar).to be_kind_of(Object)
    end

    it "should merge instantiated objects" do
      config_class = Class.new do
        attr_accessor :value
      end

      old = Vagrant::Config::V2::Root.new({ foo: config_class })
      old.foo.value = "old"

      new = Vagrant::Config::V2::Root.new({ bar: config_class })
      new.bar.value = "new"

      result = described_class.merge(old, new)
      expect(result.foo.value).to eq("old")
      expect(result.bar.value).to eq("new")
    end

    it "should merge conflicting classes by calling `merge`" do
      config_class = Class.new do
        attr_accessor :value

        def merge(new)
          result       = self.class.new
          result.value = @value + new.value
          result
        end
      end

      old = Vagrant::Config::V2::Root.new({ foo: config_class })
      old.foo.value = 10

      new = Vagrant::Config::V2::Root.new({ foo: config_class })
      new.foo.value = 15

      result = described_class.merge(old, new)
      expect(result.foo.value).to eq(25)
    end
  end

  describe "upgrading" do
    it "should continue fine if the key doesn't implement upgrade" do
      # Make an old V1 root object
      old = Vagrant::Config::V1::Root.new({ foo: Class.new })

      # It should work fine
      expect { result = described_class.upgrade(old) }.to_not raise_error
    end

    it "should upgrade the config if it implements the upgrade method" do
      # Create the old V1 class that will be upgraded
      config_class = Class.new do
        attr_accessor :value

        def upgrade(new)
          new.foo.value = value * 2

          [["foo"], ["bar"]]
        end
      end

      # Create the new V2 plugin it is writing to
      register_plugin("2") do |p|
        p.config("foo") { OpenStruct }
      end

      # Test it out!
      old = Vagrant::Config::V1::Root.new({ foo: config_class })
      old.foo.value = 5

      data = described_class.upgrade(old)
      expect(data[0].foo.value).to eq(10)
      expect(data[1]).to eq(["foo"])
      expect(data[2]).to eq(["bar"])
    end
  end
end
