require "spec_helper"

describe Lita::ConfigurationBuilder do
  let(:config) { subject.build }

  # For some reason RSpec is no longer capturing tracebacks to
  # stderr, so we do it manually to avoid cluttering the test output.
  around do |example|
    stderr = $stderr
    $stderr = StringIO.new
    example.run
    $stderr = stderr
  end

  describe ".load_user_config" do
    it "loads and evals lita_config.rb" do
      allow(File).to receive(:exist?).and_return(true)
      allow(described_class).to receive(:load) do
        Lita.configure { |c| c.robot.name = "Not Lita" }
      end
      described_class.load_user_config
      expect(Lita.config.robot.name).to eq("Not Lita")
    end

    it "doesn't attempt to load lita_config.rb if it doesn't exist" do
      allow(File).to receive(:exist?).and_return(false)
      expect(described_class).not_to receive(:load)
      described_class.load_user_config
    end

    it "raises an exception if lita_config.rb raises an exception" do
      allow(File).to receive(:exist?).and_return(true)
      allow(described_class).to receive(:load) { Lita.non_existent_method }
      expect(Lita.logger).to receive(:fatal).with(/could not be processed/)
      expect { described_class.load_user_config }.to raise_error(SystemExit)
    end
  end

  describe "a simple attribute" do
    before { subject.config :simple }

    it "is nil by default" do
      expect(config.simple).to be_nil
    end

    it "can be set to anything" do
      config.simple = 1
      expect(config.simple).to eq(1)
    end
  end

  describe "a typed attribute" do
    before { subject.config :simple, type: Symbol }

    it "is nil by default" do
      expect(config.simple).to be_nil
    end

    it "can be set to a value of the type" do
      config.simple = :foo
      expect(config.simple).to eq(:foo)
    end

    it "raises if set to a value of the wrong type" do
      expect(Lita.logger).to receive(:fatal).with(
        /Configuration type error: "simple" must be one of: Symbol/
      )
      expect { config.simple = "foo" }.to raise_error(SystemExit)
    end
  end

  describe "a composite typed attribute" do
    before { subject.config :simple, types: [Symbol, String] }

    it "is nil by default" do
      expect(config.simple).to be_nil
    end

    it "can be set to a value of the first type" do
      config.simple = :foo
      expect(config.simple).to eq(:foo)
    end

    it "can be set to a value of the second type" do
      config.simple = "foo"
      expect(config.simple).to eq("foo")
    end

    it "raises if set to a value of the wrong type" do
      expect(Lita.logger).to receive(:fatal).with(
        /Configuration type error: "simple" must be one of: Symbol, String/
      )
      expect { config.simple = 1 }.to raise_error(SystemExit)
    end
  end

  describe "an attribute with a default value" do
    before { subject.config :simple, default: :foo }

    it "starts with the default value" do
      expect(config.simple).to eq(:foo)
    end

    it "can be reassigned" do
      config.simple = :bar
      expect(config.simple).to eq(:bar)
    end
  end

  describe "a typed attribute with a default value" do
    it "starts with the default value" do
      subject.config :simple, type: Symbol, default: :foo
      expect(config.simple).to eq(:foo)
    end

    it "raises if the default is a value of the wrong type" do
      expect { subject.config :simple, type: Symbol, default: "foo" }.to raise_error(TypeError)
    end
  end

  describe "a validated attribute" do
    before do
      subject.config :simple do
        validate { |value| "must be true" unless value }
      end
    end

    it "can be set to a value that passes validation" do
      config.simple = true
      expect(config.simple).to be(true)
    end

    it "raises if the validator raises due to an invalid value" do
      expect(Lita.logger).to receive(:fatal).with(
        /Validation error on attribute "simple": must be true/
      )
      expect { config.simple = false }.to raise_error(SystemExit)
    end
  end

  describe "a validated attribute with a conflicting default value" do
    it "raises a ValidationError" do
      expect do
        subject.config :simple, default: :foo do
          validate { |value| "must be :bar" unless value == :bar }
        end
      end.to raise_error(Lita::ValidationError, /must be :bar/)
    end
  end

  describe "a simple nested attribute" do
    before do
      subject.config :nested do
        config :foo
      end
    end

    it "is nil by default" do
      expect(config.nested.foo).to be_nil
    end

    it "can be set to anything" do
      config.nested.foo = :bar
      expect(config.nested.foo).to eq(:bar)
    end

    it "prevents the parent from being assigned" do
      expect { config.nested = "impossible" }.to raise_error(NoMethodError)
    end
  end

  describe "an attribute with all the options and nested attributes" do
    before do
      subject.config :nested, type: Symbol, default: :foo do
        config :foo
      end
    end

    it "cannot be set" do
      expect { config.nested = "impossible" }.to raise_error(NoMethodError)
    end
  end

  describe "multiple nested attributes with options" do
    before do
      subject.config :nested do
        config :foo, default: "bar" do
          validate { |value| "must include bar" unless value.include?("bar") }
        end

        config :bar, type: Symbol
      end
    end

    it "can get the first nested attribute" do
      expect(config.nested.foo).to eq("bar")
    end

    it "can set the first nested attribute" do
      config.nested.foo = "foo bar baz"
      expect(config.nested.foo).to eq("foo bar baz")
    end

    it "has working validation" do
      expect(Lita.logger).to receive(:fatal).with(
        /Validation error on attribute "foo": must include bar/
      )
      expect { config.nested.foo = "baz" }.to raise_error(SystemExit)
    end

    it "can get the second nested attribute" do
      expect(config.nested.bar).to be_nil
    end

    it "can set the second nested attribute and options take effect" do
      expect(Lita.logger).to receive(:fatal).with(
        /Configuration type error: "bar" must be one of: Symbol/
      )
      expect { config.nested.bar = "not a symbol" }.to raise_error(SystemExit)
    end
  end

  describe "#has_children?" do
    it "is true when any attribute has been created" do
      subject.config :foo

      expect(subject.children?).to be_truthy
    end

    it "is false when no attributes have been created" do
      expect(subject.children?).to be_falsy
    end
  end

  describe "#combine" do
    let(:config_2) do
      config_2 = described_class.new
      config_2.config(:bar)
      config_2
    end

    it "sets the provided configuration as the value of the provided attribute" do
      subject.combine(:foo, config_2)

      expect(config.foo.bar).to be_nil
    end

    it "does not allow the combined configuration to be reassigned" do
      subject.combine(:foo, config_2)

      expect { config.foo = "bar" }.to raise_error(NoMethodError)
    end
  end
end
