Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
require_relative './spec_helper'
require 'capybara/rails'
require "capybara/dsl"
require "selenium-webdriver"
require "capybara/poltergeist"
# Put your acceptance spec helpers inside /spec/support
require_relative './support/paths'
require_relative './support/acceptance_helpers'
require_relative './support/selenium_find_patch'
Capybara.default_driver = :selenium
Capybara.default_host = Cartodb.hostname
Capybara.app_host = Cartodb.hostname
Capybara.server_port = 53716
Capybara.default_wait_time = 5
RSpec.configure do |config|
config.include Warden::Test::Helpers
config.include Capybara::DSL, type: :acceptance
config.before(:each) do
# Clearing cache makes assets pipeline to compile from scratch all assets, making specs to timeout
Capybara.reset_sessions!
end
config.after(:each) do
Capybara.use_default_driver
end
end
+512
View File
@@ -0,0 +1,512 @@
require_relative '../spec_helper'
require_relative '../../app/connectors/importer'
require_relative '../doubles/result'
require_relative '../helpers/feature_flag_helper'
require 'csv'
require 'helpers/database_connection_helper'
describe CartoDB::Importer2::Overviews do
include_context 'organization with users helper'
include DatabaseConnectionHelper
before(:all) do
@user = create_user(quota_in_bytes: 1000.megabyte, table_quota: 400)
@feature_flag = FactoryGirl.create(:feature_flag, name: 'create_overviews', restricted: true)
end
before(:each) do
bypass_named_maps
end
after(:all) do
@user.destroy
@feature_flag.destroy
end
include FeatureFlagHelper
def overview_tables(user, table)
overviews = user.in_database do |db|
db.fetch %{
SELECT * FROM CDB_Overviews('#{table}'::regclass)
}
end
overviews.map(:overview_table)
end
def has_overviews?(user, table)
!overview_tables(user, table).empty?
end
def remove_overviews(user, table)
user.in_database do |db|
db.run %{
SELECT CDB_DropOverviews('#{table}'::regclass)
}
end
end
def public_table?(user, table)
has_privilege = user.in_database do |db|
db.fetch %{
SELECT has_table_privilege('publicuser', '#{table}'::regclass, 'SELECT');
}
end
has_privilege.first[:has_table_privilege]
end
def set_table_privacy(table, privacy)
CartoDB::TablePrivacyManager.new(table)
.send(:set_from_table_privacy, privacy)
.update_cdb_tablemetadata
end
it 'should not create overviews if the feature flag is not enabled' do
set_feature_flag @user, 'create_overviews', false
Cartodb.with_config overviews: { 'min_rows' => 500 } do
@user.has_feature_flag?('create_overviews').should eq false
Cartodb.get_config(:overviews, 'min_rows').should eq 500
# cities_box is a ~900 points dataset
filepath = "#{Rails.root}/spec/support/data/cities-box.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
table_name = UserTable[id: data_import.table.id].name
has_overviews?(@user, table_name).should eq false
remove_overviews @user, table_name
has_overviews?(@user, table_name).should eq false
end
end
it 'should not create overviews for small datasets' do
set_feature_flag @user, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 1000 } do
@user.has_feature_flag?('create_overviews').should eq true
Cartodb.get_config(:overviews, 'min_rows').should eq 1000
# cities_box is a ~900 points dataset
filepath = "#{Rails.root}/spec/support/data/cities-box.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
table_name = UserTable[id: data_import.table.id].name
has_overviews?(@user, table_name).should eq false
remove_overviews @user, table_name
has_overviews?(@user, table_name).should eq false
end
end
it 'should not create overviews for datasets with non-supported geometries' do
set_feature_flag @user, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 100 } do
@user.has_feature_flag?('create_overviews').should eq true
Cartodb.get_config(:overviews, 'min_rows').should eq 100
# countries_simplified is a ~200 polygons dataset
filepath = "#{Rails.root}/spec/support/data/countries_simplified.zip"
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
table_name = UserTable[id: data_import.table.id].name
has_overviews?(@user, table_name).should eq false
remove_overviews @user, table_name
has_overviews?(@user, table_name).should eq false
end
end
it 'should create overviews for large datasets of the correct geometry kind' do
set_feature_flag @user, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 500 } do
@user.has_feature_flag?('create_overviews').should eq true
Cartodb.get_config(:overviews, 'min_rows').should eq 500
# cities_box is a ~900 points dataset
filepath = "#{Rails.root}/spec/support/data/cities-box.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
table_name = UserTable[id: data_import.table.id].name
has_overviews?(@user, table_name).should eq true
remove_overviews @user, table_name
has_overviews?(@user, table_name).should eq false
end
end
it 'should remove overviews when the table is deleted' do
set_feature_flag @user, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 500 } do
@user.has_feature_flag?('create_overviews').should eq true
Cartodb.get_config(:overviews, 'min_rows').should eq 500
# cities_box is a ~900 points dataset
filepath = "#{Rails.root}/spec/support/data/cities-box.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
table = UserTable[id: data_import.table.id]
ov_tables = overview_tables(@user, table.name)
ov_tables.size.should > 0
table.destroy
ov_tables.each do |ov_table|
expect do
@user.in_database do |db|
db.run "SELECT '#{ov_table}'::regclass"
end
end.to raise_error(Sequel::DatabaseError, /relation .+ does not exist/)
end
end
end
it 'should rename overviews when the table is renamed' do
set_feature_flag @user, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 500 } do
@user.has_feature_flag?('create_overviews').should eq true
Cartodb.get_config(:overviews, 'min_rows').should eq 500
# cities_box is a ~900 points dataset
filepath = "#{Rails.root}/spec/support/data/cities-box.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
table = UserTable[id: data_import.table.id]
ov_tables = overview_tables(@user, table.name)
ov_tables.size.should > 0
table.service.name = 'cities2_box'
(!!table.save).should eq true
table.save.name.should eq 'cities2_box'
ov_tables.each do |ov_table|
expect do
@user.in_database do |db|
db.run "SELECT '#{ov_table}'::regclass"
end
end.to raise_error(Sequel::DatabaseError, /relation .+ does not exist/)
end
ov_tables = overview_tables(@user, table.name)
ov_tables.size.should > 0
end
end
it 'synchronize overviews privacy with that of the base table' do
user = create_user(quota_in_bytes: 1000.megabyte, table_quota: 400, private_tables_enabled: true)
set_feature_flag user, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 500 } do
user.has_feature_flag?('create_overviews').should eq true
Cartodb.get_config(:overviews, 'min_rows').should eq 500
public_privacy = ::UserTable::PRIVACY_PUBLIC
private_privacy = ::UserTable::PRIVACY_PRIVATE
link_privacy = ::UserTable::PRIVACY_LINK
filepath = "#{Rails.root}/spec/support/data/cities-box.csv"
data_import = DataImport.create(
user_id: user.id,
data_source: filepath,
updated_at: Time.now,
append: false,
privacy: private_privacy
)
data_import.values[:data_source] = filepath
data_import.run_import!
data_import.success.should eq true
table = UserTable[id: data_import.table.id]
has_overviews?(user, table.name).should eq true
ov_tables = overview_tables(user, table.name)
# Check overviews are private
ov_tables.none? { |ov_table| public_table?(user, ov_table) }.should eq true
set_table_privacy table, public_privacy
# Check overviews are public
ov_tables.all? { |ov_table| public_table?(user, ov_table) }.should eq true
set_table_privacy table, private_privacy
# Check overviews are private
ov_tables.none? { |ov_table| public_table?(user, ov_table) }.should eq true
set_table_privacy table, link_privacy
# Check overviews are public
ov_tables.all? { |ov_table| public_table?(user, ov_table) }.should eq true
end
end
it 'should use the overviews-specific statement timeout' do
set_feature_flag @user, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 500, 'statement_timeout' => 1 } do
@user.has_feature_flag?('create_overviews').should eq true
Cartodb.get_config(:overviews, 'min_rows').should eq 500
# cities_box is a ~900 points dataset
filepath = "#{Rails.root}/spec/support/data/cities-box.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
# avoid noisy error messages
data_import.stubs(:puts)
CartoDB.stubs(:notify_error)
# The overviews timeout should abort overviews creation but otherwise
# import the dataset correctly.
data_import.run_import!
data_import.success.should eq true
table_name = UserTable[id: data_import.table.id].name
has_overviews?(@user, table_name).should eq false
end
end
it 'shares overviews when the base table is shared' do
# create two users from the same organization (so they share the database)
organization = create_organization_with_owner
user1 = organization.owner
# user1.quota_in_bytes = 1000.megabyte
# user1.table_quota = 400
user1.private_tables_enabled = true
user1.save
user2 = create_user(organization: organization, account_type: 'ORGANIZATION USER')
user2.save
# Import table with overviews for user1
table = ov_tables = nil
set_feature_flag user1, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 500 } do
filepath = "#{Rails.root}/spec/support/data/cities-box.csv"
data_import = DataImport.create(
user_id: user1.id,
data_source: filepath,
updated_at: Time.now,
append: false,
privacy: ::UserTable::PRIVACY_PRIVATE
)
data_import.values[:data_source] = filepath
data_import.run_import!
data_import.success.should eq true
table = UserTable[id: data_import.table.id]
has_overviews?(user1, table.name).should eq true
ov_tables = overview_tables(user1, table.name)
end
# user2 cannot access overview tables
with_connection_from_user(user2) do |connection|
begin
connection.execute("select count(1) from #{user1.database_schema}.#{ov_tables.first}")
rescue Sequel::DatabaseError => e
failed = true
e.message.should match /permission denied .* #{ov_tables.first}/
end
failed.should be_true
end
# Share table with user2
p = table.table_visualization.permission
p.acl = [{ type: 'user', entity: {id: user2.id, username: user2.username}, access: 'r' }]
p.save
# Now user2 has access to overview tables
with_connection_from_user(user2) do |connection|
connection.execute("select count(1) from #{user1.database_schema}.#{ov_tables.first}") do |result|
result[0]['count'].to_i.should > 0
end
end
user2.destroy
user1.destroy
end
it 'overviews are granted api key privileges as for base table' do
# Import two tables with overviews for @user
table1 = ov_tables1 = nil
table2 = ov_tables2 = nil
user = create_user(quota_in_bytes: 1000.megabyte, table_quota: 400, private_tables_enabled: true)
set_feature_flag user, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 500 } do
filepath = "#{Rails.root}/spec/support/data/cities-box.csv"
data_import = DataImport.create(
user_id: user.id,
data_source: filepath,
updated_at: Time.now,
append: false,
privacy: ::UserTable::PRIVACY_PRIVATE
)
data_import.values[:data_source] = filepath
data_import.run_import!
data_import.success.should eq true
table1 = UserTable[id: data_import.table.id]
has_overviews?(user, table1.name).should eq true
ov_tables1 = overview_tables(user, table1.name)
data_import = DataImport.create(
user_id: user.id,
data_source: filepath,
updated_at: Time.now,
append: false,
privacy: ::UserTable::PRIVACY_PRIVATE
)
data_import.values[:data_source] = filepath
data_import.run_import!
data_import.success.should eq true
table2 = UserTable[id: data_import.table.id]
has_overviews?(user, table2.name).should eq true
ov_tables2 = overview_tables(user, table2.name)
end
# grant access to other user to one of the tables
grants = [
{
type: 'database',
tables: [{
schema: user.database_schema,
name: table1.name,
permissions: ['select']
}]
},
{
type: 'apis',
apis: ['maps', 'sql']
}
]
api_key = user.api_keys.create_regular_key!(name: 'full', grants: grants)
ov_table1 = ov_tables1.first
ov_table2 = ov_tables2.first
with_connection_from_api_key(api_key) do |connection|
begin
connection.execute("select count(1) from #{ov_table2}")
rescue Sequel::DatabaseError => e
failed = true
e.message.should match /permission denied .* #{ov_table2}/
end
failed.should be_true
connection.execute("select count(1) from #{ov_table1}") do |result|
result[0]['count'].to_i.should > 0
end
end
api_key.destroy
user.destroy
end
it 'given user in organization overviews are granted api key privileges as for base table ' do
# Import two tables with overviews for @org_user_1
table1 = ov_tables1 = nil
table2 = ov_tables2 = nil
user = @org_user_1
set_feature_flag user, 'create_overviews', true
Cartodb.with_config overviews: { 'min_rows' => 500 } do
filepath = "#{Rails.root}/spec/support/data/cities-box.csv"
data_import = DataImport.create(
user_id: user.id,
data_source: filepath,
updated_at: Time.now,
append: false,
privacy: ::UserTable::PRIVACY_PRIVATE
)
data_import.values[:data_source] = filepath
data_import.run_import!
data_import.success.should eq true
table1 = UserTable[id: data_import.table.id]
has_overviews?(user, table1.name).should eq true
ov_tables1 = overview_tables(user, table1.name)
data_import = DataImport.create(
user_id: user.id,
data_source: filepath,
updated_at: Time.now,
append: false,
privacy: ::UserTable::PRIVACY_PRIVATE
)
data_import.values[:data_source] = filepath
data_import.run_import!
data_import.success.should eq true
table2 = UserTable[id: data_import.table.id]
has_overviews?(user, table2.name).should eq true
ov_tables2 = overview_tables(user, table2.name)
end
# grant access to other user to one of the tables
grants = [
{
type: 'database',
tables: [{
schema: user.database_schema,
name: table1.name,
permissions: ['select']
}]
},
{
type: 'apis',
apis: ['maps', 'sql']
}
]
api_key = user.api_keys.create_regular_key!(name: 'full', grants: grants)
ov_table1 = ov_tables1.first
ov_table2 = ov_tables2.first
with_connection_from_api_key(api_key) do |connection|
begin
connection.execute("select count(1) from #{ov_table2}")
rescue Sequel::DatabaseError => e
failed = true
e.message.should match /permission denied .* #{ov_table2}/
end
failed.should be_true
connection.execute("select count(1) from #{ov_table1}") do |result|
result[0]['count'].to_i.should > 0
end
end
api_key.destroy
user.destroy
end
end
+715
View File
@@ -0,0 +1,715 @@
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
@data_import.table.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 = UUIDTools::UUID.timestamp_create.to_s
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_#{UUIDTools::UUID.timestamp_create.to_s}"
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"
data_import2.table.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)
data_import2.table.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
data_import2.table.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 #<RuntimeError: Error: User doesn't have private tables enabled>
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
@data_import.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
@data_import.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"
@data_import.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/visualization_export_with_csv_table.carto"
Carto::User.any_instance.stubs(:public_map_quota).returns(0)
@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 map quota exceeded')
@data_import.success.should eq false
@data_import.error_code.should eq 8007
Carto::User.any_instance.unstub(:public_map_quota)
end
end
end
+14
View File
@@ -0,0 +1,14 @@
module Doubles
class Request
def initialize(attributes={})
@host = attributes.fetch(:host, '')
@fullpath = attributes.fetch(:fullpath, '')
@params = attributes.fetch(:params, {})
end
attr_reader :host, :params, :fullpath
end
end
+19
View File
@@ -0,0 +1,19 @@
module CartoDB
module Doubles
module Importer2
class Result
def initialize(attributes={})
self.table_name = attributes[:table_name]
self.name = attributes[:name]
self.support_tables = []
end
attr_accessor :support_tables, :table_name, :name
def update_support_tables(new_list)
end
end
end
end
end
+11
View File
@@ -0,0 +1,11 @@
module CartoDB
module Doubles
module Visualization
class SupportTables
def delete_all
end
end
end
end
end
+22
View File
@@ -0,0 +1,22 @@
# To avoid duplicated records consider using account_types_helper.rb
FactoryGirl.define do
factory :account_type, class: Carto::AccountType do
account_type 'FREE'
association :rate_limit, factory: :rate_limits
end
factory :account_type_free, class: Carto::AccountType do
account_type 'FREE'
association :rate_limit, factory: :rate_limits
end
factory :account_type_pro, class: Carto::AccountType do
account_type 'PRO'
association :rate_limit, factory: :rate_limits_pro
end
factory :account_type_org, class: Carto::AccountType do
account_type 'ORGANIZATION USER'
association :rate_limit, factory: :rate_limits
end
end
+70
View File
@@ -0,0 +1,70 @@
require_relative '../../app/models/carto/analysis'
require_dependency 'carto/uuidhelper'
require 'helpers/unique_names_helper'
include UniqueNamesHelper
module AnalysisFactoryHelper
def self.source_analysis_for_table(table_name, query)
query ||= "select * from #{table_name}"
{
id: unique_string,
type: 'source',
params: { query: query },
options: { table_name: table_name }
}
end
end
FactoryGirl.define do
factory :source_analysis, class: Carto::Analysis do
ignore do
source_table 'subway_stops'
query nil
end
analysis_definition { AnalysisFactoryHelper.source_analysis_for_table(source_table, query) }
factory :analysis, class: Carto::Analysis do
created_at { Time.now }
updated_at { Time.now }
end
end
factory :analysis_with_source, class: Carto::Analysis do
ignore do
source_table 'subway_stops'
query nil
end
analysis_definition do
{
id: unique_string,
type: "buffer",
params: {
source: AnalysisFactoryHelper.source_analysis_for_table(source_table, query)
}
}
end
end
factory :analysis_point_in_polygon, class: Carto::Analysis do
ignore do
source_table 'subway_stops'
source_query nil
target_table 'districts'
target_query nil
end
analysis_definition do
{
id: unique_string,
type: "intersection",
params: {
source: AnalysisFactoryHelper.source_analysis_for_table(source_table, source_query),
target: AnalysisFactoryHelper.source_analysis_for_table(target_table, target_query)
}
}
end
end
end
+43
View File
@@ -0,0 +1,43 @@
FactoryGirl.define do
factory :api_key_apis, class: Carto::ApiKey do
initialize_with { Carto::ApiKey.send :new }
type Carto::ApiKey::TYPE_REGULAR
name { unique_name('regular api key for apis') }
grants [{ type: "apis",
apis: ["sql", "maps"] }]
end
factory :master_api_key, class: Carto::ApiKey do
initialize_with { Carto::ApiKey.send :new }
type Carto::ApiKey::TYPE_MASTER
name Carto::ApiKey::NAME_MASTER
grants [{ type: "apis", apis: ["sql", "maps"] },
{ type: 'dataservices', services: ['geocoding', 'routing', 'isolines', 'observatory']}]
end
factory :oauth_api_key, class: Carto::ApiKey do
initialize_with { Carto::ApiKey.send :new }
type Carto::ApiKey::TYPE_OAUTH
name { unique_name('internal api key') }
grants [{ type: "apis", apis: [] }]
end
factory :oauth_api_key_user_profile_grant, class: Carto::ApiKey do
initialize_with { Carto::ApiKey.send :new }
type Carto::ApiKey::TYPE_OAUTH
name { unique_name('internal api key') }
grants [{ type: "apis", apis: [] }, { type: 'user', data: ['profile'] }]
end
factory :oauth_api_key_datasets_metadata_grant, class: Carto::ApiKey do
initialize_with { Carto::ApiKey.send :new }
type Carto::ApiKey::TYPE_OAUTH
name { unique_name('internal api key') }
grants [{ type: "apis", apis: [] }, { type: 'database', table_metadata: [] }]
end
end
+23
View File
@@ -0,0 +1,23 @@
FactoryGirl.define do
factory :asset do
to_create(&:save)
public_url 'https://manolo.es/es/co/bar.png'
end
factory :carto_asset, class: Carto::Asset do
public_url 'https://manolo.es/es/co/bar.png'
end
factory :organization_asset, class: Carto::Asset do
storage_info do
{
type: 'local',
location: 'manolo_folder',
identifier: 'could_be_a_manolo_hash_23as4g5sh6sd7hd8j9jfgk'
}
end
public_url 'https://manolo.es/es/co/bar.png'
end
end
+5
View File
@@ -0,0 +1,5 @@
FactoryGirl.define do
factory :automatic_geocoding do
to_create(&:save)
end
end
+77
View File
@@ -0,0 +1,77 @@
require_relative '../support/factories/tables'
module Carto
module Factories
module Visualizations
include CartoDB::Factories
def full_visualization_table(carto_user, map)
carto_user = Carto::User.find(carto_user.id) unless carto_user.is_a? Carto::User
map_id = map.nil? ? nil : map.id
Carto::UserTable.find(create_table(name: unique_name('fvt_table'), user_id: carto_user.id, map_id: map_id).id)
end
def create_full_builder_vis(carto_user, visualization_attributes: {})
create_full_visualization(carto_user, visualization_attributes: visualization_attributes.merge(version: 3))
end
# "Full visualization": with map, table... Including actual user table.
# Table is bound to visualization, and to data_layer if it's not passed.
def create_full_visualization(
carto_user,
canonical_map: FactoryGirl.create(:carto_map_with_layers, user_id: carto_user.id),
map: FactoryGirl.create(:carto_map_with_layers, user_id: carto_user.id),
table: full_visualization_table(carto_user, canonical_map),
data_layer: nil,
visualization_attributes: {}
)
carto_user = Carto::User.find(carto_user.id) unless carto_user.is_a? Carto::User
table_visualization = table.visualization || create_table_visualization(carto_user, table)
visualization = FactoryGirl.create(:carto_visualization, { user: carto_user, map: map }
.merge(visualization_attributes))
unless data_layer.present?
data_layer = visualization.map.data_layers.first
data_layer.options[:table_name] = table.name
data_layer.options[:query] = "select * from #{table.name}"
data_layer.options[:sql_wrap] = "select * from (<%= sql %>) __wrap"
data_layer.save
end
visualization.update_column(:active_layer_id, visualization.layers.first.id)
FactoryGirl.create(:carto_zoom_overlay, visualization: visualization)
FactoryGirl.create(:carto_search_overlay, visualization: visualization)
# Need to mock the nonexistant table because factories use Carto::* models
CartoDB::Visualization::Member.any_instance.stubs(:propagate_name_to).returns(true)
CartoDB::Visualization::Member.any_instance.stubs(:propagate_privacy_to).returns(true)
visualization.reload
return map, table, table_visualization, visualization
end
def create_table_visualization(carto_user, table)
FactoryGirl.create(
:carto_visualization, user: carto_user, type: 'table', name: table.name, map_id: table.map_id)
end
# Helper method for `create_full_visualization` results cleanup
def destroy_full_visualization(map, table, table_visualization, visualization)
table_visualization.destroy if table_visualization && Carto::Visualization.exists?(table_visualization.id)
table.service.destroy if table && table.service && Carto::UserTable.exists?(table.id)
table.destroy if table && Carto::UserTable.exists?(table.id)
visualization.destroy if visualization && Carto::Visualization.exists?(visualization.id)
map.destroy if map && Carto::Map.exists?(map.id)
end
def destroy_visualization(visualization_id)
member = CartoDB::Visualization::Member.new(id: visualization_id).fetch
member.delete
end
end
end
end
+12
View File
@@ -0,0 +1,12 @@
FactoryGirl.define do
factory :connector_provider, class: Carto::ConnectorProvider do
name 'test_provider'
end
end
FactoryGirl.define do
factory :connector_configuration, class: Carto::ConnectorConfiguration do
enabled true
max_rows 100
end
end
+5
View File
@@ -0,0 +1,5 @@
FactoryGirl.define do
factory :data_import, class: Carto::DataImport do
state 'complete'
end
end
@@ -0,0 +1,9 @@
shared_context 'database configuration' do
before(:each) do
@db = SequelRails.connection
@repository = DataRepository::Backend::Sequel.new(@db, :visualizations)
CartoDB::Visualization.repository = @repository
end
end
+10
View File
@@ -0,0 +1,10 @@
require_relative '../../app/models/carto/external_data_import'
FactoryGirl.define do
# Requires data_import_id (because DataImport links a user)
factory :external_data_import_with_external_source, class: Carto::ExternalDataImport do
external_source
end
end
+12
View File
@@ -0,0 +1,12 @@
require_relative '../../app/models/carto/external_source'
FactoryGirl.define do
factory :external_source, class: Carto::ExternalSource do
association :visualization, factory: :carto_visualization, type: 'remote'
import_url 'http://www.wadus.com'
rows_counted 1
size 1
end
end
+20
View File
@@ -0,0 +1,20 @@
require 'helpers/unique_names_helper'
include UniqueNamesHelper
FactoryGirl.define do
factory :feature_flag, class: ::FeatureFlag do
to_create(&:save)
id { unique_integer }
name { unique_name('ff') }
restricted true
end
factory :carto_feature_flag, class: Carto::FeatureFlag do
id { unique_integer }
name { unique_name('ff') }
restricted true
end
end
+9
View File
@@ -0,0 +1,9 @@
require 'helpers/unique_names_helper'
include UniqueNamesHelper
FactoryGirl.define do
factory :feature_flags_user do
id { unique_integer }
end
end
+13
View File
@@ -0,0 +1,13 @@
FactoryGirl.define do
# TODO: refactor tests that depend on default geocoding factory
factory :geocoding, class: Geocoding do
to_create(&:save)
kind 'namedplace'
formatter 'foo'
factory :high_resolution_geocoding do
kind 'high-resolution'
end
end
end
+24
View File
@@ -0,0 +1,24 @@
require 'helpers/unique_names_helper'
include UniqueNamesHelper
FactoryGirl.define do
factory :carto_group, class: Carto::Group do
initialize_with { Carto::Group.send :new }
name 'A_Group'
display_name 'A Group'
database_role 'database_role'
end
factory :new_random_group, class: Carto::Group do |_g|
initialize_with { Carto::Group.send :new }
name { unique_name('group') }
display_name { "Group #{name}" }
factory :random_group do
database_role { "database_role_#{name}" }
end
end
end
+8
View File
@@ -0,0 +1,8 @@
FactoryGirl.define do
# This can't work because activerecord-postgresql-array bug (see Carto::Invitation).
# Use Carto::Invitation.create_new instead
# factory :invitation, class: Carto::Invitation do
# users_emails ['first_email@whatever.com', 'second_email@whatever.com' ]
# welcome_text 'Welcome to the jungle'
# end
end
+136
View File
@@ -0,0 +1,136 @@
FactoryGirl.define do
factory :layer, class: Layer do
to_create(&:save)
order 1
kind 'carto'
end
factory :carto_tiled_layer, class: Carto::Layer do
order 1
kind 'tiled'
options do
{
"default": true,
"url": "http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
"subdomains": "abcd",
"minZoom": "0",
"maxZoom": "18",
"attribution": "\u00a9 <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors \u00a9 <a href=\"https://carto.com/attributions\">CARTO</a>",
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
"type": "Tiled",
"className": "positron_rainbow",
"name": "Positron (labels below)"
}
end
end
factory :carto_layer, class: Carto::Layer do
order 2
kind 'carto'
options do
{
interactivity: '',
style_version: '2.1.1',
table_name: nil,
query: nil,
tile_style: '#something {}'
}
end
factory :carto_layer_with_infowindow do
infowindow_light = {
"fields": [{ "name": "country", "title": true, "position": 1 }],
"template_name": "table/views/infowindow_light",
"template": "",
"alternative_names": {},
"width": 226,
"maxHeight": 180
}
infowindow infowindow_light
end
factory :carto_layer_with_tooltip do
tooltip_light = {
"fields": [{ "name": "amount", "title": true, "position": 0 }],
"template_name": "tooltip_light",
"template": "",
"alternative_names": {},
"maxHeight": 180
}
tooltip tooltip_light
end
factory :carto_layer_with_sql do
ignore do
table_name 'default_table'
end
options do
{
table_name: table_name,
query: "select * from #{table_name}",
sql_wrap: "select * from (<%= sql %>) __wrap"
}
end
end
end
end
module Fixtures
module Layers
module Tooltips
def custom_tooltip(template = 'wadus tooltip')
{
"fields": [{ "name": "amount", "title": true, "position": 0 }],
"template_name": "",
"template": template,
"alternative_names": {},
"maxHeight": 180
}
end
# These depend on tooltip_light.jst.mustache at the following paths:
# - /lib/assets/javascripts/cartodb/table/views/tooltip/templates/tooltip_light.jst.mustache
# - /lib/assets/javascripts/builder/mustache-templates/tooltips/tooltip_light.jst.mustache
def v2_tooltip_light_template_fragment
'<div class="cartodb-tooltip-content-wrapper">'
end
def v3_tooltip_light_template_fragment
'<div class="CDB-Tooltip CDB-Tooltip--isLight">'
end
end
# These depend on infowindow_light.jst.mustache at the following paths:
# - /lib/assets/javascripts/cartodb/table/views/infowindow/templates/infowindow_light.jst.mustache
# - /lib/assets/javascripts/builder/mustache-templates/infowindows/infowindow_light.jst.mustache
module Infowindows
def custom_infowindow(template = 'wadus infowindow')
{
"fields": [{ "name": "country", "title": true, "position": 1 }],
"template_name": "",
"template": template,
"alternative_names": {},
"width": 226,
"maxHeight": 180
}
end
def v2_infowindow_light_template_fragment
"<div class=\"cartodb-popup v2\">\n <a href=\"#close\" class=\"cartodb-popup-close-button close\">x</a>\n "\
"<div class=\"cartodb-popup-content-wrapper\">\n <div class=\"cartodb-popup-content\">\n "\
"{{#content.fields}}\n {{#title}}<h4>{{title}}</h4>{{/title}}\n {{#value}}\n "\
"<p {{#type}}class=\"{{ type }}\"{{/type}}>{{{ value }}}</p>\n {{/value}}\n "\
"{{^value}}\n <p class=\"empty\">null</p>\n {{/value}}\n {{/content.fields}}\n "\
"</div>\n </div>\n <div class=\"cartodb-popup-tip-container\"></div>\n</div>\n"
end
def v3_infowindow_light_template_fragment
'{{#title}}<h5 class="CDB-infowindow-subtitle">{{title}}</h5>{{/title}}'
end
end
end
end
+16
View File
@@ -0,0 +1,16 @@
FactoryGirl.define do
factory :ldap_configuration, :class => Carto::Ldap::Configuration do
host "0.0.0.0"
port 389
domain_bases_list [ 'dc=cartodb' ]
connection_user 'ldap_conn_user'
connection_password 'ldap_conn_pass'
email_field '.'
user_object_class '.'
group_object_class '.'
user_id_field 'user_id_field'
username_field 'user_id_field'
end
end
+4
View File
@@ -0,0 +1,4 @@
FactoryGirl.define do
factory :like, class: CartoDB::Like do
end
end
+7
View File
@@ -0,0 +1,7 @@
FactoryGirl.define do
factory :log, class: CartoDB::Log do
end
factory :carto_log, class: Carto::Log do
end
end
+36
View File
@@ -0,0 +1,36 @@
FactoryGirl.define do
factory :map, class: Map do
to_create(&:save)
end
factory :carto_map, class: Carto::Map do
view_bounds_sw '[-85.0511, -179]'
view_bounds_ne '[85.0511, 179]'
factory :carto_map_with_layers do
after(:create) do |map, evaluator|
create_list(:carto_tiled_layer, 1, maps: [map])
create_list(:carto_layer, 1, maps: [map])
map.layers.map.with_index.map do |layer, index|
layer.order = index
layer.save
end
end
end
factory :carto_map_with_2_tiled_layers do
after(:create) do |map, evaluator|
create_list(:carto_tiled_layer, 1, maps: [map])
create_list(:carto_layer, 1, maps: [map])
create_list(:carto_tiled_layer, 1, maps: [map])
map.layers.map.with_index.map do |layer, index|
layer.order = index
layer.save
end
end
end
end
end
+20
View File
@@ -0,0 +1,20 @@
# Read about factories at https://github.com/thoughtbot/factory_girl
require 'helpers/unique_names_helper'
include UniqueNamesHelper
FactoryGirl.define do
factory :mobile_app, class: Carto::MobileApp do
id UUIDTools::UUID.timestamp_create.to_s
name 'MyApp'
description 'My app description'
icon_url 'example.com/icon_url'
platform 'android'
app_type 'open'
app_id 'com.example.mycartoapp'
license_key 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
monthly_users 100
end
end
+9
View File
@@ -0,0 +1,9 @@
require_relative '../../app/models/carto/widget'
FactoryGirl.define do
factory :notification, class: Carto::Notification do
icon Carto::Notification::ICON_ALERT
body 'Empty body'
recipients Carto::Notification::RECIPIENT_ALL
end
end
+7
View File
@@ -0,0 +1,7 @@
FactoryGirl.define do
factory :oauth_access_tokens, class: Carto::OauthAccessToken do
to_create(&:save!)
skip_api_key_creation true
end
end
+7
View File
@@ -0,0 +1,7 @@
FactoryGirl.define do
factory :oauth_app_users, class: Carto::OauthAppUser do
to_create(&:save!)
skip_role_setup true
end
end
+11
View File
@@ -0,0 +1,11 @@
FactoryGirl.define do
factory :oauth_app, class: Carto::OauthApp do
to_create(&:save!)
name { unique_name('Oauth application') }
redirect_uris ['https://redirect.uri']
icon_url 'http://localhost/some_icon.png'
website_url 'http://localhost'
avoid_sync_central true
end
end
@@ -0,0 +1,5 @@
FactoryGirl.define do
factory :oauth_authorization_codes, class: Carto::OauthAuthorizationCode do
to_create(&:save!)
end
end
+8
View File
@@ -0,0 +1,8 @@
FactoryGirl.define do
factory :oauth_refresh_tokens, class: Carto::OauthRefreshToken do
to_create(&:save!)
skip_token_regeneration true
token "a-fake-and-test-token"
end
end
+95
View File
@@ -0,0 +1,95 @@
require 'helpers/account_types_helper'
require 'helpers/unique_names_helper'
include UniqueNamesHelper
FactoryGirl.define do
factory :organization do
to_create(&:save)
name { unique_name('organization') }
seats 10
quota_in_bytes 100.megabytes
geocoding_quota 1000
here_isolines_quota 1000
obs_snapshot_quota 1000
obs_general_quota 1000
map_view_quota 100000
website 'carto.com'
description 'Lorem ipsum dolor sit amet'
display_name 'Vizzuality Inc'
discus_shortname 'cartodb'
twitter_username 'cartodb'
location 'Madrid'
builder_enabled false # Most tests still assume editor
geocoder_provider 'heremaps'
isolines_provider 'heremaps'
routing_provider 'heremaps'
factory :organization_whitelist_carto do
whitelisted_email_domains ['carto.com']
auth_username_password_enabled true
end
factory :organization_with_users do
after(:create) do |org|
create_account_type_fg('ORGANIZATION USER')
owner = FactoryGirl.create(:user)
uo = CartoDB::UserOrganization.new(org.id, owner.id)
uo.promote_user_to_admin
org.reload
user = FactoryGirl.build(:user)
user.organization_id = org.id
user.enabled = true
user.save
org.reload
end
trait :mfa_enabled do
auth_username_password_enabled true
after :create do |org|
Carto::Organization.find(org.id).users.each do |user|
user.user_multifactor_auths << FactoryGirl.create(:totp, :active, user_id: user.id)
user.save!
end
end
end
end
factory :saml_organization do
auth_saml_configuration do
{
issuer: 'localhost.lan',
idp_sso_target_url: 'https://example.com/saml/signon/',
idp_slo_target_url: 'https://example.com/saml/signon/',
idp_cert_fingerprint: '',
assertion_consumer_service_url: 'https://localhost.lan/saml/finalize',
name_identifier_format: '',
email_attribute: 'username'
}.stringify_keys
end
end
factory :carto_organization, class: Carto::Organization do
to_create(&:save!)
name { unique_name('organization') }
seats 10
quota_in_bytes 100.megabytes
geocoding_quota 1000
here_isolines_quota 1000
obs_snapshot_quota 1000
obs_general_quota 1000
map_view_quota 100000
website 'carto.com'
description 'Lorem ipsum dolor sit amet'
display_name 'Vizzuality Inc'
discus_shortname 'cartodb'
twitter_username 'cartodb'
location 'Madrid'
builder_enabled false # Most tests still assume editor
end
end
end
+138
View File
@@ -0,0 +1,138 @@
require_relative '../support/factories/users'
require 'helpers/unique_names_helper'
include UniqueNamesHelper
class TestUserFactory
include CartoDB::Factories
end
module TableSharing
def share_table_with_user(table, user, access: CartoDB::Permission::ACCESS_READONLY)
vis = CartoDB::Visualization::Member.new(id: table.table_visualization.id).fetch
per = vis.permission
per.set_user_permission(user, access)
per.save
per.reload
end
def share_visualization_with_user(visualization, user, access: CartoDB::Permission::ACCESS_READONLY)
vis = CartoDB::Visualization::Member.new(id: visualization.id).fetch
per = vis.permission
per.set_user_permission(user, access)
per.save
per.reload
end
end
shared_context 'organization with users helper' do
include CacheHelper
include CartoDB::Factories
include_context 'database configuration'
before(:each) do
bypass_named_maps
end
def test_organization
organization = Organization.new
organization.name = unique_name('org')
organization.quota_in_bytes = 1234567890
organization.seats = 15
organization.viewer_seats = 15
organization.builder_enabled = false
organization.geocoder_provider = 'heremaps'
organization.isolines_provider = 'heremaps'
organization.routing_provider = 'heremaps'
organization
end
before(:all) do
@helper = TestUserFactory.new
@organization = test_organization
@organization.save
@organization_2 = test_organization
@organization_2.save
@org_user_owner = @helper.create_owner(@organization)
@org_user_1 = @helper.create_test_user(unique_name('user'), @organization)
@org_user_2 = @helper.create_test_user(unique_name('user'), @organization)
@organization.reload
@carto_organization = Carto::Organization.find(@organization.id)
@carto_organization_2 = Carto::Organization.find(@organization_2.id)
@carto_org_user_owner = Carto::User.find(@org_user_owner.id)
@carto_org_user_1 = Carto::User.find(@org_user_1.id)
@carto_org_user_2 = Carto::User.find(@org_user_2.id)
end
before(:each) do
bypass_named_maps
delete_user_data @org_user_owner
delete_user_data @org_user_1
delete_user_data @org_user_2
end
after(:all) do
bypass_named_maps
delete_user_data @org_user_owner if @org_user_owner
@organization.destroy_cascade
@organization_2.destroy_cascade
end
def share_table(table, owner, user)
bypass_named_maps
headers = {'CONTENT_TYPE' => 'application/json'}
perm_id = table.table_visualization.permission.id
put api_v1_permissions_update_url(user_domain: owner.username, api_key: owner.api_key, id: perm_id),
{ acl: [{
type: CartoDB::Permission::TYPE_USER,
entity: {
id: user.id,
},
access: CartoDB::Permission::ACCESS_READONLY
}]}.to_json, headers
response.status.should == 200
end
def share_table_with_organization(table, owner, organization)
bypass_named_maps
headers = {'CONTENT_TYPE' => 'application/json'}
perm_id = table.table_visualization.permission.id
params = { acl: [{ type: CartoDB::Permission::TYPE_ORGANIZATION,
entity: { id: organization.id },
access: CartoDB::Permission::ACCESS_READONLY
}]
}
url = api_v1_permissions_update_url(user_domain: owner.username, api_key: owner.api_key, id: perm_id)
put url, params.to_json, headers
last_response.status.should == 200
end
def share_visualization(visualization, user, access = CartoDB::Permission::ACCESS_READONLY)
shared_entity = CartoDB::SharedEntity.new(
recipient_id: user.id,
recipient_type: CartoDB::SharedEntity::RECIPIENT_TYPE_USER,
entity_id: visualization.id,
entity_type: CartoDB::SharedEntity::ENTITY_TYPE_VISUALIZATION
)
shared_entity.save
owner = visualization.user
perm_id = visualization.permission.id
params = { acl: [{ type: CartoDB::Permission::TYPE_USER,
entity: { id: user.id },
access: access
}]
}
url = api_v1_permissions_update_url(user_domain: owner.username, api_key: owner.api_key, id: perm_id)
put_json url, params do |response|
response.status.should == 200
end
end
end
+19
View File
@@ -0,0 +1,19 @@
FactoryGirl.define do
factory :carto_overlay, class: Carto::Overlay do
to_create(&:save!)
order 1
options { { display: true, x: 60, y: 20 } }
factory :carto_search_overlay do
type 'search'
end
factory :carto_zoom_overlay do
type 'zoom'
template '<a href="#zoom_in" class="zoom_in">+</a> <a href="#zoom_out" class="zoom_out">-</a>'
end
end
end
+9
View File
@@ -0,0 +1,9 @@
FactoryGirl.define do
factory :carto_permission, class: Carto::Permission do
after(:build) do |permission|
permission.owner_username = permission.owner.username if permission.owner
end
end
end
+105
View File
@@ -0,0 +1,105 @@
FactoryGirl.define do
factory :rate_limits, class: Carto::RateLimit do
maps_anonymous Carto::RateLimitValues.new([0, 1, 2])
maps_static Carto::RateLimitValues.new([3, 4, 5])
maps_static_named Carto::RateLimitValues.new([6, 7, 8])
maps_dataview Carto::RateLimitValues.new([9, 10, 11])
maps_dataview_search Carto::RateLimitValues.new([9, 10, 11])
maps_analysis Carto::RateLimitValues.new([12, 13, 14])
maps_tile Carto::RateLimitValues.new([15, 16, 17, 30, 32, 34])
maps_attributes Carto::RateLimitValues.new([18, 19, 20])
maps_named_list Carto::RateLimitValues.new([21, 22, 23])
maps_named_create Carto::RateLimitValues.new([24, 25, 26])
maps_named_get Carto::RateLimitValues.new([27, 28, 29])
maps_named Carto::RateLimitValues.new([30, 31, 32])
maps_named_update Carto::RateLimitValues.new([33, 34, 35])
maps_named_delete Carto::RateLimitValues.new([36, 37, 38])
maps_named_tiles Carto::RateLimitValues.new([39, 40, 41])
maps_analysis_catalog Carto::RateLimitValues.new([1, 1, 1])
sql_query Carto::RateLimitValues.new([0, 1, 2])
sql_query_format Carto::RateLimitValues.new([3, 4, 5])
sql_job_create Carto::RateLimitValues.new([6, 7, 8])
sql_job_get Carto::RateLimitValues.new([9, 10, 11])
sql_job_delete Carto::RateLimitValues.new([12, 13, 14])
sql_copy_from Carto::RateLimitValues.new([1, 1, 60])
sql_copy_to Carto::RateLimitValues.new([1, 1, 60])
end
factory :rate_limits_custom, class: Carto::RateLimit do
maps_anonymous Carto::RateLimitValues.new([10, 11, 12])
maps_static Carto::RateLimitValues.new([13, 14, 15])
maps_static_named Carto::RateLimitValues.new([16, 17, 18])
maps_dataview Carto::RateLimitValues.new([19, 110, 111])
maps_dataview_search Carto::RateLimitValues.new([19, 110, 111])
maps_analysis Carto::RateLimitValues.new([112, 113, 114])
maps_tile Carto::RateLimitValues.new([115, 116, 117, 230, 232, 234])
maps_attributes Carto::RateLimitValues.new([118, 119, 120])
maps_named_list Carto::RateLimitValues.new([121, 122, 123])
maps_named_create Carto::RateLimitValues.new([124, 125, 126])
maps_named_get Carto::RateLimitValues.new([127, 128, 129])
maps_named Carto::RateLimitValues.new([130, 131, 132])
maps_named_update Carto::RateLimitValues.new([133, 134, 135])
maps_named_delete Carto::RateLimitValues.new([136, 137, 138])
maps_named_tiles Carto::RateLimitValues.new([139, 140, 141])
maps_analysis_catalog Carto::RateLimitValues.new([11, 11, 11])
sql_query Carto::RateLimitValues.new([10, 11, 12])
sql_query_format Carto::RateLimitValues.new([13, 14, 15])
sql_job_create Carto::RateLimitValues.new([16, 17, 18])
sql_job_get Carto::RateLimitValues.new([19, 110, 111])
sql_job_delete Carto::RateLimitValues.new([112, 113, 114])
sql_copy_from Carto::RateLimitValues.new([11, 11, 160])
sql_copy_to Carto::RateLimitValues.new([11, 11, 160])
end
factory :rate_limits_custom2, class: Carto::RateLimit do
maps_anonymous Carto::RateLimitValues.new([10, 11, 12])
maps_static Carto::RateLimitValues.new([13, 14, 15])
maps_static_named Carto::RateLimitValues.new([16, 17, 18])
maps_dataview Carto::RateLimitValues.new([19, 110, 111])
maps_dataview_search Carto::RateLimitValues.new([19, 110, 111])
maps_analysis Carto::RateLimitValues.new([112, 113, 114])
maps_tile Carto::RateLimitValues.new([115, 116, 117, 230, 232, 234])
maps_attributes Carto::RateLimitValues.new([118, 119, 120])
maps_named_list Carto::RateLimitValues.new([121, 122, 123])
maps_named_create Carto::RateLimitValues.new([124, 125, 126])
maps_named_get Carto::RateLimitValues.new([127, 128, 129])
maps_named Carto::RateLimitValues.new([130, 131, 132])
maps_named_update Carto::RateLimitValues.new([133, 134, 135])
maps_named_delete Carto::RateLimitValues.new([136, 137, 138])
maps_named_tiles Carto::RateLimitValues.new([139, 140, 141])
maps_analysis_catalog Carto::RateLimitValues.new([11, 11, 11])
sql_query Carto::RateLimitValues.new([10, 11, 12])
sql_query_format Carto::RateLimitValues.new([13, 14, 15])
sql_job_create Carto::RateLimitValues.new([16, 17, 18])
sql_job_get Carto::RateLimitValues.new([19, 110, 111])
sql_job_delete Carto::RateLimitValues.new([112, 113, 114])
sql_copy_from Carto::RateLimitValues.new([112, 112, 161])
sql_copy_to Carto::RateLimitValues.new([112, 112, 161])
end
factory :rate_limits_pro, class: Carto::RateLimit do
maps_anonymous Carto::RateLimitValues.new([1, 1, 2])
maps_static Carto::RateLimitValues.new([2, 4, 5])
maps_static_named Carto::RateLimitValues.new([3, 7, 8])
maps_dataview Carto::RateLimitValues.new([4, 10, 11])
maps_dataview_search Carto::RateLimitValues.new([4, 10, 11])
maps_analysis Carto::RateLimitValues.new([5, 13, 14])
maps_tile Carto::RateLimitValues.new([6, 16, 17, 30, 32, 34])
maps_attributes Carto::RateLimitValues.new([7, 19, 20])
maps_named_list Carto::RateLimitValues.new([8, 22, 23])
maps_named_create Carto::RateLimitValues.new([9, 25, 26])
maps_named_get Carto::RateLimitValues.new([10, 28, 29])
maps_named Carto::RateLimitValues.new([11, 31, 32])
maps_named_update Carto::RateLimitValues.new([12, 34, 35])
maps_named_delete Carto::RateLimitValues.new([13, 37, 38])
maps_named_tiles Carto::RateLimitValues.new([14, 40, 41])
maps_analysis_catalog Carto::RateLimitValues.new([111, 111, 111])
sql_query Carto::RateLimitValues.new([1, 1, 2])
sql_query_format Carto::RateLimitValues.new([2, 4, 5])
sql_job_create Carto::RateLimitValues.new([3, 7, 8])
sql_job_get Carto::RateLimitValues.new([4, 10, 11])
sql_job_delete Carto::RateLimitValues.new([5, 13, 14])
sql_copy_from Carto::RateLimitValues.new([2, 2, 61])
sql_copy_to Carto::RateLimitValues.new([2, 2, 61])
end
end
+20
View File
@@ -0,0 +1,20 @@
FactoryGirl.define do
to_create(&:save)
factory :search_tweet do
user_id nil
table_id '96a86fb7-0270-4255-a327-15410c2d49d4'
data_import_id '96a86fb7-0270-4255-a327-15410c2d49d4'
service_item_id '555'
retrieved_items 0
state ::SearchTweet::STATE_COMPLETE
end
factory :carto_search_tweet, class: Carto::SearchTweet do
table_id '96a86fb7-0270-4255-a327-15410c2d49d4'
data_import_id '96a86fb7-0270-4255-a327-15410c2d49d4'
service_item_id '555'
retrieved_items 0
state ::SearchTweet::STATE_COMPLETE
end
end
+16
View File
@@ -0,0 +1,16 @@
require_relative '../../app/models/carto/state'
FactoryGirl.define do
factory :state, class: Carto::State do
json {
{
map: {
ne: [-89.81756220409478, -335.39062500000006],
sw: [89.81302911292107, 97.73437500000001],
center: [-0.7031073524364783, -118.82812500000001],
zoom: 1
}
}
}
end
end
+26
View File
@@ -0,0 +1,26 @@
FactoryGirl.define do
factory :carto_synchronization, class: Carto::Synchronization do
id { UUIDTools::UUID.random_uuid }
name 'histcounties_1979_present'
interval 2592000
url 'https://common-data.cartodb.com/api/v2/sql?q=select+*+from+%22histcounties_1979_present%22&format=shp&filename=histcounties_1979_present'
state 'success'
type_guessing true
quoted_fields_guessing true
content_guessing true
run_at Time.now
ran_at Time.now
after(:build) do |sync|
sync.log = FactoryGirl.build(:carto_log, type: 'sync')
end
trait :enqueued do
state Carto::Synchronization::STATE_QUEUED
ran_at nil
run_at nil
end
factory :enqueued_sync, traits: [:enqueued]
end
end
+8
View File
@@ -0,0 +1,8 @@
require 'helpers/unique_names_helper'
include UniqueNamesHelper
FactoryGirl.define do
factory :table, class: Table do
end
end
+25
View File
@@ -0,0 +1,25 @@
require 'ostruct'
FactoryGirl.define do
factory :user_creation, class: Carto::UserCreation do
username "whatever"
email "whatever@carto.com"
crypted_password "rgjreogjorejgpovrjeg"
google_sign_in false
quota_in_bytes 10000000
soft_twitter_datasource_limit false
factory :autologin_user_creation do
state 'success'
created_at { Time.now }
after(:build) do |model, evaluator|
# This is useful to test user creation logic without persistence
fake_user = OpenStruct.new(enable_account_token: nil, enabled: true, dashboard_viewed_at: nil)
model.instance_variable_set(:@cartodb_user, fake_user)
end
end
end
end
+13
View File
@@ -0,0 +1,13 @@
FactoryGirl.define do
factory :totp, class: Carto::UserMultifactorAuth do
type Carto::UserMultifactorAuth::TYPE_TOTP
trait :needs_setup do
enabled false
end
trait :active do
enabled true
end
end
end
+63
View File
@@ -0,0 +1,63 @@
require 'helpers/unique_names_helper'
include UniqueNamesHelper
FactoryGirl.define do
factory :user_table, class: UserTable do
to_create(&:save)
name { unique_name('user_table') }
end
factory :carto_user_table, class: Carto::UserTable do
name { unique_name('user_table') }
before(:create) do |user_table|
user_table.service.stubs(:before_create)
user_table.service.stubs(:after_create)
user_table.stubs(:create_canonical_visualization)
CartoDB::TablePrivacyManager.any_instance.stubs(:apply_privacy_change)
end
after(:create) do |user_table|
user_table.service.unstub(:before_create)
user_table.service.unstub(:after_create)
user_table.unstub(:create_canonical_visualization)
CartoDB::TablePrivacyManager.any_instance.unstub(:apply_privacy_change)
end
trait :with_db_table do
before(:create) do |user_table|
user_table.service.unstub(:before_create)
user_table.service.unstub(:after_create)
CartoDB::TablePrivacyManager.any_instance.unstub(:apply_privacy_change)
end
end
trait :with_canonical_visualization do
before(:create) do |user_table|
user_table.service.stubs(:is_raster?).returns(false)
user_table.unstub(:create_canonical_visualization)
end
after(:create) do |user_table|
user_table.service.unstub(:is_raster?)
end
end
factory :carto_user_table_with_canonical, traits: [:with_canonical_visualization]
trait :full do
with_canonical_visualization
with_db_table
end
factory :private_user_table do
privacy Carto::UserTable::PRIVACY_PRIVATE
end
factory :public_user_table do
privacy Carto::UserTable::PRIVACY_PUBLIC
end
end
end
+122
View File
@@ -0,0 +1,122 @@
# Read about factories at https://github.com/thoughtbot/factory_girl
require 'helpers/account_types_helper'
require 'helpers/unique_names_helper'
require 'carto/user_authenticator'
require 'cartodb-common'
include AccountTypesHelper
include UniqueNamesHelper
FactoryGirl.define do
factory :user, class: ::User do
to_create(&:save)
username { unique_name('user') }
email { unique_email }
password { email.split('@').first }
password_confirmation { email.split('@').first }
table_quota 5
quota_in_bytes 5000000
id { UUIDTools::UUID.timestamp_create.to_s }
builder_enabled nil # Most tests still assume editor
trait :admin_privileges do
username 'Admin'
email 'admin@example.com'
admin true
end
trait :private_tables do
private_tables_enabled true
end
trait :sync_tables do
sync_tables_enabled true
end
trait :enabled do
enabled true
end
trait :mobile do
mobile_max_open_users 100000
mobile_max_private_users 20000
end
trait :locked do
state 'locked'
end
trait :valid do
password 'kkkkkkkkk'
password_confirmation 'kkkkkkkkk'
crypted_password 'kkkkkkkkk'
end
factory :user_with_private_tables, traits: [:enabled, :private_tables]
factory :admin, traits: [:admin]
factory :valid_user, traits: [:valid]
factory :locked_user, traits: [:valid, :locked]
after(:build) do |user|
create_account_type_fg(user.account_type)
end
before(:create) do |user|
CartoDB::UserModule::DBService.any_instance.stubs(:enable_remote_db_user).returns(true)
create_account_type_fg(user.account_type)
end
end
factory :carto_user, class: Carto::User do
username { unique_name('user') }
email { unique_email }
password { email.split('@').first }
password_confirmation { email.split('@').first }
crypted_password do
Carto::Common::EncryptionService.encrypt(password: password, secret: Cartodb.config[:password_secret])
end
api_key '21ee521b8a107ea55d61fd7b485dd93d54c0b9d2'
table_quota nil
quota_in_bytes 5000000
id { UUIDTools::UUID.timestamp_create.to_s }
builder_enabled nil # Most tests still assume editor
before(:build) do |carto_user|
create_account_type_fg(carto_user.account_type)
end
before(:create) do |carto_user|
CartoDB::UserModule::DBService.any_instance.stubs(:enable_remote_db_user).returns(true)
create_account_type_fg(carto_user.account_type)
end
after(:create) do |carto_user|
::User.where(id: carto_user.id).first.after_create
CartoDB::UserModule::DBService.any_instance.unstub
end
trait :locked do
state 'locked'
end
trait :mfa_setup do
after :create do |carto_user|
carto_user.user_multifactor_auths << FactoryGirl.create(:totp, :needs_setup, user_id: carto_user.id)
end
end
trait :mfa_enabled do
after :create do |carto_user|
carto_user.user_multifactor_auths << FactoryGirl.create(:totp, :active, user_id: carto_user.id)
end
end
factory :carto_locked_user, traits: [:locked]
factory :carto_user_mfa_setup, traits: [:mfa_setup]
factory :carto_user_mfa, traits: [:mfa_enabled]
end
end
+46
View File
@@ -0,0 +1,46 @@
shared_context 'users helper' do
include_context 'database configuration'
include CartoDB::Factories
before(:each) do
CartoDB::UserModule::DBService.any_instance.stubs(:enable_remote_db_user).returns(true)
end
before(:all) do
@user1 = FactoryGirl.create(:valid_user, private_tables_enabled: true, private_maps_enabled: true)
@carto_user1 = Carto::User.find(@user1.id)
@user2 = FactoryGirl.create(:valid_user, private_tables_enabled: true, private_maps_enabled: true)
@carto_user2 = Carto::User.find(@user2.id)
end
before(:each) do
bypass_named_maps
delete_user_data @user1
delete_user_data @user2
end
after(:all) do
bypass_named_maps
@user1.destroy if @user1
@user2.destroy if @user2
end
end
shared_context 'user helper' do
include CartoDB::Factories
before(:all) do
@user = FactoryGirl.create(:valid_user)
@carto_user = Carto::User.find(@user.id)
end
after(:all) do
bypass_named_maps
@user.destroy
end
before(:each) do
bypass_named_maps
delete_user_data(@user)
end
end
@@ -0,0 +1,68 @@
require_relative '../support/factories/tables'
require 'helpers/unique_names_helper'
include Warden::Test::Helpers
include UniqueNamesHelper
include CartoDB
def app
CartoDB::Application.new
end
def login(user)
login_as(user, scope: user.username)
host! "#{user.username}.localhost.lan"
end
def create_random_table(user, name = unique_name('viz'), privacy = nil)
options = { user_id: user.id, name: name }
options.merge!(privacy: privacy) if privacy
create_table(options)
end
def create_table_with_options(user, headers = { 'CONTENT_TYPE' => 'application/json' }, options = {})
privacy = options.fetch(:privacy, UserTable::PRIVACY_PUBLIC)
name = unique_name('table')
payload = {
name: name,
description: "#{name} description"
}
table_attributes = nil
post_json api_v1_tables_create_url(user_domain: user.username, api_key: user.api_key), payload.to_json, headers do |r|
table_attributes = r.body.stringify_keys
table_id = table_attributes.fetch('id')
put api_v1_tables_update_url(id: table_id, user_domain: user.username, api_key: user.api_key),
{ privacy: privacy }.to_json, headers
end
table_attributes
end
shared_context 'visualization creation helpers' do
include Warden::Test::Helpers
CARTO_OPTIONS = '{"query":"","opacity":0.99,"auto_bound":false,"interactivity":"cartodb_id","debug":false,' \
'"visible":true,"tiler_domain":"localhost.lan","tiler_port":"80","tiler_protocol":"http",' \
'"sql_domain":"localhost.lan","sql_port":"80","sql_protocol":"http","extra_params":{' \
'"cache_policy":"persist"},"cdn_url":"","tile_style_history":[],"style_version":"2.1.1",' \
'"table_name":"districtes_barcelona","user_name":"ethervoid-common",' \
'"tile_style":"#districtes_barcelona {\n polygon-fill:#FF6600;\n polygon-opacity: 0.7;\n ' \
'line-opacity:1;\n line-color: #FFFFFF;\n}"}'.freeze
def create_layer(table_name, user_name, order = 1, kind = 'carto', infowindow = nil)
options = JSON.parse(CARTO_OPTIONS)
options["table_name"] = table_name
options["user_name"] = user_name
FactoryGirl.build(:carto_layer, kind: kind, options: options, order: order, infowindow: infowindow)
end
before(:each) do
bypass_named_maps
end
private
end
+12
View File
@@ -0,0 +1,12 @@
require_relative '../../app/models/carto/visualization_export'
FactoryGirl.define do
factory :visualization_export, class: Carto::VisualizationExport do
id { UUIDTools::UUID.timestamp_create.to_s }
state Carto::VisualizationExport::STATE_PENDING
file { "#{Cartodb.get_config(:exporter, "uploads_path")}/abcd/Untitled Map (on 2016-05-26 at 06.08.15).carto" }
url { "/api/v3/visualization_exports/#{id}/download" }
created_at { Time.now }
updated_at { Time.now }
end
end
+57
View File
@@ -0,0 +1,57 @@
require 'uuidtools'
require_dependency 'carto/uuidhelper'
include Carto::UUIDHelper
FactoryGirl.define do
factory :derived_visualization, class: CartoDB::Visualization::Member do
to_create(&:store)
type 'derived'
name "visualization #{random_uuid}"
privacy 'public'
end
factory :table_visualization, class: CartoDB::Visualization::Member do
to_create(&:store)
type 'table'
name "visualization_#{random_uuid}"
privacy 'public'
end
factory :carto_visualization, class: Carto::Visualization do
id { random_uuid }
type 'derived'
name 'factory visualization'
title 'visualization title'
privacy 'public'
association :user, factory: :carto_user
permission { FactoryGirl.create :carto_permission, owner: user }
factory :carto_private_visualization do
privacy Carto::Visualization::PRIVACY_PRIVATE
end
factory :carto_table_visualization do
type Carto::Visualization::TYPE_CANONICAL
end
end
factory :kuviz_visualization, class: Carto::Visualization do
id { random_uuid }
type 'kuviz'
name 'factory kuviz'
title 'kuviz title'
privacy 'public'
association :user, factory: :carto_user
permission { FactoryGirl.create :carto_permission, owner: user }
factory :kuviz_protected_visualization do
privacy Carto::Visualization::PRIVACY_PROTECTED
password 'test'
end
end
end
+40
View File
@@ -0,0 +1,40 @@
require_relative '../../app/models/carto/widget'
FactoryGirl.define do
factory :widget, class: Carto::Widget do
ignore do
column_name 'pop_max'
end
order 1
type 'formula'
title 'The Title'
source_id 'a0'
options do
{
type: "formula",
column: column_name,
operation: "min"
}
end
style do
{
widget_style: {
definition: {
fill: { color: { fixed: '#FFF' } }
}
},
auto_style: {
definition: {
fill: { color: { fixed: '#FFF' } }
}
}
}
end
created_at { Time.now }
updated_at { Time.now }
factory :widget_with_layer do
association :layer, factory: :carto_layer
end
end
end
+105
View File
@@ -0,0 +1,105 @@
{
"objectIdFieldName": "OBJECTID",
"objectIds": [
2133,
2134,
2137,
2138,
2141,
2142,
2143,
2144,
2145,
2146,
2147,
2148,
2149,
2150,
2151,
2152,
2153,
2154,
2155,
2156,
2157,
2158,
2159,
2160,
2161,
2163,
2164,
2165,
2166,
2167,
2168,
2169,
2170,
2171,
2172,
2173,
2174,
2175,
2176,
2177,
2178,
2179,
2180,
2182,
2183,
2184,
2185,
2186,
2187,
2188,
2189,
2191,
2192,
2194,
2195,
2196,
2197,
2198,
2199,
2200,
2201,
2202,
2203,
2205,
2206,
2207,
2208,
2209,
2210,
2213,
2215,
2217,
2218,
2219,
2220,
2221,
2222,
2223,
2224,
2225,
2226,
2227,
2228,
2230,
2231,
2232,
2233,
2234,
2235,
2236,
2237,
2238,
2239,
2240,
2241,
2242,
2243,
2244,
2245,
2246
]
}
+4287
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
UserMailer#ask_for_invitation
Hi, find me in app/views/user_mailer/ask_for_invitation
+3
View File
@@ -0,0 +1,3 @@
UserMailer#invitation_sent
Hi, find me in app/views/user_mailer/invitation_sent
@@ -0,0 +1,131 @@
require 'spec_helper_min'
require 'carto_gears_api/users/users_service'
describe CartoGearsApi::Users::UsersService do
let(:service) { CartoGearsApi::Users::UsersService.new }
context "password management" do
before(:all) do
@user = FactoryGirl.create(:user, password: 'my_pass', password_confirmation: 'my_pass')
end
after(:all) do
@user.destroy
end
describe '#valid_password' do
it 'returns true if the password is correct' do
service.valid_password?(@user.id, 'my_pass').should be_true
end
it 'returns false if the password is incorrect' do
service.valid_password?(@user.id, 'wrong').should be_false
end
context 'with Google/Github sign in' do
before(:all) do
@user.google_sign_in = true
@user.last_password_change_date = nil
@user.save
end
after(:all) do
@user.google_sign_in = false
@user.save
end
it 'raises an error' do
expect {
service.valid_password?(@user.id, 'whatever')
}.to raise_error(CartoGearsApi::Errors::ValidationFailed, /no password/)
end
end
end
describe '#change_password' do
context 'right parameters' do
before(:each) do
@user = FactoryGirl.create(:user, password: 'my_pass', password_confirmation: 'my_pass')
end
after(:each) do
@user.destroy
end
it 'updates crypted_password' do
expect {
service.change_password(@user.id, 'new_password')
}.to (change { @user.reload.crypted_password })
end
it 'updates last_password_change_date' do
expect {
service.change_password(@user.id, 'new_password')
}.to (change { @user.reload.last_password_change_date })
end
end
it 'raises a validation error if the new password is blank' do
expect {
service.change_password(@user.id, nil)
}.to raise_error(CartoGearsApi::Errors::ValidationFailed, /blank/)
end
it 'raises a validation error if the new password is the same as the username' do
expect {
service.change_password(@user.id, @user.username)
}.to raise_error(CartoGearsApi::Errors::ValidationFailed, /must be different than the user name/)
end
it 'raises a validation error if the new password is a common one' do
expect {
service.change_password(@user.id, 'galina')
}.to raise_error(CartoGearsApi::Errors::ValidationFailed, /can't be a common password/)
end
it 'raises a validation error if the new password is not strong' do
expect {
organization = mock
organization.stubs(:strong_passwords_enabled).returns(true)
User.any_instance.stubs(:organization).returns(organization)
service.change_password(@user.id, 'galinaa')
}.to raise_error(CartoGearsApi::Errors::ValidationFailed, /must be at least 8 characters long/)
end
it 'raises a validation error if the new password is too short' do
expect {
service.change_password(@user.id, 'a')
}.to raise_error(CartoGearsApi::Errors::ValidationFailed, /at least/)
end
it 'raises a validation error if the new password is too long' do
expect {
service.change_password(@user.id, 'a' * 70)
}.to raise_error(CartoGearsApi::Errors::ValidationFailed, /at most/)
end
it 'raises a validation error if the new password is the same as the old' do
expect {
service.change_password(@user.id, 'my_pass')
}.to raise_error(CartoGearsApi::Errors::ValidationFailed, /the same/)
end
it 'raises an exception if the save operation fails' do
User.any_instance.stubs(:save).raises(Sequel::ValidationFailed, 'Saving error')
expect {
service.change_password(@user.id, 'new_password')
}.to raise_error(CartoGearsApi::Errors::SavingError)
end
it 'raises an exception if update_in_central operation fails' do
User.any_instance.stubs(:update_in_central).raises(CartoDB::CentralCommunicationFailure, 'Updating error')
expect {
service.change_password(@user.id, 'new_password')
}.to raise_error(CartoGearsApi::Errors::SavingError)
end
end
end
end
+53
View File
@@ -0,0 +1,53 @@
module AccountTypesHelper
def create_account_type(account_type_name)
rate_limit = Carto::RateLimit.create!(maps_anonymous: Carto::RateLimitValues.new([0, 1, 2]),
maps_static: Carto::RateLimitValues.new([3, 4, 5]),
maps_static_named: Carto::RateLimitValues.new([6, 7, 8]),
maps_dataview: Carto::RateLimitValues.new([9, 10, 11]),
maps_dataview_search: Carto::RateLimitValues.new([9, 10, 11]),
maps_analysis: Carto::RateLimitValues.new([12, 13, 14]),
maps_tile: Carto::RateLimitValues.new([15, 16, 17, 30, 32, 34]),
maps_attributes: Carto::RateLimitValues.new([18, 19, 20]),
maps_named_list: Carto::RateLimitValues.new([21, 22, 23]),
maps_named_create: Carto::RateLimitValues.new([24, 25, 26]),
maps_named_get: Carto::RateLimitValues.new([27, 28, 29]),
maps_named: Carto::RateLimitValues.new([30, 31, 32]),
maps_named_update: Carto::RateLimitValues.new([33, 34, 35]),
maps_named_delete: Carto::RateLimitValues.new([36, 37, 38]),
maps_named_tiles: Carto::RateLimitValues.new([39, 40, 41]),
maps_analysis_catalog: Carto::RateLimitValues.new([1, 1, 1]),
sql_query: Carto::RateLimitValues.new([0, 1, 2]),
sql_query_format: Carto::RateLimitValues.new([3, 4, 5]),
sql_job_create: Carto::RateLimitValues.new([6, 7, 8]),
sql_job_get: Carto::RateLimitValues.new([9, 10, 11]),
sql_job_delete: Carto::RateLimitValues.new([12, 13, 14]),
sql_copy_from: Carto::RateLimitValues.new([1, 1, 60]),
sql_copy_to: Carto::RateLimitValues.new([1, 1, 60]))
account_type = Carto::AccountType.new
account_type.rate_limit_id = rate_limit.id
account_type.account_type = account_type_name || 'FREE'
account_type.save! unless Carto::AccountType.exists?(account_type.account_type)
end
def create_account_type_fg(account_type)
account_type ||= 'FREE'
get_account_type(account_type) || new_account_type(account_type)
end
private
def get_account_type(account_type)
Carto::AccountType.find(account_type)
rescue ActiveRecord::RecordNotFound
end
def new_account_type(account_type)
if account_type == 'PRO'
FactoryGirl.create(:account_type_pro)
else
FactoryGirl.create(:account_type, account_type: account_type)
end
end
end
+49
View File
@@ -0,0 +1,49 @@
require 'spec_helper_min'
describe ApplicationHelper do
describe '#app_assets_base_url' do
before(:each) do
CartoDB.stubs(:protocol).returns('http')
CartoDB.stubs(:http_port).returns(nil)
end
describe 'with asset_host (SaaS)' do
it 'returns asset_host' do
Cartodb.stubs(:get_config).with(:app_assets, 'asset_host').returns('https://carto.global.ssl.fastly.net/cartodbui')
ApplicationHelper.app_assets_base_url.should eq 'https://carto.global.ssl.fastly.net/cartodbui/assets'
end
it 'returns asset_host and adds the protocol if needed' do
Cartodb.stubs(:get_config).with(:app_assets, 'asset_host').returns('//carto.global.ssl.fastly.net/cartodbui')
ApplicationHelper.app_assets_base_url.should eq 'http://carto.global.ssl.fastly.net/cartodbui/assets'
end
end
describe 'without asset_host' do
before(:each) do
Cartodb.stubs(:get_config).with(:app_assets, 'asset_host').returns('')
request = mock
request.stubs(:params).returns(user_domain: 'pepe')
request.stubs(:host).returns('org.localhost.lan')
ApplicationHelper.stubs(:request).returns(request)
end
it 'with subdomainful urls (OSS/dev) returns a subdomain' do
CartoDB.stubs(:subdomainless_urls?).returns(false)
CartoDB.stubs(:session_domain).returns('.localhost.lan')
ApplicationHelper.app_assets_base_url.should eq 'http://org.localhost.lan/assets'
end
it 'with subdomainless urls (onpremise) returns the domain' do
CartoDB.stubs(:subdomainless_urls?).returns(true)
CartoDB.stubs(:session_domain).returns('localhost.lan')
ApplicationHelper.app_assets_base_url.should eq 'http://localhost.lan/assets'
end
end
end
end
+24
View File
@@ -0,0 +1,24 @@
require 'active_support/core_ext/object/blank'
require_relative '../../../app/helpers/carto/html_safe'
describe Carto::HtmlSafe do
let(:html_safe) do
class TestModule; include Carto::HtmlSafe; end.new
end
it 'sets target="blank" for links' do
link = 'http://www.carto.com'
html_safe.markdown_html_safe("[text](#{link})").should eq "<p><a href=\"#{link}\" target=\"_blank\">text</a></p>\n"
end
it 'does not set target="blank" for mailto markdown' do
mailto = 'mailto:wadus@example.com'
html_safe.markdown_html_safe("[text](#{mailto})").should eq "<p><a href=\"#{mailto}\">text</a></p>\n"
end
it 'does not set target="blank" for mailto links' do
mail = 'wadus@example.com'
mailto = 'mailto:' + mail
html_safe.markdown_html_safe("<#{mailto}>").should eq "<p><a href=\"#{mailto}\">#{mail}</a></p>\n"
end
end
+55
View File
@@ -0,0 +1,55 @@
require_relative '../simplecov_helper'
require 'rails'
require 'ostruct'
require_relative '../rspec_configuration'
require_relative '../../config/initializers/carto_db'
# Just "open" the modules so that we make sure they are defined,
# but do not overwrite anything so that it doesn't affect other tests.
module CartoDB
module Cartodb; end
end
describe 'CartoDB' do
describe '#ip?' do
it 'detects ips' do
CartoDB.ip?(nil).should == false
CartoDB.ip?('').should == false
CartoDB.ip?('.').should == false
CartoDB.ip?('...').should == false
CartoDB.ip?(192).should == false
CartoDB.ip?('a').should == false
CartoDB.ip?('a.b.c.d').should == false
CartoDB.ip?('192.168.1.').should == false
CartoDB.ip?('192.168.1.0').should == true
end
end
describe 'extract_subdomain' do
it 'extracts subdomain without subdomainless_urls' do
CartoDB::Cartodb.stubs(:config).returns(subdomainless_urls: false)
CartoDB.stubs(:session_domain).returns('.localhost.lan')
CartoDB.extract_subdomain(OpenStruct.new(host: 'localhost.lan', params: { user_domain: '' })).should == ''
CartoDB.extract_subdomain(OpenStruct.new(host: 'localhost.lan', params: { user_domain: nil })).should == ''
CartoDB.extract_subdomain(OpenStruct.new(host: 'auser.localhost.lan', params: { user_domain: 'auser' })).should == 'auser'
CartoDB.extract_subdomain(OpenStruct.new(host: 'localhost.lan', params: { user_domain: 'auser' })).should == 'auser'
CartoDB.extract_subdomain(OpenStruct.new(host: 'auser.localhost.lan', params: { user_domain: 'otheruser' })).should == 'otheruser'
end
it 'extracts subdomain with subdomainless_urls' do
CartoDB::Cartodb.stubs(:config).returns(subdomainless_urls: true)
CartoDB.stubs(:session_domain).returns('localhost.lan')
CartoDB.extract_subdomain(OpenStruct.new(host: 'localhost.lan', params: { user_domain: '' })).should == ''
CartoDB.extract_subdomain(OpenStruct.new(host: 'localhost.lan', params: { user_domain: nil })).should == ''
CartoDB.extract_subdomain(OpenStruct.new(host: 'auser.localhost.lan', params: { user_domain: 'auser' })).should == 'auser'
CartoDB.extract_subdomain(OpenStruct.new(host: 'localhost.lan', params: { user_domain: 'auser' })).should == 'auser'
CartoDB.extract_subdomain(OpenStruct.new(host: 'auser.localhost.lan', params: { user_domain: 'otheruser' })).should == 'otheruser'
CartoDB.extract_subdomain(OpenStruct.new(host: '192.168.1.1', params: { user_domain: '' })).should == ''
CartoDB.extract_subdomain(OpenStruct.new(host: '192.168.1.1', params: { user_domain: nil })).should == ''
CartoDB.extract_subdomain(OpenStruct.new(host: '192.168.1.1', params: { user_domain: 'otheruser' })).should == 'otheruser'
end
end
end
@@ -0,0 +1,31 @@
module DatabaseConnectionHelper
def with_connection(options)
connection = ::Sequel.connect(options)
begin
yield connection
ensure
connection.disconnect
end
end
def with_connection_from_user(user, &block)
options = ::SequelRails.configuration.environment_for(Rails.env).merge(
'database' => user.database_name,
'username' => user.database_username,
'password' => user.database_password,
'host' => user.database_host
)
with_connection options, &block
end
def with_connection_from_api_key(api_key, &block)
user = api_key.user
options = ::SequelRails.configuration.environment_for(Rails.env).merge(
'database' => user.database_name,
'username' => api_key.db_role,
'password' => api_key.db_password,
'host' => user.database_host
)
with_connection options, &block
end
end
+27
View File
@@ -0,0 +1,27 @@
module FeatureFlagHelper
def set_feature_flag(user, feature, state)
user.reload
previous_state = user.has_feature_flag?(feature)
if state != previous_state
ff = FeatureFlag[name: feature]
ffu = FeatureFlagsUser[feature_flag_id: ff.id, user_id: user.id]
if state
unless ffu
FeatureFlagsUser.new(feature_flag_id: ff.id, user_id: user.id).save
end
else
ff.update restricted: false unless ff.restricted
ffu.delete if ffu
end
user.reload
end
previous_state
end
def with_feature_flag(user, feature, state)
user.reload
previous_state = set_feature_flag user, feature, state
yield
set_feature_flag user, feature, previous_state if state != previous_state
end
end
+140
View File
@@ -0,0 +1,140 @@
module FileServerHelper
def serve_file(file_path, options = {})
port = get_unused_port
require 'webrick'
server = WEBrick::HTTPServer.new(
AccessLog: [],
Logger: WEBrick::Log::new("/dev/null", 7), # comment this line if weird things happen
Port: port,
DocumentRoot: File.dirname(file_path),
RequestCallback: Proc.new do |_req, res|
options[:headers].each { |k, v| res[k] = v } unless options[:headers].nil?
unless options[:headers].nil? || options[:headers]['content-type'].nil?
res.content_type = options[:headers]['content-type']
end
end
)
trap("INT") { server.shutdown }
a = Thread.new { server.start }
begin
yield "http://localhost:#{port}/#{File.basename(file_path)}" if block_given?
rescue => e
raise e
ensure
b = Thread.new { server.shutdown }
b.join
a.join
end
end
def get_unused_port
used_ports_command = `netstat -tln | tail -n +3 | awk '{ print $4 }' | cut -f2 -d ':'`
used_ports = used_ports_command.split("\n").map(&:to_i)
(10000..65535).each do |port|
return port if !used_ports.include?(port)
end
raise "No ports available on machine."
end
def stub_download(url:, filepath:, headers: {}, content_disposition: true)
Typhoeus.stub(url).and_return(response_for(filepath, headers, content_disposition: content_disposition))
end
def stub_failed_download(options)
url = options.fetch(:url)
filepath = options.fetch(:filepath)
headers = options.fetch(:headers, {})
Typhoeus.stub(url).and_return(failed_response_for(filepath, headers))
end
def response_for(filepath, headers = {}, content_disposition: true)
response = Typhoeus::Response.new(
code: 200,
body: File.new(filepath).read.to_s,
headers: headers.merge(headers_for(filepath, content_disposition: content_disposition))
)
response
end
def failed_response_for(_filepath, headers={})
Typhoeus::Response.new(code: 404, body: nil, headers: {})
end
def headers_for(filepath, content_disposition: true)
return {} unless content_disposition
filename = filepath.split('/').last
{ "Content-Disposition" => "attachment; filename=#{filename}" }
end
def stub_arcgis_response_with_file(
absolute_filepath,
absolute_metadata_filepath = File.expand_path('spec/fixtures/arcgis_metadata.json')
)
# Metadata of a layer
Typhoeus.stub(/\/arcgis\/rest\/services\/Planning\/EPI_Primary_Planning_Layers\/MapServer\/2\?f=json/) do
body = File.read(absolute_metadata_filepath)
Typhoeus::Response.new(
code: 200,
headers: { 'Content-Type' => 'application/json' },
body: body
)
end
# IDs list of a layer
Typhoeus.stub(/\/arcgis\/rest\/(.*)query\?where=/) do
json_file = JSON.parse(File.read(absolute_filepath))
Typhoeus::Response.new(
code: 200,
headers: { 'Content-Type' => 'application/json' },
body: JSON.dump(
objectIdFieldName: "OBJECTID",
objectIds: json_file['features'].map { |f| f['attributes']['OBJECTID'] }
)
)
end
Typhoeus.stub(/\/arcgis\/rest\/(.*)query$/) do |request|
response_body = File.read(absolute_filepath)
response_body = ::JSON.parse(response_body)
request_body = request.options[:body]
requested_object_id = nil
lower_match = nil
upper_match = nil
if request_body[:objectIds]
requested_object_id = request_body[:objectIds]
else
lower_match = /OBJECTID\s+>=(\d+)/ =~ request.options[:body][:where]
upper_match = /OBJECTID\s+<=(\d+)/ =~ request.options[:body][:where]
end
response_body['features'] = response_body['features'].select do |f|
object_id = f['attributes']['OBJECTID']
if requested_object_id
object_id == requested_object_id
elsif lower_match && upper_match
object_id >= lower_match[1].to_i && object_id <= upper_match[1].to_i
elsif lower_match
object_id >= lower_match[1].to_i
elsif upper_match
object_id <= upper_match[1].to_i
end
end
Typhoeus::Response.new(
code: 200,
headers: { 'Content-Type' => 'application/json' },
body: ::JSON.dump(response_body)
)
end
end
end
+5
View File
@@ -0,0 +1,5 @@
module MetricsHelper
def bypass_metrics
Carto::Tracking::Events::Event.any_instance.stubs(:report!).returns(true)
end
end
+15
View File
@@ -0,0 +1,15 @@
module NamedMapsHelper
def bypass_named_maps
Carto::NamedMaps::Api.any_instance.stubs(show: nil, create: true, update: true, destroy: true)
end
def bypass_named_maps_requests
mocked_response = mock
mocked_response.stubs(code: 200, response_body: '{}')
mocked_http_client = Carto::Http::Client.get('named_maps')
mocked_http_client.stubs(:perform_request).returns(mocked_response)
Carto::NamedMaps::Api.any_instance.stubs(http_client: mocked_http_client, show: nil)
end
end
+117
View File
@@ -0,0 +1,117 @@
module RateLimitsHelper
def expect_rate_limits_saved_to_redis(username)
map_prefix = "limits:rate:store:#{username}:maps:"
sql_prefix = "limits:rate:store:#{username}:sql:"
$limits_metadata.LRANGE("#{map_prefix}anonymous", 0, 2).should == ["0", "1", "2"]
$limits_metadata.LRANGE("#{map_prefix}static", 0, 2).should == ["3", "4", "5"]
$limits_metadata.LRANGE("#{map_prefix}static_named", 0, 2).should == ["6", "7", "8"]
$limits_metadata.LRANGE("#{map_prefix}dataview", 0, 2).should == ["9", "10", "11"]
$limits_metadata.LRANGE("#{map_prefix}dataview_search", 0, 2).should == ["9", "10", "11"]
$limits_metadata.LRANGE("#{map_prefix}analysis", 0, 2).should == ["12", "13", "14"]
$limits_metadata.LRANGE("#{map_prefix}tile", 0, 5).should == ["15", "16", "17", "30", "32", "34"]
$limits_metadata.LRANGE("#{map_prefix}attributes", 0, 2).should == ["18", "19", "20"]
$limits_metadata.LRANGE("#{map_prefix}named_list", 0, 2).should == ["21", "22", "23"]
$limits_metadata.LRANGE("#{map_prefix}named_create", 0, 2).should == ["24", "25", "26"]
$limits_metadata.LRANGE("#{map_prefix}named_get", 0, 2).should == ["27", "28", "29"]
$limits_metadata.LRANGE("#{map_prefix}named", 0, 2).should == ["30", "31", "32"]
$limits_metadata.LRANGE("#{map_prefix}named_update", 0, 2).should == ["33", "34", "35"]
$limits_metadata.LRANGE("#{map_prefix}named_delete", 0, 2).should == ["36", "37", "38"]
$limits_metadata.LRANGE("#{map_prefix}named_tiles", 0, 2).should == ["39", "40", "41"]
$limits_metadata.LRANGE("#{map_prefix}analysis_catalog", 0, 2).should == ["1", "1", "1"]
$limits_metadata.LRANGE("#{sql_prefix}query", 0, 2).should == ["0", "1", "2"]
$limits_metadata.LRANGE("#{sql_prefix}query_format", 0, 2).should == ["3", "4", "5"]
$limits_metadata.LRANGE("#{sql_prefix}job_create", 0, 2).should == ["6", "7", "8"]
$limits_metadata.LRANGE("#{sql_prefix}job_get", 0, 2).should == ["9", "10", "11"]
$limits_metadata.LRANGE("#{sql_prefix}job_delete", 0, 2).should == ["12", "13", "14"]
$limits_metadata.LRANGE("#{sql_prefix}copy_from", 0, 2).should == ["1", "1", "60"]
$limits_metadata.LRANGE("#{sql_prefix}copy_to", 0, 2).should == ["1", "1", "60"]
end
def expect_rate_limits_custom_saved_to_redis(username)
map_prefix = "limits:rate:store:#{username}:maps:"
sql_prefix = "limits:rate:store:#{username}:sql:"
$limits_metadata.LRANGE("#{map_prefix}anonymous", 0, 2).should == ["10", "11", "12"]
$limits_metadata.LRANGE("#{map_prefix}static", 0, 2).should == ["13", "14", "15"]
$limits_metadata.LRANGE("#{map_prefix}static_named", 0, 2).should == ["16", "17", "18"]
$limits_metadata.LRANGE("#{map_prefix}dataview", 0, 2).should == ["19", "110", "111"]
$limits_metadata.LRANGE("#{map_prefix}dataview_search", 0, 2).should == ["19", "110", "111"]
$limits_metadata.LRANGE("#{map_prefix}analysis", 0, 2).should == ["112", "113", "114"]
$limits_metadata.LRANGE("#{map_prefix}tile", 0, 5).should == ["115", "116", "117", "230", "232", "234"]
$limits_metadata.LRANGE("#{map_prefix}attributes", 0, 2).should == ["118", "119", "120"]
$limits_metadata.LRANGE("#{map_prefix}named_list", 0, 2).should == ["121", "122", "123"]
$limits_metadata.LRANGE("#{map_prefix}named_create", 0, 2).should == ["124", "125", "126"]
$limits_metadata.LRANGE("#{map_prefix}named_get", 0, 2).should == ["127", "128", "129"]
$limits_metadata.LRANGE("#{map_prefix}named", 0, 2).should == ["130", "131", "132"]
$limits_metadata.LRANGE("#{map_prefix}named_update", 0, 2).should == ["133", "134", "135"]
$limits_metadata.LRANGE("#{map_prefix}named_delete", 0, 2).should == ["136", "137", "138"]
$limits_metadata.LRANGE("#{map_prefix}named_tiles", 0, 2).should == ["139", "140", "141"]
$limits_metadata.LRANGE("#{map_prefix}analysis_catalog", 0, 2).should == ["11", "11", "11"]
$limits_metadata.LRANGE("#{sql_prefix}query", 0, 2).should == ["10", "11", "12"]
$limits_metadata.LRANGE("#{sql_prefix}query_format", 0, 2).should == ["13", "14", "15"]
$limits_metadata.LRANGE("#{sql_prefix}job_create", 0, 2).should == ["16", "17", "18"]
$limits_metadata.LRANGE("#{sql_prefix}job_get", 0, 2).should == ["19", "110", "111"]
$limits_metadata.LRANGE("#{sql_prefix}job_delete", 0, 2).should == ["112", "113", "114"]
$limits_metadata.LRANGE("#{sql_prefix}copy_from", 0, 2).should == ["11", "11", "160"]
$limits_metadata.LRANGE("#{sql_prefix}copy_to", 0, 2).should == ["11", "11", "160"]
end
def expect_rate_limits_pro_saved_to_redis(username)
map_prefix = "limits:rate:store:#{username}:maps:"
sql_prefix = "limits:rate:store:#{username}:sql:"
$limits_metadata.LRANGE("#{map_prefix}anonymous", 0, 2).should == ["1", "1", "2"]
$limits_metadata.LRANGE("#{map_prefix}static", 0, 2).should == ["2", "4", "5"]
$limits_metadata.LRANGE("#{map_prefix}static_named", 0, 2).should == ["3", "7", "8"]
$limits_metadata.LRANGE("#{map_prefix}dataview", 0, 2).should == ["4", "10", "11"]
$limits_metadata.LRANGE("#{map_prefix}dataview_search", 0, 2).should == ["4", "10", "11"]
$limits_metadata.LRANGE("#{map_prefix}analysis", 0, 2).should == ["5", "13", "14"]
$limits_metadata.LRANGE("#{map_prefix}tile", 0, 5).should == ["6", "16", "17", "30", "32", "34"]
$limits_metadata.LRANGE("#{map_prefix}attributes", 0, 2).should == ["7", "19", "20"]
$limits_metadata.LRANGE("#{map_prefix}named_list", 0, 2).should == ["8", "22", "23"]
$limits_metadata.LRANGE("#{map_prefix}named_create", 0, 2).should == ["9", "25", "26"]
$limits_metadata.LRANGE("#{map_prefix}named_get", 0, 2).should == ["10", "28", "29"]
$limits_metadata.LRANGE("#{map_prefix}named", 0, 2).should == ["11", "31", "32"]
$limits_metadata.LRANGE("#{map_prefix}named_update", 0, 2).should == ["12", "34", "35"]
$limits_metadata.LRANGE("#{map_prefix}named_delete", 0, 2).should == ["13", "37", "38"]
$limits_metadata.LRANGE("#{map_prefix}named_tiles", 0, 2).should == ["14", "40", "41"]
$limits_metadata.LRANGE("#{map_prefix}analysis_catalog", 0, 2).should == ["111", "111", "111"]
$limits_metadata.LRANGE("#{sql_prefix}query", 0, 2).should == ["1", "1", "2"]
$limits_metadata.LRANGE("#{sql_prefix}query_format", 0, 2).should == ["2", "4", "5"]
$limits_metadata.LRANGE("#{sql_prefix}job_create", 0, 2).should == ["3", "7", "8"]
$limits_metadata.LRANGE("#{sql_prefix}job_get", 0, 2).should == ["4", "10", "11"]
$limits_metadata.LRANGE("#{sql_prefix}job_delete", 0, 2).should == ["5", "13", "14"]
$limits_metadata.LRANGE("#{sql_prefix}copy_from", 0, 2).should == ["2", "2", "61"]
$limits_metadata.LRANGE("#{sql_prefix}copy_to", 0, 2).should == ["2", "2", "61"]
end
def expect_rate_limits_exist(username)
map_prefix = "limits:rate:store:#{username}:maps:"
sql_prefix = "limits:rate:store:#{username}:sql:"
$limits_metadata.EXISTS("#{map_prefix}anonymous").should eq 0
$limits_metadata.EXISTS("#{map_prefix}static").should eq 0
$limits_metadata.EXISTS("#{map_prefix}static_named").should eq 0
$limits_metadata.EXISTS("#{map_prefix}dataview").should eq 0
$limits_metadata.EXISTS("#{map_prefix}dataview_search").should eq 0
$limits_metadata.EXISTS("#{map_prefix}analysis").should eq 0
$limits_metadata.EXISTS("#{map_prefix}tile").should eq 0
$limits_metadata.EXISTS("#{map_prefix}attributes").should eq 0
$limits_metadata.EXISTS("#{map_prefix}named_list").should eq 0
$limits_metadata.EXISTS("#{map_prefix}named_create").should eq 0
$limits_metadata.EXISTS("#{map_prefix}named_get").should eq 0
$limits_metadata.EXISTS("#{map_prefix}named").should eq 0
$limits_metadata.EXISTS("#{map_prefix}named_update").should eq 0
$limits_metadata.EXISTS("#{map_prefix}named_delete").should eq 0
$limits_metadata.EXISTS("#{map_prefix}named_tiles").should eq 0
$limits_metadata.EXISTS("#{map_prefix}analysis_catalog").should eq 0
$limits_metadata.EXISTS("#{sql_prefix}query").should eq 0
$limits_metadata.EXISTS("#{sql_prefix}query_format").should eq 0
$limits_metadata.EXISTS("#{sql_prefix}job_create").should eq 0
$limits_metadata.EXISTS("#{sql_prefix}job_get").should eq 0
$limits_metadata.EXISTS("#{sql_prefix}job_delete").should eq 0
$limits_metadata.EXISTS("#{sql_prefix}copy_from").should eq 0
$limits_metadata.EXISTS("#{sql_prefix}copy_to").should eq 0
end
end
+54
View File
@@ -0,0 +1,54 @@
module SpecHelperHelpers
def clean_redis_databases
$tables_metadata.flushdb
$api_credentials.flushdb
$users_metadata.flushdb
end
def clean_metadata_database
if ENV['STELLAR'].present?
SequelRails.connection.disconnect
system "#{ENV['STELLAR']} restore"
else
protected_tables = [:schema_migrations, :spatial_ref_sys]
SequelRails.connection.tables.each do |t|
if !protected_tables.include?(t)
begin
SequelRails.connection.run("TRUNCATE TABLE \"#{t}\" CASCADE")
rescue Sequel::DatabaseError => e
raise e unless e.message =~ /PG::Error: ERROR: relation ".*" does not exist/
end
end
end
end
end
def close_pool_connections
# To avoid Travis and connection leaks
$pool.close_connections!
end
def drop_leaked_test_user_databases
user_database_names = SequelRails.connection[
"SELECT datname FROM pg_database WHERE datistemplate IS FALSE AND datallowconn IS TRUE AND datname like 'cartodb_test_user_%'"
]
user_database_names.map(:datname).each do |user_database_name|
puts "Dropping leaked test database #{user_database_name}"
CartoDB::UserModule::DBService.terminate_database_connections(
user_database_name, ::SequelRails.configuration.environment_for(Rails.env)['host']
)
SequelRails.connection.run("drop database \"#{user_database_name}\"")
end
end
def delete_database_test_users
usernames = SequelRails.connection['SELECT u.usename FROM pg_catalog.pg_user u'].map do |r|
r.values.first
end
usernames.each do |username|
SequelRails.connection.run("drop user \"#{username}\"") if username =~ /^test_cartodb_user_/
end
end
end
+9
View File
@@ -0,0 +1,9 @@
module StorageHelper
def bypass_storage(identifier: 'es/co/bar.png', url: "https://manolo.es/#{identifier}")
Carto::Storage.instance.stubs(:s3_enabled?).returns(false)
Carto::StorageOptions::Local.any_instance
.stubs(:upload)
.returns([identifier, url])
Carto::StorageOptions::Local.any_instance.stubs(:remove)
end
end
+11
View File
@@ -0,0 +1,11 @@
def stub_domainful(subdomain)
CartoDB.stubs(:session_domain).returns('.localhost.lan')
CartoDB.stubs(:subdomainless_urls?).returns(false)
host! "#{subdomain}.localhost.lan"
end
def stub_subdomainless
CartoDB.stubs(:session_domain).returns('localhost.lan')
CartoDB.stubs(:subdomainless_urls?).returns(true)
host! "localhost.lan"
end
+33
View File
@@ -0,0 +1,33 @@
# These helpers are meant to be used in tests to generate random data which may collide, e.g: usernames
# The methods are parameter-less in purpose, so it is easier to modify the name generation if needed
module UniqueNamesHelper
@@item_count = 0
def unique_string
sprintf '%08d', unique_integer
end
def unique_email
r = unique_string
"e#{r}@d#{r}.com"
end
def unique_name(prefix)
prefix + unique_string
end
def unique_integer
@@item_count += 1
test_run_id * 1000000 + @@item_count
end
def test_run_id
@@test_run_id ||= if ENV['PARALLEL_SEQ']
ENV['PARALLEL_SEQ'].to_i
elsif ENV['CHECK_SPEC']
ENV['CHECK_SPEC'].to_i
else
0
end
end
end
+49
View File
@@ -0,0 +1,49 @@
require 'spec_helper_min'
require_relative '../simplecov_helper'
require_relative '../rspec_configuration'
require_relative '../../lib/carto/url_validator'
class Carto::UrlValidatorInstance
include Carto::UrlValidator
end
describe 'UUIDHelper' do
before(:each) do
@url_validator = Carto::UrlValidatorInstance.new
end
it 'raises an error if the URL is not valid at all' do
expect { @url_validator.validate_url!("foo://bar-zzz.com") }
.to raise_error(Carto::UrlValidator::InvalidUrlError)
end
it 'raises an error if it points to a non-standard port' do
expect { @url_validator.validate_url!("http://example.com:8080") }
.to raise_error(Carto::UrlValidator::InvalidUrlError)
end
it 'raises an error if the IP is blacklisted' do
@url_validator.instance_variable_set("@blacklisted_ip_ranges", [IPAddr.new("169.254.169.1")])
expect { @url_validator.validate_url!("http://169.254.169.1/blob/blub.csv") }
.to raise_error(Carto::UrlValidator::InvalidUrlError)
end
it 'raises an error if the IP belongs to a blacklisted range' do
@url_validator.instance_variable_set("@blacklisted_ip_ranges", [IPAddr.new("10.0.0.0/8")])
expect { @url_validator.validate_url!("http://10.0.0.92/blob/blub.csv") }
.to raise_error(Carto::UrlValidator::InvalidUrlError)
end
it 'does nothing if everything is ok' do
@url_validator.instance_variable_set("@blacklisted_ip_ranges", [IPAddr.new("169.254.169.1")])
@url_validator.validate_url!("http://169.254.169.2/foo.csv")
@url_validator.validate_url!("http://example.com/foo.csv")
@url_validator.validate_url!("https://example.com/bar.kml")
@url_validator.validate_url!("http://example.com/foo.csv:80")
@url_validator.validate_url!("https://example.com/bar.kml:443")
end
it 'allows ftp' do
expect { @url_validator.validate_url!("ftp://example.com") }.to_not raise_error
end
end
+39
View File
@@ -0,0 +1,39 @@
require_relative '../simplecov_helper'
require_relative '../rspec_configuration.rb'
require_relative '../../lib/carto/uuidhelper'
class Carto::UUIDHelperInstance
include Carto::UUIDHelper
end
describe 'UUIDHelper' do
before(:each) do
@uuid_helper = Carto::UUIDHelperInstance.new
end
it 'validates a valid UUID' do
@uuid_helper.is_uuid?('5b632a9e-ae07-11e4-ac8d-080027880ca6').should be(true)
end
it 'validates a random UUID' do
@uuid_helper.is_uuid?(SecureRandom.uuid).should be(true)
end
it 'fails if content prepended' do
@uuid_helper.is_uuid?("hi" + SecureRandom.uuid).should be(false)
end
it 'fails if content appended' do
@uuid_helper.is_uuid?(SecureRandom.uuid + "hola").should be(false)
end
it 'fails if content prepended with newlines' do
@uuid_helper.is_uuid?("hi\n" + SecureRandom.uuid).should be(false)
end
it 'fails if content appended with newlines' do
@uuid_helper.is_uuid?(SecureRandom.uuid + "\nhola").should be(false)
end
end
@@ -0,0 +1,31 @@
module VisualizationDestructionHelper
def expect_visualization_to_be_destroyed(visualization)
visualization_id = visualization.id
map_id = visualization.map.id
layers_ids = visualization.layers.map(&:id)
layers_user_tables_ids = visualization.layers.flat_map(&:layers_user_tables).map(&:id)
widgets_ids = visualization.widgets.map(&:id)
analyses_ids = visualization.analyses.map(&:id)
overlays_ids = visualization.overlays.map(&:id)
permission_id = visualization.permission.id
mapcaps_ids = visualization.mapcaps.map(&:id)
state_id = visualization.state.id
snapshots_ids = visualization.snapshots.map(&:id)
synchronization_id = visualization.synchronization.try(:id)
yield
expect(Carto::Visualization.exists?(visualization_id)).to be_false
expect(Carto::Map.exists?(map_id)).to be_false
expect(Carto::Permission.exists?(permission_id)).to be_false
expect(Carto::State.exists?(state_id)).to be_false
expect(Carto::Synchronization.exists?(synchronization_id)).to be_false if synchronization_id
layers_ids.each { |id| expect(Carto::Layer.exists?(id)).to be_false }
layers_user_tables_ids.each { |id| expect(Carto::LayersUserTable.exists?(id)).to be_false }
widgets_ids.each { |id| expect(Carto::Widget.exists?(id)).to be_false }
analyses_ids.each { |id| expect(Carto::Analysis.exists?(id)).to be_false }
overlays_ids.each { |id| expect(Carto::Overlay.exists?(id)).to be_false }
mapcaps_ids.each { |id| expect(Carto::Mapcap.exists?(id)).to be_false }
snapshots_ids.each { |id| expect(Carto::Snapshot.exists?(id)).to be_false }
end
end
+180
View File
@@ -0,0 +1,180 @@
require 'spec_helper'
require_relative '../../lib/cartodb/stats/api_calls'
require 'helpers/unique_names_helper'
describe CartoDB::Stats::APICalls do
include UniqueNamesHelper
describe "Stats API Calls" do
before(:all) do
@api_calls = CartoDB::Stats::APICalls.new
@default_date_to = Date.today
@default_date_from = @default_date_to - 29.days
@dates = @default_date_to.downto(@default_date_from).map {|d| d.strftime("%Y%m%d")}
@dates_example_values = {}
@dates.each do |d|
@dates_example_values[d] = rand(50)
end
@redis_sources_count = CartoDB::Stats::APICalls::REDIS_SOURCES.length
end
it "should sum correctly api calls from all sources and return array without date" do
@api_calls.stubs(:get_old_api_calls).returns(@dates_example_values.values)
@api_calls.stubs(:get_api_calls_from_redis_source).returns(@dates_example_values)
expected_total_calls = @dates_example_values.values.map {|v| v * (@redis_sources_count + 1)}
@api_calls.get_api_calls_without_dates('wadus', {old_api_calls: true}).should == expected_total_calls
end
it "should sum correctly api calls from all redis sources and return hash with dates" do
@api_calls.stubs(:get_api_calls_from_redis_source).returns(@dates_example_values)
expected_total_calls = {}
@dates_example_values.each do |d, value|
expected_total_calls[d] = value * @redis_sources_count
end
@api_calls.get_api_calls_with_dates('wadus').should == expected_total_calls
end
it "should sum correctly api calls from all redis sources and return absolute value" do
api_calls_value = 3
@api_calls.stubs(:get_total_api_calls_from_redis_source).returns(api_calls_value)
expected_total_calls = @redis_sources_count * api_calls_value
@api_calls.get_total_api_calls('wadus', '123456').should == expected_total_calls
end
it "should sum correctly api calls with custom dates from all redis sources and return hash with dates" do
pending "Find a way to stub only redis call"
@api_calls.stubs(:get_api_calls_from_redis_source).returns(@dates_example_values)
expected_total_calls = {}
@dates_example_values.each do |d, value|
expected_total_calls[d] = value * @redis_sources_count
end
@api_calls.get_api_calls_with_dates(
'wadus',
{
from: (Date.today - 6.days),
to: Date.today
}
).should == expected_total_calls.to_a[0..6].to_h
end
end
describe 'get_api_calls_from_redis_source' do
before(:each) do
@api_calls = CartoDB::Stats::APICalls.new
@username = unique_name('user')
@type = unique_name('type')
@options = { stat_tag: '000d1206-1fed-11e5-9964-080027880ca6' }
end
it 'fetches 30 days in inverse order even if there is no data' do
today = Date.today
first_expected_day = (today - 29.days).strftime('%Y%m%d')
last_expected_day = today.strftime('%Y%m%d')
calls = @api_calls.get_api_calls_from_redis_source('nonexisting_user', 'nonexisting_api_call_type')
calls.count.should == 30
calls.each { |day, count|
count.should == 0
}
calls.first[0] = last_expected_day
calls[last_expected_day].should == 0
calls[first_expected_day].should == 0
end
it 'fetches data' do
key = @api_calls.redis_api_call_key(@username, @type, @options[:stat_tag])
today = Date.strptime('20180501', '%Y%m%d')
# Test data: 3 months, incremental count
date_to = today + 30.days
date_from = today - 60.days
score = 0
scores = {}
date_to.downto(date_from) do |date|
stat_date = date.strftime("%Y%m%d")
score += 1
$users_metadata.ZADD(key, score, stat_date).to_i
scores[stat_date] = score
end
Delorean.time_travel_to today
calls = @api_calls.get_api_calls_from_redis_source(@username, @type, @options)
Delorean.back_to_the_present
calls.count.should == 30
date_to = today
date_from = today - 29.days
date_to.downto(date_from) do |date|
stat_date = date.strftime("%Y%m%d")
calls[stat_date].should eq(scores[stat_date]), "Failed day #{stat_date}, it was #{calls[stat_date]} instead of #{scores[stat_date]}"
end
end
it 'fetches random data from 2 months' do
key = @api_calls.redis_api_call_key(@username, @type, @options[:stat_tag])
today = Date.strptime('20180501', '%Y%m%d')
random_data = {}
(0..365).each do |n|
$users_metadata.ZADD(key, 1, (today - n.days).strftime("%Y%m%d")).to_i
end
requested_days = 60
date_to = today
date_from = today - (requested_days - 1).days
calls = @api_calls.get_api_calls_from_redis_source(@username, @type, @options.merge({from: date_from, to: date_to}))
calls.count.should == requested_days
calls.values.reduce(:+).to_i.should == 60
end
it 'fetches data for one day' do
key = @api_calls.redis_api_call_key(@username, @type, @options[:stat_tag])
today = Date.today
random_data = {}
random_data[(today - 2.days).strftime("%Y%m%d")] = 7
random_data[(today - 1.days).strftime("%Y%m%d")] = 13
random_data.each do |date, score|
$users_metadata.ZADD(key, score, date).to_i
end
chose_date = Date.today - 1.days
calls = @api_calls.get_api_calls_from_redis_source(@username, @type, @options.merge({from: chose_date, to: chose_date}))
calls.count.should == 1
chose_date_key = chose_date.strftime("%Y%m%d")
calls[chose_date_key].should eq(random_data.fetch(chose_date_key, 0)), "Failed day #{chose_date_key}, it was #{calls[chose_date_key]} instead of #{random_data[chose_date_key]}"
end
describe 'should execute ZSCAN' do
it 'once for a single month' do
key = @api_calls.redis_api_call_key(@username, @type)
$users_metadata.stubs(:zscan_each).with(key, match: '201612*').once.returns([])
@api_calls.get_api_calls_from_redis_source(@username, @type, from: '20161201', to: '20161202')
end
it 'twice across month boundaries' do
key = @api_calls.redis_api_call_key(@username, @type)
$users_metadata.stubs(:zscan_each).with(key, match: '201611*').once.returns([])
$users_metadata.stubs(:zscan_each).with(key, match: '201612*').once.returns([])
@api_calls.get_api_calls_from_redis_source(@username, @type, from: '20161101', to: '20161211')
end
it 'twice across year boundaries' do
key = @api_calls.redis_api_call_key(@username, @type)
$users_metadata.stubs(:zscan_each).with(key, match: '201612*').once.returns([])
$users_metadata.stubs(:zscan_each).with(key, match: '201701*').once.returns([])
@api_calls.get_api_calls_from_redis_source(@username, @type, from: '20161231', to: '20170101')
end
end
end
end
@@ -0,0 +1,43 @@
require 'spec_helper_min'
require_relative '../../../../app/controllers/carto/controller_helper'
describe Carto::ImageAssetsService do
describe('#fetch_file') do
it 'reject files that are too big' do
max_size = Carto::ImageAssetsService.new.max_size_in_bytes
IO.stubs(:copy_stream).returns(max_size + 1)
expect {
Carto::ImageAssetsService.new.fetch_file(Tempfile.new(['manolo', '.png']))
}.to raise_error(Carto::UnprocesableEntityError, "resource is too big (> #{max_size} bytes)")
end
it 'validates file dimensions' do
file = File.new(Rails.root + 'spec/support/data/images/1025x1.jpg')
expect {
Carto::ImageAssetsService.new.fetch_file(file)
}.to raise_error(Carto::UnprocesableEntityError, "file is too big, 1024x1024 max")
end
it 'keeps original extension' do
file = Tempfile.new(['test', '.svg'])
file.write('wadus')
file.rewind
uploaded_file = Rack::Test::UploadedFile.new(file)
temp_file = Carto::ImageAssetsService.new.fetch_file(uploaded_file)
temp_file.path.should end_with '.svg'
end
it 'rejects invalid extensions' do
file = Tempfile.new(['test', '.exe'])
file.write('wadus')
file.rewind
uploaded_file = Rack::Test::UploadedFile.new(file)
expect {
Carto::ImageAssetsService.new.fetch_file(uploaded_file)
}.to raise_error(Carto::UnprocesableEntityError, "extension not accepted")
end
end
end
@@ -0,0 +1,36 @@
require 'fileutils'
require 'spec_helper_min'
require_relative '../../../../app/controllers/carto/controller_helper'
require_relative '../../../../lib/carto/configuration'
describe Carto::KuvizAssetsService do
after(:all) do
FileUtils.rmtree(Carto::Conf.new.public_uploads_path + '/kuviz_assets')
end
describe('#fetch_file') do
it 'rejects files that are too big' do
max_size = Carto::KuvizAssetsService.instance.max_size_in_bytes
IO.stubs(:copy_stream).returns(max_size + 1)
expect {
Carto::KuvizAssetsService.instance.fetch_file(StringIO.new('test'))
}.to raise_error(Carto::UnprocesableEntityError, "resource is too big (> #{max_size} bytes)")
end
it 'create a file has html extension' do
temp_file = Carto::KuvizAssetsService.instance.fetch_file(StringIO.new('test'))
temp_file.path.should end_with '.html'
end
end
describe('#upload') do
it 'uploads file and stores in the local system' do
visualization = FactoryGirl.create(:carto_visualization)
storage_info, url = Carto::KuvizAssetsService.instance.upload(visualization, StringIO.new('test'))
storage_info.present?.should be true
url.present?.should be true
end
end
end
@@ -0,0 +1,60 @@
require 'spec_helper_min'
describe Carto::OrganizationImageAssetsService do
describe('#location') do
after(:each) do
Carto::OrganizationImageAssetsService.instance.instance_variable_set(:@location, nil)
end
it 'uses bucket when available' do
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'bucket')
.returns('manolo_bucket')
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'location')
.returns('escobar_location')
Carto::OrganizationImageAssetsService.instance.location.should eq 'manolo_bucket'
end
it 'uses location when available if bucket is not' do
Cartodb.stubs(:get_config).with(:assets, 'organization', 'bucket')
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'location')
.returns('escobar_location')
Carto::OrganizationImageAssetsService.instance.location.should eq 'escobar_location'
end
it 'uses default when neither bucket nor location are available' do
Cartodb.stubs(:get_config).with(:assets, 'organization', 'bucket')
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'location')
default_location = Carto::OrganizationImageAssetsService::DEFAULT_LOCATION
Carto::OrganizationImageAssetsService.instance.location.should eq default_location
end
end
describe('#max_size_in_bytes') do
after(:each) do
Carto::OrganizationImageAssetsService.instance.instance_variable_set(:@max_size_in_bytes, nil)
end
it 'uses conf when available' do
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'max_size_in_bytes')
.returns(123456789)
Carto::OrganizationImageAssetsService.instance.max_size_in_bytes.should eq 123456789
end
it 'uses default when conf is not available' do
Cartodb.stubs(:get_config).with(:assets, 'organization', 'max_size_in_bytes')
default_max_size = Carto::ImageAssetsService.new.max_size_in_bytes
Carto::OrganizationImageAssetsService.instance.max_size_in_bytes.should eq default_max_size
end
end
end
+118
View File
@@ -0,0 +1,118 @@
require_relative '../../spec_helper_min.rb'
require_relative '../../../lib/carto/bolt.rb'
module Carto
describe Bolt do
before(:each) do
@bolt = Carto::Bolt.new('manolo_bolt_locked')
end
it 'should expect block' do
expect { @bolt.run_locked.should_raise }.to raise_error('no code block given')
end
it 'should allow access in block if locked and unlocked automatically' do
bolt = Carto::Bolt.new('manolo_bolt_locked', ttl_ms: 60000)
bolt.run_locked {}.should be_true
bolt.run_locked {}.should be_true
end
it 'should not allow access if locked' do
other_bolt = Carto::Bolt.new('manolo_bolt_locked')
@bolt.run_locked { other_bolt.run_locked {}.should be_false }.should be_true
end
it 'should wait for execution if we pass attempts parameters' do
main = Thread.new do
@bolt.run_locked {
sleep(2)
}.should be_true
end
sleep(0.5)
thr = Thread.new do
flag = false
Carto::Bolt.new('manolo_bolt_locked').run_locked(attempts: 5, timeout: 1000) { flag = true }
flag.should be_true
end
thr.join
main.join
end
it 'should wait for execution and exit without complete it if timeout and retries reach the limit' do
main = Thread.new do
@bolt.run_locked {
sleep(2)
}.should be_true
end
sleep(0.5)
thr = Thread.new do
flag = false
Carto::Bolt.new('manolo_bolt_locked').run_locked(attempts: 5, timeout: 1) { flag = true }
flag.should be_false
end
thr.join
main.join
end
it 'should retry an execution when other process tries to acquire bolt and has retriable flag set' do
main = Thread.new do
flag = 0
@bolt.run_locked(rerun_func: lambda { flag += 1 }) {
flag += 1
sleep(2)
}.should be_true
flag.should eq(2)
end
sleep(0.5)
thr = Thread.new do
Carto::Bolt.new('manolo_bolt_locked').run_locked {}.should be_false
end
thr.join
main.join
end
it 'should execute once the rerun_func part despite of the number of calls to acquire the lock' do
main = Thread.new do
flag = 0
rerun_func = lambda do
sleep(1)
flag += 1
end
@bolt.run_locked(rerun_func: rerun_func) {
flag += 1
sleep(2)
}.should be_true
flag.should > 2
end
sleep(0.5)
20.times do
t = Thread.new do
Carto::Bolt.new('manolo_bolt_locked').run_locked {}
sleep(0.25)
end
t.join
end
main.join
end
it 'should raise error if rerun_func is not a lambda' do
expect {
@bolt.run_locked(rerun_func: "lala") {}.should_raise
}.to raise_error('no proc/lambda passed as rerun_func')
end
it 'should expire a lock after ttl_ms' do
ttl_ms = 200
bolt = Carto::Bolt.new('manolo_bolt_locked', ttl_ms: ttl_ms)
bolt.run_locked do
sleep((ttl_ms * 2 / 1000.0).second)
Carto::Bolt.new('manolo_bolt_locked').run_locked {}.should be_true
end
end
end
end
+113
View File
@@ -0,0 +1,113 @@
require_relative '../../../../lib/carto/db/sanitize.rb'
module Carto
module DB
module Sanitize
describe '#sanitize_identifier' do
it 'should prepend table_ to identifiers starting with numbers' do
identifier = '13102016manolo'
sanitized_identifier = 'table_13102016manolo'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should prepend table_ to identifiers starting with _' do
identifier = '_manolo'
sanitized_identifier = 'table_manolo'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should strip whitespaces' do
identifier = ' manolo '
sanitized_identifier = 'manolo'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should remove repated _' do
identifier = 'manolo___________escobar'
sanitized_identifier = 'manolo_escobar'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should convert to lowercase' do
identifier = 'MaNoLo'
sanitized_identifier = 'manolo'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should replace whitespaces with _' do
identifier = 'manolo escobar'
sanitized_identifier = 'manolo_escobar'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should replace disallowed characters with _' do
identifier = 'mañolo!#^èscœbar'
sanitized_identifier = 'ma_olo_sc_bar'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should do all together' do
identifier = '!#_ where'
sanitized_identifier = 'table_where'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should append _t to identifiers that are RESERVED_WORDS' do
Carto::DB::Sanitize::RESERVED_WORDS.each do |reserved_word_identifier|
sanitized_identifier = reserved_word_identifier + '_t'
Carto::DB::Sanitize.sanitize_identifier(reserved_word_identifier).should eq sanitized_identifier
end
end
it 'should append _t to identifiers that are RESERVED_TABLE_NAMES' do
Carto::DB::Sanitize::RESERVED_TABLE_NAMES.each do |reserved_word_identifier|
sanitized_identifier = reserved_word_identifier + '_t'
Carto::DB::Sanitize.sanitize_identifier(reserved_word_identifier).should eq sanitized_identifier
end
end
it 'should append _t to identifiers that are SYSTEM_TABLE_NAMES' do
Carto::DB::Sanitize::SYSTEM_TABLE_NAMES.each do |reserved_word_identifier|
sanitized_identifier = reserved_word_identifier + '_t'
Carto::DB::Sanitize.sanitize_identifier(reserved_word_identifier).should eq sanitized_identifier
end
end
end
describe '#append_with_truncate_and_sanitize' do
it 'should append when length is normal' do
identifier = 'manolo'
suffix = '_escobar'
appended_and_truncated = 'manolo_escobar'
Carto::DB::Sanitize.append_with_truncate_and_sanitize(identifier, suffix).should eq appended_and_truncated
end
it 'should append when identifier is too long' do
identifier = 'manolo' * 12
suffix = '_escobar'
Carto::DB::Sanitize.append_with_truncate_and_sanitize(identifier, suffix).end_with?(suffix).should be_true
end
it 'should raise when suffix is too long' do
identifier = 'manolo' * 12
expect { Carto::DB::Sanitize.append_with_truncate_and_sanitize(identifier, identifier) }.to raise_error
end
end
end
end
end
+81
View File
@@ -0,0 +1,81 @@
require 'carto/db/sql_interface'
require_relative '../../../spec_helper'
module Carto
module Db
describe SqlInterface do
shared_examples 'run examples' do
it 'should work with run' do
db = Carto::Db::SqlInterface.new(@connection)
result = db.run("SELECT version()")
result.should be nil
end
it 'should raise an exception with run' do
expect {
db = Carto::Db::SqlInterface.new(@connection)
db.run("SELECT this_function_does_not_exist()")
}.to raise_error(Carto::Db::SqlInterface::Error,
/ERROR: function this_function_does_not_exist\(\) does not exist/)
end
end
shared_examples 'fetch examples' do
it 'should work with fetch/block' do
db = Carto::Db::SqlInterface.new(@connection)
db.fetch("SELECT version()") do |result|
result[:version].should match /PostgreSQL/
end
end
it 'should work with fetch/noblock' do
db = Carto::Db::SqlInterface.new(@connection)
result = db.fetch("SELECT version()")
result.length.should be 1
result[0][:version].should match /PostgreSQL/
end
it 'should raise an exception with fetch/block' do
db = Carto::Db::SqlInterface.new(@connection)
expect {
db.fetch("SELECT this_function_does_not_exist()") do
nil
end
}.to raise_error(Carto::Db::SqlInterface::Error,
/ERROR: function this_function_does_not_exist\(\) does not exist/)
end
it 'should raise an exception with fetch/noblock' do
db = Carto::Db::SqlInterface.new(@connection)
expect {
db.fetch("SELECT this_function_does_not_exist()")
}.to raise_error(Carto::Db::SqlInterface::Error,
/ERROR: function this_function_does_not_exist\(\) does not exist/)
end
end
context 'with sequel connection' do
before do
options = ::SequelRails.configuration.environment_for(Rails.env)
@connection = ::Sequel.connect(options)
end
after do
@connection.disconnect
end
include_examples 'run examples'
include_examples 'fetch examples'
end
context 'with active record connection' do
before do
@connection = ::ActiveRecord::Base.connection
end
include_examples 'run examples'
include_examples 'fetch examples'
end
end
end
end
+50
View File
@@ -0,0 +1,50 @@
require_relative '../../../spec_helper_min.rb'
require 'carto/db/user_schema'
module Carto
module Db
describe UserSchema do
before(:all) do
@user = ::User[FactoryGirl.create(:carto_user).id]
@user_schema = Carto::Db::UserSchema.new(@user)
end
before(:each) do
bypass_named_maps
end
after(:all) do
@user.destroy
end
def run_in_user_database(query)
@user.in_database.run(query)
end
describe '#table_names' do
it 'Regression for CDB-3446' do
new_name = 'table_'
run_in_user_database(%{
CREATE TABLE #{new_name} ("description" text);
})
@user_schema.table_names.should include(new_name)
end
it 'should find unused names' do
run_in_user_database(%{
CREATE TABLE table_m_nolo_es_co_bar_ ("description" text);
CREATE TABLE table_m_nolo_es_co_bar_1 ("description" text);
CREATE TABLE table_m_nolo_es_co_bar_3 ("description" text);
})
table_names = @user_schema.table_names
table_names.should include('table_m_nolo_es_co_bar_')
table_names.should include('table_m_nolo_es_co_bar_1')
table_names.should include('table_m_nolo_es_co_bar_3')
end
end
end
end
end
+24
View File
@@ -0,0 +1,24 @@
require 'spec_helper_min'
module Carto
describe Definition do
before (:all) { @definition = Carto::Definition.instance }
after (:all) { @definition = nil }
it 'handles inexesitent file paths' do
expect { @definition.load_from_file('/fake/path.json') }.to raise_error do
'Carto::Definition: Couldn\'t read from file'
end
end
it 'doesn\'t read the same file twice' do
file_path = 'lib/assets/javascripts/builder/data/default-cartography.json'
File.expects(:read).with(file_path).returns('{}').at_most(1)
2.times do
@definition.load_from_file(file_path)
end
end
end
end
@@ -0,0 +1,40 @@
require_relative '../../../lib/carto/email_domain_validator.rb'
module Carto
describe EmailDomainValidator do
describe '#validate email domains against whitelist' do
it 'validate a non-wildcard domain' do
Carto::EmailDomainValidator.validate_domain('test@carto.com', ['carto.com']).should == true
end
it 'Error on not whitelisted domain' do
Carto::EmailDomainValidator.validate_domain('test@test.com', ['carto.com']).should == false
end
it 'validate wildcard domain' do
Carto::EmailDomainValidator.validate_domain('test@a.carto.com', ['*.carto.com']).should == true
Carto::EmailDomainValidator.validate_domain('test@a.b.carto.com', ['*.carto.com']).should == true
Carto::EmailDomainValidator.validate_domain('test@a.b.c.carto.com', ['*.carto.com']).should == true
end
it 'validate subdomains wildcard' do
Carto::EmailDomainValidator.validate_domain('test@b.a.carto.com', ['*.a.carto.com']).should == true
Carto::EmailDomainValidator.validate_domain('test@b.b.carto.com', ['*.a.carto.com']).should == false
end
it 'validate multiple domains' do
whitelisted_domains = ['carto.com', '*.mit.edu', '*.cs.harvard.edu']
Carto::EmailDomainValidator.validate_domain('test@carto.com', whitelisted_domains).should == true
Carto::EmailDomainValidator.validate_domain('test@a.b.carto.com', whitelisted_domains).should == false
Carto::EmailDomainValidator.validate_domain('test@cs.mit.edu', whitelisted_domains).should == true
Carto::EmailDomainValidator.validate_domain('test@a.cs.harvard.edu', whitelisted_domains).should == true
Carto::EmailDomainValidator.validate_domain('test@a.bio.harvard.edu', whitelisted_domains).should == false
Carto::EmailDomainValidator.validate_domain('test@nonwhite.com', whitelisted_domains).should == false
end
it 'does not fail validating empty emails' do
Carto::EmailDomainValidator.validate_domain('', []).should == false
end
end
end
end
@@ -0,0 +1,22 @@
require_relative '../../../../lib/carto/file_system/sanitize.rb'
module Carto
module FileSystem
module Sanitize
describe '#sanitize_identifier' do
it 'should allow for non ascii characters' do
identifier = 'MæđrÏD, ĘŠPªÑÅ'
Carto::FileSystem::Sanitize.sanitize_identifier(identifier).should eq identifier
end
it 'should sanitize disallowed characters' do
identifier = "M\x00æđ/rÏD,\\:* : ! ? \" ' >Ę<ŠP|ªÑÅ"
sanitized_identifier = 'M_æđ_rÏD,___ _ ! _ _ \' _Ę_ŠP_ªÑÅ'
Carto::FileSystem::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
end
end
end
end
+53
View File
@@ -0,0 +1,53 @@
require 'active_support'
require 'active_support/core_ext/object/blank'
require_relative '../../../lib/carto/filename_generator'
describe Carto::FilenameGenerator do
class TestFilenameGenerator
include Carto::FilenameGenerator
end
describe '#filename_from_url' do
let(:generator) { TestFilenameGenerator.new }
let(:supported_extensions) { %w(.csv .carto .zip) }
it 'returns the full url for just filenames' do
['a_name.csv', 'a_name.carto'].each do |filename|
generator.filename_from_url(filename, supported_extensions).should eq filename
end
end
it 'returns nil for unsupported extensions' do
['a_name.wadus', 'a_name'].each do |filename|
generator.filename_from_url(filename, supported_extensions).should be_nil
end
end
it 'returns the filename for normal urls' do
[%w(http://carto.com/name.csv name.csv), %w(http://carto.com/sub/file.carto file.carto)].each do |url, filename|
generator.filename_from_url(url, supported_extensions).should eq filename
end
end
it 'returns the filename for filenames with dots and other characters' do
name1 = 'name.something (one - two : three).carto'
[
%w(http://carto.com/name.something.csv name.something.csv),
["http://carto.com/sub/#{name1.gsub(' ', '%20')}", name1]
].each do |url, filename|
generator.filename_from_url(url, supported_extensions).should eq filename
end
end
it 'returns the filename for weirds URLs with filenames within the parameters (see #5704)' do
inner_filename = '1259030001_MB_2011_ASGC_NSW_csv.zip'
weird_url = "http://www.abs.gov.au/ausstats/subscriber.nsf/log?openagent&"\
"#{inner_filename}"\
"&1259.0.30.001&Data%20Cubes&2787F2FFF3F6E607CA2578CC001268ED&0&July%202011&05.10.2011&Latest"
[[weird_url, inner_filename]].each do |url, filename|
generator.filename_from_url(url, supported_extensions).should eq filename
end
end
end
end
+171
View File
@@ -0,0 +1,171 @@
require 'spec_helper_min'
module Carto
describe Form do
let(:point_form) do
{
"fill" => {
"size" => {
"fixed" => 7
},
"color" => {
"fixed" => "#EE4D5A",
"opacity" => 0.9
}
},
"stroke" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"blending" => "none",
"aggregation" => {},
"labels" => {
"enabled" => false,
"attribute" => nil,
"font" => "DejaVu Sans Book",
"fill" => {
"size" => {
"fixed" => 10
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"halo" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#6F808D",
"opacity" => 1
}
},
"offset" => -10,
"overlap" => true,
"placement" => "point"
}
}
end
let(:polygon_form) do
{
"fill" => {
"color" => {
"fixed" => "#826DBA",
"opacity" => 0.9
}
},
"stroke" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 0.5
}
},
"blending" => "none",
"aggregation" => {},
"labels" => {
"enabled" => false,
"attribute" => nil,
"font" => "DejaVu Sans Book",
"fill" => {
"size" => {
"fixed" => 10
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"halo" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#6F808D",
"opacity" => 1
}
},
"offset" => -10,
"overlap" => true,
"placement" => "point"
}
}
end
let(:line_form) do
{
"fill" => {},
"stroke" => {
"size" => {
"fixed" => 1.5
},
"color" => {
"fixed" => "#4CC8A3",
"opacity" => 1
}
},
"blending" => "none",
"aggregation" => {},
"labels" => {
"enabled" => false,
"attribute" => nil,
"font" => "DejaVu Sans Book",
"fill" => {
"size" => {
"fixed" => 10
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"halo" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#6F808D",
"opacity" => 1
}
},
"offset" => -10,
"overlap" => true,
"placement" => "point"
}
}
end
it 'return point form if inexisting type is specified' do
form = Carto::Form.new('geometry')
form.to_hash.should eq point_form
end
it 'returns a correct line form' do
form = Carto::Form.new('line')
form.to_hash.should eq line_form
end
it 'returns a correct polygon form' do
form = Carto::Form.new('polygon')
form.to_hash.should eq polygon_form
end
it 'returns a correct point form' do
form = Carto::Form.new('point')
form.to_hash.should eq point_form
end
end
end
+62
View File
@@ -0,0 +1,62 @@
require 'spec_helper_min'
module Carto
describe 'Forms definition' do
let(:production_forms_definition) do
{
"fill" => {
"size" => {
"fixed" => 7
},
"color" => {
"fixed" => "#EE4D5A",
"opacity" => 0.9
}
},
"stroke" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"blending" => "none",
"aggregation" => {},
"labels" => {
"enabled" => false,
"attribute" => nil,
"font" => "DejaVu Sans Book",
"fill" => {
"size" => {
"fixed" => 10
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"halo" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#6F808D",
"opacity" => 1
}
},
"offset" => -10,
"overlap" => true,
"placement" => "point"
}
}
end
it 'default has stayed the same' do
file_path = Carto::Form::DEFAULT_FORMS_DEFINITION_LOCATION
Carto::Definition.instance.load_from_file(file_path).should eq production_forms_definition
end
end
end
+403
View File
@@ -0,0 +1,403 @@
require_relative '../../spec_helper_min.rb'
require_relative '../../../lib/carto/ghost_tables_manager'
require 'helpers/database_connection_helper'
module Carto
describe GhostTablesManager do
include DatabaseConnectionHelper
before(:all) do
@sequel_user = FactoryGirl.create(:valid_user)
@user = Carto::User.find(@sequel_user.id)
@ghost_tables_manager = Carto::GhostTablesManager.new(@user.id)
end
before(:each) do
bypass_named_maps
CartoDB::Logger.expects(:error).never
end
after(:all) do
::User[@user.id].destroy
end
def run_in_user_database(query)
::User[@user.id].in_database.run(query)
end
it 'should be consistent when no new/renamed/dropped tables' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should not run sync when more than MAX_TABLES_FOR_SYNC_RUN need to be linked' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns([*1..Carto::GhostTablesManager::MAX_TABLES_FOR_SYNC_RUN])
@ghost_tables_manager.send(:should_run_synchronously?).should be_false
end
it 'should not run sync when more than MAX_TABLES_FOR_SYNC_RUN need to be linked including new tables' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns(['manolo'])
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns([*1..Carto::GhostTablesManager::MAX_TABLES_FOR_SYNC_RUN])
@ghost_tables_manager.send(:should_run_synchronously?).should be_false
end
it 'should run sync when more than 0 but less than MAX_TABLES_FOR_SYNC_RUN need to be linked' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns([*1..(Carto::GhostTablesManager::MAX_TABLES_FOR_SYNC_RUN - 1)])
@ghost_tables_manager.send(:should_run_synchronously?).should be_true
end
it 'should not run sync when no tables are stale or dropped' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns([])
@ghost_tables_manager.stubs(:find_new_tables)
.returns([*1..Carto::GhostTablesManager::MAX_TABLES_FOR_SYNC_RUN])
@ghost_tables_manager.send(:should_run_synchronously?).should be_false
end
it 'should not run when no tables are changed with tables detected as raster and non-raster' do
raster_tables = [Carto::TableFacade.new(123, 'manolito', @user.id)]
@ghost_tables_manager.stubs(:fetch_non_raster_cartodbfied_tables).returns(raster_tables)
@ghost_tables_manager.stubs(:fetch_raster_tables).returns(raster_tables)
@ghost_tables_manager.stubs(:fetch_user_tables).returns(raster_tables)
@ghost_tables_manager.send(:user_tables_synced_with_db?).should be_true
end
it 'should link sql created table, relink sql renamed tables and unlink sql dropped tables' do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'manoloescobar'
run_in_user_database(%{
ALTER TABLE manoloescobar RENAME TO escobar;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'escobar'
run_in_user_database(%{
DROP TABLE escobar;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should link sql created table using regular api key with create permissions' do
grants = [
{
type: 'apis',
apis: ['maps', 'sql']
},
{
type: "database",
schemas: [
{
name: "#{@user.database_schema}",
permissions: ['create']
}
]
}
]
api_key = @user.api_keys.create_regular_key!(name: 'ghost_tables', grants: grants)
with_connection_from_api_key(api_key) do |connection|
sql = %{
CREATE TABLE test_table ("description" text);
SELECT * FROM CDB_CartodbfyTable('test_table');
}
connection.execute(sql)
end
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'test_table'
with_connection_from_api_key(api_key) do |connection|
connection.execute('DROP TABLE test_table')
end
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
api_key.destroy
end
it 'should link sql created table using oauth_app api key with create permissions' do
scopes = ['offline', 'user:profile', 'schemas:c']
app = FactoryGirl.create(:oauth_app, user: @user)
oau = OauthAppUser.create!(user: @user, oauth_app: app, scopes: scopes)
refresh_token = oau.oauth_refresh_tokens.create!(scopes: scopes)
access_token = refresh_token.exchange!(requested_scopes: scopes)[0]
with_connection_from_api_key(access_token.api_key) do |connection|
sql = %{
CREATE TABLE test_table ("description" text);
SELECT * FROM CDB_CartodbfyTable('test_table');
}
connection.execute(sql)
end
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'test_table'
with_connection_from_api_key(access_token.api_key) do |connection|
connection.execute('DROP TABLE test_table')
end
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
oau.destroy
app.destroy
end
it 'should not link non cartodbyfied tables' do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.link_ghost_tables_synchronously
run_in_user_database(%{
DROP TABLE manoloescobar;
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should link raster tables' do
run_in_user_database(%{
CREATE TABLE manolo_raster ("cartodb_id" uuid, "the_raster_webmercator" raster);
CREATE TRIGGER test_quota_per_row
BEFORE INSERT OR UPDATE
ON manolo_raster
FOR EACH ROW
EXECUTE PROCEDURE cdb_checkquota('0.001', '-1', 'public');
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 1
@user.tables.first.name.should == 'manolo_raster'
run_in_user_database(%{
DROP TABLE manolo_raster;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should regenerate user tables with bad table_ids' do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'manoloescobar'
user_table = @user.tables.first
original_oid = user_table.table_id
user_table.table_id = original_oid + 1
user_table.save
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'manoloescobar'
@user.tables.first.table_id.should == original_oid
run_in_user_database(%{
DROP TABLE manoloescobar;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should preseve maps in drop create scenarios' do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
original_user_table = @user.tables.first
original_user_table.name.should == 'manoloescobar'
original_user_table_id = original_user_table.id
original_map_id = original_user_table.map.id
run_in_user_database(%{
DROP TABLE manoloescobar;
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.id.should == original_user_table_id
@user.tables.first.map.id.should == original_map_id
run_in_user_database(%{
DROP TABLE manoloescobar;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'perform a successfully ghost tables execution when is called from LinkGhostTablesByUsername' do
Carto::GhostTablesManager.expects(:new).with(@user.id).returns(@ghost_tables_manager).once
@ghost_tables_manager.expects(:link_ghost_tables_synchronously).once
::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTablesByUsername.perform(@user.username)
end
it 'should call the rerun_func and execute sync twice becuase other worker tried to get the lock' do
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
main = Thread.new do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
rerun_func = lambda do
Carto::GhostTablesManager.new(@user.id).send(:sync)
end
gtm = Carto::GhostTablesManager.new(@user.id)
gtm.get_bolt.run_locked(rerun_func: rerun_func) do
sleep(1)
Carto::GhostTablesManager.new(@user.id).send(:sync)
end
end
thr = Thread.new do
run_in_user_database(%{
CREATE TABLE manoloescobar2 ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar2');
})
Carto::GhostTablesManager.new(@user.id).get_bolt.run_locked {}
end
thr.join
main.join
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 2
run_in_user_database(%{
DROP TABLE manoloescobar;
DROP TABLE manoloescobar2;
})
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
end
end
+33
View File
@@ -0,0 +1,33 @@
require_relative '../../spec_helper_min.rb'
require 'carto/google_maps_api'
module Carto
describe GoogleMapsApi do
let(:url) { 'https://maps.googleapis.com/maps/api/staticmap?center=40.714%2c-73.998&zoom=12&size=400x400' }
describe '#sign' do
describe 'with client_id' do
# You can generate test signature with https://developers.google.com/maps/documentation/static-maps/get-api-key
let(:user) do
FactoryGirl.build(:user, google_maps_key: 'client=wds', google_maps_private_key: 'MjM0MzJk-3N_czQzJmFkc2Rhc2Q=')
end
it 'signs google maps urls' do
gmas = GoogleMapsApi.new
expect(gmas.sign_url(user, url)).to eq "#{url}&client=wds&signature=fK5mHV9MLROw4xZnBPcrCOUdjio="
end
end
describe 'with api_key' do
let(:user) do
FactoryGirl.build(:user, google_maps_key: 'key=xxx')
end
it 'signs google maps urls' do
gmas = GoogleMapsApi.new
expect(gmas.sign_url(user, url)).to eq "#{url}&key=xxx"
end
end
end
end
end
+97
View File
@@ -0,0 +1,97 @@
require_relative '../../../spec_helper'
require 'rspec/core'
require 'rspec/expectations'
require 'rspec/mocks'
require 'mocha'
require_relative '../../../../lib/carto/http/client'
require_relative '../../../../lib/carto/http/request'
describe Carto::Http::Client do
before(:each) do
http_client_tag = 'test'
@client = Carto::Http::Client.get(http_client_tag, log_requests: true)
end
describe '#request' do
it 'wraps creation of typhoeus request objects' do
expected_url = 'www.example.com'
expected_options = {
method: :post,
body: "this is a request body",
params: { field1: "a field" },
headers: { Accept: "text/html",
"User-Agent" => Carto::Http::Request::DEFAULT_USER_AGENT
}
}
Typhoeus::Request.expects(:new).once.with(expected_url, expected_options)
@client.request(
"www.example.com",
method: :post,
body: "this is a request body",
params: { field1: "a field" },
headers: { Accept: "text/html", "User-Agent" => Carto::Http::Request::DEFAULT_USER_AGENT}
)
end
end
describe 'Request#run' do
it 'performs a request through its typhoeus request object' do
expected_response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
Typhoeus.stub('www.example.com').and_return(expected_response)
request = @client.request(
"www.example.com",
method: :post,
body: "this is a request body",
params: { field1: "a field" },
headers: { Accept: "text/html", "User-Agent" => Carto::Http::Request::DEFAULT_USER_AGENT}
)
request.run.should eq expected_response
end
end
describe '#get' do
it 'creates a wrapped GET request and runs it' do
expected_response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
Typhoeus.stub("www.example.com").and_return(expected_response)
@client.get("www.example.com").should eq expected_response
end
end
describe '#post' do
it 'creates a wrapped POST request and runs it' do
expected_response = Typhoeus::Response.new(code: 200, body: "{'success': 'true'}")
Typhoeus.stub("www.example.com/posts").and_return(expected_response)
@client.post("www.example.com/posts", body: { title: "test post", content: "this is my test"})
.should eq expected_response
end
end
describe '#head' do
it 'creates a wrapped HEAD request and runs it' do
expected_response = Typhoeus::Response.new(code: 200)
Typhoeus.stub("www.example.com").and_return(expected_response)
@client.head("www.example.com").should eq expected_response
end
end
describe '#put' do
it 'creates a wrapped PUT request and runs it' do
expected_response = Typhoeus::Response.new(code: 200, body: "{'success': 'true'}")
Typhoeus.stub("www.example.com/posts/1").and_return(expected_response)
@client.put("www.example.com/posts/1", body: "whoo, a body").should eq expected_response
end
end
describe '#delete' do
it 'creates a wrapped DELETE request and runs it' do
expected_response = Typhoeus::Response.new(code: 204)
Typhoeus.stub("www.example.com/posts/1").and_return(expected_response)
@client.delete("www.example.com/posts/1").should eq expected_response
end
end
end
@@ -0,0 +1,139 @@
require 'ostruct'
require 'uuidtools'
require_relative '../../spec_helper'
require_relative '../../../lib/carto/http_header_authentication'
require_relative '../../requests/http_authentication_helper'
describe Carto::HttpHeaderAuthentication do
include HttpAuthenticationHelper
EMAIL = "user@carto.com"
USERNAME = "user"
ID = UUIDTools::UUID.timestamp_create.to_s
let(:mock_unauthenticated_request) do
OpenStruct.new(headers: {})
end
let(:mock_email_request) { OpenStruct.new(headers: { "#{authenticated_header}" => EMAIL }) }
let(:mock_username_request) { OpenStruct.new(headers: { "#{authenticated_header}" => USERNAME }) }
let(:mock_id_request) { OpenStruct.new(headers: { "#{authenticated_header}" => ID }) }
let(:mock_user) do
OpenStruct.new(
email: EMAIL
)
end
let(:mock_user_search) { OpenStruct.new(first: mock_user) }
describe '#valid?' do
it 'returns false without configuration' do
Carto::HttpHeaderAuthentication.new.valid?(mock_email_request).should == false
end
it 'returns false with configuration without header' do
stub_http_header_authentication_configuration(field: 'email')
Carto::HttpHeaderAuthentication.new.valid?(mock_unauthenticated_request).should == false
end
it 'returns true with configuration and header' do
stub_http_header_authentication_configuration(field: 'email')
Carto::HttpHeaderAuthentication.new.valid?(mock_email_request).should == true
end
end
describe '#get_user?' do
before(:each) do
stub_http_header_authentication_configuration(field: 'email')
end
it 'returns nil without header' do
Carto::HttpHeaderAuthentication.new.get_user(mock_unauthenticated_request).should be_nil
end
it 'searches by email with header' do
User.expects(:where).with("email = ?", mock_email_request.headers[authenticated_header]).returns mock_user_search
Carto::HttpHeaderAuthentication.new.get_user(mock_email_request).should == mock_user
end
describe 'auto field' do
before(:each) do
stub_http_header_authentication_configuration(field: 'auto')
end
it 'searches by field depending on header' do
User.expects(:where).with("email = ?", mock_email_request.headers[authenticated_header]).once.returns mock_user_search
Carto::HttpHeaderAuthentication.new.get_user(mock_email_request).should == mock_user
User.expects(:where).with("username = ?", mock_username_request.headers[authenticated_header]).once.returns mock_user_search
Carto::HttpHeaderAuthentication.new.get_user(mock_username_request).should == mock_user
User.expects(:where).with("id = ?", mock_id_request.headers[authenticated_header]).once.returns mock_user_search
Carto::HttpHeaderAuthentication.new.get_user(mock_id_request).should == mock_user
end
end
end
describe '#autocreation_enabled?' do
it 'returns autocreation configuration' do
stub_http_header_authentication_configuration(field: 'auto', autocreation: true)
Carto::HttpHeaderAuthentication.new.autocreation_enabled?.should be_true
stub_http_header_authentication_configuration(field: 'auto', autocreation: false)
Carto::HttpHeaderAuthentication.new.autocreation_enabled?.should be_false
end
end
describe '#email' do
let(:authenticator) { Carto::HttpHeaderAuthentication.new }
it 'returns email for email requests' do
stub_http_header_authentication_configuration(field: 'email')
authenticator.email(mock_email_request).should == EMAIL
end
it 'raises error if configuration is not email (or auto with an email in the request)' do
stub_http_header_authentication_configuration(field: 'id')
expect { authenticator.email(mock_email_request) }.to raise_error
stub_http_header_authentication_configuration(field: 'username')
expect { authenticator.email(mock_email_request) }.to raise_error
stub_http_header_authentication_configuration(field: 'auto')
expect { authenticator.email(mock_username_request) }.to raise_error
end
it 'returns email if configuration is auto and request contains an email' do
stub_http_header_authentication_configuration(field: 'auto')
expect { authenticator.email(mock_email_request) }.not_to raise_error
end
end
describe '#creation_in_progress?' do
let(:authenticator) { Carto::HttpHeaderAuthentication.new }
it 'returns true if there is a matching creation in progress by (user) id' do
stub_http_header_authentication_configuration(field: 'id')
uc = FactoryGirl.create(:user_creation, state: 'enqueuing', user_id: ID)
authenticator.creation_in_progress?(mock_id_request).should be_true
uc.destroy
end
it 'returns true if there is a matching creation in progress by username' do
stub_http_header_authentication_configuration(field: 'username')
uc = FactoryGirl.create(:user_creation, state: 'enqueuing', username: USERNAME)
authenticator.creation_in_progress?(mock_username_request).should be_true
uc.destroy
end
it 'returns true if there is a matching creation in progress by email' do
stub_http_header_authentication_configuration(field: 'email')
uc = FactoryGirl.create(:user_creation, state: 'enqueuing', email: EMAIL)
authenticator.creation_in_progress?(mock_email_request).should be_true
uc.destroy
end
it 'returns false if there is not a matching creation in progress' do
stub_http_header_authentication_configuration(field: 'auto')
authenticator.creation_in_progress?(mock_email_request).should be_false
end
end
end
@@ -0,0 +1,259 @@
require 'spec_helper_min'
module Carto
describe LegendDefinitionValidator do
let(:random_definition) do
{
hey: 'teacher',
leave: {
the: 'kids alone'
}
}.with_indifferent_access
end
let(:bubble_definition) do
definition_location = "#{Rails.root}/lib/formats/legends/definitions/bubble.json"
Carto::Definition.instance
.load_from_file(definition_location)
end
describe '#bubble' do
let(:definition) do
{
color: '#fff'
}
end
it 'accepts a correct definition' do
validator = LegendDefinitionValidator.new(:bubble, definition)
validator.errors.should be_empty
end
it 'rejects spammy definitions' do
spammed_definition = definition.merge(spam: 'the ham')
validator = LegendDefinitionValidator.new(:bubble, spammed_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
it 'rejects incomplete definitions' do
incomplete_definition = definition.except(:color)
validator = LegendDefinitionValidator.new(:bubble, incomplete_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not contain a required property')
end
it 'rejects wrongly formatted colors' do
definition[:color] = '#notcool'
validator = LegendDefinitionValidator.new(:bubble, definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not match the regex')
end
end
describe '#choropleth' do
let(:definition) do
{
prefix: "123",
suffix: "foo"
}
end
it 'accepts a correct definition' do
validator = LegendDefinitionValidator.new(:choropleth, definition)
validator.errors.should be_empty
end
it 'rejects spammy definitions' do
spammed_definition = definition.merge(spam: 'the ham')
validator = LegendDefinitionValidator.new(:choropleth,
spammed_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
it 'doesn\'t reject incomplete definitions' do
incomplete_definition = definition.except(:prefix)
validator = LegendDefinitionValidator.new(:choropleth,
incomplete_definition)
validator.errors.should be_empty
end
end
describe '#custom' do
let(:definition) do
{
categories: [
{ title: 'Manolo Escobar' },
{ title: 'Manolo Escobar', color: '#fff' },
{ title: 'Manolo Escobar', icon: 'super.png' },
{ title: 'Manolo Escobar', icon: 'super.png', color: '#fff' }
]
}
end
it 'accepts a correct definition' do
validator = LegendDefinitionValidator.new(:custom, definition)
validator.errors.should be_empty
end
it 'rejects spammy definitions' do
spammed_definition = definition.merge(spam: 'the ham')
validator = LegendDefinitionValidator.new(:custom,
spammed_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
it 'rejects incomplete definitions' do
incomplete_definition = definition.except(:categories)
validator = LegendDefinitionValidator.new(:custom,
incomplete_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not contain a required property')
end
describe 'categories' do
it 'rejects spammy definitions' do
definition[:categories] << { title: 'foo', spam: 'the ham' }
validator = LegendDefinitionValidator.new(:custom, definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
it 'rejects incomplete definitions' do
definition[:categories] << {}
validator = LegendDefinitionValidator.new(:custom, definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not contain a required property')
end
it 'rejects wrongly formatted colors' do
definition[:categories] << { color: '#notcool' }
validator = LegendDefinitionValidator.new(:custom, definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not match the regex')
end
end
end
describe '#category' do
let(:definition) do
{}
end
it 'accepts a correct definition' do
validator = LegendDefinitionValidator.new(:category, definition)
validator.errors.should be_empty
end
it 'rejects spammy definitions' do
spammed_definition = definition.merge(spam: 'the ham')
validator = LegendDefinitionValidator.new(:category, spammed_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
end
describe '#errors' do
it 'handles non defined schemas' do
validator = LegendDefinitionValidator.new(nil, random_definition)
validator.errors.should eq ['could not be validated']
end
it 'handles invalid definitions' do
validator = LegendDefinitionValidator.new(:bubble, nil)
validator.errors.should eq ['could not be validated']
end
it 'performs validations' do
validator = LegendDefinitionValidator
.new(:bubble, random_definition)
validation_errors = validator.errors
validation_errors.should_not be_empty
validation_errors.should_not include('could not be validated')
end
it 'is memoized' do
validator = LegendDefinitionValidator.new('bubble', random_definition)
JSON::Validator.expects(:fully_validate)
.with(bubble_definition, random_definition)
.returns(['Cool validation error'])
.once
2.times { validator.errors.should_not be_empty }
end
end
describe '#location' do
it 'returns nil for inexistent locations' do
validator = LegendDefinitionValidator.new(:foo, nil)
validator.send(:location).should be_nil
end
it 'returns location for existent locations' do
validator = LegendDefinitionValidator.new(:bubble, nil)
expected_location = "#{Rails.root}/lib/formats/legends/definitions/bubble.json"
validator.send(:location).should eq expected_location
end
it 'returns location for existent locations when type is string' do
validator = LegendDefinitionValidator.new('bubble', nil)
expected_location = "#{Rails.root}/lib/formats/legends/definitions/bubble.json"
validator.send(:location).should eq expected_location
end
it 'is memoized' do
expected_location = "#{Rails.root}/lib/formats/legends/definitions/bubble.json"
validator = LegendDefinitionValidator.new('bubble', nil)
File.expects(:exists?)
.with(expected_location)
.returns(true)
.once
validator.send(:location).should eq expected_location
validator.send(:location).should eq expected_location
end
end
end
end
+507
View File
@@ -0,0 +1,507 @@
require 'spec_helper_min'
require 'carto/legend_migrator'
module Carto
describe LegendMigrator do
before(:all) do
@layer = Carto::Layer.create(kind: 'carto')
end
after(:all) do
@layer.destroy
end
describe('#with templates') do
let(:old_legend_with_template) do
{
"show_title" => false,
"title" => "",
"template" => "<h1>Manolo Escobar</h1>",
"visible" => true,
"items" => []
}
end
it 'should migrate to custom for type custom' do
@old_legend = old_legend_with_template.merge(type: 'custom')
end
it 'should migrate to custom for type category' do
@old_legend = old_legend_with_template.merge(type: 'category')
end
it 'should migrate to custom for type bubble' do
@old_legend = old_legend_with_template.merge(type: 'bubble')
end
it 'should migrate to custom for type choropleth' do
@old_legend = old_legend_with_template.merge(type: 'choropleth')
end
it 'should migrate to custom for type intensity' do
@old_legend = old_legend_with_template.merge(type: 'intensity')
end
it 'should migrate to custom for type density' do
@old_legend = old_legend_with_template.merge(type: 'density')
end
after(:each) do
new_legend = Carto::LegendMigrator.new(@layer.id, @old_legend).build
new_legend.definition[:html].should include('<h1>Manolo Escobar</h1>')
new_legend.type.should eq 'custom'
end
end
describe('#custom types') do
describe('#with categories') do
let(:old_category) do
{
"type" => "category",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => 0,
"visible" => true,
"value" => "super.png"
},
{
"name" => 1350,
"visible" => true,
"value" => "#1F78B4"
},
{
"name" => 1440,
"visible" => true,
"value" => "#B2DF8A"
},
{
"name" => 1800,
"visible" => true,
"value" => "#33A02C"
},
{
"name" => 2250,
"visible" => true,
"value" => "#FB9A99"
},
{
"name" => 2700,
"visible" => true,
"value" => "#E31A1C"
},
{
"name" => 4500,
"visible" => true,
"value" => "#FDBF6F"
},
{
"name" => 450000,
"visible" => true,
"value" => "#FF7F00"
},
{
"name" => 900,
"visible" => true,
"value" => "duper.png"
},
{
"name" => 90000,
"visible" => true,
"value" => "#6A3D9A"
},
{
"name" => "Others",
"visible" => true,
"value" => "#DDDDDD"
}
]
}
end
let(:old_custom) do
{
"type" => "custom",
"show_title" => true,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "preta",
"visible" => true,
"value" => "#41006D",
"sync" => true
},
{
"name" => "Untitled",
"visible" => true,
"value" => "url(http://com.cartodb.users-assets.production.s3.amazonaws.com/superduper.png)",
"sync" => true
},
{
"name" => "patata",
"visible" => true,
"value" => "#cccccc",
"sync" => true
},
{
"name" => "Untitled",
"visible" => true,
"value" => "#cccccc",
"sync" => true
}
]
}
end
it 'omits badly formatted colors' do
truncated = old_category.dup
truncated['items'].first['value'] = '#fatal#fatal#fatal'
@old_legend = truncated
end
it 'migrates old category to new custom' do
@old_legend = old_category
end
it 'migrates old custom to new custom with no template' do
@old_legend = old_custom
end
after(:each) do
new_legend = Carto::LegendMigrator.new(@layer.id, @old_legend).build
new_legend.type.should eq 'custom'
new_legend.valid?.should be_true
category_keys = new_legend.definition[:categories]
.map(&:keys)
.flatten
.uniq
category_keys.should include(:title)
category_keys.should include(:color)
category_keys.should include(:icon)
if @old_legend['type'] == 'custom'
new_legend.definition[:categories].map { |category| category[:icon] }.each do |icon|
unless icon.blank?
icon.should include("https://s3.amazonaws.com/com.cartodb.users-assets.production/superduper.png")
icon.should_not include("url(")
end
end
end
end
end
describe('#with html') do
let(:old_custom) do
{
"type" => "custom",
"show_title" => true,
"title" => "",
"template" => "<h1>Manolo Escobar</h1>",
"visible" => true,
"items" => []
}
end
let(:old_bubble) do
{
"type" => "bubble",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Left label",
"visible" => true,
"value" => 787.5,
"legend_type" => "bubble",
"type" => "text",
"sync" => false
},
{
"name" => "Right Label",
"visible" => true,
"value" => 6273765,
"legend_type" => "bubble",
"type" => "text",
"sync" => false
},
{
"name" => "Color",
"visible" => true,
"value" => "#FF5C00",
"type" => "color"
}
]
}
end
let(:old_bubble_with_custom_labels) do
{
"type" => "bubble",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Left label",
"visible" => true,
"value" => "few",
"legend_type" => "bubble",
"type" => "text",
"sync" => false
},
{
"name" => "Right Label",
"visible" => true,
"value" => "many",
"legend_type" => "bubble",
"type" => "text",
"sync" => false
},
{
"name" => "Color",
"visible" => true,
"value" => "#FF5C00",
"type" => "color"
}
]
}
end
let(:old_choropleth) do
{
"type" => "choropleth",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Left label",
"visible" => true, "value" => "1350.00",
"type" => "text"
},
{
"name" => "Right label",
"visible" => true,
"value" => "6273765.00",
"type" => "text"
},
{
"name" => "Color",
"visible" => true, "value" => "#FFFFB2",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FED976",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FEB24C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FD8D3C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FC4E2A",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#E31A1C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#B10026",
"type" => "color"
}
]
}
end
let(:old_density) do
{
"type" => "density",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Less",
"visible" => true,
"value" => "less",
"legend_type" => "density",
"type" => "text",
"sync" => true
},
{
"name" => "More",
"visible" => true,
"value" => "more",
"legend_type" => "density",
"type" => "text",
"sync" => true
},
{
"name" => "Color",
"visible" => true, "value" => "#FFFFB2",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FECC5C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FD8D3C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#F03B20",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#BD0026",
"type" => "color"
}
]
}
end
let(:old_intensity) do
{
"type" => "intensity",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Left label",
"visible" => true,
"value" => "Less",
"legend_type" => "intensity",
"type" => "text",
"sync" => true
},
{
"name" => "Right label",
"visible" => true,
"value" => "More",
"legend_type" => "intensity",
"type" => "text",
"sync" => true
},
{
"name" => "Color",
"visible" => true, "value" => "#FFCC00",
"type" => "color"
}
]
}
end
it 'migrates old bubble with custom labels to new custom' do
@old_legend = old_bubble_with_custom_labels
end
it 'migrates old custom with template to new custom' do
@old_legend = old_custom
end
it 'migrates old bubble to new custom' do
@old_legend = old_bubble
end
it 'migrates old choropleth to new custom' do
@old_legend = old_choropleth
end
it 'migrates old density to new custom' do
@old_legend = old_density
end
it 'migrates old density without labels to new custom' do
truncated = old_density.dup
truncated['items'].delete_at(0)
truncated['items'].delete_at(0)
@old_legend = truncated
end
it 'migrates old intensity to new custom' do
@old_legend = old_intensity
end
it 'migrates old intensity without labels to new custom' do
truncated = old_intensity.dup
truncated['items'].delete_at(0)
truncated['items'].delete_at(0)
@old_legend = truncated
end
after(:each) do
new_legend = Carto::LegendMigrator.new(@layer.id, @old_legend).build
new_legend.type.should eq 'custom'
new_legend.valid?.should be_true
unless @old_legend['type'] == 'bubble' || @old_legend['type'] == 'custom'
html = new_legend.definition[:html]
# Result should have labels, colors, and icons somewhere in generated
# html
@old_legend['items'].map { |item| item['value'] }.each do |value|
html.downcase.should include(value.to_s.downcase)
end
# No incomplete gradients allowed
html.scan(/#(?:[0-9a-fA-F]{3}){1,2}/).size.should be >= 2
end
@old_legend = nil
end
end
end
describe('#bad legends') do
let(:bad_legend) do
{
patata_pochada: 'buena',
pero: 'mejor que pochada',
frita: 'a que si!'
}
end
it 'returns invalid legends for invalid definitions' do
migrator = Carto::LegendMigrator.new(@layer.id, bad_legend)
migrator.build.should_not be_valid
end
end
end
end
@@ -0,0 +1,55 @@
require 'spec_helper_min'
require 'carto/mapcapped_visualization_updater'
module Carto
describe MapcappedVisualizationUpdater do
include Factories::Visualizations
include MapcappedVisualizationUpdater
before(:all) do
@user = FactoryGirl.create(:carto_user)
@map, @table, @table_visualization, @visualization = create_full_visualization(@user)
end
after(:all) do
destroy_full_visualization(@map, @table, @table_visualization, @visualization)
@user.destroy
end
it 'updates a non-mapcapped visualization' do
Map.any_instance.stubs(:notify_map_change).once
success = update_visualization_and_mapcap(@visualization) do |visualization, persisted|
layer = visualization.layers.first
layer.options[:wadus] = 'wadus!'
layer.save if persisted
end
success.should be_true
@visualization.reload
@visualization.mapcapped?.should be_false
@visualization.layers.first.options[:wadus].should eq 'wadus!'
end
it 'updates a mapcapped visualization' do
@visualization.create_mapcap!
@visualization.layers.first.update_attribute(:tooltip, do_not_publish_this: true)
Map.any_instance.stubs(:notify_map_change).once
success = update_visualization_and_mapcap(@visualization) do |visualization, persisted|
layer = visualization.layers.first
layer.options[:something] = 'else'
layer.save if persisted
end
success.should be_true
@visualization.reload
@visualization.mapcapped?.should be_true
@visualization.layers.first.options[:something].should eq 'else'
mapcap_visualization = @visualization.latest_mapcap.regenerate_visualization
mapcap_visualization.layers.first.options[:something].should eq 'else'
mapcap_visualization.layers.first.tooltip.should be_nil
end
end
end

Some files were not shown because too many files have changed in this diff Show More