Initial commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
module CartoDB
|
||||
module Doubles
|
||||
module Visualization
|
||||
class SupportTables
|
||||
def delete_all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
FactoryGirl.define do
|
||||
factory :automatic_geocoding do
|
||||
to_create(&:save)
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
require 'helpers/unique_names_helper'
|
||||
|
||||
include UniqueNamesHelper
|
||||
|
||||
FactoryGirl.define do
|
||||
factory :feature_flags_user do
|
||||
id { unique_integer }
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
FactoryGirl.define do
|
||||
factory :like, class: CartoDB::Like do
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
FactoryGirl.define do
|
||||
factory :log, class: CartoDB::Log do
|
||||
end
|
||||
|
||||
factory :carto_log, class: Carto::Log do
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
FactoryGirl.define do
|
||||
factory :oauth_app_users, class: Carto::OauthAppUser do
|
||||
to_create(&:save!)
|
||||
|
||||
skip_role_setup true
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
require 'helpers/unique_names_helper'
|
||||
|
||||
include UniqueNamesHelper
|
||||
|
||||
FactoryGirl.define do
|
||||
factory :table, class: Table do
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Vendored
+105
@@ -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
|
||||
]
|
||||
}
|
||||
Vendored
+4287
File diff suppressed because it is too large
Load Diff
+4292
File diff suppressed because it is too large
Load Diff
+45219
File diff suppressed because it is too large
Load Diff
+4064
File diff suppressed because it is too large
Load Diff
+76329
File diff suppressed because it is too large
Load Diff
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
UserMailer#ask_for_invitation
|
||||
|
||||
Hi, find me in app/views/user_mailer/ask_for_invitation
|
||||
+3
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
module MetricsHelper
|
||||
def bypass_metrics
|
||||
Carto::Tracking::Events::Event.any_instance.stubs(:report!).returns(true)
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user