# encoding: utf-8
require 'spec_helper'
require 'support/pipeline/pipeline_helpers'

module ConditionalFanciness
  include PipelineHelpers
  def description
    return self.metadata[:description]
  end

  def conditional(expression, &block)
    describe(expression) do
      config <<-CONFIG
        filter {
          if #{expression} {
            mutate { add_tag => "success" }
          } else {
            mutate { add_tag => "failure" }
          }
        }
      CONFIG
      instance_eval(&block)
    end
  end
end

describe "conditionals in output" do
  extend ConditionalFanciness

  class DummyNullOutput < LogStash::Outputs::Base
    config_name "dummynull"

    def register
    end
    def multi_receive(events)
    end
  end

  before do
    LogStash::PLUGIN_REGISTRY.add(:output, "dummynull", DummyNullOutput)
  end

  describe "simple" do
    config <<-CONFIG
      input {
        generator {
          message => '{"foo":{"bar"},"baz": "quux"}'
          count => 1
        }
      }
      output {
        if [foo] == "bar" {
          dummynull { }
        }
      }
    CONFIG

    agent do
      #LOGSTASH-2288, should not fail raising an exception
    end
  end
end

describe "conditionals in filter" do
  extend ConditionalFanciness

  describe "simple" do
    config <<-CONFIG
      filter {
        mutate { add_field => { "always" => "awesome" } }
        if [foo] == "bar" {
          mutate { add_field => { "hello" => "world" } }
        } else if [bar] == "baz" {
          mutate { add_field => { "fancy" => "pants" } }
        } else {
          mutate { add_field => { "free" => "hugs" } }
        }
      }
    CONFIG

    sample_one({"foo" => "bar"}) do
      expect(subject.get("always")).to eq("awesome")
      expect(subject.get("hello")).to eq("world")
      expect(subject.get("fancy")).to be_nil
      expect(subject.get("free")).to be_nil
    end

    sample_one({"notfoo" => "bar"}) do
      expect(subject.get("always")).to eq("awesome")
      expect(subject.get("hello")).to be_nil
      expect(subject.get("fancy")).to be_nil
      expect(subject.get("free")).to eq("hugs")
    end

    sample_one({"bar" => "baz"}) do
      expect(subject.get("always")).to eq("awesome")
      expect(subject.get("hello")).to be_nil
      expect(subject.get("fancy")).to eq("pants")
      expect(subject.get("free")).to be_nil
    end
  end

  describe "nested" do
    config <<-CONFIG
      filter {
        if [nest] == 123 {
          mutate { add_field => { "always" => "awesome" } }
          if [foo] == "bar" {
            mutate { add_field => { "hello" => "world" } }
          } else if [bar] == "baz" {
            mutate { add_field => { "fancy" => "pants" } }
          } else {
            mutate { add_field => { "free" => "hugs" } }
          }
        }
      }
    CONFIG

    sample_one("foo" => "bar", "nest" => 124) do
      expect(subject.get("always")).to be_nil
      expect(subject.get("hello")).to be_nil
      expect(subject.get("fancy")).to be_nil
      expect(subject.get("free")).to be_nil
    end

    sample_one("foo" => "bar", "nest" => 123) do
      expect(subject.get("always")).to eq("awesome")
      expect(subject.get("hello")).to eq("world")
      expect(subject.get("fancy")).to be_nil
      expect(subject.get("free")).to be_nil
    end

    sample_one("notfoo" => "bar", "nest" => 123) do
      expect(subject.get("always")).to eq("awesome")
      expect(subject.get("hello")).to be_nil
      expect(subject.get("fancy")).to be_nil
      expect(subject.get("free")).to eq("hugs")
    end

    sample_one("bar" => "baz", "nest" => 123) do
      expect(subject.get("always")).to eq("awesome")
      expect(subject.get("hello")).to be_nil
      expect(subject.get("fancy")).to eq("pants")
      expect(subject.get("free")).to be_nil
    end
  end

  describe "comparing two fields" do
    config <<-CONFIG
      filter {
        if [foo] == [bar] {
          mutate { add_tag => woot }
        }
      }
    CONFIG

    sample_one("foo" => 123, "bar" => 123) do
      expect(subject.get("tags") ).to include("woot")
    end
  end

  describe "the 'in' operator" do
    config <<-CONFIG
      filter {
        if [foo] in [foobar] {
          mutate { add_tag => "field in field" }
        }
        if [foo] in "foo" {
          mutate { add_tag => "field in string" }
        }
        if "hello" in [greeting] {
          mutate { add_tag => "string in field" }
        }
        if [foo] in ["hello", "world", "foo"] {
          mutate { add_tag => "field in list" }
        }
        if [missing] in [alsomissing] {
          mutate { add_tag => "shouldnotexist" }
        }
        if !("foo" in ["hello", "world"]) {
          mutate { add_tag => "shouldexist" }
        }
      }
    CONFIG

    sample_one("foo" => "foo", "foobar" => "foobar", "greeting" => "hello world") do
      expect(subject.get("tags")).to include("field in field")
      expect(subject.get("tags")).to include("field in string")
      expect(subject.get("tags")).to include("string in field")
      expect(subject.get("tags")).to include("field in list")
      expect(subject.get("tags")).not_to include("shouldnotexist")
      expect(subject.get("tags")).to include("shouldexist")
    end
  end

  describe "the 'not in' operator" do
    config <<-CONFIG
      filter {
        if "foo" not in "baz" { mutate { add_tag => "baz" } }
        if "foo" not in "foo" { mutate { add_tag => "foo" } }
        if !("foo" not in "foo") { mutate { add_tag => "notfoo" } }
        if "foo" not in [somelist] { mutate { add_tag => "notsomelist" } }
        if "one" not in [somelist] { mutate { add_tag => "somelist" } }
        if "foo" not in [alsomissing] { mutate { add_tag => "no string in missing field" } }
      }
    CONFIG

    sample_one("foo" => "foo", "somelist" => [ "one", "two" ], "foobar" => "foobar", "greeting" => "hello world", "tags" => [ "fancypantsy" ]) do
      # verify the original exists
      expect(subject.get("tags")).to include("fancypantsy")

      expect(subject.get("tags")).to include("baz")
      expect(subject.get("tags")).not_to include("foo")
      expect(subject.get("tags")).to include("notfoo")
      expect(subject.get("tags")).to include("notsomelist")
      expect(subject.get("tags")).not_to include("somelist")
      expect(subject.get("tags")).to include("no string in missing field")
    end
  end

  describe "operators" do
    conditional "[message] == 'sample'" do
      sample_one("sample") { expect(subject.get("tags") ).to include("success") }
      sample_one("different") { expect(subject.get("tags") ).to include("failure") }
    end

    conditional "'sample' == [message]" do
      sample_one("sample") {expect(subject.get("tags")).to include("success")}
      sample_one("different") {expect(subject.get("tags")).to include("failure")}
    end

    conditional "'value' == 'value'" do
      sample_one("sample") {expect(subject.get("tags")).to include("success")}
    end

    conditional "'value' == 'other'" do
      sample_one("sample") {expect(subject.get("tags")).to include("failure")}
    end

    conditional "[message] != 'sample'" do
      sample_one("sample") { expect(subject.get("tags") ).to include("failure") }
      sample_one("different") { expect(subject.get("tags") ).to include("success") }
    end

    conditional "[message] < 'sample'" do
      sample_one("apple") { expect(subject.get("tags") ).to include("success") }
      sample_one("zebra") { expect(subject.get("tags") ).to include("failure") }
    end

    conditional "[message] > 'sample'" do
      sample_one("zebra") { expect(subject.get("tags") ).to include("success") }
      sample_one("apple") { expect(subject.get("tags") ).to include("failure") }
    end

    conditional "[message] <= 'sample'" do
      sample_one("apple") { expect(subject.get("tags") ).to include("success") }
      sample_one("zebra") { expect(subject.get("tags") ).to include("failure") }
      sample_one("sample") { expect(subject.get("tags") ).to include("success") }
    end

    conditional "[message] >= 'sample'" do
      sample_one("zebra") { expect(subject.get("tags") ).to include("success") }
      sample_one("sample") { expect(subject.get("tags") ).to include("success") }
      sample_one("apple") { expect(subject.get("tags") ).to include("failure") }
    end

    conditional "[message] == 5" do
      sample_one("message" => 5) {expect(subject.get("tags")).to include("success")}
      sample_one("message" => 3) {expect(subject.get("tags")).to include("failure")}
    end

    conditional "5 == [message]" do
      sample_one("message" => 5) {expect(subject.get("tags")).to include("success")}
      sample_one("message" => 3) {expect(subject.get("tags")).to include("failure")}
    end

    conditional "7 == 7" do
      sample_one("message" => 7) {expect(subject.get("tags")).to include("success")}
      sample_one("message" => 3) {expect(subject.get("tags")).to include("success")}
    end

    conditional "5 == 7" do
      sample_one("message" => 3) {expect(subject.get("tags")).to include("failure")}
      sample_one("message" => 2) {expect(subject.get("tags")).to include("failure")}
    end

    conditional "[message] != 5" do
      sample_one("message" => 5) {expect(subject.get("tags")).to include("failure")}
      sample_one("message" => 3) {expect(subject.get("tags")).to include("success")}
    end

    conditional "[message] < 5" do
      sample_one("message" => 3) {expect(subject.get("tags")).to include("success")}
      sample_one("message" => 5) {expect(subject.get("tags")).to include("failure")}
      sample_one("message" => 9) {expect(subject.get("tags")).to include("failure")}
    end

    conditional "[message] > 5" do
      sample_one("message" => 9) {expect(subject.get("tags")).to include("success")}
      sample_one("message" => 5) {expect(subject.get("tags")).to include("failure")}
      sample_one("message" => 4) {expect(subject.get("tags")).to include("failure")}
    end

    conditional "[message] <= 5" do
      sample_one("message" => 9) {expect(subject.get("tags")).to include("failure")}
      sample_one("message" => 5) {expect(subject.get("tags")).to include("success")}
      sample_one("message" => 3) {expect(subject.get("tags")).to include("success")}
    end

    conditional "[message] >= 5" do
      sample_one("message" => 5) {expect(subject.get("tags")).to include("success")}
      sample_one("message" => 7) {expect(subject.get("tags")).to include("success")}
      sample_one("message" => 3) {expect(subject.get("tags")).to include("failure")}
    end

    conditional "[message] =~ /sample/" do
      sample_one("apple") { expect(subject.get("tags") ).to include("failure") }
      sample_one("sample") { expect(subject.get("tags") ).to include("success") }
      sample_one("some sample") { expect(subject.get("tags") ).to include("success") }
    end

    conditional "[message] !~ /sample/" do
      sample_one("apple") { expect(subject.get("tags")).to include("success") }
      sample_one("sample") { expect(subject.get("tags")).to include("failure") }
      sample_one("some sample") { expect(subject.get("tags")).to include("failure") }
    end

  end

  describe "negated expressions" do
    conditional "!([message] == 'sample')" do
      sample_one("sample") { expect(subject.get("tags")).not_to include("success") }
      sample_one("different") { expect(subject.get("tags")).not_to include("failure") }
    end

    conditional "!([message] != 'sample')" do
      sample_one("sample") { expect(subject.get("tags")).not_to include("failure") }
      sample_one("different") { expect(subject.get("tags")).not_to include("success") }
    end

    conditional "!([message] < 'sample')" do
      sample_one("apple") { expect(subject.get("tags")).not_to include("success") }
      sample_one("zebra") { expect(subject.get("tags")).not_to include("failure") }
    end

    conditional "!([message] > 'sample')" do
      sample_one("zebra") { expect(subject.get("tags")).not_to include("success") }
      sample_one("apple") { expect(subject.get("tags")).not_to include("failure") }
    end

    conditional "!([message] <= 'sample')" do
      sample_one("apple") { expect(subject.get("tags")).not_to include("success") }
      sample_one("zebra") { expect(subject.get("tags")).not_to include("failure") }
      sample_one("sample") { expect(subject.get("tags")).not_to include("success")}
    end

    conditional "!([message] >= 'sample')" do
      sample_one("zebra") { expect(subject.get("tags")).not_to include("success") }
      sample_one("sample") { expect(subject.get("tags")).not_to include("success") }
      sample_one("apple") { expect(subject.get("tags")).not_to include("failure") }
    end

    conditional "!([message] =~ /sample/)" do
      sample_one("apple") { expect(subject.get("tags")).not_to include("failure") }
      sample_one("sample") { expect(subject.get("tags")).not_to include("success") }
      sample_one("some sample") { expect(subject.get("tags")).not_to include("success") }
    end

    conditional "!([message] !~ /sample/)" do
      sample_one("apple") { expect(subject.get("tags")).not_to include("success") }
      sample_one("sample") { expect(subject.get("tags")).not_to include("failure") }
      sample_one("some sample") { expect(subject.get("tags")).not_to include("failure") }
    end

  end

  describe "value as an expression" do
    # testing that a field has a value should be true.
    conditional "[message]" do
      sample_one("apple") { expect(subject.get("tags")).to include("success") }
      sample_one("sample") { expect(subject.get("tags")).to include("success") }
      sample_one("some sample") { expect(subject.get("tags")).to include("success") }
    end

    # testing that a missing field has a value should be false.
    conditional "[missing]" do
      sample_one("apple") { expect(subject.get("tags")).to include("failure") }
      sample_one("sample") { expect(subject.get("tags")).to include("failure") }
      sample_one("some sample") { expect(subject.get("tags")).to include("failure") }
    end
  end

  describe "logic operators" do
    describe "and" do
      conditional "[message] and [message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("success") }
      end
      conditional "[message] and ![message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("failure") }
      end
      conditional "![message] and [message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("failure") }
      end
      conditional "![message] and ![message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("failure") }
      end
    end

    describe "nand" do
      conditional "[message] nand [message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("failure") }
      end
      conditional "[message] nand ![message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("success") }
      end
      conditional "![message] nand [message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("success") }
      end
      conditional "![message] nand ![message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("success") }
      end
    end

    describe "xor" do
      conditional "[message] xor [message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("failure") }
      end
      conditional "[message] xor ![message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("success") }
      end
      conditional "![message] xor [message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("success") }
      end
      conditional "![message] xor ![message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("failure") }
      end
    end

    describe "or" do
      conditional "[message] or [message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("success") }
      end
      conditional "[message] or ![message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("success") }
      end
      conditional "![message] or [message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("success") }
      end
      conditional "![message] or ![message]" do
        sample_one("whatever") { expect(subject.get("tags")).to include("failure") }
      end
    end
  end

  describe "field references" do
    conditional "[field with space]" do
      sample_one("field with space" => "hurray") do
        expect(subject.get("tags")).to include("success")
      end
    end

    conditional "[field with space] == 'hurray'" do
      sample_one("field with space" => "hurray") do
        expect(subject.get("tags")).to include("success")
      end
    end

    conditional "[nested field][reference with][some spaces] == 'hurray'" do
      sample_one({"nested field" => { "reference with" => { "some spaces" => "hurray" } } }) do
        expect(subject.get("tags")).to include("success")
      end
    end
  end

  describe "new events from root" do
    config <<-CONFIG
      filter {
        if [type] == "original" {
          clone {
            clones => ["clone"]
          }
        }
        if [type] == "original" {
          mutate { add_field => { "cond1" => "true" } }
        } else {
          mutate { add_field => { "cond2" => "true" } }
        }
      }
    CONFIG

    sample_one({"type" => "original"}) do
      expect(subject).to be_an(Array)
      expect(subject.length).to eq(2)
      subject.sort! {|a, b| a.get("type") <=> b.get("type")}

      expect(subject[1].get("type")).to eq("original")
      expect(subject[1].get("cond1")).to eq("true")
      expect(subject[1].get("cond2")).to eq(nil)

      expect(subject[0].get("type")).to eq("clone")
      # expect(subject[1].get("cond1")).to eq(nil)
      # expect(subject[1].get("cond2")).to eq("true")
    end
  end

  describe "multiple new events from root" do
    config <<-CONFIG
      filter {
        if [type] == "original" {
          clone {
            clones => ["clone1", "clone2"]
          }
        }
        if [type] == "clone1" {
          mutate { add_field => { "cond1" => "true" } }
        } else if [type] == "clone2" {
          mutate { add_field => { "cond2" => "true" } }
        }
      }
    CONFIG

    sample_one({"type" => "original"}) do
      expect(subject.length).to eq(3)
      subject.sort! {|a, b| a.get("type") <=> b.get("type")}

      expect(subject[0].get("type")).to eq("clone1")
      expect(subject[0].get("cond1")).to eq("true")
      expect(subject[0].get("cond2")).to eq(nil)

      expect(subject[1].get("type")).to eq("clone2")
      expect(subject[1].get("cond1")).to eq(nil)
      expect(subject[1].get("cond2")).to eq("true")

      expect(subject[2].get("type")).to eq("original")
      expect(subject[2].get("cond1")).to eq(nil)
      expect(subject[2].get("cond2")).to eq(nil)
    end
  end

  describe "complex case" do
    config <<-CONFIG
      filter {
        if ("foo" in [tags]) {
          mutate { id => addbar add_tag => bar }
      
          if ("bar" in [tags]) {
            mutate { id => addbaz  add_tag => baz }
          }
      
          if ("baz" in [tags]) {
            mutate { id => addbot add_tag => bot }
      
            if ("bot" in [tags]) {
              mutate { id => addbonk add_tag => bonk }
            }
          }
        }
      
        if ("bot" in [tags]) {
          mutate { id => addwat add_tag => wat }
        }
      
        mutate { id => addprev add_tag => prev }
      
        mutate { id => addfinal add_tag => final }
      
      }
    CONFIG

    sample_one("tags" => ["bot"]) do
      tags = subject.get("tags")
      expect(tags[0]).to eq("bot")
      expect(tags[1]).to eq("wat")
      expect(tags[2]).to eq("prev")
      expect(tags[3]).to eq("final")
    end

    sample_one("tags" => ["foo"]) do
      tags = subject.get("tags")
      expect(tags[0]).to eq("foo")
      expect(tags[1]).to eq("bar")
      expect(tags[2]).to eq("baz")
      expect(tags[3]).to eq("bot")
      expect(tags[4]).to eq("bonk")
      expect(tags[5]).to eq("wat")
      expect(tags[6]).to eq("prev")
      expect(tags[7]).to eq("final")
    end
    
    sample_one("type" => "original") do
      tags = subject.get("tags")
      expect(tags[0]).to eq("prev")
      expect(tags[1]).to eq("final")
    end
  end
end