require_relative '../spec_helper' require_relative '../../app/connectors/importer' require_relative '../doubles/result' require 'csv' describe CartoDB::Connector::Importer do before(:all) do @user = create_user(quota_in_bytes: 1000.megabyte, table_quota: 400, max_layers: 4) end before(:each) do bypass_named_maps end after(:each) do if @data_import ::Table.get_by_table_id(@data_import.table_id)&.destroy if @data_import.table_id.present? @data_import.destroy end @visualization.destroy if @visualization ::UserTable[@existing_table.id].destroy if @existing_table end after(:all) do bypass_named_maps @user.destroy end let(:skip) { DataImport::COLLISION_STRATEGY_SKIP } it 'should not fail to return a new_name when ALTERing the INDEX fails' do # this basically validates the empty rescue handling in rename_the_geom_index_if_exists, # if you remove that rescue this test will fail runner = mock log = mock runner.stubs(:log).returns(log) log.expects(:append).at_least(0) quota_checker = mock id = Carto::UUIDHelper.random_uuid destination_schema = 'public' database = mock database.stubs(:execute).with { |query| /ALTER INDEX/.match(query) }.raises('wadus') database.stubs(:execute).with { |query| /ALTER TABLE/.match(query) }.returns(nil) table_registrar = mock table_registrar.stubs(:user).returns(@user) importer_table_name = "table_#{Carto::UUIDHelper.random_uuid}" desired_table_name = 'european_countries' result_mock = CartoDB::Doubles::Importer2::Result.new({table_name: importer_table_name, name: desired_table_name}) importer = CartoDB::Connector::Importer.new( runner: runner, table_registrar: table_registrar, quota_checker: quota_checker, database: database, data_import_id: id, destination_schema: destination_schema ) new_table_name = importer.rename(result_mock, importer_table_name, desired_table_name, destination_schema) new_table_name.should_not == nil end # This test checks that the importer detects files with names that are # psql reserved words and knows how to rename them (appending '_t') it 'should allow importing tables with reserved names' do reserved_word = Carto::DB::Sanitize::RESERVED_WORDS.sample filepath = "/tmp/#{reserved_word.downcase}.csv" expected_rename = reserved_word.downcase + '_t' CSV.open(filepath, 'wb') do |csv| csv << ['nombre', 'apellido', 'profesion'] csv << ['Manolo', 'Escobar', 'Artista'] end data_import = DataImport.create( :user_id => @user.id, :data_source => filepath, :updated_at => Time.now, :append => false ) data_import.values[:data_source] = filepath data_import.run_import! File.delete(filepath) data_import.success.should(eq(true), "File with reserved name '#{filepath}' failed to be renamed") data_import.table_name.should(eq(expected_rename), "Table was incorrectly renamed to '#{data_import.table_name}', should be '#{expected_rename}'") end it 'should import tables as public if privacy param is set to public' do @user.private_tables_enabled = false @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" @data_import = DataImport.create( :user_id => @user.id, :data_source => filepath, :updated_at => Time.now, :append => false, :privacy => (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['public'] ) @data_import.values[:data_source] = filepath @data_import.run_import! UserTable[id: @data_import.table_id].privacy.should eq ::UserTable::PRIVACY_VALUES_TO_TEXTS.invert['public'] end it 'importing the same file twice should import the table twice renaming the second import' do name = 'elecciones2008' filepath = "#{Rails.root}/spec/support/data/#{name}.csv" @data_import = DataImport.create(user_id: @user.id, data_source: filepath) @data_import.values[:data_source] = filepath @data_import.run_import! @data_import.success.should eq true UserTable[id: @data_import.table_id].name.should eq name data_import2 = DataImport.create(user_id: @user.id, data_source: filepath) data_import2.values[:data_source] = filepath data_import2.run_import! data_import2.success.should eq true UserTable[id: data_import2.table_id].name.should eq "#{name}_1" ::Table.get_by_table_id(data_import2.table_id).destroy if data_import2 && data_import2.table_id.present? data_import2.destroy end it 'handles import with almost identical long file names properly' do long_name1 = 'carto_long_filename_that_almost_matches_another_one_63chars_aaa' filepath1 = "#{Rails.root}/spec/support/data/#{long_name1}.csv" @data_import = DataImport.create(user_id: @user.id, data_source: filepath1) @data_import.values[:data_source] = filepath1 @data_import.run_import! expect(@data_import.success).to be_true expect(UserTable[id: @data_import.table_id].name).to eq(long_name1) long_name2 = 'carto_long_filename_that_almost_matches_another_one_63chars_aab' filepath2 = "#{Rails.root}/spec/support/data/#{long_name2}.csv" data_import2 = DataImport.create(user_id: @user.id, data_source: filepath2) data_import2.values[:data_source] = filepath2 data_import2.run_import! expect(data_import2.success).to be_true expected_name = 'carto_long_filename_that_almost_matches_another_one_63chars_a_1' expect(UserTable[id: data_import2.table_id].name).to eq(expected_name) ::Table.get_by_table_id(data_import2.table_id).destroy if data_import2 && data_import2.table_id.present? data_import2.destroy end it 'importing the same file twice with collision strategy skip should import the table once' do name = 'elecciones2008' filepath = "#{Rails.root}/spec/support/data/#{name}.csv" @data_import = DataImport.create(user_id: @user.id, data_source: filepath, collision_strategy: skip) @data_import.values[:data_source] = filepath @data_import.run_import! UserTable[id: @data_import.table_id].name.should eq name @data_import.success.should eq true data_import2 = DataImport.create(user_id: @user.id, data_source: filepath, collision_strategy: skip) data_import2.values[:data_source] = filepath data_import2.run_import! data_import2.error_code = 1022 data_import2.success.should eq true data_import2.tables_created_count.should eq 0 data_import2.table_names.should eq '' data_import2.table_name.should be_nil ::Table.get_by_table_id(data_import2.table_id).destroy if data_import2 && data_import2.table_id.present? data_import2.destroy end it 'should import tables as private if privacy param is set to private' do @user.private_tables_enabled = true @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( :user_id => @user.id, :data_source => filepath, :updated_at => Time.now, :append => false, :privacy => (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['private'] ) data_import.values[:data_source] = filepath data_import.run_import! UserTable[id: data_import.table_id].privacy.should eq (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['private'] end it 'should import table and vis as private if privacy param is set to private' do @user.private_tables_enabled = true @user.private_maps_enabled = true @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, privacy: (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['private'], create_visualization: true ) data_import.values[:data_source] = filepath data_import.run_import! data_import.success.should eq true Carto::Visualization.find_by_id(data_import.visualization_id).privacy.should eq 'private' data_import.values[:data_source] = filepath data_import.run_import! UserTable[id: data_import.table_id].privacy.should eq (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['private'] end it 'should import vis as public if privacy param is set to private and user doesn\' have private maps' do @user.private_tables_enabled = true @user.private_maps_enabled = false @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, privacy: ::UserTable::PRIVACY_VALUES_TO_TEXTS.invert['private'], create_visualization: true ) data_import.values[:data_source] = filepath data_import.run_import! data_import.success.should eq true Carto::Visualization.find_by_id(data_import.visualization_id).privacy.should eq 'public' data_import.values[:data_source] = filepath data_import.run_import! UserTable[id: data_import.table_id].privacy.should eq ::UserTable::PRIVACY_VALUES_TO_TEXTS.invert['private'] end it 'should import tables as private by default if user has private tables enabled' do @user.private_tables_enabled = true @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( :user_id => @user.id, :data_source => filepath, :updated_at => Time.now, :append => false ) data_import.values[:data_source] = filepath data_import.run_import! UserTable[id: data_import.table_id].privacy.should eq (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['private'] end it 'should import tables as public by default if user doesnt have private tables enabled' do @user.private_tables_enabled = false @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( :user_id => @user.id, :data_source => filepath, :updated_at => Time.now, :append => false ) data_import.values[:data_source] = filepath data_import.run_import! UserTable[id: data_import.table_id].privacy.should eq (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['public'] end it 'should import as public with private_tables enabled' do @user.private_tables_enabled = true @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( :user_id => @user.id, :data_source => filepath, :updated_at => Time.now, :append => false, :privacy => (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['public'] ) data_import.values[:data_source] = filepath data_import.run_import! data_import.success.should eq true end it 'should import table and vis as public with private_tables enabled' do @user.private_tables_enabled = true @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, privacy: (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['public'], create_visualization: true ) data_import.values[:data_source] = filepath data_import.run_import! data_import.success.should eq true Carto::Visualization.find_by_id(data_import.visualization_id).privacy.should eq 'public' end it 'imported visualization should have registered table dependencies' do filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) data_import.values[:data_source] = filepath data_import.run_import! data_import.success.should eq true visualization = Carto::Visualization.find_by_id(data_import.visualization_id) data_layer = visualization.data_layers.first data_layer.user_tables.size.should eq 1 data_layer.user_tables.first.name.should include 'elecciones2008' end it 'imports files for Builder users' do old_builder_enabled = @user.builder_enabled @user.builder_enabled = true @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) data_import.values[:data_source] = filepath data_import.run_import! data_import.success.should eq true visualization = Carto::Visualization.find_by_id(data_import.visualization_id) data_layer = visualization.data_layers.first data_layer.user_tables.size.should eq 1 data_layer.user_tables.first.name.should include 'elecciones2008' @user.builder_enabled = old_builder_enabled @user.save end it 'should not import as private if private_tables_enabled is disabled' do @user.private_tables_enabled = false @user.save filepath = "#{Rails.root}/spec/support/data/elecciones2008.csv" data_import = DataImport.create( :user_id => @user.id, :data_source => filepath, :updated_at => Time.now, :append => false, :privacy => (::UserTable::PRIVACY_VALUES_TO_TEXTS.invert)['private'] ) data_import.values[:data_source] = filepath # It raises # expect { data_import.run_import! }.to raise_error(RuntimeError) data_import.success.should_not eq true end it 'should be able to handle wrong type guessing' do filepath = "#{Rails.root}/spec/support/data/wrong_the_geom_guessing.csv" data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now, append: false ) data_import.values[:data_source] = filepath data_import.run_import! data_import.success.should eq true end it 'should be able to import a multi file zip as a multilayer map' do @user.max_layers = 5 @user.save filepath = "#{Rails.root}/spec/support/data/multilayer_shp.zip" data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) data_import.values[:data_source] = filepath data_import.run_import! data_import.success.should eq true vis = Carto::Visualization.find_by_id(data_import.visualization_id) vis.map.data_layers.count.should eq 5 end it 'should be able to handle a zip with more files max_layers' do @user.max_layers = 2 @user.save filepath = "#{Rails.root}/spec/support/data/multilayer_shp.zip" data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) data_import.values[:data_source] = filepath data_import.run_import! data_import.success.should eq true vis = Carto::Visualization.find_by_id(data_import.visualization_id) vis.map.data_layers.count.should eq @user.max_layers data_import.rejected_layers.split(',').count.should eq 3 end describe 'visualization importing' do it 'imports a visualization export' do filepath = "#{Rails.root}/services/importer/spec/fixtures/visualization_export_with_csv_table.carto" @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) @data_import.values[:data_source] = filepath @data_import.run_import! @data_import.success.should eq true # Fixture file checks @data_import.table_name.should eq 'twitter_t3chfest_reduced' @visualization = Carto::Visualization.find(@data_import.visualization_id) @visualization.name.should eq "exported map" @visualization.description.should eq "A map that has been exported" @visualization.tags.should include('exported') map = @visualization.map map.center.should eq "[39.75365697136308, -2.318115234375]" end it 'imports a visualization export when the table already exists' do @existing_table = create_table(name: 'twitter_t3chfest_reduced', user_id: @user.id) filepath = "#{Rails.root}/services/importer/spec/fixtures/visualization_export_with_csv_table.carto" @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) @data_import.values[:data_source] = filepath @data_import.run_import! @data_import.success.should eq true # Fixture file checks renamed_table = 'twitter_t3chfest_reduced_1' @data_import.table_name.should eq renamed_table @visualization = Carto::Visualization.find(@data_import.visualization_id) @visualization.data_layers.first.options['table_name'].should eq renamed_table end it 'imports a visualization export and table is not duplicated if collision strategy is skip' do table_name = 'twitter_t3chfest_reduced' @existing_table = create_table(name: table_name, user_id: @user.id) filepath = "#{Rails.root}/services/importer/spec/fixtures/visualization_export_with_csv_table.carto" @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true, collision_strategy: skip ) @data_import.values[:data_source] = filepath @data_import.run_import! @data_import.success.should eq true @data_import.tables_created_count.should eq 0 @data_import.table_names.should be_empty @data_import.table_name.should be_nil @visualization = Carto::Visualization.find(@data_import.visualization_id) @visualization.data_layers.first.options['table_name'].should eq table_name @visualization.data_layers.count.should eq 1 layer = @visualization.data_layers.first layer.user_tables.count.should eq 1 user_table = layer.user_tables.first user_table.name.should eq table_name end it 'imports a visualization export but not existing tables if collision strategy is skip' do existing_table_name = 'guess_country' not_existing_table_name = 'guess_ip' @existing_table = create_table(name: existing_table_name, user_id: @user.id) filepath = "#{Rails.root}/services/importer/spec/fixtures/guess_country_ip.carto" @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true, collision_strategy: skip ) @data_import.values[:data_source] = filepath @data_import.run_import! @data_import.success.should eq true @data_import.tables_created_count.should eq 1 @data_import.table_names.should eq not_existing_table_name @data_import.table_name.should eq not_existing_table_name expected_table_names = [existing_table_name, not_existing_table_name] @visualization = Carto::Visualization.find(@data_import.visualization_id) layer_table_names = @visualization.data_layers.map { |l| l.options['table_name'] }.sort layer_table_names.should eq expected_table_names @visualization.data_layers.count.should eq 2 expected_table_names.each do |table_name| layer = @visualization.data_layers.find { |l| l.user_tables.first.name == table_name } layer.should_not be_nil layer.user_tables.count.should eq 1 end Carto::DataImport.find_by(id: @data_import.id).user_tables.map(&:destroy) end it 'imports a visualization export with two data layers' do filepath = "#{Rails.root}/services/importer/spec/fixtures/visualization_export_with_two_tables.carto" @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) @data_import.values[:data_source] = filepath @data_import.run_import! @data_import.success.should eq true # Fixture file checks @data_import.table_names.should eq "guess_country twitter_t3chfest_reduced" @visualization = Carto::Visualization.find(@data_import.visualization_id) @visualization.name.should eq "map with two layers" @visualization.layers.count.should eq 3 # basemap + 2 data layers layers = @visualization.layers layers[0].options['name'].should eq "CartoDB World Eco" layer1 = @visualization.layers[1] layer1.options['type'].should eq "CartoDB" layer1.options['table_name'].should eq "guess_country" layer1.user_tables.count.should eq 1 layer2 = @visualization.layers[2] layer2.options['type'].should eq "CartoDB" layer2.options['table_name'].should eq "twitter_t3chfest_reduced" layer2.user_tables.count.should eq 1 Carto::DataImport.find_by(id: @data_import.id).user_tables.map(&:destroy) end it 'imports a visualization export without data' do filepath = "#{Rails.root}/services/importer/spec/fixtures/visualization_export_without_tables.carto" @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) @data_import.values[:data_source] = filepath @data_import.run_import! @data_import.success.should eq true # Fixture file checks @visualization = Carto::Visualization.find(@data_import.visualization_id) @visualization.name.should eq "map with two layers" layers = @visualization.layers @visualization.layers.count.should eq 3 # basemap + 2 data layers layers[0].options['name'].should eq "CartoDB World Eco" layer1 = @visualization.layers[1] layer1.options['type'].should eq "CartoDB" layer1.options['table_name'].should eq "guess_country" layer2 = @visualization.layers[2] layer2.options['type'].should eq "CartoDB" layer2.options['table_name'].should eq "twitter_t3chfest_reduced" Carto::DataImport.find_by(id: @data_import.id).user_tables.map(&:destroy) end it 'registers table dependencies for .carto import' do filepath = "#{Rails.root}/services/importer/spec/fixtures/visualization_export_with_csv_table.carto" @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) @data_import.values[:data_source] = filepath @data_import.run_import! @data_import.success.should eq true # Fixture file checks @data_import.table_name.should eq 'twitter_t3chfest_reduced' @visualization = Carto::Visualization.find(@data_import.visualization_id) layer = @visualization.data_layers.first layer.user_tables.count.should eq 1 user_table = layer.user_tables.first user_table.name.should eq 'twitter_t3chfest_reduced' canonical_layer = user_table.visualization.data_layers.first canonical_layer.user_tables.count.should eq 1 end it 'fails to import a visualization export if public map quota is exceeded' do filepath = "#{Rails.root}/services/importer/spec/fixtures/public_visualization_export_with_csv_table.carto" @user.public_map_quota = 0 @user.save @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) @data_import.values[:data_source] = filepath expect { @data_import.run_import! }.to raise_error('Map quota exceeded') @data_import.success.should eq false @data_import.error_code.should eq 8007 @user.public_map_quota = nil @user.save end it 'fails to import a visualization export if public dataset quota is exceeded' do filepath = "#{Rails.root}/services/importer/spec/fixtures/public_visualization_export_with_csv_table.carto" @user.public_dataset_quota = 0 @user.save @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) @data_import.values[:data_source] = filepath expect { @data_import.run_import! }.to raise_error('Public dataset quota exceeded') @data_import.success.should eq false @data_import.error_code.should eq 8008 @user.public_dataset_quota = nil @user.save end it 'fails to import a visualization export if private map quota is exceeded' do filepath = "#{Rails.root}/services/importer/spec/fixtures/visualization_export_with_csv_table.carto" @user.private_map_quota = 0 @user.save @data_import = DataImport.create( user_id: @user.id, data_source: filepath, updated_at: Time.now.utc, append: false, create_visualization: true ) @data_import.values[:data_source] = filepath expect { @data_import.run_import! }.to raise_error('Map quota exceeded') @data_import.success.should eq false @data_import.error_code.should eq 8007 Carto::User.any_instance.unstub(:public_map_quota) @user.private_map_quota = nil @user.save end end end