require_relative '../test_helper'
require 'fluent/plugin/out_slack'
require 'time'

class SlackOutputTest < Test::Unit::TestCase

  def setup
    super
    Fluent::Test.setup
    @icon_url = 'http://www.google.com/s2/favicons?domain=www.google.de'
  end

  CONFIG = %[
    channel channel
    webhook_url https://hooks.slack.com/services/XXXX/XXXX/XXX
  ]

  def default_payload
    {
      channel:    '#channel',
      mrkdwn:     true,
      link_names: true,
    }
  end

  def default_attachment
    {
      mrkdwn_in: %w[text fields],
    }
  end

  def create_driver(conf = CONFIG)
    Fluent::Test::BufferedOutputTestDriver.new(Fluent::SlackOutput).configure(conf)
  end

  # old version compatibility with v0.4.0"
  def test_old_config
    # default check
    d = create_driver
    assert_equal true, d.instance.localtime
    assert_equal nil,  d.instance.username   # 'fluentd'    break lower version compatibility
    assert_equal nil,  d.instance.color      # 'good'       break lower version compatibility
    assert_equal nil,  d.instance.icon_emoji # ':question:' break lower version compatibility
    assert_equal nil,  d.instance.icon_url
    assert_equal true, d.instance.mrkdwn
    assert_equal true, d.instance.link_names
    assert_equal nil,  d.instance.parse

    assert_nothing_raised do
      create_driver(CONFIG + %[api_key testtoken])
    end

    # incoming webhook endpoint was changed. team option should be ignored
    assert_nothing_raised do
      create_driver(CONFIG + %[team sowasowa])
    end

    # rtm? it was not calling `rtm.start`. rtm option was removed and should be ignored
    assert_nothing_raised do
      create_driver(CONFIG + %[rtm true])
    end

    # channel should be URI.unescape-ed
    d = create_driver(CONFIG + %[channel %23test])
    assert_equal '#test', d.instance.channel

    # timezone should work
    d = create_driver(CONFIG + %[timezone Asia/Tokyo])
    assert_equal 'Asia/Tokyo', d.instance.timezone
  end

  def test_configure
    d = create_driver(%[
      channel      channel
      time_format  %Y/%m/%d %H:%M:%S
      username     username
      color        bad
      icon_emoji   :ghost:
      token        XX-XX-XX
      title        slack notice!
      message      %s
      message_keys message
    ])
    assert_equal '#channel', d.instance.channel
    assert_equal '%Y/%m/%d %H:%M:%S', d.instance.time_format
    assert_equal 'username', d.instance.username
    assert_equal 'bad', d.instance.color
    assert_equal ':ghost:', d.instance.icon_emoji
    assert_equal 'XX-XX-XX', d.instance.token
    assert_equal '%s', d.instance.message
    assert_equal ['message'], d.instance.message_keys

    assert_raise(Fluent::ConfigError) do
      create_driver(CONFIG + %[title %s %s\ntitle_keys foo])
    end

    assert_raise(Fluent::ConfigError) do
      create_driver(CONFIG + %[message %s %s\nmessage_keys foo])
    end

    assert_raise(Fluent::ConfigError) do
      create_driver(CONFIG + %[channel %s %s\nchannel_keys foo])
    end
  end

  def test_slack_configure
    # One of webhook_url or slackbot_url, or token is required
    assert_raise(Fluent::ConfigError) do
      create_driver(%[channel foo])
    end

    # webhook_url is an empty string
    assert_raise(Fluent::ConfigError) do
      create_driver(%[channel foo\nwebhook_url])
    end

    # webhook without channel (it works because webhook has a default channel)
    assert_nothing_raised do
      create_driver(%[webhook_url https://example.com/path/to/webhook])
    end

    # slackbot_url is an empty string
    assert_raise(Fluent::ConfigError) do
      create_driver(%[channel foo\nslackbot_url])
    end

    # slackbot without channel
    assert_raise(Fluent::ConfigError) do
      create_driver(%[slackbot_url https://example.com/path/to/slackbot])
    end

    # token is an empty string
    assert_raise(Fluent::ConfigError) do
      create_driver(%[channel foo\ntoken])
    end

    # slack webapi token without channel
    assert_raise(Fluent::ConfigError) do
      create_driver(%[token some_token])
    end
  end

  def test_timezone_configure
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i

    d = create_driver(CONFIG + %[localtime])
    with_timezone('Asia/Tokyo') do
      assert_equal true,       d.instance.localtime
      assert_equal "07:00:00", d.instance.timef.format(time)
    end

    d = create_driver(CONFIG + %[utc])
    with_timezone('Asia/Tokyo') do
      assert_equal false,      d.instance.localtime
      assert_equal "22:00:00", d.instance.timef.format(time)
    end

    d = create_driver(CONFIG + %[timezone Asia/Taipei])
    with_timezone('Asia/Tokyo') do
      assert_equal "Asia/Taipei", d.instance.timezone
      assert_equal "06:00:00",    d.instance.timef.format(time)
    end
  end

  def test_time_format_configure
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i

    d = create_driver(CONFIG + %[time_format %Y/%m/%d %H:%M:%S])
    with_timezone('Asia/Tokyo') do
      assert_equal "2014/01/02 07:00:00", d.instance.timef.format(time)
    end
  end

  def test_buffer_configure
    assert_nothing_raised do
      create_driver(CONFIG + %[buffer_type file\nbuffer_path tmp/])
    end
  end

  def test_icon_configure
    # default
    d = create_driver(CONFIG)
    assert_equal nil, d.instance.icon_emoji
    assert_equal nil, d.instance.icon_url

    # either of icon_emoji or icon_url can be specified
    assert_raise(Fluent::ConfigError) do
      d = create_driver(CONFIG + %[icon_emoji :ghost:\nicon_url #{@icon_url}])
    end

    # icon_emoji
    d = create_driver(CONFIG + %[icon_emoji :ghost:])
    assert_equal ':ghost:', d.instance.icon_emoji
    assert_equal nil, d.instance.icon_url

    # icon_url
    d = create_driver(CONFIG + %[icon_url #{@icon_url}])
    assert_equal nil, d.instance.icon_emoji
    assert_equal @icon_url, d.instance.icon_url
  end

  def test_link_names_configure
    # default
    d = create_driver(CONFIG)
    assert_equal true, d.instance.link_names

    # true
    d = create_driver(CONFIG + %[link_names true])
    assert_equal true, d.instance.link_names

    # false
    d = create_driver(CONFIG + %[link_names false])
    assert_equal false, d.instance.link_names
  end

  def test_parse_configure
    # default
    d = create_driver(CONFIG)
    assert_equal nil, d.instance.parse

    # none
    d = create_driver(CONFIG + %[parse none])
    assert_equal 'none', d.instance.parse

    # full
    d = create_driver(CONFIG + %[parse full])
    assert_equal 'full', d.instance.parse

    # invalid
    assert_raise(Fluent::ConfigError) do
      d = create_driver(CONFIG + %[parse invalid])
    end
  end

  def test_mrkwn_configure
    # default
    d = create_driver(CONFIG)
    assert_equal true, d.instance.mrkdwn
    assert_equal %w[text fields], d.instance.mrkdwn_in

    # true
    d = create_driver(CONFIG + %[mrkdwn true])
    assert_equal true, d.instance.mrkdwn
    assert_equal %w[text fields], d.instance.mrkdwn_in

    # false
    d = create_driver(CONFIG + %[mrkdwn false])
    assert_equal false, d.instance.mrkdwn
    assert_equal nil, d.instance.mrkdwn_in
  end

  def test_https_proxy_configure
    # default
    d = create_driver(CONFIG)
    assert_equal nil, d.instance.slack.https_proxy
    assert_equal Net::HTTP, d.instance.slack.proxy_class

    # https_proxy
    d = create_driver(CONFIG + %[https_proxy https://proxy.foo.bar:443])
    assert_equal URI.parse('https://proxy.foo.bar:443'), d.instance.slack.https_proxy
    assert_not_equal Net::HTTP, d.instance.slack.proxy_class # Net::HTTP.Proxy
  end

  def test_auto_channels_create_configure
    # default
    d = create_driver(CONFIG)
    assert_equal false, d.instance.auto_channels_create
    assert_equal({}, d.instance.post_message_opts)

    # require `token`
    assert_raise(Fluent::ConfigError) do
      d = create_driver(CONFIG + %[auto_channels_create true])
    end

    # auto_channels_create
    d = create_driver(CONFIG + %[auto_channels_create true\ntoken XXX-XX-XXX])
    assert_equal true, d.instance.auto_channels_create
    assert_equal({auto_channels_create: true}, d.instance.post_message_opts)
  end

  def test_default_incoming_webhook
    d = create_driver(%[
      channel channel
      webhook_url https://hooks.slack.com/services/XXX/XXX/XXX
    ])
    assert_equal Fluent::SlackClient::IncomingWebhook, d.instance.slack.class
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      text: "sowawa1\nsowawa2\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1'}, time)
      d.emit({message: 'sowawa2'}, time)
      d.run
    end
  end

  def test_default_slackbot
    d = create_driver(%[
      channel channel
      slackbot_url https://xxxxx.slack.com/services/hooks/slackbot?token=XXXXXXX
    ])
    assert_equal Fluent::SlackClient::Slackbot, d.instance.slack.class
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      text: "sowawa1\nsowawa2\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1'}, time)
      d.emit({message: 'sowawa2'}, time)
      d.run
    end
  end

  def test_default_slack_api
    d = create_driver(%[
      channel channel
      token   XX-XX-XX
    ])
    assert_equal Fluent::SlackClient::WebApi, d.instance.slack.class
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      token: 'XX-XX-XX',
      text:  "sowawa1\nsowawa2\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1'}, time)
      d.emit({message: 'sowawa2'}, time)
      d.run
    end
  end

  def test_title_payload
    title = "mytitle"
    d = create_driver(CONFIG + %[title #{title}])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    # attachments field should be changed to show the title
    mock(d.instance.slack).post_message(default_payload.merge({
      attachments: [default_attachment.merge({
        fallback: title,
        fields:   [
          {
            title: title,
            value: "sowawa1\nsowawa2\n",
          }
        ],
      })]
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1'}, time)
      d.emit({message: 'sowawa2'}, time)
      d.run
    end
  end

  def test_title_payload_with_verbose_fallback_option
    title = "mytitle"
    d = create_driver(CONFIG + %[title #{title}\nverbose_fallback true])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    # attachments field should be changed to show the title
    mock(d.instance.slack).post_message(default_payload.merge({
      attachments: [default_attachment.merge({
        fallback: "#{title} sowawa1\nsowawa2\n",
        fields:   [
          {
            title: title,
            value: "sowawa1\nsowawa2\n",
          }
        ],
      })]
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1'}, time)
      d.emit({message: 'sowawa2'}, time)
      d.run
    end
  end

  def test_color_payload
    color = 'good'
    d = create_driver(CONFIG + %[color #{color}])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    # attachments field should be changed to show the title
    mock(d.instance.slack).post_message(default_payload.merge({
      attachments: [default_attachment.merge({
        color:    color,
        fallback: "sowawa1\nsowawa2\n",
        text:     "sowawa1\nsowawa2\n",
      })]
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1'}, time)
      d.emit({message: 'sowawa2'}, time)
      d.run
    end
  end

  def test_plain_payload
    d = create_driver(CONFIG)
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    # attachments field should be changed to show the title
    mock(d.instance.slack).post_message(default_payload.merge({
      text: "sowawa1\nsowawa2\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1'}, time)
      d.emit({message: 'sowawa2'}, time)
      d.run
    end
  end

  def test_title_keys
    d = create_driver(CONFIG + %[title [%s] %s\ntitle_keys time,tag])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    # attachments field should be changed to show the title
    mock(d.instance.slack).post_message(default_payload.merge({
      attachments: [default_attachment.merge({
        fallback: "[07:00:00] #{d.tag}",
        fields:   [
          {
            title: "[07:00:00] #{d.tag}",
            value: "sowawa1\nsowawa2\n",
          }
        ],
      })]
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1'}, time)
      d.emit({message: 'sowawa2'}, time)
      d.run
    end
  end

  def test_message_keys
    d = create_driver(CONFIG + %[message [%s] %s %s\nmessage_keys time,tag,message])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      text: "[07:00:00] test sowawa1\n[07:00:00] test sowawa2\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1'}, time)
      d.emit({message: 'sowawa2'}, time)
      d.run
    end
  end

  def test_channel_keys
    d = create_driver(CONFIG + %[channel %s\nchannel_keys channel])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      channel: '#channel1',
      text:    "sowawa1\n",
    }), {})
    mock(d.instance.slack).post_message(default_payload.merge({
      channel: '#channel2',
      text:    "sowawa2\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'sowawa1', channel: 'channel1'}, time)
      d.emit({message: 'sowawa2', channel: 'channel2'}, time)
      d.run
    end
  end

  def test_icon_emoji
    d = create_driver(CONFIG + %[icon_emoji :ghost:])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      icon_emoji: ':ghost:',
      text:       "foo\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'foo'}, time)
      d.run
    end
  end

  def test_icon_url
    d = create_driver(CONFIG + %[icon_url #{@icon_url}])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      icon_url: @icon_url,
      text:     "foo\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'foo'}, time)
      d.run
    end
  end

  def test_mrkdwn
    d = create_driver(CONFIG + %[mrkdwn true])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      mrkdwn: true,
      text:   "foo\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'foo'}, time)
      d.run
    end
  end

  def test_mrkdwn_in
    d = create_driver(CONFIG + %[mrkdwn true\ncolor good])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      attachments: [default_attachment.merge({
        color:    "good",
        fallback: "foo\n",
        text:     "foo\n",
        mrkdwn_in: ["text", "fields"],
      })]
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'foo'}, time)
      d.run
    end
  end

  def test_link_names
    d = create_driver(CONFIG + %[link_names true])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      link_names: true,
      text:       "foo\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'foo'}, time)
      d.run
    end
  end

  def test_parse
    d = create_driver(CONFIG + %[parse full])
    time = Time.parse("2014-01-01 22:00:00 UTC").to_i
    d.tag  = 'test'
    mock(d.instance.slack).post_message(default_payload.merge({
      parse: "full",
      text:  "foo\n",
    }), {})
    with_timezone('Asia/Tokyo') do
      d.emit({message: 'foo'}, time)
      d.run
    end
  end
end