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