require 'rails_helper'

describe Agents::CsvAgent do
  before(:each) do
    @valid_params = {
                      'mode' => 'parse',
                      'separator' => ',',
                      'use_fields' => '',
                      'output' => 'event_per_row',
                      'with_header' => 'true',
                      'data_path' => '$.data',
                      'data_key' => 'data'
                    }

    @checker = Agents::CsvAgent.new(:name => 'somename', :options => @valid_params)
    @checker.user = users(:jane)
    @checker.save!
    @lfa = Agents::LocalFileAgent.new(name: 'local', options: {path: '{{}}', watch: 'false', append: 'false', mode: 'read'})
    @lfa.user = users(:jane)
    @lfa.save!
  end

  it_behaves_like 'FileHandlingConsumer'

  context '#validate_options' do
    it 'is valid with the given options' do
      expect(@checker).to be_valid
    end

    it "requires with_header to be either 'true' or 'false'" do
      @checker.options['with_header'] = 'true'
      expect(@checker).to be_valid
      @checker.options['with_header'] = 'false'
      expect(@checker).to be_valid
      @checker.options['with_header'] = 'test'
      expect(@checker).not_to be_valid
    end

    it "data_path has to be set in serialize mode" do
      @checker.options['mode'] = 'serialize'
      @checker.options['data_path'] = ''
      expect(@checker).not_to be_valid
    end
  end

  context '#working' do
    it 'is not working without having received an event' do
      expect(@checker).not_to be_working
    end

    it 'is working after receiving an event without error' do
      @checker.last_receive_at = Time.now
      expect(@checker).to be_working
    end
  end

  context '#receive' do
    after(:all) do
      FileUtils.rm(File.join(Rails.root, 'tmp', 'csv'))
    end

    def event_with_contents(contents)
      path = File.join(Rails.root, 'tmp', 'csv')
      File.open(path, 'w') do |f|
        f.write(contents)
      end
      Event.new(payload: { 'file_pointer' => {'agent_id' => @lfa.id, 'file' => path } }, user_id: @checker.user_id)
    end

    context "agent options" do
      let(:with_headers) { event_with_contents("one,two\n1,2\n2,3") }
      let(:without_headers) { event_with_contents("1,2\n2,3") }

      context "output" do
        it "creates one event per row" do
          @checker.options['output'] = 'event_per_row'
          expect { @checker.receive([with_headers]) }.to change(Event, :count).by(2)
          expect(Event.last.payload).to eq(@checker.options['data_key'] => {'one' => '2', 'two' => '3'})
        end

        it "creates one event per file" do
          @checker.options['output'] = 'event_per_file'
          expect { @checker.receive([with_headers]) }.to change(Event, :count).by(1)
          expect(Event.last.payload).to eq(@checker.options['data_key'] => [{"one"=>"1", "two"=>"2"}, {"one"=>"2", "two"=>"3"}])
        end
      end

      context "with_header" do
        it "works without headers" do
          @checker.options['with_header'] = 'false'
          expect { @checker.receive([without_headers]) }.to change(Event, :count).by(2)
          expect(Event.last.payload).to eq({@checker.options['data_key']=>["2", "3"]})
        end

        it "works without headers and event_per_file" do
          @checker.options['with_header'] = 'false'
          @checker.options['output'] = 'event_per_file'
          expect { @checker.receive([without_headers]) }.to change(Event, :count).by(1)
          expect(Event.last.payload).to eq({@checker.options['data_key']=>[['1', '2'], ["2", "3"]]})
        end
      end

      context "use_fields" do
        it "extracts the specified columns" do
          @checker.options['use_fields'] = 'one'
          expect { @checker.receive([with_headers]) }.to change(Event, :count).by(2)
          expect(Event.last.payload).to eq(@checker.options['data_key'] => {'one' => '2'})
        end
      end

      context "data_path" do
        it "can receive the CSV via a regular event" do
          @checker.options['data_path'] = '$.data'
          event = Event.new(payload: {'data' => "one,two\r\n1,2\r\n2,3"})
          expect { @checker.receive([event]) }.to change(Event, :count).by(2)
          expect(Event.last.payload).to eq(@checker.options['data_key'] => {'one' => '2', 'two' => '3'})
        end
      end
    end

    context "handling different CSV formats" do
      it "works with windows line endings" do
        event = event_with_contents("one,two\r\n1,2\r\n2,3")
        expect { @checker.receive([event]) }.to change(Event, :count).by(2)
        expect(Event.last.payload).to eq(@checker.options['data_key'] => {'one' => '2', 'two' => '3'})
      end

      it "works with OSX line endings" do
        event = event_with_contents("one,two\r1,2\r2,3")
        expect { @checker.receive([event]) }.to change(Event, :count).by(2)
        expect(Event.last.payload).to eq(@checker.options['data_key'] => {'one' => '2', 'two' => '3'})
      end

      it "handles quotes correctly" do
        event = event_with_contents("\"one\",\"two\"\n1,2\n\"\"\"2, two\",3")
        expect { @checker.receive([event]) }.to change(Event, :count).by(2)
        expect(Event.last.payload).to eq(@checker.options['data_key'] => {'one' => '"2, two', 'two' => '3'})
      end

      it "works with tab seperated csv" do
        event = event_with_contents("one\ttwo\r\n1\t2\r\n2\t3")
        @checker.options['separator'] = '\\t'
        expect { @checker.receive([event]) }.to change(Event, :count).by(2)
        expect(Event.last.payload).to eq(@checker.options['data_key'] => {'one' => '2', 'two' => '3'})
      end
    end

    context "serializing" do
      before(:each) do
        @checker.options['mode'] = 'serialize'
        @checker.options['data_path'] = '$.data'
        @checker.options['data_key'] = 'data'
      end

      it "writes headers when with_header is true" do
        event = Event.new(payload: { 'data' => {'key' => 'value', 'key2' => 'value2', 'key3' => 'value3'} })
        expect { @checker.receive([event])}.to change(Event, :count).by(1)
        expect(Event.last.payload).to eq('data' => "\"key\",\"key2\",\"key3\"\n\"value\",\"value2\",\"value3\"\n")
      end

      it "writes one row per received event" do
        event = Event.new(payload: { 'data' => {'key' => 'value', 'key2' => 'value2', 'key3' => 'value3'} })
        event2 = Event.new(payload: { 'data' => {'key' => '2value', 'key2' => '2value2', 'key3' => '2value3'} })
        expect { @checker.receive([event, event2])}.to change(Event, :count).by(1)
        expect(Event.last.payload).to eq('data' => "\"key\",\"key2\",\"key3\"\n\"value\",\"value2\",\"value3\"\n\"2value\",\"2value2\",\"2value3\"\n")
      end

      it "accepts multiple rows per event" do
        event = Event.new(payload: { 'data' => [{'key' => 'value', 'key2' => 'value2', 'key3' => 'value3'}, {'key' => '2value', 'key2' => '2value2', 'key3' => '2value3'}] })
        expect { @checker.receive([event])}.to change(Event, :count).by(1)
        expect(Event.last.payload).to eq('data' => "\"key\",\"key2\",\"key3\"\n\"value\",\"value2\",\"value3\"\n\"2value\",\"2value2\",\"2value3\"\n")
      end

      it "does not write the headers when with_header is false" do
        @checker.options['with_header'] = 'false'
        event = Event.new(payload: { 'data' => {'key' => 'value', 'key2' => 'value2', 'key3' => 'value3'} })
        expect { @checker.receive([event])}.to change(Event, :count).by(1)
        expect(Event.last.payload).to eq('data' => "\"value\",\"value2\",\"value3\"\n")
      end

      it "only serialize the keys specified in use_fields" do
        @checker.options['use_fields'] = 'key2, key3'
        event = Event.new(payload: { 'data' => {'key' => 'value', 'key2' => 'value2', 'key3' => 'value3'} })
        expect { @checker.receive([event])}.to change(Event, :count).by(1)
        expect(Event.last.payload).to eq('data' => "\"key2\",\"key3\"\n\"value2\",\"value3\"\n")
      end

      it "respects the order of use_fields" do
        @checker.options['use_fields'] = 'key3, key'
        event = Event.new(payload: { 'data' => {'key' => 'value', 'key2' => 'value2', 'key3' => 'value3'} })
        expect { @checker.receive([event])}.to change(Event, :count).by(1)
        expect(Event.last.payload).to eq('data' => "\"key3\",\"key\"\n\"value3\",\"value\"\n")
      end

      it "respects use_fields and writes no header" do
        @checker.options['with_header'] = 'false'
        @checker.options['use_fields'] = 'key2, key3'
        event = Event.new(payload: { 'data' => {'key' => 'value', 'key2' => 'value2', 'key3' => 'value3'} })
        expect { @checker.receive([event])}.to change(Event, :count).by(1)
        expect(Event.last.payload).to eq('data' => "\"value2\",\"value3\"\n")
      end

      context "arrays" do
        it "does not write a header" do
          @checker.options['with_header'] = 'false'
          event = Event.new(payload: { 'data' => ['value1', 'value2'] })
          event2 = Event.new(payload: { 'data' => ['value3', 'value4'] })
          expect { @checker.receive([event, event2])}.to change(Event, :count).by(1)
          expect(Event.last.payload).to eq('data' => "\"value1\",\"value2\"\n\"value3\",\"value4\"\n")
        end

        it "handles nested arrays" do
          event = Event.new(payload: { 'data' => [['value1', 'value2'], ['value3', 'value4']] })
          expect { @checker.receive([event])}.to change(Event, :count).by(1)
          expect(Event.last.payload).to eq('data' => "\"value1\",\"value2\"\n\"value3\",\"value4\"\n")
        end
      end
    end
  end

  context '#event_description' do
    it "works with event_per_row and headers" do
      @checker.options['output'] = 'event_per_row'
      @checker.options['with_header'] = 'true'
      description = @checker.event_description
      expect(description).not_to match(/\n\s+\[\n/)
      expect(description).to include(": {\n")
    end

    it "works with event_per_file and without headers" do
      @checker.options['output'] = 'event_per_file'
      @checker.options['with_header'] = 'false'
      description = @checker.event_description
      expect(description).to match(/\n\s+\[\n/)
      expect(description).not_to include(": {\n")
    end

    it "shows dummy CSV when in serialize mode" do
      @checker.options['mode'] = 'serialize'
      description = @checker.event_description
      expect(description).to include('"generated\",\"csv')
    end
  end
end
