require "test_helper"

describe Cabin::Channel do

  # Cabin::Channel is a subscription thing, so implement
  # a simple receiver that just stores events in an array
  # for later access - this lets us see exactly what is
  # logged, in order.
  class Receiver
    attr_accessor :data

    public
    def initialize
      @data = []
    end

    public
    def <<(data)
      @data << data
    end
  end # class Receiver

  class TtyReceiver < Receiver
    def tty?
      true
    end
  end

  before do
    @logger = Cabin::Channel.new
    @target = Receiver.new
    @logger.subscribe(@target)
  end

  test "simple string publishing" do
    @logger.publish("Hello world")
    assert_equal(1, @target.data.length)
    assert_equal("Hello world", @target.data[0][:message])
  end

  test "simple context data" do
    @logger[:foo] = "bar"
    @logger.publish("Hello world")
    assert_equal(1, @target.data.length)
    assert_equal("Hello world", @target.data[0][:message])
    assert_equal("bar", @target.data[0][:foo])
  end

  test "time something" do
    timer = @logger.time("some sample")
    timer.stop

    event = @target.data[0]
    assert_equal("some sample", event[:message])
    assert(event[:duration].is_a?(Numeric))
  end

  test "double subscription should still only subscribe once" do
    @logger.subscribe(@target)
    @logger.publish("Hello world")
    assert_equal(1, @target.data.length)
    assert_equal("Hello world", @target.data[0][:message])
  end 

  test "subscribe with a level impacts log publishing" do
    sub1 = Receiver.new
    sub2 = Receiver.new
    @logger.subscribe(sub1, :level => :info)
    @logger.subscribe(sub2, :level => :error)
    @logger.trace("test trace")
    @logger.debug("test debug")
    @logger.info("test info")
    @logger.error("test error")

    assert_equal("test info", sub1.data[0][:message])
    assert_equal("test error", sub1.data[1][:message])
    assert_equal("test error", sub2.data[0][:message])
  end

  test "context values" do
    context = @logger.context
    context["foo"] = "hello"
    @logger.publish("testing")
    assert_equal(1, @target.data.length)
    assert_equal("hello", @target.data[0]["foo"])
    assert_equal("testing", @target.data[0][:message])
  end

  test "context values clear properly" do
    context = @logger.context
    context["foo"] = "hello"
    context.clear
    @logger.publish("testing")
    assert_equal(1, @target.data.length)
    assert(!@target.data[0].has_key?("foo"))
    assert_equal("testing", @target.data[0][:message])
  end

  %w(fatal error warn info debug).each do |level|
    level = level.to_sym
    test "standard use case, '#{level}' logging when enabled" do
      @logger.level = level
      @logger.send(level, "Hello world")
      event = @target.data[0]
      assert(@logger.send("#{level}?"),
             "At level #{level}, Channel##{level}? should return true.")
      assert_equal("Hello world", event[:message])
      assert_equal(level, event[:level])
    end
  end

  %w(error warn info debug).each do |level|
    level = level.to_sym
    test "standard use case, '#{level}' logging when wrong level" do
      @logger.level = :fatal
      # Should not log since log level is :fatal and we are above that.
      @logger.send(level, "Hello world")
      assert_equal(0, @target.data.length)
    end
  end

  test "standard use case, 'terminal' logging when there is no tty" do
    @logger.terminal('Hello world')
    assert_equal(true, @target.data.empty?)
  end

  test "standard use case, 'terminal' logging when there is a tty" do
    tty_target = TtyReceiver.new
    id   = @logger.subscribe(tty_target)
    @logger.terminal('Hello world')
    assert_equal('Hello world', tty_target.data[0][:message])
    @logger.unsubscribe(id)
  end

  test "extra debugging data" do 
    @logger.level = :debug
    @logger.info("Hello world")
    event = @target.data[0]
    assert(event.include?(:file), "At debug level, there should be a :file attribute")
    assert(event.include?(:line), "At debug level, there should be a :line attribute")
    assert(event.include?(:method), "At debug level, there should be a :method attribute")
  end

  test "extra debugging data absent if log level is not debug" do 
    @logger.level = :info
    @logger.info("Hello world")
    event = @target.data[0]
    assert(!event.include?(:file), "At non-debug level, there should not be a :file attribute")
    assert(!event.include?(:line), "At non-debug level, there should not be a :line attribute")
    assert(!event.include?(:method), "At non-debug level, there should not be a :method attribute")
  end

  test "invalid arguments to logger.info raises ArgumentError" do
    assert_raises(ArgumentError, "logger.info() should raise ArgumentError") do
      @logger.info()
    end

    assert_raises(ArgumentError, "logger.info('foo', 'bar') should raise " \
                  "ArgumentError because 'bar' is not a Hash.") do
      @logger.info("foo", "bar")
    end

    assert_raises(ArgumentError, "logger.info('foo', { 'foo': 'bar' }, 'baz')" \
                  "should raise ArgumentError for too many arguments") do
      @logger.info("foo", { "foo" => "bar" }, "bar")
    end
  end

  test "output to queue" do
    require "thread"
    queue = Queue.new
    @logger.subscribe(queue)

    @logger.info("Hello world")
    event = queue.pop
    assert_equal("Hello world", event[:message])
    assert_equal(:info, event[:level])
  end

end # describe Cabin::Channel do
