require 'rails_helper'

describe Agents::JqAgent do
  def create_event(payload)
    agents(:jane_weather_agent).events.create!(payload: payload)
  end

  let!(:agent) {
    Agents::JqAgent.create!(
      name: 'somename',
      options: {
        filter: '.+{"total": .numbers | add} | del(.numbers)'
      },
      user: users(:jane)
    )
  }

  describe '.should_run?' do
    it 'should be true' do
      expect(Agents::JqAgent).to be_should_run
    end

    context 'when not enabled' do
      before do
        allow(ENV).to receive(:[]) { nil }
        allow(ENV).to receive(:[]).with('USE_JQ') { nil }
      end

      it 'should be false' do
        expect(Agents::JqAgent).not_to be_should_run
      end
    end

    context 'when jq command is not available' do
      before do
        allow(Agents::JqAgent).to receive(:jq_version) { nil }
      end

      it 'should be false' do
        expect(Agents::JqAgent).not_to be_should_run
      end
    end
  end

  describe 'validation' do
    before do
      expect(agent).to be_valid
    end

    it 'should validate filter' do
      agent.options.delete(:filter)
      expect(agent).not_to be_valid

      agent.options[:filter] = [1]
      expect(agent).not_to be_valid

      # An empty expression is OK
      agent.options[:filter] = ''
      expect(agent).to be_valid
    end

    it 'should validate variables' do
      agent.options[:variables] = []
      expect(agent).not_to be_valid

      agent.options[:variables] = ''
      expect(agent).not_to be_valid

      agent.options[:variables] = { 'x' => [1, 2, 3] }
      expect(agent).to be_valid
    end
  end

  describe '#receive' do
    let!(:event) { create_event({ name: 'foo', numbers: [1, 2, 3, 4] }) }

    it 'should filter an event and create a single event if the result is an object' do
      expect {
        agent.receive([event])
      }.to change(Event, :count).by(1)

      created_event = agent.events.last

      expect(created_event.payload).to eq({
        'name' => 'foo',
        'total' => 10
      })
    end

    it 'should filter an event and create no event if the result is an empty array' do
      agent.update!(options: { filter: '[]' })

      expect {
        agent.receive([event])
      }.not_to change(Event, :count)
    end

    it 'should filter an event and create no event if the result is a scalar value' do
      agent.update!(options: { filter: '.numbers | add' })

      expect {
        agent.receive([event])
      }.not_to change(Event, :count)
    end

    it 'should filter an event and create no event if the result is an array of scalar values' do
      agent.update!(options: { filter: '.numbers' })

      expect {
        agent.receive([event])
      }.not_to change(Event, :count)
    end

    it 'should filter an event and create multiple events if the result is an array of objects' do
      agent.update!(options: { filter: '. as $original | .numbers[] | $original + { "number": . } | del(.numbers)' })

      expect {
        agent.receive([event])
      }.to change(Event, :count).by(4)

      created_events = agent.events.limit(4)

      expect(created_events.map(&:payload)).to eq([
        {
          'name' => 'foo',
          'number' => 4
        },
        {
          'name' => 'foo',
          'number' => 3
        },
        {
          'name' => 'foo',
          'number' => 2
        },
        {
          'name' => 'foo',
          'number' => 1
        }
      ])
    end

    it 'should reference passed in variables and filter an event' do
      agent.update!(
        options: {
          filter: '.+{ "extra": $somevar }',
          variables: {
            somevar: { foo: ['bar', 'baz'] }
          }
        }
      )

      expect {
        agent.receive([event])
      }.to change(Event, :count).by(1)

      created_event = agent.events.last

      expect(created_event.payload).to eq({
        'name' => 'foo',
        'numbers' => [1, 2, 3, 4],
        'extra' => { 'foo' => ['bar', 'baz'] }
      })
    end
  end
end
