Initial commit
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,804 @@
|
||||
require_relative '../../../spec_helper_min'
|
||||
require_relative '../../../../lib/carto/named_maps/template'
|
||||
|
||||
module Carto
|
||||
module NamedMaps
|
||||
describe Template do
|
||||
include Carto::Factories::Visualizations
|
||||
|
||||
before(:each) do
|
||||
bypass_named_maps
|
||||
end
|
||||
|
||||
before(:all) do
|
||||
@user = FactoryGirl.create(:carto_user, private_tables_enabled: true, private_maps_enabled: true)
|
||||
|
||||
@map, _, _, @visualization = create_full_visualization(@user)
|
||||
|
||||
@map.layers.reject(&:basemap?).each(&:destroy)
|
||||
@map.reload
|
||||
end
|
||||
|
||||
describe '#to_hash' do
|
||||
after(:all) do
|
||||
destroy_full_visualization(@map1, @table1, @table_visualization1, @visualization1)
|
||||
end
|
||||
|
||||
it 'should get view info from the map when the state center does not exist' do
|
||||
@map1, @table1, @table_visualization1, @visualization1 = create_full_visualization(@user)
|
||||
@visualization1.state = FactoryGirl.create(:state, visualization: @visualization1)
|
||||
@visualization1.state.json[:map].delete(:center)
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization1).to_hash
|
||||
template_hash[:view][:center].values.reverse.should eq @visualization1.map.center_data.map(&:to_f)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#name' do
|
||||
it 'should generate the template name correctly' do
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
template_name = template_hash[:name]
|
||||
|
||||
template_name.should match("^#{Carto::NamedMaps::Template::NAME_PREFIX}")
|
||||
template_name.should_not match(/[^a-zA-Z0-9\-\_.]/)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should have correct Named Maps version' do
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
|
||||
template_hash[:version].should eq Carto::NamedMaps::Template::NAMED_MAPS_VERSION
|
||||
end
|
||||
|
||||
describe '#layers' do
|
||||
describe 'carto layers' do
|
||||
before(:all) do
|
||||
@carto_layer = FactoryGirl.create(:carto_layer, kind: 'carto', maps: [@map])
|
||||
@visualization.reload
|
||||
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@carto_layer.destroy
|
||||
@visualization.reload
|
||||
|
||||
@template_hash = nil
|
||||
end
|
||||
|
||||
it 'should generate placeholders' do
|
||||
@template_hash[:placeholders].length.should be @map.layers.reject(&:basemap?).count
|
||||
end
|
||||
|
||||
it 'should have options' do
|
||||
@template_hash[:layergroup][:layers].second[:options].should_not be_nil
|
||||
end
|
||||
|
||||
it 'should be cartodb type' do
|
||||
@template_hash[:layergroup][:layers].second[:type].should eq 'cartodb'
|
||||
end
|
||||
|
||||
it 'should contain layer id' do
|
||||
@template_hash[:layergroup][:layers].second[:id].should_not be_nil
|
||||
@template_hash[:layergroup][:layers].second[:id].should eq @carto_layer.id
|
||||
end
|
||||
|
||||
describe 'with popups' do
|
||||
let(:dummy_infowindow) do
|
||||
{
|
||||
"fields" => [
|
||||
{
|
||||
"name" => "click_status",
|
||||
"title" => true,
|
||||
"position" => 8
|
||||
}],
|
||||
"template_name" => "table/views/infowindow_light",
|
||||
"template" => "",
|
||||
"alternative_names" => {},
|
||||
"width" => 226,
|
||||
"maxHeight" => 180
|
||||
}
|
||||
end
|
||||
|
||||
let(:dummy_tooltip) do
|
||||
{
|
||||
"fields" => [
|
||||
{
|
||||
"name" => "hover_status",
|
||||
"title" => true,
|
||||
"position" => 8
|
||||
}],
|
||||
"template_name" => "table/views/infowindow_light",
|
||||
"template" => "",
|
||||
"alternative_names" => {},
|
||||
"width" => 226,
|
||||
"maxHeight" => 180
|
||||
}
|
||||
end
|
||||
|
||||
before(:all) do
|
||||
@carto_layer.options[:interactivity] = 'cartodb_id,click_status'
|
||||
@carto_layer.save
|
||||
|
||||
@visualization.reload
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@carto_layer.options[:interactivity] = nil
|
||||
@carto_layer.save
|
||||
|
||||
@visualization.reload
|
||||
end
|
||||
|
||||
describe 'triggered on hover only' do
|
||||
before(:all) do
|
||||
@carto_layer.tooltip = dummy_tooltip
|
||||
@carto_layer.save
|
||||
|
||||
@visualization.reload
|
||||
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
@layer_options_hash = template_hash[:layergroup][:layers].second[:options]
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@carto_layer.tooltip = nil
|
||||
@carto_layer.save
|
||||
|
||||
@visualization.reload
|
||||
@layer_options_hash = nil
|
||||
end
|
||||
|
||||
it 'interactivity contain fields but not cartodb_id' do
|
||||
@layer_options_hash[:interactivity].should eq 'hover_status'
|
||||
end
|
||||
|
||||
it 'should not contain attributes' do
|
||||
@layer_options_hash[:attributes].should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'triggered onclick only' do
|
||||
before(:all) do
|
||||
@carto_layer.infowindow = dummy_infowindow
|
||||
@carto_layer.save
|
||||
|
||||
@visualization.reload
|
||||
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
@layer_options_hash = template_hash[:layergroup][:layers].second[:options]
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@carto_layer.infowindow = nil
|
||||
@carto_layer.save
|
||||
|
||||
@visualization.reload
|
||||
@layer_options_hash = nil
|
||||
end
|
||||
|
||||
it 'interactivity should only contain cartodb_id' do
|
||||
@layer_options_hash[:interactivity].should eq 'cartodb_id'
|
||||
end
|
||||
|
||||
it 'should have attributes' do
|
||||
@layer_options_hash[:attributes].should_not be_nil
|
||||
@layer_options_hash[:attributes].should_not be_empty
|
||||
end
|
||||
|
||||
it 'attributes should have id: cartodb_id' do
|
||||
@layer_options_hash[:attributes][:id].should eq 'cartodb_id'
|
||||
end
|
||||
|
||||
it 'attributes should contain correct columns' do
|
||||
@layer_options_hash[:attributes][:columns].should eq ['click_status']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'triggered onclick and onhover' do
|
||||
before(:all) do
|
||||
@carto_layer.infowindow = dummy_infowindow
|
||||
@carto_layer.tooltip = dummy_tooltip
|
||||
@carto_layer.save
|
||||
|
||||
@visualization.reload
|
||||
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
@layer_options_hash = template_hash[:layergroup][:layers].second[:options]
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@carto_layer.infowindow = nil
|
||||
@carto_layer.save
|
||||
|
||||
@visualization.reload
|
||||
@layer_options_hash = nil
|
||||
end
|
||||
|
||||
it 'interactivity should contain cartodb_id and selected columns' do
|
||||
@layer_options_hash[:interactivity].should include 'cartodb_id'
|
||||
@layer_options_hash[:interactivity].should_not include 'click_status'
|
||||
@layer_options_hash[:interactivity].should include 'hover_status'
|
||||
end
|
||||
|
||||
it 'should have attributes' do
|
||||
@layer_options_hash[:attributes].should_not be_nil
|
||||
@layer_options_hash[:attributes].should_not be_empty
|
||||
end
|
||||
|
||||
it 'attributes should have id: cartodb_id' do
|
||||
@layer_options_hash[:attributes][:id].should eq 'cartodb_id'
|
||||
end
|
||||
|
||||
it 'attributes should contain correct columns but not cartodb_id' do
|
||||
@layer_options_hash[:attributes][:columns].should include 'click_status'
|
||||
@layer_options_hash[:attributes][:columns].should_not include 'hover_status'
|
||||
@layer_options_hash[:attributes][:columns].should_not include 'cartodb_id'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with aggregations' do
|
||||
before(:all) do
|
||||
@carto_layer.options[:query_wrapper] = 'SELECT manolo FROM (<%= sql %>)'
|
||||
@carto_layer.save
|
||||
@visualization.reload
|
||||
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@carto_layer.options[:query_wrapper] = nil
|
||||
@carto_layer.save
|
||||
@visualization.reload
|
||||
@template_hash = nil
|
||||
end
|
||||
|
||||
it 'should contain sql wrap' do
|
||||
@template_hash[:layergroup][:layers].second[:options][:sql_wrap].should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with analyses' do
|
||||
before(:all) do
|
||||
@analysis = FactoryGirl.create(:source_analysis, visualization_id: @visualization.id, user_id: @user.id)
|
||||
@visualization.reload
|
||||
|
||||
@carto_layer.options[:source] = @analysis.natural_id
|
||||
@carto_layer.save
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@analysis.destroy
|
||||
@carto_layer.options.delete(:source)
|
||||
@carto_layer.save
|
||||
@visualization.reload
|
||||
@template_hash = nil
|
||||
end
|
||||
|
||||
describe 'and builder' do
|
||||
before(:all) do
|
||||
@visualization.stubs(:version).returns(3)
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
it 'should not contain sql' do
|
||||
@template_hash[:layergroup][:layers].second[:options][:sql].should be_nil
|
||||
end
|
||||
|
||||
it 'should contain source' do
|
||||
@template_hash[:layergroup][:layers].second[:options][:source][:id].should eq @analysis.natural_id
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and editor' do
|
||||
before(:all) do
|
||||
@visualization.stubs(:version).returns(2)
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
it 'should contain sql' do
|
||||
@template_hash[:layergroup][:layers].second[:options].should_not be_nil
|
||||
end
|
||||
|
||||
it 'should not contain source' do
|
||||
@template_hash[:layergroup][:layers].second[:options][:source].should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with no analyses' do
|
||||
before(:all) do
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@template_hash = nil
|
||||
end
|
||||
|
||||
it 'should contain sql' do
|
||||
@template_hash[:layergroup][:layers].second[:options].should_not be_nil
|
||||
end
|
||||
|
||||
it 'should not contain source' do
|
||||
@template_hash[:layergroup][:layers].second[:options][:source].should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'torque layers' do
|
||||
before(:all) do
|
||||
@torque_layer = FactoryGirl.create(:carto_layer, kind: 'torque', maps: [@map])
|
||||
@visualization.reload
|
||||
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@torque_layer.destroy
|
||||
@visualization.reload
|
||||
|
||||
@template_hash = nil
|
||||
end
|
||||
|
||||
it 'should generate placeholders' do
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
|
||||
template_hash[:placeholders].length.should be @map.layers.reject(&:basemap?).count
|
||||
end
|
||||
|
||||
it 'should have options' do
|
||||
@template_hash[:layergroup][:layers].second[:options].should_not be_nil
|
||||
end
|
||||
|
||||
it 'should be torque type' do
|
||||
@template_hash[:layergroup][:layers].second[:type].should eq 'torque'
|
||||
end
|
||||
|
||||
it 'should contain layer id' do
|
||||
@template_hash[:layergroup][:layers].second[:id].should_not be_nil
|
||||
@template_hash[:layergroup][:layers].second[:id].should eq @torque_layer.id
|
||||
end
|
||||
|
||||
describe 'with aggregations' do
|
||||
before(:all) do
|
||||
@torque_layer.options[:query_wrapper] = 'SELECT manolo FROM (<%= sql %>)'
|
||||
@torque_layer.save
|
||||
@visualization.reload
|
||||
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@torque_layer.options[:query_wrapper] = nil
|
||||
@torque_layer.save
|
||||
@visualization.reload
|
||||
@template_hash = nil
|
||||
end
|
||||
|
||||
it 'should contain sql wrap' do
|
||||
@template_hash[:layergroup][:layers].second[:options][:sql_wrap].should eq 'SELECT manolo FROM (<%= sql %>)'
|
||||
end
|
||||
|
||||
it 'should not wrap sql' do
|
||||
@template_hash[:layergroup][:layers].second[:options][:sql].should_not =~ /SELECT manolo FROM/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'basemap layers' do
|
||||
before(:all) do
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@template_hash = nil
|
||||
end
|
||||
|
||||
it 'should not generate placeholders' do
|
||||
@template_hash[:placeholders].length.should be 0
|
||||
end
|
||||
|
||||
it 'should have options' do
|
||||
@template_hash[:layergroup][:layers].first[:options].should_not be_nil
|
||||
end
|
||||
|
||||
it 'should be http type' do
|
||||
@template_hash[:layergroup][:layers].first[:type].should eq 'http'
|
||||
end
|
||||
|
||||
it 'should not have sql' do
|
||||
@template_hash[:layergroup][:layers].first[:options][:sql].should be_nil
|
||||
end
|
||||
|
||||
it 'should not have sql wrap' do
|
||||
@template_hash[:layergroup][:layers].first[:options][:sql_wrap].should be_nil
|
||||
end
|
||||
|
||||
it 'should not have source' do
|
||||
@template_hash[:layergroup][:layers].first[:options][:source].should be_nil
|
||||
end
|
||||
|
||||
describe 'when basemap options' do
|
||||
before(:all) do
|
||||
@tms_layer = @visualization.layers.first
|
||||
@tms_layer.options[:tms] = true
|
||||
@tms_layer.options[:urlTemplate] = 'http://url_template_example'
|
||||
@tms_layer.options[:subdomains] = 'subdomains_example'
|
||||
|
||||
@tms_layer.save
|
||||
@visualization.reload
|
||||
|
||||
@tms_layer_hash = Carto::NamedMaps::Template.new(@visualization).to_hash[:layergroup][:layers][0]
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@tms_layer = @visualization.layers.first
|
||||
@tms_layer.options[:tms] = nil
|
||||
@tms_layer.options[:urlTemplate] = nil
|
||||
@tms_layer.options[:subdomains] = nil
|
||||
|
||||
@tms_layer.save
|
||||
@visualization.reload
|
||||
|
||||
@tms_layer_hash = nil
|
||||
end
|
||||
|
||||
it 'should have tms options' do
|
||||
@tms_layer_hash[:options][:tms].should be_true
|
||||
end
|
||||
|
||||
it 'should have urlTemplate options' do
|
||||
@tms_layer_hash[:options][:urlTemplate].should eq 'http://url_template_example'
|
||||
end
|
||||
|
||||
it 'should have subdomains options' do
|
||||
@tms_layer_hash[:options][:subdomains].should eq 'subdomains_example'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when background' do
|
||||
before(:all) do
|
||||
@background_layer = @visualization.layers.first
|
||||
|
||||
@background_layer.options[:type] = 'plain'
|
||||
|
||||
@background_layer.save
|
||||
@visualization.reload
|
||||
|
||||
@background_layer_hash = Carto::NamedMaps::Template.new(@visualization).to_hash[:layergroup][:layers][0]
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@background_layer = @visualization.layers.first
|
||||
|
||||
@background_layer.options[:type] = 'plain'
|
||||
|
||||
@background_layer.save
|
||||
@visualization.reload
|
||||
|
||||
@background_layer_hash = nil
|
||||
end
|
||||
|
||||
it 'should have options' do
|
||||
@background_layer_hash[:options].should_not be_nil
|
||||
end
|
||||
|
||||
it 'should be http type' do
|
||||
@background_layer_hash[:type].should eq 'http'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when google maps' do
|
||||
before(:all) do
|
||||
@gmaps_layer = @visualization.layers.first
|
||||
|
||||
@gmaps_layer.options[:type] = 'gmapsbase'
|
||||
|
||||
@gmaps_layer.save
|
||||
@visualization.reload
|
||||
|
||||
@named_map_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
it 'should not be included' do
|
||||
@named_map_hash[:layergroup][:layers].none? { |l| l[:type] == 'gmapsbase' }.should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#auth' do
|
||||
describe 'when mapcaps exist' do
|
||||
before(:all) do
|
||||
@mapcap = Carto::Mapcap.create(visualization_id: @visualization.id)
|
||||
@regenerated_visualization = @visualization.latest_mapcap.regenerate_visualization
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@mapcap.destroy
|
||||
@regenerated_visualization = nil
|
||||
end
|
||||
|
||||
it 'should use non-mapcapped visualization for auth' do
|
||||
@visualization.stubs(:password_protected?).returns(true)
|
||||
@visualization.stubs(:has_password?).returns(true)
|
||||
@visualization.stubs(:non_mapcapped).returns(@visualization) # return stubbed object
|
||||
|
||||
@regenerated_visualization.password_protected?.should be_false
|
||||
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
template_hash[:auth][:method].should eq Carto::NamedMaps::Template::AUTH_TYPE_SIGNED
|
||||
end
|
||||
end
|
||||
|
||||
describe 'should be open' do
|
||||
after(:each) do
|
||||
@visualization.save
|
||||
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
template_hash[:auth][:method].should eq Carto::NamedMaps::Template::AUTH_TYPE_OPEN
|
||||
end
|
||||
|
||||
it 'for public visualizations' do
|
||||
@visualization.privacy = Carto::Visualization::PRIVACY_PUBLIC
|
||||
end
|
||||
|
||||
it 'for link visualizations' do
|
||||
@visualization.privacy = Carto::Visualization::PRIVACY_LINK
|
||||
end
|
||||
end
|
||||
|
||||
describe 'should be signed' do
|
||||
after(:each) do
|
||||
@visualization.save!
|
||||
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
template_hash[:auth][:valid_tokens].should_not be_empty
|
||||
template_hash[:auth][:method].should eq Carto::NamedMaps::Template::AUTH_TYPE_SIGNED
|
||||
end
|
||||
|
||||
it 'for password protected visualizations' do
|
||||
@visualization.privacy = Carto::Visualization::PRIVACY_PROTECTED
|
||||
@visualization.password_salt = 'xxx'
|
||||
@visualization.encrypted_password = 'yyy'
|
||||
end
|
||||
|
||||
it 'for organization private visualizations' do
|
||||
@visualization.privacy = Carto::Visualization::PRIVACY_PRIVATE
|
||||
@visualization.stubs(:organization?).returns(true)
|
||||
end
|
||||
|
||||
it 'for private visualizations' do
|
||||
@visualization.privacy = Carto::Visualization::PRIVACY_PRIVATE
|
||||
end
|
||||
|
||||
describe 'and include tokens' do
|
||||
before(:all) do
|
||||
@org = mock
|
||||
@org.stubs(:get_auth_token).returns(SecureRandom.urlsafe_base64(nil, false))
|
||||
@group = mock
|
||||
@group.stubs(:get_auth_token).returns(SecureRandom.urlsafe_base64(nil, false))
|
||||
@other_user = mock
|
||||
@other_user.stubs(:get_auth_token).returns(SecureRandom.urlsafe_base64(nil, false))
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@visualization.privacy = Carto::Visualization::PRIVACY_PRIVATE
|
||||
@visualization.stubs(:organization?).returns(true)
|
||||
end
|
||||
|
||||
it 'for owner user always' do
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
template_hash[:auth][:valid_tokens].should eq [@visualization.user.get_auth_token]
|
||||
end
|
||||
|
||||
it 'for organization' do
|
||||
@visualization.permission.stubs(:entities_with_read_permission).returns([@org])
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
auth_tokens = template_hash[:auth][:valid_tokens]
|
||||
auth_tokens.count.should eq 2
|
||||
auth_tokens.should include @user.get_auth_token
|
||||
auth_tokens.should include @org.get_auth_token
|
||||
end
|
||||
|
||||
it 'for group' do
|
||||
@visualization.permission.stubs(:entities_with_read_permission).returns([@group])
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
auth_tokens = template_hash[:auth][:valid_tokens]
|
||||
auth_tokens.count.should eq 2
|
||||
auth_tokens.should include @user.get_auth_token
|
||||
auth_tokens.should include @group.get_auth_token
|
||||
end
|
||||
|
||||
it 'for other user' do
|
||||
@visualization.permission.stubs(:entities_with_read_permission).returns([@other_user])
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
auth_tokens = template_hash[:auth][:valid_tokens]
|
||||
auth_tokens.count.should eq 2
|
||||
auth_tokens.should include @user.get_auth_token
|
||||
auth_tokens.should include @other_user.get_auth_token
|
||||
end
|
||||
|
||||
it 'for multiple entities' do
|
||||
@visualization.permission.stubs(:entities_with_read_permission).returns([@other_user, @group, @org])
|
||||
template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
auth_tokens = template_hash[:auth][:valid_tokens]
|
||||
auth_tokens.count.should eq 4
|
||||
auth_tokens.should include @user.get_auth_token
|
||||
auth_tokens.should include @other_user.get_auth_token
|
||||
auth_tokens.should include @group.get_auth_token
|
||||
auth_tokens.should include @org.get_auth_token
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#layergroup' do
|
||||
before (:all) { @layergroup_hash = Carto::NamedMaps::Template.new(@visualization).to_hash[:layergroup] }
|
||||
after (:all) { @layergroup_hash = nil }
|
||||
|
||||
it 'should have version according to Map Config' do
|
||||
@layergroup_hash[:version].should eq Carto::NamedMaps::Template::MAP_CONFIG_VERSION
|
||||
end
|
||||
|
||||
it 'should not have any dataview if no widgets are present' do
|
||||
@layergroup_hash[:dataviews].should be_empty
|
||||
end
|
||||
|
||||
it 'should not have any analysis if no analyses are present' do
|
||||
@layergroup_hash[:analyses].should be_empty
|
||||
end
|
||||
|
||||
describe 'dataviews' do
|
||||
before(:all) do
|
||||
@carto_layer = FactoryGirl.create(:carto_layer, kind: 'carto', maps: [@map])
|
||||
@widget = FactoryGirl.create(:widget, layer: @carto_layer)
|
||||
@visualization.reload
|
||||
|
||||
@dataview_hash = Carto::NamedMaps::Template.new(@visualization).to_hash[:layergroup][:dataviews]
|
||||
@template_widget = @dataview_hash[@widget.id]
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@carto_layer.destroy
|
||||
@widget.destroy
|
||||
@visualization.reload
|
||||
|
||||
@dataview_hash = nil
|
||||
@template_widget = nil
|
||||
end
|
||||
|
||||
it 'should add dataviews if widgets are present' do
|
||||
@dataview_hash.should_not be_empty
|
||||
@template_widget.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should add type correctly' do
|
||||
@template_widget[:type].should eq Carto::NamedMaps::Template::TILER_WIDGET_TYPES[@widget.type.to_sym]
|
||||
end
|
||||
|
||||
it 'should have only required options' do
|
||||
expected_options = @widget.options.merge(aggregationColumn: nil).select do |k, _v|
|
||||
Carto::NamedMaps::Template::DATAVIEW_TEMPLATE_OPTIONS.include?(k)
|
||||
end
|
||||
|
||||
@template_widget[:options].should eq expected_options
|
||||
end
|
||||
end
|
||||
|
||||
describe '#analyses' do
|
||||
before(:all) do
|
||||
@analysis = FactoryGirl.create(:source_analysis, visualization_id: @visualization.id, user_id: @user.id)
|
||||
@visualization.reload
|
||||
|
||||
@analysis_hash = Carto::NamedMaps::Template.new(@visualization).to_hash[:layergroup][:analyses].first
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@analysis.destroy
|
||||
@visualization.reload
|
||||
|
||||
@analysis_hash = nil
|
||||
end
|
||||
|
||||
it 'should add analyses if analyses are present' do
|
||||
@analysis_hash.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should have the right definition' do
|
||||
@analysis_hash.should eq @analysis.analysis_definition_for_api
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#view' do
|
||||
before(:each) do
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
it 'should contain map zoom' do
|
||||
@template_hash[:view][:zoom].should eq @map.zoom
|
||||
end
|
||||
|
||||
it 'should contain center' do
|
||||
map_center_data = @map.center_data
|
||||
template_center = @template_hash[:view][:center]
|
||||
|
||||
template_center[:lat].should eq map_center_data[0].to_f
|
||||
template_center[:lng].should eq map_center_data[1].to_f
|
||||
end
|
||||
|
||||
it 'should contain bounds' do
|
||||
@template_hash[:view][:bounds].should eq @map.view_bounds_data
|
||||
end
|
||||
|
||||
describe 'it should use the state when it is present' do
|
||||
before do
|
||||
@visualization.state.json = {
|
||||
map: { ne: [-58.9, -143.9], sw: [37.4, 41.6], center: [-16.2, -51.1], zoom: 4 }
|
||||
}
|
||||
@template_hash = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
end
|
||||
|
||||
after do
|
||||
@visualization.state.json = {}
|
||||
end
|
||||
|
||||
it 'should use the state info for the view when instantiating a named map' do
|
||||
@template_hash[:view][:zoom].should eq 4
|
||||
template_center = @template_hash[:view][:center]
|
||||
template_center[:lat].should eq -16.2
|
||||
template_center[:lng].should eq -51.1
|
||||
expected_bounds = { west: 37.4, south: 41.6, east: -58.9, north: -143.9 }
|
||||
@template_hash[:view][:bounds].should eq expected_bounds
|
||||
end
|
||||
|
||||
it 'should not fail when invalid state' do
|
||||
@visualization.state.json[:map][:ne] = nil
|
||||
result = Carto::NamedMaps::Template.new(@visualization).to_hash
|
||||
result[:view][:zoom].should eq 3
|
||||
expected_center = { lng: 0.0, lat: 30.0 }
|
||||
result[:view][:center].should eq expected_center
|
||||
end
|
||||
end
|
||||
|
||||
describe '#preview_layers' do
|
||||
before(:all) do
|
||||
@carto_layer = FactoryGirl.create(:carto_layer, kind: 'carto', maps: [@map])
|
||||
@visualization.reload
|
||||
|
||||
template = Carto::NamedMaps::Template.new(@visualization)
|
||||
@preview_layers = template.to_hash[:view][:preview_layers]
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@carto_layer.destroy
|
||||
@visualization.reload
|
||||
|
||||
@preview_layers = nil
|
||||
end
|
||||
|
||||
it 'should contain preview_layers' do
|
||||
@preview_layers.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should not generate preview_layers for basemaps' do
|
||||
preview_layers_ids = @preview_layers.keys.flatten
|
||||
|
||||
@visualization.base_layers.map(&:id).each do |id|
|
||||
preview_layers_ids.should_not include(id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should generate preview_layers correctly' do
|
||||
@visualization.data_layers.should_not be_empty
|
||||
@visualization.data_layers.each do |layer|
|
||||
expected_visibility = layer.options['visible'] || false
|
||||
@preview_layers[layer.id.to_sym].should eq expected_visibility
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
require 'spec_helper_min'
|
||||
require 'carto/oauth/google/api'
|
||||
|
||||
describe Carto::Oauth::Google::Api do
|
||||
|
||||
before(:all) do
|
||||
@valid_token = 'valid_token'
|
||||
stub_api_request(200, 'userinfo_valid_response.json')
|
||||
end
|
||||
|
||||
describe '#user_params' do
|
||||
it 'parses the user data from google' do
|
||||
expected_result = {
|
||||
username: 'fulanito',
|
||||
email: 'fulanito@example.com',
|
||||
name: 'Fulano',
|
||||
last_name: 'de Tal',
|
||||
google_sign_in: true
|
||||
}
|
||||
|
||||
api = Carto::Oauth::Google::Api.new(nil, @valid_token)
|
||||
|
||||
api.user_params.should eql expected_result
|
||||
end
|
||||
end
|
||||
|
||||
describe '#user' do
|
||||
context 'with existing user' do
|
||||
before(:all) do
|
||||
@user = FactoryGirl.create(:carto_user, email: 'fulanito@example.com', username: 'fulanito')
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@user.destroy
|
||||
end
|
||||
|
||||
it 'returns the user when the email matches' do
|
||||
api = Carto::Oauth::Google::Api.new(nil, @valid_token)
|
||||
|
||||
api.user.should eql @user
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns nil if there is no matching user' do
|
||||
api = Carto::Oauth::Google::Api.new(nil, @valid_token)
|
||||
|
||||
api.user.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
def stub_api_request(code, response_filename)
|
||||
response_path = File.join(File.dirname(__FILE__), response_filename)
|
||||
response = File.open(response_path).read
|
||||
|
||||
Typhoeus.stub(/openidconnect\.googleapis\.com\/v1\/userinfo\?access_token=#{@valid_}/).and_return(
|
||||
Typhoeus::Response.new(code: code, body: response)
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sub": "123456",
|
||||
"given_name": "Fulano",
|
||||
"family_name": "de Tal",
|
||||
"email": "fulanito@example.com"
|
||||
}
|
||||
@@ -0,0 +1,655 @@
|
||||
require 'spec_helper_min'
|
||||
require_dependency 'carto/oauth_provider/scopes/scopes'
|
||||
require_relative '../../../../factories/organizations_contexts'
|
||||
|
||||
describe Carto::OauthProvider::Scopes do
|
||||
include_context 'organization with users helper'
|
||||
|
||||
describe '#invalid_scopes' do
|
||||
include Carto::OauthProvider::Scopes
|
||||
before :all do
|
||||
@user = Carto::User.find(create_user.id)
|
||||
@user_table = FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @user.id)
|
||||
end
|
||||
|
||||
after :all do
|
||||
@user_table.destroy
|
||||
@user.destroy
|
||||
end
|
||||
|
||||
it 'detects invalid dataset and schemas scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes(
|
||||
[
|
||||
"datasets:r:twf",
|
||||
"datasets:rw:twf",
|
||||
"datasets:rw:a.twf",
|
||||
"datasets:rw:a.a.twf",
|
||||
"datasets:c:a.twf",
|
||||
"schemas:c",
|
||||
"schemas:c:public",
|
||||
"schemas:c:a",
|
||||
'schemas:c:a.a',
|
||||
"schemas:w:a",
|
||||
"datasets:metadata"
|
||||
]
|
||||
)
|
||||
expect(scopes).to eq ["datasets:rw:a.a.twf", "datasets:c:a.twf", "schemas:w:a"]
|
||||
end
|
||||
|
||||
it 'validates supported scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
Carto::OauthProvider::Scopes::SUPPORTED_SCOPES,
|
||||
@user
|
||||
)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'returns non existent datasets scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(['datasets:r:wtf'], @user)
|
||||
expect(scopes).to eq(['datasets:r:wtf'])
|
||||
end
|
||||
|
||||
it 'returns non existent schemas scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(['schemas:c:wtf'], @user)
|
||||
expect(scopes).to eq(['schemas:c:wtf'])
|
||||
end
|
||||
|
||||
it 'validates non existent datasets scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes(['datasets:r:wtf'])
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'validates non existent schemas scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes(['schemas:c:wtf'])
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'validates existing datasets scopes' do
|
||||
scopes_to_validate = ["datasets:r:#{@user_table.name}", "datasets:metadata"]
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(scopes_to_validate, @user)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'validates user schema scope' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(["schemas:c:#{@user.database_schema}"], @user)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'validates empty schema scope' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(["schemas:c"], @user)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'validates existing datasets with schema scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
["datasets:r:#{@user.database_schema}.#{@user_table.name}"],
|
||||
@user
|
||||
)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'returns datasets non existent datasets schema scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(["datasets:r:wtf.#{@user_table.name}"], @user)
|
||||
expect(scopes).to eq(["datasets:r:wtf.#{@user_table.name}"])
|
||||
end
|
||||
|
||||
it 'returns datasets with 2 schemas scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
["datasets:r:#{@user.database_schema}.#{@user.database_schema}.#{@user_table.name}"],
|
||||
@user
|
||||
)
|
||||
expect(scopes).to eq(["datasets:r:#{@user.database_schema}.#{@user.database_schema}.#{@user_table.name}"])
|
||||
end
|
||||
|
||||
it 'returns datasets scopes with non existent permissions' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(["datasets:f:#{@user_table.name}"], @user)
|
||||
expect(scopes).to eq(["datasets:f:#{@user_table.name}"])
|
||||
end
|
||||
|
||||
it 'returns schemas scopes with non existent permissions' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(["schemas:c:pg_catalog"], @user)
|
||||
expect(scopes).to eq(["schemas:c:pg_catalog"])
|
||||
end
|
||||
|
||||
it 'returns invalid datasets scopes' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(["wadusdatasets:r:#{@user_table.name}"], @user)
|
||||
expect(scopes).to eq(["wadusdatasets:r:#{@user_table.name}"])
|
||||
end
|
||||
|
||||
describe 'views' do
|
||||
before :all do
|
||||
@view_name = "#{@user_table.name}_view"
|
||||
@materialized_view_name = "#{@user_table.name}_matview"
|
||||
@user.in_database do |db|
|
||||
query = %{
|
||||
CREATE VIEW #{@view_name} AS SELECT * FROM #{@user_table.name};
|
||||
CREATE MATERIALIZED VIEW #{@materialized_view_name} AS SELECT * FROM #{@user_table.name};
|
||||
}
|
||||
db.execute(query)
|
||||
end
|
||||
end
|
||||
|
||||
after :all do
|
||||
@user.in_database do |db|
|
||||
query = %{
|
||||
DROP VIEW #{@view_name};
|
||||
DROP MATERIALIZED VIEW #{@materialized_view_name};
|
||||
}
|
||||
db.execute(query)
|
||||
end
|
||||
end
|
||||
|
||||
it 'validates view scope' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
["datasets:r:#{@view_name}"],
|
||||
@user
|
||||
)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'validates materialized view scope' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
["datasets:r:#{@materialized_view_name}"],
|
||||
@user
|
||||
)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
# org schemas
|
||||
describe 'org schemas' do
|
||||
it 'validates own user schema' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
["schemas:c:#{@carto_org_user_2.database_schema}"], @carto_org_user_2
|
||||
)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'returns scopes for other user schema' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
["schemas:c:#{@carto_org_user_1.database_schema}"], @carto_org_user_2
|
||||
)
|
||||
expect(scopes).to eq(
|
||||
[
|
||||
"schemas:c:#{@carto_org_user_1.database_schema}"
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns scopes for same org users schemas' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
[
|
||||
"schemas:c:#{@carto_org_user_1.database_schema}",
|
||||
"schemas:c:#{@carto_org_user_2.database_schema}",
|
||||
"schemas:c:#{@carto_org_user_owner.database_schema}"
|
||||
], @carto_org_user_2
|
||||
)
|
||||
expect(scopes).to eq(
|
||||
[
|
||||
"schemas:c:#{@carto_org_user_1.database_schema}",
|
||||
"schemas:c:#{@carto_org_user_owner.database_schema}"
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns scopes for non-user org schema' do
|
||||
@helper = TestUserFactory.new
|
||||
@org_user_owner2 = @helper.create_owner(@organization_2)
|
||||
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
[
|
||||
"schemas:c:#{@carto_org_user_1.database_schema}",
|
||||
"schemas:c:#{@carto_org_user_2.database_schema}",
|
||||
"schemas:c:#{@org_user_owner2.database_schema}"
|
||||
], @carto_org_user_2
|
||||
)
|
||||
expect(scopes).to eq(
|
||||
[
|
||||
"schemas:c:#{@carto_org_user_1.database_schema}",
|
||||
"schemas:c:#{@org_user_owner2.database_schema}"
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'shared datasets' do
|
||||
before :each do
|
||||
@shared_table = create_table(user_id: @carto_org_user_1.id)
|
||||
not_shared_table = create_table(user_id: @carto_org_user_1.id)
|
||||
|
||||
perm = @shared_table.table_visualization.permission
|
||||
perm.acl = [{ type: 'user', entity: { id: @org_user_2.id }, access: 'r' }]
|
||||
perm.save!
|
||||
|
||||
@shared_dataset_scope = "datasets:r:#{@carto_org_user_1.database_schema}.#{@shared_table.name}"
|
||||
@non_shared_dataset_scope = "datasets:r:#{@carto_org_user_1.database_schema}.#{not_shared_table.name}"
|
||||
end
|
||||
|
||||
it 'validates shared dataset' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables([@shared_dataset_scope], @carto_org_user_2)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'returns non shared dataset' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables([@non_shared_dataset_scope], @carto_org_user_2)
|
||||
expect(scopes).to eq([@non_shared_dataset_scope])
|
||||
end
|
||||
|
||||
it 'returns only non shared dataset' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
[@shared_dataset_scope, @non_shared_dataset_scope],
|
||||
@carto_org_user_2
|
||||
)
|
||||
expect(scopes).to eq([@non_shared_dataset_scope])
|
||||
end
|
||||
|
||||
it 'should fail write scope in shared dataset with only read perms' do
|
||||
rw_scope = "datasets:rw:#{@carto_org_user_1.database_schema}.#{@shared_table.name}"
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables([rw_scope], @carto_org_user_2)
|
||||
expect(scopes).to eq([rw_scope])
|
||||
end
|
||||
|
||||
describe 'organization shared datasets' do
|
||||
before :each do
|
||||
@org_shared_table = create_table(user_id: @carto_org_user_1.id)
|
||||
non_org_shared_table = create_table(user_id: @carto_org_user_1.id)
|
||||
|
||||
perm = @org_shared_table.table_visualization.permission
|
||||
perm.acl = [
|
||||
{
|
||||
type: Permission::TYPE_ORGANIZATION,
|
||||
entity: { id: @carto_organization.id },
|
||||
access: Permission::ACCESS_READONLY
|
||||
}
|
||||
]
|
||||
perm.save!
|
||||
|
||||
@org_shared_dataset_scope = "datasets:r:#{@carto_org_user_1.database_schema}.#{@org_shared_table.name}"
|
||||
@non_org_shared_dataset_scope = "datasets:r:#{@carto_org_user_1.database_schema}.#{non_org_shared_table.name}"
|
||||
end
|
||||
|
||||
it 'validates org shared dataset' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
[@org_shared_dataset_scope],
|
||||
@carto_org_user_2
|
||||
)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'returns non org shared dataset' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
[@non_org_shared_dataset_scope],
|
||||
@carto_org_user_2
|
||||
)
|
||||
expect(scopes).to eq([@non_org_shared_dataset_scope])
|
||||
end
|
||||
|
||||
it 'returns only non org shared dataset' do
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables(
|
||||
[@org_shared_dataset_scope, @non_org_shared_dataset_scope],
|
||||
@carto_org_user_2
|
||||
)
|
||||
expect(scopes).to eq([@non_org_shared_dataset_scope])
|
||||
end
|
||||
|
||||
it 'should fail write scope in org shared dataset with only read perms' do
|
||||
rw_scope = "datasets:rw:#{@carto_org_user_1.database_schema}.#{@org_shared_table.name}"
|
||||
scopes = Carto::OauthProvider::Scopes.invalid_scopes_and_tables([rw_scope], @carto_org_user_2)
|
||||
expect(scopes).to eq([rw_scope])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Carto::OauthProvider::Scopes::DataservicesScope do
|
||||
describe '#add_to_api_key_grants' do
|
||||
let(:scope) { Carto::OauthProvider::Scopes::DataservicesScope.new('geocoding', 'GC') }
|
||||
|
||||
it 'adds SQL api and dataservice' do
|
||||
grants = [{ type: 'apis', apis: [] }]
|
||||
scope.add_to_api_key_grants(grants, nil)
|
||||
expect(grants).to(eq([{ type: 'apis', apis: ['sql'] }, { type: 'dataservices', services: ['geocoding'] }]))
|
||||
end
|
||||
|
||||
it 'does not add duplicate SQL api' do
|
||||
grants = [{ type: 'apis', apis: ['sql'] }]
|
||||
scope.add_to_api_key_grants(grants, nil)
|
||||
expect(grants).to(include(type: 'apis', apis: ['sql']))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Carto::OauthProvider::Scopes::UserScope do
|
||||
describe '#add_to_api_key_grants' do
|
||||
it 'adds user scope with profile subset' do
|
||||
scope = Carto::OauthProvider::Scopes::UserScope.new('profile', 'User public profile')
|
||||
grants = [{ type: 'apis', apis: [] }]
|
||||
scope.add_to_api_key_grants(grants, nil)
|
||||
expect(grants).to(eq([{ type: 'apis', apis: [] }, { type: 'user', data: ['profile'] }]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Carto::OauthProvider::Scopes::ApisScope do
|
||||
describe '#add_to_api_key_grants' do
|
||||
it 'adds apis scope with do subset' do
|
||||
scope = Carto::OauthProvider::Scopes::ApisScope.new('do', 'Data Observatory API')
|
||||
grants = [{ type: 'apis', apis: [] }]
|
||||
scope.add_to_api_key_grants(grants, nil)
|
||||
expect(grants).to(eq([{ type: 'apis', apis: ['do'] }]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Carto::OauthProvider::Scopes::DatasetsScope do
|
||||
describe '#add_to_api_key_grants' do
|
||||
let(:full_dataset_scope) { Carto::OauthProvider::Scopes::DatasetsScope.new('datasets:rw:untitled_table') }
|
||||
let(:read_dataset_scope) { Carto::OauthProvider::Scopes::DatasetsScope.new('datasets:r:untitled_table') }
|
||||
let(:schema_scope) { Carto::OauthProvider::Scopes::SchemasScope.new('schemas:c') }
|
||||
let(:full_table_grants) do
|
||||
[
|
||||
{
|
||||
apis: [
|
||||
'maps',
|
||||
'sql'
|
||||
],
|
||||
type: 'apis'
|
||||
},
|
||||
{
|
||||
tables: [
|
||||
{
|
||||
name: 'untitled_table',
|
||||
permissions: [
|
||||
'select',
|
||||
'insert',
|
||||
'update',
|
||||
'delete'
|
||||
],
|
||||
schema: 'wadus'
|
||||
}
|
||||
],
|
||||
schemas: [
|
||||
{
|
||||
name: 'wadus',
|
||||
permissions: [
|
||||
'create'
|
||||
]
|
||||
}
|
||||
],
|
||||
type: 'database'
|
||||
}
|
||||
]
|
||||
end
|
||||
let(:read_table_grants) do
|
||||
[
|
||||
{
|
||||
apis: [
|
||||
'maps',
|
||||
'sql'
|
||||
],
|
||||
type: 'apis'
|
||||
},
|
||||
{
|
||||
tables: [
|
||||
{
|
||||
name: 'untitled_table',
|
||||
permissions: [
|
||||
'select'
|
||||
],
|
||||
schema: 'wadus'
|
||||
}
|
||||
],
|
||||
type: 'database'
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
before(:all) do
|
||||
@user = mock
|
||||
@user.stubs(:database_schema).returns('wadus')
|
||||
end
|
||||
|
||||
it 'adds full access permissions' do
|
||||
grants = [{ type: 'apis', apis: [] }]
|
||||
full_dataset_scope.add_to_api_key_grants(grants, @user)
|
||||
schema_scope.add_to_api_key_grants(grants, @user)
|
||||
expect(grants).to(eq(full_table_grants))
|
||||
end
|
||||
|
||||
it 'does not add write permissions' do
|
||||
grants = [{ type: 'apis', apis: [] }]
|
||||
read_dataset_scope.add_to_api_key_grants(grants, @user)
|
||||
expect(grants).to(eq(read_table_grants))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#subtract_scopes' do
|
||||
it 'r - r' do
|
||||
scopes1 = ['datasets:r:schema.table']
|
||||
scopes2 = ['datasets:r:schema.table']
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'r - rw' do
|
||||
scopes1 = ['datasets:r:schema.table']
|
||||
scopes2 = ['datasets:rw:schema.table']
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'empties' do
|
||||
scopes1 = []
|
||||
scopes2 = []
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'rw - r' do
|
||||
scopes1 = ['datasets:rw:schema.table']
|
||||
scopes2 = ['datasets:r:schema.table']
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to(eq(scopes1))
|
||||
end
|
||||
|
||||
it 'rw - rw' do
|
||||
scopes1 = ['datasets:rw:schema.table']
|
||||
scopes2 = ['datasets:rw:schema.table']
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'table1 - table2' do
|
||||
scopes1 = ['datasets:rw:schema.table1']
|
||||
scopes2 = ['datasets:rw:schema.table2']
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to(eq(scopes1))
|
||||
end
|
||||
|
||||
it 'table - empty' do
|
||||
scopes1 = ['datasets:rw:schema.table']
|
||||
scopes2 = []
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to(eq(scopes1))
|
||||
end
|
||||
|
||||
it 'empty - table' do
|
||||
scopes1 = []
|
||||
scopes2 = ['datasets:rw:schema.table']
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it '(rw & profile) - r' do
|
||||
scopes1 = ['datasets:rw:schema.table', 'user:profile']
|
||||
scopes2 = ['datasets:r:schema.table']
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to(eq(scopes1))
|
||||
end
|
||||
|
||||
it 'profile - profile' do
|
||||
scopes1 = ['user:profile']
|
||||
scopes2 = ['user:profile']
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
|
||||
it 'schema - schena' do
|
||||
scopes1 = ['schemas:c']
|
||||
scopes2 = ['schemas:c']
|
||||
scopes = Carto::OauthProvider::Scopes.subtract_scopes(scopes1, scopes2, nil)
|
||||
expect(scopes).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '#scopes_by_category' do
|
||||
include Carto::OauthProvider::Scopes
|
||||
before :all do
|
||||
@user = Carto::User.find(create_user.id)
|
||||
@table = FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @user.id)
|
||||
end
|
||||
|
||||
after :all do
|
||||
@table.destroy
|
||||
@user.destroy
|
||||
end
|
||||
|
||||
it 'read-write permission having no one' do
|
||||
new_scopes = ["datasets:rw:#{@table.name}"]
|
||||
previous_scopes = []
|
||||
expected = [
|
||||
{
|
||||
description: "User and personal data",
|
||||
icon: nil,
|
||||
scopes: [{ description: "Username and organization name", new: true }]
|
||||
},
|
||||
{
|
||||
description: "Access to your datasets",
|
||||
icon: nil,
|
||||
scopes: [{ description: "#{@table.name} (read/write access)", new: true }]
|
||||
}
|
||||
]
|
||||
|
||||
scopes_by_category = Carto::OauthProvider::Scopes.scopes_by_category(new_scopes, previous_scopes)
|
||||
expect(scopes_by_category).to(eq(expected))
|
||||
end
|
||||
|
||||
it 'read permission having read-write' do
|
||||
new_scopes = ["datasets:r:#{@table.name}"]
|
||||
previous_scopes = ["datasets:rw:#{@table.name}"]
|
||||
expected = [
|
||||
{
|
||||
description: "User and personal data",
|
||||
icon: nil,
|
||||
scopes: [{ description: "Username and organization name", new: false }]
|
||||
},
|
||||
{
|
||||
description: "Access to your datasets",
|
||||
icon: nil,
|
||||
scopes: [{ description: "#{@table.name} (read/write access)", new: false }]
|
||||
}
|
||||
]
|
||||
|
||||
scopes_by_category = Carto::OauthProvider::Scopes.scopes_by_category(new_scopes, previous_scopes)
|
||||
expect(scopes_by_category).to(eq(expected))
|
||||
end
|
||||
|
||||
it 'read-write permission having read-write' do
|
||||
new_scopes = ["datasets:rw:#{@table.name}"]
|
||||
previous_scopes = ["datasets:rw:#{@table.name}"]
|
||||
expected = [
|
||||
{
|
||||
description: "User and personal data",
|
||||
icon: nil,
|
||||
scopes: [{ description: "Username and organization name", new: false }]
|
||||
},
|
||||
{
|
||||
description: "Access to your datasets",
|
||||
icon: nil,
|
||||
scopes: [{ description: "#{@table.name} (read/write access)", new: false }]
|
||||
}
|
||||
]
|
||||
|
||||
scopes_by_category = Carto::OauthProvider::Scopes.scopes_by_category(new_scopes, previous_scopes)
|
||||
expect(scopes_by_category).to(eq(expected))
|
||||
end
|
||||
|
||||
it 'read-write permission having read' do
|
||||
new_scopes = ["datasets:rw:#{@table.name}"]
|
||||
previous_scopes = ["datasets:r:#{@table.name}"]
|
||||
expected = [
|
||||
{
|
||||
description: "User and personal data",
|
||||
icon: nil,
|
||||
scopes: [{ description: "Username and organization name", new: false }]
|
||||
},
|
||||
{
|
||||
description: "Access to your datasets",
|
||||
icon: nil,
|
||||
scopes: [{ description: "#{@table.name} (read/write access)", new: true }]
|
||||
}
|
||||
]
|
||||
|
||||
scopes_by_category = Carto::OauthProvider::Scopes.scopes_by_category(new_scopes, previous_scopes)
|
||||
expect(scopes_by_category).to(eq(expected))
|
||||
end
|
||||
|
||||
it 'shows create table permission' do
|
||||
create_scope = ["schemas:c:public"]
|
||||
expected = [
|
||||
{
|
||||
description: "User and personal data",
|
||||
icon: nil,
|
||||
scopes: [{ description: "Username and organization name", new: true }]
|
||||
},
|
||||
{
|
||||
description: "Create tables",
|
||||
icon: nil,
|
||||
scopes: [{ description: "public schema (create tables)", new: true }]
|
||||
}
|
||||
]
|
||||
|
||||
scopes_by_category = Carto::OauthProvider::Scopes.scopes_by_category(create_scope, [])
|
||||
expect(scopes_by_category).to(eq(expected))
|
||||
end
|
||||
|
||||
it 'shows datasets metadata permission' do
|
||||
scope = ["datasets:metadata"]
|
||||
expected = [
|
||||
{
|
||||
description: "User and personal data",
|
||||
icon: nil,
|
||||
scopes: [{ description: "Username and organization name", new: true }]
|
||||
},
|
||||
{
|
||||
description: "List your datasets",
|
||||
icon: nil,
|
||||
scopes: [{ description: "Table names", new: true }]
|
||||
}
|
||||
]
|
||||
|
||||
scopes_by_category = Carto::OauthProvider::Scopes.scopes_by_category(scope, [])
|
||||
expect(scopes_by_category).to(eq(expected))
|
||||
end
|
||||
|
||||
it 'shows Data Observatory API permission' do
|
||||
scope = ["apis:do"]
|
||||
expected = [
|
||||
{
|
||||
description: "User and personal data",
|
||||
icon: nil,
|
||||
scopes: [{ description: "Username and organization name", new: true }]
|
||||
},
|
||||
{
|
||||
description: "Access to CARTO APIs",
|
||||
icon: nil,
|
||||
scopes: [{ description: "Data Observatory API", new: true }]
|
||||
}
|
||||
]
|
||||
|
||||
scopes_by_category = Carto::OauthProvider::Scopes.scopes_by_category(scope, [])
|
||||
expect(scopes_by_category).to(eq(expected))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,143 @@
|
||||
require 'spec_helper_min'
|
||||
|
||||
module Carto
|
||||
describe 'PasswordValidator' do
|
||||
PASSWORD = '2{Patrañas}'.freeze
|
||||
|
||||
it 'should be invalid when password too short' do
|
||||
strategy = Carto::StrongPasswordStrategy.new(min_length: PASSWORD.length + 1)
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(PASSWORD, PASSWORD)
|
||||
errors.empty?.should be_false
|
||||
validator.formatted_error_message(errors).should == "must be at least #{PASSWORD.length + 1} characters long"
|
||||
end
|
||||
|
||||
it 'should be invalid when same as username' do
|
||||
validator = Carto::PasswordValidator.new
|
||||
|
||||
user = mock
|
||||
user.stubs(:username).returns(PASSWORD)
|
||||
user.stubs(:organization).returns(nil)
|
||||
|
||||
errors = validator.validate(PASSWORD, PASSWORD, user)
|
||||
errors.empty?.should be_false
|
||||
validator.formatted_error_message(errors).should == "must be different than the user name"
|
||||
end
|
||||
|
||||
it 'should check strong passwords for org users with strong_passwords_enabled' do
|
||||
user = mock
|
||||
org = mock
|
||||
org.stubs(:strong_passwords_enabled).returns(true)
|
||||
user.stubs(:username).returns(PASSWORD)
|
||||
user.stubs(:organization_user?).returns(true)
|
||||
user.stubs(:organization).returns(org)
|
||||
|
||||
strategy = Carto::StrongPasswordStrategy.new(min_length: PASSWORD.length + 1)
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(PASSWORD, PASSWORD, user)
|
||||
errors.empty?.should be_false
|
||||
error = "must be different than the user name and must be at least #{PASSWORD.length + 1} characters long"
|
||||
validator.formatted_error_message(errors).should == error
|
||||
end
|
||||
|
||||
it 'should be invalid when in common password list' do
|
||||
strategy = Carto::StrongPasswordStrategy.new
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate('123456789q', '123456789q')
|
||||
errors.empty?.should be_false
|
||||
validator.formatted_error_message(errors).should == "can't be a common password"
|
||||
end
|
||||
|
||||
it 'should be invalid when password too long' do
|
||||
strategy = Carto::StrongPasswordStrategy.new(max_length: PASSWORD.length - 1)
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(PASSWORD, PASSWORD)
|
||||
errors.empty?.should be_false
|
||||
validator.formatted_error_message(errors).should == "must be at most #{PASSWORD.length - 1} characters long"
|
||||
end
|
||||
|
||||
it 'should be invalid when password does not have enough letters' do
|
||||
strategy = Carto::StrongPasswordStrategy.new(min_letters: 9)
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(PASSWORD, PASSWORD)
|
||||
errors.empty?.should be_false
|
||||
validator.formatted_error_message(errors).should == "must contain at least 9 letters"
|
||||
end
|
||||
|
||||
it 'should be invalid when password does not have enough numbers or symbols' do
|
||||
strategy = Carto::StrongPasswordStrategy.new(min_symbols: 3, min_numbers: 2)
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(PASSWORD, PASSWORD)
|
||||
errors.empty?.should be_false
|
||||
validator.formatted_error_message(errors).should == "must contain at least 3 symbols or 2 numbers"
|
||||
end
|
||||
|
||||
it 'should be valid when password has enough numbers but not enough symbols' do
|
||||
strategy = Carto::StrongPasswordStrategy.new(min_symbols: 3, min_numbers: 1)
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(PASSWORD, PASSWORD)
|
||||
errors.empty?.should be_true
|
||||
validator.formatted_error_message(errors).should be_nil
|
||||
end
|
||||
|
||||
it 'should be valid when password has enough symbols but not enough numbers' do
|
||||
strategy = Carto::StrongPasswordStrategy.new(min_symbols: 2, min_numbers: 3)
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(PASSWORD, PASSWORD)
|
||||
errors.empty?.should be_true
|
||||
validator.formatted_error_message(errors).should be_nil
|
||||
end
|
||||
|
||||
it 'should invalidate a nil password' do
|
||||
strategy = Carto::StrongPasswordStrategy.new
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(nil, nil)
|
||||
errors.empty?.should be_false
|
||||
validator.formatted_error_message(errors).should == "can't be blank"
|
||||
end
|
||||
|
||||
it 'should invalidate an empty password' do
|
||||
strategy = Carto::StrongPasswordStrategy.new
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate('', '')
|
||||
errors.empty?.should be_false
|
||||
validator.formatted_error_message(errors).should == 'must be at least 8 characters long, must contain at ' +
|
||||
'least 1 letter and must contain at least 1 symbol or ' +
|
||||
'1 number'
|
||||
end
|
||||
|
||||
it 'should return an error array' do
|
||||
strategy = Carto::StrongPasswordStrategy.new
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(nil, nil)
|
||||
errors.empty?.should be_false
|
||||
|
||||
message = validator.formatted_error_message(errors)
|
||||
message.should == "can't be blank"
|
||||
|
||||
errors.each do |error|
|
||||
message.should include(error)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should validate a good password' do
|
||||
strategy = Carto::StrongPasswordStrategy.new
|
||||
validator = Carto::PasswordValidator.new(strategy)
|
||||
|
||||
errors = validator.validate(PASSWORD, PASSWORD)
|
||||
errors.empty?.should be_true
|
||||
validator.formatted_error_message(errors).should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,81 @@
|
||||
require 'carto/saml_service'
|
||||
require 'spec_helper_min'
|
||||
|
||||
describe Carto::SamlService do
|
||||
let(:service) do
|
||||
Carto::SamlService.new(@organization)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@organization = FactoryGirl.create(:saml_organization)
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
@organization.delete
|
||||
end
|
||||
|
||||
describe 'configuration support' do
|
||||
it 'is disabled if there is no configuration or it is empty' do
|
||||
org = FactoryGirl.build(:organization, auth_saml_configuration: nil)
|
||||
Carto::SamlService.new(org).enabled?.should be_false
|
||||
|
||||
org = FactoryGirl.build(:organization, auth_saml_configuration: {})
|
||||
Carto::SamlService.new(org).enabled?.should be_false
|
||||
end
|
||||
|
||||
it 'is enabled if there is configuration' do
|
||||
service.enabled?.should be_true
|
||||
end
|
||||
|
||||
it 'email_attribute doesnt return the default value if its defined' do
|
||||
saml_config = @organization.auth_saml_configuration
|
||||
saml_config['email_attribute'] = 'defined_username'
|
||||
service.send(:email_attribute).should eq 'defined_username'
|
||||
saml_config['email_attribute'] = nil
|
||||
service.send(:email_attribute).should eq 'name_id'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Integration logic' do
|
||||
# This stubs the SAML external integration
|
||||
let(:response_mock) { mock }
|
||||
let(:saml_response_param_mock) { mock }
|
||||
let(:saml_config) { @organization.auth_saml_configuration }
|
||||
|
||||
before(:each) do
|
||||
Cartodb.stubs(:config).returns(saml_authentication: saml_config)
|
||||
service.stubs(:get_saml_response).returns(response_mock)
|
||||
service.stubs(:debug_response)
|
||||
end
|
||||
|
||||
describe '#get_user_email' do
|
||||
it 'returns nil if response is invalid' do
|
||||
response_mock.stubs(:is_valid?).raises(OneLogin::RubySaml::ValidationError.new)
|
||||
|
||||
service.get_user_email(saml_response_param_mock).should be_nil
|
||||
end
|
||||
|
||||
it 'returns nil if response lacks email' do
|
||||
response_mock.stubs(:is_valid?).returns(true)
|
||||
response_mock.stubs(:attributes).returns(saml_config.except(:email_attribute))
|
||||
|
||||
service.get_user_email(saml_response_param_mock).should be_nil
|
||||
end
|
||||
|
||||
it 'returns the user with matching email' do
|
||||
user = create_test_saml_user
|
||||
response_mock.stubs(:is_valid?).returns(true)
|
||||
response_mock.stubs(:attributes).returns(saml_config['email_attribute'] => user.email)
|
||||
|
||||
service.get_user_email(saml_response_param_mock).should eq user.email
|
||||
|
||||
user.delete
|
||||
end
|
||||
end
|
||||
|
||||
def create_test_saml_user
|
||||
::User.any_instance.stubs(:after_create).returns(true)
|
||||
FactoryGirl.create(:carto_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
require 'spec_helper_min'
|
||||
require 'tempfile'
|
||||
require 'helpers/subdomainless_helper'
|
||||
|
||||
describe Carto::StorageOptions::Local do
|
||||
# Just here to allow subdomainless helpers to work
|
||||
def host!(_) end
|
||||
|
||||
shared_examples_for 'upload' do
|
||||
before(:all) do
|
||||
@prefix = unique_name('prefix')
|
||||
@storage = Carto::StorageOptions::Local.new(@prefix)
|
||||
@storage.stubs(:public_uploaded_assets_path).returns(upload_path)
|
||||
|
||||
@file = Tempfile.new('test')
|
||||
@file.write('wadus')
|
||||
@file.close
|
||||
|
||||
@path, @url = @storage.upload('123', @file)
|
||||
end
|
||||
|
||||
it 'uploads a file' do
|
||||
File.exists?(@path).should be_true
|
||||
open(@path).read.should eq 'wadus'
|
||||
end
|
||||
|
||||
it 'deletes source file' do
|
||||
File.exists?(@file.path).should be_false
|
||||
end
|
||||
|
||||
it 'url starts with domain/uploads/' do
|
||||
@url.should start_with "/uploads/#{@prefix}/123/"
|
||||
end
|
||||
|
||||
it 'url should not contain filesystem paths' do
|
||||
@url.should_not include '/tmp'
|
||||
@url.should_not include '/carto_uploads'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'upload paths' do
|
||||
describe 'with default path' do
|
||||
let(:upload_path) { 'public/uploads' }
|
||||
|
||||
it_behaves_like 'upload'
|
||||
end
|
||||
|
||||
describe 'with custom path' do
|
||||
let(:upload_path) { '/tmp/carto_uploads' }
|
||||
|
||||
it_behaves_like 'upload'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'domainful' do
|
||||
before(:each) do
|
||||
stub_domainful('org')
|
||||
end
|
||||
|
||||
it_behaves_like 'upload paths'
|
||||
end
|
||||
|
||||
describe 'subdomainless' do
|
||||
before(:each) do
|
||||
stub_subdomainless
|
||||
end
|
||||
|
||||
it_behaves_like 'upload paths'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
require 'spec_helper_min'
|
||||
|
||||
module Carto
|
||||
describe 'Default cartography' do
|
||||
let(:production_default_cartography) do
|
||||
{
|
||||
"simple" => {
|
||||
"point" => {
|
||||
"fill" => {
|
||||
"size" => {
|
||||
"fixed" => 7
|
||||
},
|
||||
"color" => {
|
||||
"fixed" => "#EE4D5A",
|
||||
"opacity" => 0.9
|
||||
}
|
||||
},
|
||||
"stroke" => {
|
||||
"size" => {
|
||||
"fixed" => 1
|
||||
},
|
||||
"color" => {
|
||||
"fixed" => "#FFFFFF",
|
||||
"opacity" => 1
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"line" => {
|
||||
"fill" => {},
|
||||
"stroke" => {
|
||||
"size" => {
|
||||
"fixed" => 1.5
|
||||
},
|
||||
"color" => {
|
||||
"fixed" => "#4CC8A3",
|
||||
"opacity" => 1
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"polygon" => {
|
||||
"fill" => {
|
||||
"color" => {
|
||||
"fixed" => "#826DBA",
|
||||
"opacity" => 0.9
|
||||
}
|
||||
},
|
||||
"stroke" => {
|
||||
"size" => {
|
||||
"fixed" => 1
|
||||
},
|
||||
"color" => {
|
||||
"fixed" => "#FFFFFF",
|
||||
"opacity" => 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'has stayed the same' do
|
||||
cartography_file_path = Carto::Styles::Style::CARTOGRAPHY_DEFINITION_LOCATION
|
||||
cartography = Carto::Definition.instance.load_from_file(cartography_file_path)
|
||||
|
||||
cartography.should eq production_default_cartography
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
require 'spec_helper_min'
|
||||
|
||||
module Carto
|
||||
module Styles
|
||||
describe Geometry do
|
||||
describe '#default' do
|
||||
let(:prod_default_geometry_cartocss) do
|
||||
"#layer['mapnik::geometry_type'=1] {\n"\
|
||||
" marker-width: 7;\n"\
|
||||
" marker-fill: #EE4D5A;\n"\
|
||||
" marker-fill-opacity: 0.9;\n"\
|
||||
" marker-line-color: #FFFFFF;\n"\
|
||||
" marker-line-width: 1;\n"\
|
||||
" marker-line-opacity: 1;\n"\
|
||||
" marker-placement: point;\n"\
|
||||
" marker-type: ellipse;\n"\
|
||||
" marker-allow-overlap: true;\n"\
|
||||
"}\n"\
|
||||
"#layer['mapnik::geometry_type'=2] {\n"\
|
||||
" line-color: #4CC8A3;\n"\
|
||||
" line-width: 1.5;\n"\
|
||||
" line-opacity: 1;\n"\
|
||||
"}\n"\
|
||||
"#layer['mapnik::geometry_type'=3] {\n"\
|
||||
" polygon-fill: #826DBA;\n"\
|
||||
" polygon-opacity: 0.9;\n"\
|
||||
" ::outline {\n"\
|
||||
" line-color: #FFFFFF;\n"\
|
||||
" line-width: 1;\n"\
|
||||
" line-opacity: 0.5;\n"\
|
||||
" }\n"\
|
||||
"}"
|
||||
end
|
||||
|
||||
it 'has stayed the same' do
|
||||
default_point_cartocss = Carto::Styles::Geometry.new.to_cartocss
|
||||
|
||||
default_point_cartocss.should eq prod_default_geometry_cartocss
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
require 'spec_helper_min'
|
||||
|
||||
module Carto
|
||||
module Styles
|
||||
describe Line do
|
||||
describe '#default' do
|
||||
let(:production_default_line_cartocss) do
|
||||
"#layer {\n"\
|
||||
" line-color: #4CC8A3;\n"\
|
||||
" line-width: 1.5;\n"\
|
||||
" line-opacity: 1;\n"\
|
||||
"}"
|
||||
end
|
||||
|
||||
it 'has stayed the same' do
|
||||
current_default_line_cartocss = Carto::Styles::Line.new.to_cartocss
|
||||
|
||||
current_default_line_cartocss.should eq production_default_line_cartocss
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
require 'spec_helper_min'
|
||||
|
||||
module Carto
|
||||
module Styles
|
||||
describe Point do
|
||||
describe '#default' do
|
||||
let(:production_default_point_cartocss) do
|
||||
"#layer {\n"\
|
||||
" marker-width: 7;\n"\
|
||||
" marker-fill: #EE4D5A;\n"\
|
||||
" marker-fill-opacity: 0.9;\n"\
|
||||
" marker-line-color: #FFFFFF;\n"\
|
||||
" marker-line-width: 1;\n"\
|
||||
" marker-line-opacity: 1;\n"\
|
||||
" marker-placement: point;\n"\
|
||||
" marker-type: ellipse;\n"\
|
||||
" marker-allow-overlap: true;\n"\
|
||||
"}"
|
||||
end
|
||||
|
||||
it 'has stayed the same' do
|
||||
current_default_point_cartocss = Carto::Styles::Point.new.to_cartocss
|
||||
|
||||
current_default_point_cartocss.should eq production_default_point_cartocss
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
require 'spec_helper_min'
|
||||
|
||||
module Carto
|
||||
module Styles
|
||||
describe Polygon do
|
||||
describe '#default' do
|
||||
let(:production_default_polygon_cartocss) do
|
||||
"#layer {\n"\
|
||||
" polygon-fill: #826DBA;\n"\
|
||||
" polygon-opacity: 0.9;\n"\
|
||||
" ::outline {\n"\
|
||||
" line-color: #FFFFFF;\n"\
|
||||
" line-width: 1;\n"\
|
||||
" line-opacity: 0.5;\n"\
|
||||
" }\n"\
|
||||
"}"
|
||||
end
|
||||
|
||||
it 'has stayed the same' do
|
||||
current_default_polygon_cartocss = Carto::Styles::Polygon.new.to_cartocss
|
||||
|
||||
current_default_polygon_cartocss.should eq production_default_polygon_cartocss
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
require 'spec_helper_min'
|
||||
|
||||
module Carto
|
||||
module Styles
|
||||
module Presenters
|
||||
describe CartoCSS do
|
||||
before (:all) { @presenter_class = Carto::Styles::Presenters::CartoCSS }
|
||||
after (:all) { @presenter_class = nil }
|
||||
|
||||
let(:empty_cartocss) do
|
||||
"#layer {\n"\
|
||||
"\n"\
|
||||
"}"
|
||||
end
|
||||
|
||||
let(:cartocss_array) do
|
||||
["color-camisa: blanco;",
|
||||
"talla-zapatos: 46;",
|
||||
"altura: 186cm;"]
|
||||
end
|
||||
|
||||
let(:cartocss) do
|
||||
"#layer {\n"\
|
||||
" color-camisa: blanco;\n"\
|
||||
" talla-zapatos: 46;\n"\
|
||||
" altura: 186cm;\n"\
|
||||
"}"
|
||||
end
|
||||
|
||||
it 'returns empty cartocss when no params specified' do
|
||||
@presenter_class.new.to_s.should eq empty_cartocss
|
||||
end
|
||||
|
||||
it 'returns empty cartocss when empty cartocss array is provided' do
|
||||
@presenter_class.new(cartocss_array: []).to_s.should eq empty_cartocss
|
||||
end
|
||||
|
||||
it 'respects provided class name' do
|
||||
@presenter_class.new(class_name: 'manolo').to_s.should include("#manolo {\n")
|
||||
end
|
||||
|
||||
it 'returns properties formatted' do
|
||||
@presenter_class.new(cartocss_array: cartocss_array).to_s.should eq cartocss
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
require_relative '../../../lib/carto/table_utils'
|
||||
|
||||
describe Carto::TableUtils do
|
||||
class TableUtilsTest
|
||||
include Carto::TableUtils
|
||||
end
|
||||
|
||||
table_utils = TableUtilsTest.new
|
||||
|
||||
shared_examples 'safe quoting' do
|
||||
# https://www.postgresql.org/docs/9.3/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||
it 'does not quotes table names if not needed' do
|
||||
table_utils.safe_table_name_quoting('mytable1').should eq 'mytable1'
|
||||
table_utils.safe_table_name_quoting('my_table').should eq 'my_table'
|
||||
table_utils.safe_table_name_quoting('my$table').should eq 'my$table'
|
||||
end
|
||||
|
||||
it 'quotes table names if needed' do
|
||||
table_utils.safe_table_name_quoting('my-table').should eq '"my-table"'
|
||||
table_utils.safe_table_name_quoting('02-34').should eq '"02-34"'
|
||||
table_utils.safe_table_name_quoting('my""ta-ble').should eq '"my""ta-ble"'
|
||||
end
|
||||
|
||||
it 'does not quote already quoted strings' do
|
||||
table_utils.safe_table_name_quoting('"my-table"').should eq '"my-table"'
|
||||
table_utils.safe_table_name_quoting('"my""ta-ble"').should eq '"my""ta-ble"'
|
||||
end
|
||||
|
||||
it 'does not quote names with schema, even if they are wrong (quoting should be fixed upstream)' do
|
||||
['user.table', '"user".table', 'user."table"', '"user"."table"',
|
||||
'u-ser.t-able', '"u-ser".t-able', 'u-ser."t-able"', '"u-ser"."t-able"'].each do |table_name|
|
||||
table_utils.safe_table_name_quoting(table_name).should eq table_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#safe_table_name_quoting' do
|
||||
include_examples 'safe quoting'
|
||||
end
|
||||
|
||||
describe '#safe_schema_name_quoting' do
|
||||
include_examples 'safe quoting'
|
||||
end
|
||||
|
||||
describe '#safe_schema_and_table_quoting' do
|
||||
it 'quotes table names only if needed' do
|
||||
table_utils.safe_schema_and_table_quoting('public', 'my_table').should eq 'public.my_table'
|
||||
table_utils.safe_schema_and_table_quoting('public', 'my-table').should eq 'public."my-table"'
|
||||
table_utils.safe_schema_and_table_quoting('public', 'my""ta-ble').should eq 'public."my""ta-ble"'
|
||||
end
|
||||
|
||||
it 'quotes table names starting with numbers' do
|
||||
table_utils.safe_schema_and_table_quoting('public', '42').should eq 'public."42"'
|
||||
table_utils.safe_schema_and_table_quoting('public', 'a42').should eq 'public.a42'
|
||||
end
|
||||
|
||||
it 'does not quote already quoted strings' do
|
||||
table_utils.safe_schema_and_table_quoting('public', '"my-table"').should eq 'public."my-table"'
|
||||
table_utils.safe_schema_and_table_quoting('public', '"my""ta-ble"').should eq 'public."my""ta-ble"'
|
||||
end
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
require_relative '../../spec_helper'
|
||||
require_relative '../../../lib/carto/user_db_size_cache'
|
||||
|
||||
describe Carto::UserDbSizeCache do
|
||||
let(:user_mock) do
|
||||
OpenStruct.new(id: 'kk', username: 'myusername', db_size_in_bytes: 123)
|
||||
end
|
||||
|
||||
let(:umrc) do
|
||||
Carto::UserDbSizeCache.new
|
||||
end
|
||||
|
||||
let(:redis_key) do
|
||||
umrc.send(:db_size_in_bytes_key, user_mock.username)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
$users_metadata.del(redis_key)
|
||||
end
|
||||
|
||||
describe '#update_if_old' do
|
||||
it 'sets db_size_in_bytes for users that have not been updated in 2 days' do
|
||||
umrc.expects(:set_db_size_in_bytes).with(user_mock).once
|
||||
umrc.update_if_old(user_mock)
|
||||
|
||||
umrc.db_size_in_bytes(user_mock).should eq 0
|
||||
end
|
||||
|
||||
it 'does not set db_size_in_bytes for users that have been updated in an hour' do
|
||||
$users_metadata.setex(redis_key, 2.days - 1.hour, 456)
|
||||
umrc.expects(:set_db_size_in_bytes).never
|
||||
|
||||
umrc.update_if_old(user_mock)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#db_size_in_bytes_change_users' do
|
||||
it 'returns db_size_in_bytes_change in a hash with username keys' do
|
||||
umrc.update_if_old(user_mock)
|
||||
|
||||
db_size_in_bytes_change_users = umrc.db_size_in_bytes_change_users
|
||||
db_size_in_bytes_change_users.keys.include?(user_mock.username).should be_true
|
||||
db_size_in_bytes_change_users[user_mock.username].should == user_mock.db_size_in_bytes
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,90 @@
|
||||
require_relative '../../../lib/carto/valid_table_name_proposer.rb'
|
||||
|
||||
module Carto
|
||||
describe ValidTableNameProposer do
|
||||
before(:all) do
|
||||
@valid_table_name_proposer = Carto::ValidTableNameProposer.new
|
||||
end
|
||||
|
||||
let(:taken) { %w{table_m_nolo_es_co_bar_ table_m_nolo_es_co_bar_1 table_m_nolo_es_co_bar_3} }
|
||||
|
||||
describe '#propose_valid_table_name' do
|
||||
it 'Regression for CDB-3446' do
|
||||
@valid_table_name_proposer.propose_valid_table_name('table_', taken_names: []).should == 'table_'
|
||||
@valid_table_name_proposer.propose_valid_table_name('table_', taken_names: ['table_']).should == 'table_1'
|
||||
@valid_table_name_proposer.propose_valid_table_name('table_', taken_names: ['table_1']).should == 'table_'
|
||||
@valid_table_name_proposer.propose_valid_table_name('table_1', taken_names: ['table_1']).should == 'table_1_1'
|
||||
end
|
||||
|
||||
it 'sanitizes reserved words appending _t' do
|
||||
@valid_table_name_proposer.propose_valid_table_name('table', taken_names: ['table_1']).should == 'table_t'
|
||||
end
|
||||
|
||||
it 'should sanitize name' do
|
||||
valid_name = @valid_table_name_proposer.propose_valid_table_name("Mªnolo !Es'co`bar##!", taken_names: taken)
|
||||
|
||||
valid_name.should eq 'm_nolo_es_co_bar_'
|
||||
end
|
||||
|
||||
it 'handles long titles properly' do
|
||||
long_name1 = 'carto_long_filename_that_almost_matches_another_one_63chars_aaa'
|
||||
long_name2 = 'carto_long_filename_that_almost_matches_another_one_63chars_aab'
|
||||
expected_name = 'carto_long_filename_that_almost_matches_another_one_63chars_a_1'
|
||||
|
||||
expect(
|
||||
@valid_table_name_proposer.propose_valid_table_name(long_name1, taken_names: [])
|
||||
).to eq(long_name1)
|
||||
|
||||
expect(
|
||||
@valid_table_name_proposer.propose_valid_table_name(long_name2, taken_names: [long_name1])
|
||||
).to eq(expected_name)
|
||||
end
|
||||
|
||||
it 'should remove disallowed starting characters' do
|
||||
valid_name = @valid_table_name_proposer.propose_valid_table_name("____Mªnolo !Es'co`bar##!", taken_names: [])
|
||||
valid_name.should eq 'table_m_nolo_es_co_bar_'
|
||||
|
||||
valid_name = @valid_table_name_proposer.propose_valid_table_name("____Mªnolo !Es'co`bar##!", taken_names: taken)
|
||||
valid_name.should eq 'table_m_nolo_es_co_bar_2'
|
||||
end
|
||||
|
||||
it 'should find unused names' do
|
||||
valid_name = @valid_table_name_proposer.propose_valid_table_name("____Mªnolo !Es'co`bar##!", taken_names: taken)
|
||||
|
||||
valid_name.should eq 'table_m_nolo_es_co_bar_2'
|
||||
end
|
||||
|
||||
it 'should find unused names when taken_names is specified' do
|
||||
taken = %w(manolo_escobar manolo_escobar_1)
|
||||
|
||||
valid_name = @valid_table_name_proposer.propose_valid_table_name("manolo_escobar", taken_names: taken)
|
||||
|
||||
valid_name.should eq 'manolo_escobar_2'
|
||||
end
|
||||
|
||||
it 'should propose valid names when no contendent is specified' do
|
||||
valid_name = @valid_table_name_proposer.propose_valid_table_name(taken_names: taken)
|
||||
|
||||
valid_name.should eq 'untitled_table'
|
||||
end
|
||||
|
||||
it 'should propose valid names when nil contendent is specified' do
|
||||
valid_name = @valid_table_name_proposer.propose_valid_table_name(nil, taken_names: taken)
|
||||
|
||||
valid_name.should eq 'untitled_table'
|
||||
end
|
||||
|
||||
it 'should propose valid names when empty contendent is specified' do
|
||||
valid_name = @valid_table_name_proposer.propose_valid_table_name('', taken_names: taken)
|
||||
|
||||
valid_name.should eq 'untitled_table'
|
||||
end
|
||||
|
||||
it 'should propose valid names when empty taken_names is specified' do
|
||||
valid_name = @valid_table_name_proposer.propose_valid_table_name(taken_names: taken)
|
||||
|
||||
valid_name.should eq 'untitled_table'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,98 @@
|
||||
require 'spec_helper_min'
|
||||
require 'carto/visualization_invalidation_service'
|
||||
require 'factories/carto_visualizations'
|
||||
|
||||
describe Carto::VisualizationInvalidationService do
|
||||
include Carto::Factories::Visualizations
|
||||
|
||||
before(:all) do
|
||||
@user = FactoryGirl.create(:carto_user)
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@user.destroy
|
||||
end
|
||||
|
||||
shared_examples_for 'cache invalidator' do
|
||||
it 'should invalidate all caches: redis/varnish vizjson and embed' do
|
||||
CartoDB::Visualization::RedisVizjsonCache.any_instance.expects(:invalidate).with(@visualization.id)
|
||||
CartoDB::Varnish.any_instance.expects(:purge).with(".*#{@visualization.id}:vizjson")
|
||||
EmbedRedisCache.any_instance.expects(:invalidate).with(@visualization.id)
|
||||
@service.invalidate
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on update' do
|
||||
before(:each) do
|
||||
@map, @table, @table_visualization, @visualization = create_full_visualization(@user)
|
||||
@service = Carto::VisualizationInvalidationService.new(@visualization)
|
||||
end
|
||||
|
||||
# NOTE: Not destroying the visualizations in an after hook to avoid it triggering the wrong expectations
|
||||
|
||||
it_behaves_like 'cache invalidator'
|
||||
|
||||
describe 'named map invalidation' do
|
||||
it 'should create/update named map' do
|
||||
Carto::NamedMaps::Api.any_instance.stubs(:show).returns(nil)
|
||||
Carto::NamedMaps::Api.any_instance.expects(:create).once
|
||||
@service.invalidate
|
||||
end
|
||||
|
||||
it 'should not create/update named map for maps without layers' do
|
||||
@map.layers.each(&:destroy)
|
||||
Carto::NamedMaps::Api.any_instance.stubs(:show).returns(nil)
|
||||
Carto::NamedMaps::Api.any_instance.expects(:create).never
|
||||
@service.invalidate
|
||||
end
|
||||
|
||||
it 'should not create/update named map for remote visualizations' do
|
||||
@visualization.update_attribute(:type, 'remote')
|
||||
Carto::NamedMaps::Api.any_instance.stubs(:show).returns(nil)
|
||||
Carto::NamedMaps::Api.any_instance.expects(:create).never
|
||||
@service.invalidate
|
||||
end
|
||||
end
|
||||
|
||||
describe 'affected visualizations invalidation' do
|
||||
before(:each) do
|
||||
@canonical_service = Carto::VisualizationInvalidationService.new(@table_visualization)
|
||||
end
|
||||
|
||||
it 'should not invalidate cache of related maps by default' do
|
||||
Carto::VisualizationInvalidationService.expects(:new).never
|
||||
@canonical_service.invalidate
|
||||
end
|
||||
|
||||
describe 'should invalidate cache of related maps when description changes' do
|
||||
before(:each) do
|
||||
@service = @canonical_service
|
||||
@table_visualization.description = 'wadus'
|
||||
@table_visualization.save
|
||||
|
||||
CartoDB::Visualization::RedisVizjsonCache.any_instance.expects(:invalidate).with(@table_visualization.id)
|
||||
CartoDB::Varnish.any_instance.expects(:purge).with(".*#{@table_visualization.id}:vizjson")
|
||||
EmbedRedisCache.any_instance.expects(:invalidate).with(@table_visualization.id)
|
||||
end
|
||||
|
||||
# Should run all invalidations on the affected map, when invalidating the canonical visualization
|
||||
it_behaves_like 'cache invalidator'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on destruction' do
|
||||
before(:all) do
|
||||
@visualization = FactoryGirl.create(:carto_visualization, user: @user)
|
||||
@visualization.destroy
|
||||
@service = Carto::VisualizationInvalidationService.new(@visualization)
|
||||
end
|
||||
|
||||
it_behaves_like 'cache invalidator'
|
||||
|
||||
it 'should destroy named map' do
|
||||
Carto::NamedMaps::Api.any_instance.expects(:destroy).once
|
||||
@service.invalidate
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,71 @@
|
||||
require 'spec_helper_min'
|
||||
require 'carto/visualization_migrator'
|
||||
|
||||
describe Carto::VisualizationMigrator do
|
||||
include Carto::Factories::Visualizations
|
||||
include_context 'visualization creation helpers'
|
||||
|
||||
class VizMigrator
|
||||
include Carto::VisualizationMigrator
|
||||
end
|
||||
|
||||
let(:migrator) { VizMigrator.new }
|
||||
|
||||
shared_context 'full visualization' do
|
||||
before(:all) do
|
||||
@user_1 = FactoryGirl.create(:carto_user, private_tables_enabled: false)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@map, @table, @table_visualization, @visualization = create_full_visualization(Carto::User.find(@user_1.id), visualization_attributes: { version: 3 })
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
destroy_full_visualization(@map, @table, @table_visualization, @visualization)
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@user_1.destroy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#migrate_visualization_to_v3' do
|
||||
include_context 'full visualization'
|
||||
|
||||
it 'migrates layer selector for ActiveRecord model' do
|
||||
@visualization.overlays << Carto::Overlay.new(type: 'layer_selector')
|
||||
@visualization.reload
|
||||
|
||||
migrator.migrate_visualization_to_v3(@visualization)
|
||||
|
||||
@visualization.reload
|
||||
@visualization.overlays.any? { |o| o.type == 'layer_selector' }.should be_false
|
||||
@visualization.map.options['layer_selector'].should be_true
|
||||
end
|
||||
|
||||
it 'fixes GMaps options' do
|
||||
basemap = @visualization.layers.first
|
||||
basemap.kind = 'gmapsbase'
|
||||
basemap.options = { "type" => "GMapsBase", "base_type" => "roadmap" }
|
||||
basemap.save!
|
||||
|
||||
migrator.migrate_visualization_to_v3(@visualization)
|
||||
|
||||
@visualization.reload
|
||||
@visualization.layers.first.options.should have_key(:baseType)
|
||||
@visualization.layers.first.options.should_not have_key(:base_type)
|
||||
end
|
||||
|
||||
it 'adds analyses' do
|
||||
# Fixture check
|
||||
expect(@visualization.analyses.none?).to be_true
|
||||
expect(@visualization.data_layers.none?(&:source_id)).to be_true
|
||||
|
||||
migrator.migrate_visualization_to_v3(@visualization)
|
||||
|
||||
@visualization.reload
|
||||
expect(@visualization.analyses.any?).to be_true
|
||||
expect(@visualization.data_layers.all?(&:source_id)).to be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
require_relative '../../../app/helpers/common_data_redis_cache'
|
||||
require_relative '../../spec_helper_min'
|
||||
require 'mock_redis'
|
||||
|
||||
describe CommonDataRedisCache do
|
||||
it 'handles encoding issues' do
|
||||
string_with_wrong_encoding = "\xc2\xa0".force_encoding('ascii-8bit')
|
||||
expected_result = { headers: {}, body: string_with_wrong_encoding }
|
||||
@mock_redis = MockRedis.new
|
||||
cdrc = CommonDataRedisCache.new(@mock_redis)
|
||||
cdrc.set(true, {}, string_with_wrong_encoding).should eq "OK"
|
||||
cdrc.get(true).should eq expected_result
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,86 @@
|
||||
require_relative '../../spec_helper'
|
||||
|
||||
describe CartoDB::ConnectionPool do
|
||||
before(:all) do
|
||||
@max_pool_size = ConnectionPool::MAX_POOL_SIZE
|
||||
ConnectionPool::MAX_POOL_SIZE = 2
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
ConnectionPool::MAX_POOL_SIZE = @max_pool_size
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
# Need to close the connections because there might be more than the new maximum already in the pool
|
||||
$pool.close_connections!
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
@users.map(&:destroy) if @users
|
||||
end
|
||||
|
||||
def database_object_count
|
||||
GC.start
|
||||
ObjectSpace.each_object(Sequel::Postgres::Database).count
|
||||
end
|
||||
|
||||
def user_object_count
|
||||
GC.start
|
||||
ObjectSpace.each_object(User).count
|
||||
end
|
||||
|
||||
def pool_contains?(connection)
|
||||
$pool.all.values.map { |a| a[:connection] }.include?(connection)
|
||||
end
|
||||
|
||||
describe '#eviction_policy' do
|
||||
it 'evicts older connection (LRU)' do
|
||||
@users = [create_user, create_user, create_user]
|
||||
conns = @users.map(&:in_database)
|
||||
|
||||
pool_contains?(conns[0]).should be_false
|
||||
pool_contains?(conns[1]).should be_true
|
||||
pool_contains?(conns[2]).should be_true
|
||||
|
||||
conns[0] = @users[0].in_database
|
||||
pool_contains?(conns[0]).should be_true
|
||||
pool_contains?(conns[1]).should be_false
|
||||
pool_contains?(conns[2]).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#user_databases' do
|
||||
it 'does not leak user databases' do
|
||||
# The maximum number of connections allowed in memory before the test is considered to be leaking (failure).
|
||||
# Although the pool will only keep 2 connections open (MAX_POOL_SIZE set at the beginning of the test),
|
||||
# there are some other connections that are not tracked by the pool (tracked by Rails instead).
|
||||
# Typical # of connections is 2 for correct tests and 45 for leaky tests but vary depending on execution order.
|
||||
MAX_ALLOWABLE_CONNECTIONS = 10
|
||||
|
||||
initial_user_count = user_object_count
|
||||
initial_db_count = database_object_count
|
||||
@users = [create_user, create_user, create_user]
|
||||
|
||||
# Create some connections to user database and check that they are not leaked
|
||||
(0..4).each do |_|
|
||||
@users.each do |user|
|
||||
user.in_database.test_connection.should be_true
|
||||
end
|
||||
end
|
||||
database_object_count.should < (initial_db_count + MAX_ALLOWABLE_CONNECTIONS)
|
||||
|
||||
# Destroy new user and ensure it does not leaks (as soon as his db connection is evicted)
|
||||
@users.delete_at(0).destroy
|
||||
|
||||
(0..4).each do |_|
|
||||
@users.each do |user|
|
||||
user.in_database.test_connection.should be_true
|
||||
end
|
||||
end
|
||||
database_object_count.should < (initial_db_count + MAX_ALLOWABLE_CONNECTIONS)
|
||||
|
||||
pending 'Inconsistent, depending on ruby version'
|
||||
user_object_count.should eq initial_user_count
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
require 'spec_helper_min'
|
||||
require_relative '../../../lib/cartodb/redis_vizjson_cache'
|
||||
require 'mock_redis'
|
||||
require 'ostruct'
|
||||
require_relative '../../support/redis_test_doubles'
|
||||
require_relative '../../../lib/carto/uuidhelper'
|
||||
|
||||
describe CartoDB::Visualization::RedisVizjsonCache do
|
||||
include Carto::UUIDHelper
|
||||
|
||||
# http and https
|
||||
PROTOCOL_KEYS_COUNT = 2
|
||||
|
||||
# v2 and v3 and v3 forcing named/anonymous maps
|
||||
VIZJSON_VERSION_COUNT = 4
|
||||
|
||||
KEYS_PER_VISUALIZATION = PROTOCOL_KEYS_COUNT * VIZJSON_VERSION_COUNT
|
||||
|
||||
let(:redis_spy) { RedisDoubles::RedisSpy.new }
|
||||
let(:redis_mock) { MockRedis.new }
|
||||
|
||||
describe '#cached' do
|
||||
let(:fake_vizjson) { { test: 1 } }
|
||||
|
||||
it 'caches by id' do
|
||||
visualization_id = random_uuid
|
||||
cache = CartoDB::Visualization::RedisVizjsonCache.new
|
||||
# Not cached
|
||||
returned = cache.cached(visualization_id) { fake_vizjson }
|
||||
returned.should eq fake_vizjson
|
||||
# Cached
|
||||
cache.cached(visualization_id) { fake_vizjson2 }
|
||||
returned.should eq fake_vizjson
|
||||
end
|
||||
end
|
||||
|
||||
describe '#invalidate' do
|
||||
it 'deletes all keys from a visualization' do
|
||||
visualization_id = random_uuid
|
||||
cache = CartoDB::Visualization::RedisVizjsonCache.new(redis_spy)
|
||||
cache.invalidate(visualization_id)
|
||||
|
||||
deleted = redis_spy.deleted
|
||||
|
||||
deleted.count.should eq KEYS_PER_VISUALIZATION
|
||||
|
||||
deleted.count { |d| d.match(/:http:/) }.should eq VIZJSON_VERSION_COUNT
|
||||
deleted.count { |d| d.match(/:https:/) }.should eq VIZJSON_VERSION_COUNT
|
||||
deleted.count { |d| d.match(/#{visualization_id}/) }.should eq KEYS_PER_VISUALIZATION
|
||||
end
|
||||
end
|
||||
|
||||
describe '#purge' do
|
||||
it 'deletes all keys from all visualizations from redis' do
|
||||
vs = [OpenStruct.new(id: random_uuid), OpenStruct.new(id: random_uuid)]
|
||||
cache = CartoDB::Visualization::RedisVizjsonCache.new(redis_spy)
|
||||
cache.purge(vs)
|
||||
|
||||
deleted = redis_spy.deleted
|
||||
|
||||
deleted.count.should eq vs.count * KEYS_PER_VISUALIZATION
|
||||
|
||||
deleted.count { |d| d.match(/:http:/) }.should eq vs.count * VIZJSON_VERSION_COUNT
|
||||
deleted.count { |d| d.match(/:https:/) }.should eq vs.count * VIZJSON_VERSION_COUNT
|
||||
|
||||
deleted.count { |d| d.match(/#{vs[0].id}/) }.should eq KEYS_PER_VISUALIZATION
|
||||
deleted.count { |d| d.match(/#{vs[1].id}/) }.should eq KEYS_PER_VISUALIZATION
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,149 @@
|
||||
require_relative '../../../simplecov_helper'
|
||||
require 'rspec/core'
|
||||
require 'rspec/expectations'
|
||||
require 'rspec/mocks'
|
||||
require_relative '../../../rspec_configuration.rb'
|
||||
|
||||
require_relative '../../../../lib/cartodb/stats/importer'
|
||||
|
||||
include Mocha::ParameterMatchers
|
||||
|
||||
module CartoDB
|
||||
module Stats
|
||||
describe Importer do
|
||||
TEST_HOST = '172.28.128.3'.freeze
|
||||
TEST_PORT = 8125
|
||||
TIMING_TEST_KEY_A = 'test'.freeze
|
||||
TIMING_TEST_KEY_B = 'timing'.freeze
|
||||
TIMING_TEST_KEY = "#{TIMING_TEST_KEY_A}.#{TIMING_TEST_KEY_B}".freeze
|
||||
GAUGE_TEST_KEY = 'gauge'.freeze
|
||||
GAUGE_TEST_VALUE = 0.5
|
||||
HOST_INFO = "fake-test-queue".freeze
|
||||
|
||||
EXPECTED_PREFIX = "importer.#{HOST_INFO}".freeze
|
||||
|
||||
describe '#timing' do
|
||||
it "sends key with importer prefix" do
|
||||
expected_send("#{EXPECTED_PREFIX}.#{TIMING_TEST_KEY}")
|
||||
CartoDB::Stats::Importer.instance(
|
||||
{
|
||||
'host' => TEST_HOST,
|
||||
'port' => TEST_PORT
|
||||
}, HOST_INFO).timing(TIMING_TEST_KEY) { foo }
|
||||
end
|
||||
|
||||
it "doesn't send anything if host or port are not met" do
|
||||
expected_send_nothing
|
||||
CartoDB::Stats::Importer.instance(
|
||||
'host' => nil,
|
||||
'port' => nil
|
||||
).timing(TIMING_TEST_KEY) { foo }
|
||||
CartoDB::Stats::Importer.instance(
|
||||
'host' => TEST_HOST,
|
||||
'port' => nil
|
||||
).timing(TIMING_TEST_KEY) { foo }
|
||||
CartoDB::Stats::Importer.instance(
|
||||
'host' => nil,
|
||||
'port' => TEST_PORT
|
||||
).timing(TIMING_TEST_KEY) { foo }
|
||||
end
|
||||
|
||||
it "runs block even without host or port" do
|
||||
count = 0
|
||||
CartoDB::Stats::Importer.instance(
|
||||
'host' => nil,
|
||||
'port' => nil
|
||||
).timing(TIMING_TEST_KEY) do
|
||||
count += 1
|
||||
end
|
||||
CartoDB::Stats::Importer.instance(
|
||||
'host' => TEST_HOST,
|
||||
'port' => nil
|
||||
).timing(TIMING_TEST_KEY) do
|
||||
count += 1
|
||||
end
|
||||
CartoDB::Stats::Importer.instance(
|
||||
'host' => nil,
|
||||
'port' => TEST_PORT
|
||||
).timing(TIMING_TEST_KEY) do
|
||||
count += 1
|
||||
end
|
||||
count.should eq 3
|
||||
end
|
||||
|
||||
it "registers nested timing" do
|
||||
expected_send("#{EXPECTED_PREFIX}.#{TIMING_TEST_KEY_A}")
|
||||
expected_send("#{EXPECTED_PREFIX}.#{TIMING_TEST_KEY}")
|
||||
importer_stats = CartoDB::Stats::Importer.instance(
|
||||
{
|
||||
'host' => TEST_HOST,
|
||||
'port' => TEST_PORT
|
||||
}, HOST_INFO)
|
||||
importer_stats.timing(TIMING_TEST_KEY_A) do
|
||||
importer_stats.timing(TIMING_TEST_KEY_B) do
|
||||
foo
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'sends nothing and discards key fragment if block fails' do
|
||||
expected_send("#{EXPECTED_PREFIX}.#{TIMING_TEST_KEY_B}")
|
||||
importer_stats = CartoDB::Stats::Importer.instance(
|
||||
{
|
||||
'host' => TEST_HOST,
|
||||
'port' => TEST_PORT
|
||||
}, HOST_INFO)
|
||||
begin
|
||||
importer_stats.timing(TIMING_TEST_KEY_A) do
|
||||
raise 'error'
|
||||
end
|
||||
rescue
|
||||
# INFO: keep going
|
||||
end
|
||||
importer_stats.timing(TIMING_TEST_KEY_B) do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#gauge' do
|
||||
it 'sends key with importer prefix' do
|
||||
expected_send("#{EXPECTED_PREFIX}.#{GAUGE_TEST_KEY}")
|
||||
CartoDB::Stats::Importer.instance(
|
||||
{
|
||||
'host' => TEST_HOST,
|
||||
'port' => TEST_PORT
|
||||
}, HOST_INFO).gauge(GAUGE_TEST_KEY, GAUGE_TEST_VALUE)
|
||||
end
|
||||
|
||||
it "doesn't send anything if host or port are not met" do
|
||||
expected_send_nothing
|
||||
CartoDB::Stats::Importer.instance(
|
||||
'host' => nil,
|
||||
'port' => nil
|
||||
).gauge(GAUGE_TEST_KEY, GAUGE_TEST_VALUE)
|
||||
CartoDB::Stats::Importer.instance(
|
||||
'host' => nil,
|
||||
'port' => TEST_PORT
|
||||
).gauge(GAUGE_TEST_KEY, GAUGE_TEST_VALUE)
|
||||
CartoDB::Stats::Importer.instance(
|
||||
'host' => TEST_HOST,
|
||||
'port' => nil
|
||||
).gauge(GAUGE_TEST_KEY, GAUGE_TEST_VALUE)
|
||||
end
|
||||
end
|
||||
|
||||
def foo
|
||||
sleep 0.001
|
||||
end
|
||||
|
||||
def expected_send(buf)
|
||||
buf_re = %r{^#{buf}:}
|
||||
UDPSocket.any_instance.expects(:send).with(regexp_matches(buf_re), 0, TEST_HOST, TEST_PORT).once
|
||||
end
|
||||
|
||||
def expected_send_nothing
|
||||
UDPSocket.any_instance.expects(:send).never
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
describe CartoDB::Stats::Platform do
|
||||
describe '#pay_users' do
|
||||
it 'returns only paid users' do
|
||||
pay_users = CartoDB::Stats::Platform.new.pay_users
|
||||
|
||||
FactoryGirl.create(:user, account_type: 'FREE')
|
||||
FactoryGirl.create(:user, account_type: 'MAGELLAN')
|
||||
|
||||
CartoDB::Stats::Platform.new.pay_users.should == (pay_users + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,106 @@
|
||||
require 'spec_helper'
|
||||
require_relative '../../lib/cartodb/central'
|
||||
|
||||
def config
|
||||
Cartodb.config[:cartodb_central_api].present? ? Cartodb.config[:cartodb_central_api].deep_symbolize_keys : nil
|
||||
end
|
||||
|
||||
def config_present?
|
||||
config.present? && config[:username].present? && config[:password].present?
|
||||
end
|
||||
|
||||
def assert_headers_and_auth(request)
|
||||
request.options[:headers]["Content-Type"].should == "application/json"
|
||||
request.options[:userpwd].should == "#{ config[:username] }:#{ config[:password] }"
|
||||
end
|
||||
|
||||
describe Cartodb::Central do
|
||||
|
||||
before(:all) do
|
||||
if config_present?
|
||||
@organization = create_organization_with_users(name: 'test-org')
|
||||
@user = @organization.users.first
|
||||
@user.username = 'user1'
|
||||
@user.save
|
||||
@cartodb_central_client = Cartodb::Central.new
|
||||
@org_path = "/api/organizations/#{ @organization.name }"
|
||||
@users_path = "#{ @org_path }/users"
|
||||
@user_path = "#{ @users_path }/#{ @user.username }"
|
||||
end
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
bypass_named_maps
|
||||
@user && @user.destroy
|
||||
end
|
||||
|
||||
describe "Central synchronization client" do
|
||||
|
||||
before(:each) do
|
||||
pending "Central API credentials not present in app_config.yml" unless config_present?
|
||||
end
|
||||
|
||||
describe "Organization users" do
|
||||
it "gets all users from an organization" do
|
||||
request = @cartodb_central_client.build_request(@users_path, nil, :get)
|
||||
|
||||
request.url.should == "#{ @cartodb_central_client.host }/#{ @users_path }"
|
||||
request.options[:method].should == :get
|
||||
assert_headers_and_auth(request)
|
||||
end
|
||||
|
||||
it "gets an organization user" do
|
||||
request = @cartodb_central_client.build_request(@user_path, nil, :get)
|
||||
|
||||
request.url.should == "#{ @cartodb_central_client.host }/#{ @user_path }"
|
||||
request.options[:method].should == :get
|
||||
assert_headers_and_auth(request)
|
||||
end
|
||||
|
||||
it "creates an organization user" do
|
||||
body = { user: @user.allowed_attributes_to_central(:create) }
|
||||
|
||||
request = @cartodb_central_client.build_request(@user_path, body, :post)
|
||||
|
||||
request.url.should == "#{ @cartodb_central_client.host }/#{ @user_path }"
|
||||
request.options[:method].should == :post
|
||||
request.options[:body].should == { user: @user.allowed_attributes_to_central(:create) }.to_json
|
||||
assert_headers_and_auth(request)
|
||||
end
|
||||
|
||||
it "updates an organization user" do
|
||||
body = { user: @user.allowed_attributes_to_central(:update) }
|
||||
|
||||
request = @cartodb_central_client.build_request(@user_path, body, :put)
|
||||
|
||||
request.url.should == "#{ @cartodb_central_client.host }/#{ @user_path }"
|
||||
request.options[:method].should == :put
|
||||
request.options[:body].should == { user: @user.allowed_attributes_to_central(:update) }.to_json
|
||||
assert_headers_and_auth(request)
|
||||
end
|
||||
|
||||
it "deletes an organization user" do
|
||||
request = @cartodb_central_client.build_request(@user_path, nil, :delete)
|
||||
|
||||
request.url.should == "#{ @cartodb_central_client.host }/#{ @user_path }"
|
||||
request.options[:method].should == :delete
|
||||
assert_headers_and_auth(request)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Organizations" do
|
||||
it "updates an organization" do
|
||||
body = { organization: @organization.allowed_attributes_to_central(:update) }
|
||||
|
||||
request = @cartodb_central_client.build_request(@org_path, body, :put)
|
||||
|
||||
request.url.should == "#{ @cartodb_central_client.host }/#{ @org_path }"
|
||||
request.options[:headers]["Content-Type"].should == "application/json"
|
||||
request.options[:method].should == :put
|
||||
request.options[:body].should == { organization: @organization.allowed_attributes_to_central(:update) }.to_json
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe CartoDB do
|
||||
describe '#base_cartodb_error' do
|
||||
it 'Tests usage of the base cartodb exception/error' do
|
||||
error_message = 'something !!!'
|
||||
nested_exception = StandardError.new('123456 cartodb')
|
||||
|
||||
expect {
|
||||
begin
|
||||
raise nested_exception
|
||||
rescue StandardError => exc
|
||||
raise CartoDB::BaseCartoDBError.new(error_message, exc)
|
||||
end
|
||||
}.to raise_exception CartoDB::BaseCartoDBError
|
||||
|
||||
begin
|
||||
begin
|
||||
raise nested_exception
|
||||
rescue StandardError => exc
|
||||
raise CartoDB::BaseCartoDBError.new(error_message, exc)
|
||||
end
|
||||
rescue CartoDB::BaseCartoDBError => our_exception
|
||||
our_exception.backtrace.should eq nested_exception.backtrace
|
||||
our_exception.message.should eq (error_message + CartoDB::BaseCartoDBError::APPENDED_MESSAGE_PREFIX + nested_exception.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe CartoDB::CentralCommunicationFailure do
|
||||
describe '#user_message' do
|
||||
it 'supports single error JSON messages' do
|
||||
error = 'Something is really broken'
|
||||
response = mock
|
||||
response.stubs(:code).returns(403)
|
||||
response.stubs(:body).returns("{\"errors\":\"#{error}\"}")
|
||||
e = CartoDB::CentralCommunicationFailure.new(response)
|
||||
e.user_message.should eq "There was a problem with authentication server. #{error}"
|
||||
end
|
||||
|
||||
it 'supports multiple error JSON messages' do
|
||||
response = mock
|
||||
response.stubs(:code).returns(403)
|
||||
response.stubs(:body).returns("{\"errors\":[\"A\", \"B\"]}")
|
||||
e = CartoDB::CentralCommunicationFailure.new(response)
|
||||
e.user_message.should eq "There was a problem with authentication server. A ; B"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
# Monkeypatch overriding bind_as, not supported by FakeNetLdap.
|
||||
# Patched code:
|
||||
# https://github.com/ruby-ldap/ruby-net-ldap/blob/3bf849d415a691b5632f2e20cc637e377b15b2ad/lib/net/ldap.rb#L912
|
||||
# Incomplete fake:
|
||||
# https://github.com/roovo/fake_net_ldap/blob/a1d20013103c70ed0bb66792286c7657d51c6cf9/lib/fake_net_ldap/ext/net_ldap.rb
|
||||
class Net::LDAP
|
||||
def bind_as(args)
|
||||
rs = search(args)
|
||||
if rs && rs.first && dn = rs.first[:dn]
|
||||
password = args[:password]
|
||||
password = password.call if password.respond_to?(:call)
|
||||
@username = dn
|
||||
@password = password
|
||||
rs if bind
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,80 @@
|
||||
require_relative '../simplecov_helper'
|
||||
require_relative '../../lib/cartodb/image_metadata.rb'
|
||||
require_relative '../../spec/rspec_configuration.rb'
|
||||
|
||||
describe CartoDB::ImageMetadata do
|
||||
let(:png_path) { File.expand_path('../../support/data/images/pattern.png', __FILE__) }
|
||||
let(:jpg_path) { File.expand_path('../../support/data/images/pattern.jpg', __FILE__) }
|
||||
let(:svg_path) { File.expand_path('../../support/data/images/pattern.svg', __FILE__) }
|
||||
let(:svg_no_xml_header_path) { File.expand_path('../../support/data/images/svg_without_xml_header', __FILE__) }
|
||||
|
||||
describe '#extract_metadata' do
|
||||
let(:metadata) { CartoDB::ImageMetadata.new(png_path) }
|
||||
it 'should call parse_identify if magick is installed' do
|
||||
metadata.stubs(:has_magick?).returns(true)
|
||||
metadata.expects(:parse_identify).once
|
||||
metadata.expects(:parse_file).never
|
||||
metadata.extract_metadata
|
||||
end
|
||||
|
||||
it 'should call parse_file if magick is not installed' do
|
||||
metadata.stubs(:has_magick?).returns(false)
|
||||
metadata.expects(:parse_file).once
|
||||
metadata.expects(:parse_identify).never
|
||||
metadata.extract_metadata
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parse_file' do
|
||||
it 'should extract png dimensions' do
|
||||
metadata = CartoDB::ImageMetadata.new(png_path)
|
||||
metadata.parse_file
|
||||
metadata.width.should eq 260
|
||||
metadata.height.should eq 260
|
||||
end
|
||||
|
||||
it 'should not raise error when trying to extract jpg dimensions' do
|
||||
metadata = CartoDB::ImageMetadata.new(jpg_path)
|
||||
expect { metadata.parse_file }.to_not raise_error
|
||||
metadata.width.should eq 0
|
||||
metadata.height.should eq 0
|
||||
end
|
||||
|
||||
it 'should not raise error when trying to extract svg dimensions' do
|
||||
metadata = CartoDB::ImageMetadata.new(svg_path)
|
||||
expect { metadata.parse_file }.to_not raise_error
|
||||
metadata.width.should eq 0
|
||||
metadata.height.should eq 0
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parse_identify' do
|
||||
it 'should extract png dimensions' do
|
||||
metadata = CartoDB::ImageMetadata.new(png_path)
|
||||
metadata.parse_identify
|
||||
metadata.width.should eq 260
|
||||
metadata.height.should eq 260
|
||||
end
|
||||
|
||||
it 'should extract jpg dimensions' do
|
||||
metadata = CartoDB::ImageMetadata.new(jpg_path)
|
||||
metadata.parse_identify
|
||||
metadata.width.should eq 250
|
||||
metadata.height.should eq 250
|
||||
end
|
||||
|
||||
it 'should extract svg dimensions' do
|
||||
metadata = CartoDB::ImageMetadata.new(svg_path)
|
||||
expect { metadata.parse_identify }.to_not raise_error
|
||||
metadata.width.should eq 645
|
||||
metadata.height.should eq 585
|
||||
end
|
||||
|
||||
it 'should extract svg dimensions in files without XML header if forced as svg' do
|
||||
metadata = CartoDB::ImageMetadata.new(svg_no_xml_header_path, extension: '.svg')
|
||||
expect { metadata.parse_identify }.to_not raise_error
|
||||
metadata.width.should eq 11
|
||||
metadata.height.should eq 11
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,214 @@
|
||||
require_relative '../../spec_helper'
|
||||
require_relative '../../../spec/doubles/request'
|
||||
|
||||
module CartoDB
|
||||
def self.clear_internal_cache
|
||||
remove_class_variable(:@@request_host) if defined?(@@request_host)
|
||||
remove_class_variable(:@@hostname) if defined?(@@hostname)
|
||||
remove_class_variable(:@@http_port) if defined?(@@http_port)
|
||||
remove_class_variable(:@@https_port) if defined?(@@http_ports)
|
||||
remove_class_variable(:@@session_domain) if defined?(@@session_domain)
|
||||
remove_class_variable(:@@domain) if defined?(@@domain)
|
||||
remove_class_variable(:@@subdomainless_urls) if defined?(@@subdomainless_urls)
|
||||
remove_class_variable(:@@account_host) if defined?(@@account_host)
|
||||
remove_class_variable(:@@account_path) if defined?(@@account_path)
|
||||
remove_class_variable(:@@data_library_path) if defined?(@@data_library_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe CartoDB do
|
||||
after(:each) do
|
||||
CartoDB.clear_internal_cache
|
||||
end
|
||||
|
||||
describe '#url_methods' do
|
||||
|
||||
it 'Tests extract_real_subdomain()' do
|
||||
username = 'test'
|
||||
expected_session_domain = '.carto.com'
|
||||
|
||||
CartoDB.clear_internal_cache
|
||||
CartoDB.expects(:get_session_domain).returns(expected_session_domain)
|
||||
|
||||
request = Doubles::Request.new({
|
||||
host: "#{username}#{expected_session_domain}"
|
||||
})
|
||||
CartoDB.subdomain_from_request(request).should eq username
|
||||
end
|
||||
|
||||
it 'Tests extract_host_subdomain()' do
|
||||
username = 'test'
|
||||
expected_session_domain = '.carto.com'
|
||||
|
||||
CartoDB.expects(:get_session_domain).returns(expected_session_domain)
|
||||
|
||||
# test.carto.com
|
||||
request = Doubles::Request.new({
|
||||
host: "#{username}#{expected_session_domain}"
|
||||
})
|
||||
CartoDB.extract_host_subdomain(request).should eq nil
|
||||
|
||||
# test.carto.com/u/whatever
|
||||
request = Doubles::Request.new({
|
||||
host: "#{username}#{expected_session_domain}",
|
||||
params: {
|
||||
user_domain: 'whatever'
|
||||
}
|
||||
})
|
||||
CartoDB.extract_host_subdomain(request).should eq username
|
||||
end
|
||||
|
||||
it 'Tests extract_subdomain() and username_from_request()' do
|
||||
username = 'test'
|
||||
orgname = 'testorg'
|
||||
user_domain = 'whatever'
|
||||
expected_session_domain = '.carto.com'
|
||||
|
||||
CartoDB.expects(:get_session_domain).at_least(0).returns(expected_session_domain)
|
||||
|
||||
# testorg.carto.com/u/whatever
|
||||
request = Doubles::Request.new({
|
||||
host: "#{orgname}#{expected_session_domain}",
|
||||
params: {
|
||||
user_domain: user_domain
|
||||
}
|
||||
})
|
||||
CartoDB.username_from_request(request).should eq user_domain
|
||||
# test.carto.com
|
||||
request = Doubles::Request.new({
|
||||
host: "#{username}#{expected_session_domain}"
|
||||
})
|
||||
CartoDB.username_from_request(request).should eq nil
|
||||
|
||||
# For extract_subdomain() no need to call again real methods
|
||||
|
||||
user_domain_param = "test_user"
|
||||
host_username = "another_user"
|
||||
host = "#{host_username}#{expected_session_domain}"
|
||||
|
||||
CartoDB.extract_subdomain(Doubles::Request.new({
|
||||
params: { user_domain: user_domain_param}
|
||||
}))
|
||||
.should eq user_domain_param
|
||||
|
||||
CartoDB.extract_subdomain(Doubles::Request.new({
|
||||
host: host
|
||||
}))
|
||||
.should eq host_username
|
||||
end
|
||||
|
||||
it 'Tests base_url()' do
|
||||
expected_session_domain = '.carto.com'
|
||||
expected_http_port = ':12345'
|
||||
expected_https_port = ':67890'
|
||||
username = 'test'
|
||||
orgname = 'testorg'
|
||||
protocol_override_https = 'https'
|
||||
protocol_override_http = 'http'
|
||||
|
||||
CartoDB.expects(:get_subdomainless_urls).returns(false)
|
||||
|
||||
CartoDB.expects(:use_https?).at_least(0).returns(false)
|
||||
CartoDB.expects(:get_session_domain).returns(expected_session_domain)
|
||||
CartoDB.expects(:get_http_port).at_least(1).returns(expected_http_port)
|
||||
CartoDB.expects(:get_https_port).at_least(1).returns(expected_https_port)
|
||||
|
||||
CartoDB.base_url(username, nil, nil).should eq "http://#{username}#{expected_session_domain}#{expected_http_port}"
|
||||
|
||||
CartoDB.base_url(username, nil, protocol_override_https)
|
||||
.should eq "#{protocol_override_https}://#{username}#{expected_session_domain}#{expected_https_port}"
|
||||
|
||||
CartoDB.base_url(orgname, username, nil)
|
||||
.should eq "http://#{orgname}#{expected_session_domain}#{expected_http_port}/u/#{username}"
|
||||
|
||||
CartoDB.base_url(orgname, username, protocol_override_https)
|
||||
.should eq "#{protocol_override_https}://#{orgname}#{expected_session_domain}#{expected_https_port}/u/#{username}"
|
||||
|
||||
CartoDB.unstub(:use_https?)
|
||||
CartoDB.expects(:use_https?).at_least(0).returns(true)
|
||||
|
||||
CartoDB.base_url(username, nil, nil)
|
||||
.should eq "https://#{username}#{expected_session_domain}#{expected_https_port}"
|
||||
|
||||
CartoDB.base_url(username, nil, protocol_override_http)
|
||||
.should eq "#{protocol_override_http}://#{username}#{expected_session_domain}#{expected_http_port}"
|
||||
|
||||
# Reset and check without subdomains
|
||||
CartoDB.clear_internal_cache
|
||||
|
||||
expected_session_domain = 'carto.com'
|
||||
|
||||
CartoDB.unstub(:get_subdomainless_urls)
|
||||
CartoDB.expects(:get_subdomainless_urls).returns(true)
|
||||
|
||||
CartoDB.unstub(:get_session_domain)
|
||||
CartoDB.expects(:get_session_domain).returns(expected_session_domain)
|
||||
CartoDB.expects(:use_https?).at_least(0).returns(false)
|
||||
|
||||
CartoDB.base_url(username, nil, nil)
|
||||
.should eq "http://#{expected_session_domain}#{expected_http_port}/user/#{username}"
|
||||
|
||||
CartoDB.base_url(username, nil, protocol_override_https)
|
||||
.should eq "#{protocol_override_https}://#{expected_session_domain}#{expected_https_port}/user/#{username}"
|
||||
|
||||
# Because without subdomains organizations are ignored, acts as previous scenario
|
||||
CartoDB.base_url(orgname, username, nil)
|
||||
.should eq "http://#{expected_session_domain}#{expected_http_port}/user/#{orgname}"
|
||||
|
||||
CartoDB.base_url(orgname, username, protocol_override_https)
|
||||
.should eq "#{protocol_override_https}://#{expected_session_domain}#{expected_https_port}/user/#{orgname}"
|
||||
|
||||
CartoDB.unstub(:use_https?)
|
||||
CartoDB.expects(:use_https?).at_least(0).returns(true)
|
||||
|
||||
CartoDB.base_url(username, nil, nil)
|
||||
.should eq "https://#{expected_session_domain}#{expected_https_port}/user/#{username}"
|
||||
|
||||
CartoDB.base_url(username, nil, protocol_override_http)
|
||||
.should eq "#{protocol_override_http}://#{expected_session_domain}#{expected_http_port}/user/#{username}"
|
||||
end
|
||||
|
||||
it 'tests base_url() without logged user' do
|
||||
expected_session_domain = 'carto.com'
|
||||
expected_ip = '127.0.0.1'
|
||||
expected_https_port = ':67890'
|
||||
|
||||
CartoDB.clear_internal_cache
|
||||
CartoDB.stubs(:use_https?).returns(true)
|
||||
CartoDB.stubs(:get_https_port).returns(expected_https_port)
|
||||
|
||||
CartoDB.stubs(:get_subdomainless_urls).returns(true)
|
||||
CartoDB.stubs(:get_session_domain).returns(expected_session_domain)
|
||||
CartoDB.base_url(nil, nil, nil).should eq "https://#{expected_session_domain}#{expected_https_port}"
|
||||
|
||||
CartoDB.stubs(:request_host).returns(expected_ip)
|
||||
CartoDB.base_url(nil, nil, nil).should eq "https://#{expected_ip}#{expected_https_port}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#base_domain_from_request' do
|
||||
it 'extracts subdomain without domain based urls' do
|
||||
CartoDB.stubs(:subdomainless_urls?).returns(false)
|
||||
CartoDB.stubs(:session_domain).returns('.localhost.lan')
|
||||
CartoDB.stubs(:protocol).returns('http')
|
||||
|
||||
CartoDB.base_domain_from_request(
|
||||
OpenStruct.new(host: 'auser.localhost.lan', params: { user_domain: 'buser' })
|
||||
).should == "http://auser.localhost.lan#{CartoDB.http_port}"
|
||||
CartoDB.base_domain_from_request(
|
||||
OpenStruct.new(host: 'org.localhost.lan', params: { user_domain: 'auser' })
|
||||
).should == "http://org.localhost.lan#{CartoDB.http_port}"
|
||||
end
|
||||
|
||||
it 'extracts subdomain with subdomainless urls' do
|
||||
CartoDB.stubs(:subdomainless_urls?).returns(true)
|
||||
CartoDB.stubs(:session_domain).returns('localhost.lan')
|
||||
CartoDB.stubs(:protocol).returns('http')
|
||||
|
||||
CartoDB.base_domain_from_request(
|
||||
OpenStruct.new(host: 'localhost.lan', params: { user_domain: 'auser' })
|
||||
).should == "http://localhost.lan#{CartoDB.http_port}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
require_relative '../../spec_helper_min'
|
||||
|
||||
describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
|
||||
describe '#survive_failure' do
|
||||
|
||||
it 'Should survive a PostgreSQL disconnection' do
|
||||
# We don't care about whether a visualization exists, we just want some activity
|
||||
Carto::Visualization.first
|
||||
# break the AR connection on purpose
|
||||
expect {
|
||||
ActiveRecord::Base.connection.exec_query("SELECT pg_terminate_backend(pg_backend_pid())")
|
||||
}.to raise_exception(ActiveRecord::StatementInvalid)
|
||||
Carto::Visualization.first
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe User do
|
||||
before(:all) do
|
||||
@user = create_user :email => 'admin@example.com', :username => 'admin', :password => 'admin123'
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
bypass_named_maps
|
||||
@user.destroy
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
CartoDB::Varnish.any_instance.stubs(:send_command).returns(true)
|
||||
end
|
||||
|
||||
it "should pass all the sql tests" do
|
||||
@user.should pass_sql_tests
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe String do
|
||||
describe "Convert from SQL data type to CartoDB types" do
|
||||
it "should convert smallint to number" do
|
||||
"smallint".convert_to_cartodb_type.should == "number"
|
||||
end
|
||||
it "should convert numeric(\d+,\d+) to number" do
|
||||
"numeric(10,0)".convert_to_cartodb_type.should == "number"
|
||||
end
|
||||
it "should convert integer to number" do
|
||||
"integer".convert_to_cartodb_type.should == "number"
|
||||
end
|
||||
it "should convert real to number" do
|
||||
"real".convert_to_cartodb_type.should == "number"
|
||||
end
|
||||
it "should convert double precision to number" do
|
||||
"double precision".convert_to_cartodb_type.should == "number"
|
||||
end
|
||||
it "should convert varchar to string" do
|
||||
"varchar".convert_to_cartodb_type.should == "string"
|
||||
end
|
||||
it "should convert text to string" do
|
||||
"text".convert_to_cartodb_type.should == "string"
|
||||
end
|
||||
it "should convert character varying to string" do
|
||||
"character varying".convert_to_cartodb_type.should == "string"
|
||||
end
|
||||
it "should convert varchar to string" do
|
||||
"varchar".convert_to_cartodb_type.should == "string"
|
||||
end
|
||||
it "should convert character varying(20) to string" do
|
||||
"character varying(20)".convert_to_cartodb_type.should == "string"
|
||||
end
|
||||
it "should convert timestamp to date" do
|
||||
"timestamp".convert_to_cartodb_type.should == "timestamp"
|
||||
end
|
||||
it "should convert timestamp without time zone to date" do
|
||||
"timestamp without time zone".convert_to_cartodb_type.should == "date"
|
||||
end
|
||||
it "should convert boolean to boolean" do
|
||||
"boolean".convert_to_cartodb_type.should == "boolean"
|
||||
end
|
||||
end
|
||||
describe "Convert from CartoDB types to the first SQL type" do
|
||||
it "should convert number to smallint" do
|
||||
"number".convert_to_db_type.should == "double precision"
|
||||
end
|
||||
it "should convert string to varchar" do
|
||||
"string".convert_to_db_type.should == "text"
|
||||
end
|
||||
it "should convert date to timestamp" do
|
||||
"date".convert_to_db_type.should == "timestamptz"
|
||||
end
|
||||
it "should convert boolean to boolean" do
|
||||
"boolean".convert_to_db_type.should == "boolean"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,86 @@
|
||||
require 'spec_helper_min'
|
||||
require 'rake'
|
||||
|
||||
describe 'data_observatory.rake' do
|
||||
before(:all) do
|
||||
Rake.application.rake_require('tasks/data_observatory')
|
||||
Rake::Task.define_task(:environment)
|
||||
|
||||
@user = FactoryGirl.create(:valid_user, username: 'fulano')
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@user.destroy
|
||||
end
|
||||
|
||||
describe '#purchase_datasets' do
|
||||
before(:each) do
|
||||
Rake::Task['cartodb:data_observatory:purchase_datasets'].reenable
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
File.unstub(:open)
|
||||
end
|
||||
|
||||
it 'throws an error if username is not provided' do
|
||||
expect {
|
||||
Rake::Task['cartodb:data_observatory:purchase_datasets'].invoke
|
||||
}.to raise_error(RuntimeError, 'USAGE: data_observatory:purchase_datasets["username","path/datasets.csv"]')
|
||||
end
|
||||
|
||||
it 'throws an error if the datasets path is not provided' do
|
||||
expect {
|
||||
Rake::Task['cartodb:data_observatory:purchase_datasets'].invoke('fulano')
|
||||
}.to raise_error(RuntimeError, 'USAGE: data_observatory:purchase_datasets["username","path/datasets.csv"]')
|
||||
end
|
||||
|
||||
it 'calls subscribe from Carto::DoLicensingService with the expected parameters' do
|
||||
File.stubs(:open).returns(csv_example)
|
||||
expected_datasets = [
|
||||
{ dataset_id: 'dataset1', available_in: ['bq', 'spanner'], price: 100.0,
|
||||
expires_at: Time.new(2020, 9, 27, 8, 0, 0) },
|
||||
{ dataset_id: 'dataset2', available_in: ['spanner'], price: 200.0,
|
||||
expires_at: Time.new(2020, 12, 31, 12, 0, 0) }
|
||||
]
|
||||
service_mock = mock
|
||||
service_mock.expects(:subscribe).once.with(expected_datasets)
|
||||
Carto::DoLicensingService.expects(:new).once.with('fulano').returns(service_mock)
|
||||
|
||||
Rake::Task['cartodb:data_observatory:purchase_datasets'].invoke('fulano', 'datasets.csv')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove_purchase' do
|
||||
before(:each) do
|
||||
Rake::Task['cartodb:data_observatory:remove_purchase'].reenable
|
||||
end
|
||||
|
||||
it 'throws an error if username is not provided' do
|
||||
expect {
|
||||
Rake::Task['cartodb:data_observatory:remove_purchase'].invoke
|
||||
}.to raise_error(RuntimeError, 'USAGE: data_observatory:remove_purchase["username","project.schema.table"]')
|
||||
end
|
||||
|
||||
it 'throws an error if dataset_id is not provided' do
|
||||
expect {
|
||||
Rake::Task['cartodb:data_observatory:remove_purchase'].invoke('fulano')
|
||||
}.to raise_error(RuntimeError, 'USAGE: data_observatory:remove_purchase["username","project.schema.table"]')
|
||||
end
|
||||
|
||||
it 'calls unsubscribe from Carto::DoLicensingService with the expected parameters' do
|
||||
service_mock = mock
|
||||
service_mock.expects(:unsubscribe).once.with('carto.abc.dataset')
|
||||
Carto::DoLicensingService.expects(:new).once.with('fulano').returns(service_mock)
|
||||
|
||||
Rake::Task['cartodb:data_observatory:remove_purchase'].invoke('fulano', 'carto.abc.dataset')
|
||||
end
|
||||
end
|
||||
|
||||
def csv_example
|
||||
CSV.generate do |csv|
|
||||
csv << ["dataset1", "bq;spanner", "100", "2020-09-27T08:00:00"]
|
||||
csv << ["dataset2", "spanner", "200", "2020-12-31T12:00:00"]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
require 'spec_helper_min'
|
||||
require 'rake'
|
||||
require 'factories/carto_visualizations'
|
||||
|
||||
describe 'cartodb:fix_unique_legends' do
|
||||
include Carto::Factories::Visualizations
|
||||
|
||||
before(:each) do
|
||||
Rake.application.rake_require "tasks/fix_unique_legends"
|
||||
Rake::Task.define_task(:environment)
|
||||
Rake.application['cartodb:fix_unique_legends'].reenable
|
||||
|
||||
user = FactoryGirl.create(:carto_user)
|
||||
_, _, _, visualization = create_full_visualization(user)
|
||||
@layer = visualization.layers.find(&:data_layer?)
|
||||
@legend = Carto::Legend.create!(layer: @layer,
|
||||
title: "bird_name",
|
||||
pre_html: "",
|
||||
post_html: "",
|
||||
type: "torque",
|
||||
definition: {
|
||||
"categories" => [
|
||||
{ "title" => "Perico", "color"=>"#ff382a", "icon"=>"" },
|
||||
{ "title" => "Palotes", "color"=>"#0038d1", "icon"=>"" },
|
||||
{ "title" => "Jr", "color"=>"#d4cf34", "icon"=>"" }
|
||||
]
|
||||
},
|
||||
conf: { "columns" => [] })
|
||||
end
|
||||
|
||||
it 'deletes duplicate legend of unique type' do
|
||||
legend2 = @legend.dup
|
||||
legend2.type = 'choropleth'
|
||||
legend2.updated_at = @legend.updated_at - 1.minute
|
||||
legend2.save(validate: false)
|
||||
@layer.reload.legends.count.should eq 2
|
||||
|
||||
Rake.application['cartodb:fix_unique_legends'].invoke
|
||||
|
||||
@layer.reload.legends.count.should eq 1
|
||||
@layer.reload.legends.first.id.should eq @legend.id
|
||||
end
|
||||
|
||||
it 'keeps layers if different type' do
|
||||
legend2 = @legend.dup
|
||||
legend2.type = 'bubble'
|
||||
legend2.save(validate: false)
|
||||
@layer.reload.legends.count.should eq 2
|
||||
|
||||
Rake.application['cartodb:fix_unique_legends'].invoke
|
||||
|
||||
@layer.reload.legends.count.should eq 2
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
require 'spec_helper_min'
|
||||
require 'rake'
|
||||
require_relative '../../../app/models/visualization/member'
|
||||
require_relative '../../../app/models/visualization/overlays'
|
||||
|
||||
describe 'cartodb:fix_unique_overlays' do
|
||||
before(:each) do
|
||||
Rake.application.rake_require "tasks/fix_unique_overlays"
|
||||
Rake::Task.define_task(:environment)
|
||||
|
||||
member = Visualization::Member.new
|
||||
@visualization = member
|
||||
end
|
||||
|
||||
it 'deletes duplicate overlays of unique type' do
|
||||
Visualization::Overlays.new(@visualization).create_default_overlays
|
||||
dup_overlay = Carto::Overlay.new(visualization_id: @visualization.id, type: 'logo')
|
||||
dup_overlay.save(validate: false)
|
||||
dup_overlay = Carto::Overlay.new(visualization_id: @visualization.id, type: 'logo')
|
||||
dup_overlay.save(validate: false)
|
||||
|
||||
@visualization.overlays.select { |o| o.type == 'logo' }.count.should eq 3
|
||||
|
||||
Rake.application['cartodb:fix_unique_overlays'].invoke
|
||||
|
||||
@visualization.overlays.select { |o| o.type == 'logo' }.count.should eq 1
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,134 @@
|
||||
require 'spec_helper_min'
|
||||
require 'rake'
|
||||
require 'factories/carto_visualizations'
|
||||
|
||||
describe 'layers.rake' do
|
||||
include Carto::Factories::Visualizations
|
||||
|
||||
before(:all) do
|
||||
Rake.application.rake_require "tasks/layers"
|
||||
Rake::Task.define_task(:environment)
|
||||
end
|
||||
|
||||
describe '#sync_basemaps_from_app_config' do
|
||||
before(:all) do
|
||||
@user = FactoryGirl.create(:carto_user, private_maps_enabled: true)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@map, @table, @table_visualization, @visualization = create_full_visualization(@user)
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
Rake::Task['carto:db:sync_basemaps_from_app_config'].reenable
|
||||
destroy_full_visualization(@map, @table, @table_visualization, @visualization)
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
::User[@user.id].destroy
|
||||
end
|
||||
|
||||
it 'updates single layers' do
|
||||
class_name = @visualization.user_layers.sort_by(&:order).first.options['className']
|
||||
|
||||
Cartodb.with_config(basemaps: { 'CARTO' => { class_name => { 'className' => class_name, 'dummy' => 'opt' } } }) do
|
||||
Rake::Task['carto:db:sync_basemaps_from_app_config'].invoke
|
||||
end
|
||||
|
||||
@visualization.reload
|
||||
expect(@visualization.user_layers.first.options).to eq('className' => class_name, 'dummy' => 'opt')
|
||||
end
|
||||
|
||||
it 'updates sandwiched layers' do
|
||||
class_name = @table_visualization.user_layers.sort_by(&:order).first.options['className']
|
||||
|
||||
dummy_options = {
|
||||
'className' => class_name,
|
||||
'dummy' => 'opt',
|
||||
'name' => 'X',
|
||||
'labels' => {
|
||||
'dummy' => 'label'
|
||||
}
|
||||
}
|
||||
|
||||
Cartodb.with_config(basemaps: { 'CARTO' => { class_name => dummy_options } }) do
|
||||
Rake::Task['carto:db:sync_basemaps_from_app_config'].invoke
|
||||
end
|
||||
|
||||
@table_visualization.reload
|
||||
bottom_layer, labels_layer = @table_visualization.user_layers.sort_by(&:order)
|
||||
expect(bottom_layer.options).to eq(dummy_options)
|
||||
expect(labels_layer.options).to eq('dummy' => 'label', 'name' => 'X Labels', 'type' => 'Tiled')
|
||||
end
|
||||
|
||||
it 'updates mapcaps' do
|
||||
class_name = @visualization.user_layers.sort_by(&:order).first.options['className']
|
||||
@visualization.create_mapcap!
|
||||
|
||||
Cartodb.with_config(basemaps: { 'CARTO' => { class_name => { 'className' => class_name, 'dummy' => 'opt' } } }) do
|
||||
Rake::Task['carto:db:sync_basemaps_from_app_config'].invoke
|
||||
end
|
||||
|
||||
@visualization.reload
|
||||
mapcapped_layer = @visualization.mapcaps.first.regenerate_visualization.user_layers.first.options
|
||||
expect(mapcapped_layer).to eq('className' => class_name, 'dummy' => 'opt')
|
||||
end
|
||||
|
||||
describe 'for viewer users' do
|
||||
before(:each) do
|
||||
@user.viewer = true
|
||||
@user.save
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
@user.viewer = false
|
||||
@user.save
|
||||
end
|
||||
|
||||
it 'updates layers for viewer users' do
|
||||
class_name = @table_visualization.user_layers.sort_by(&:order).first.options['className']
|
||||
|
||||
dummy_options = {
|
||||
'className' => class_name,
|
||||
'dummy' => 'opt',
|
||||
'name' => 'X',
|
||||
'labels' => {
|
||||
'dummy' => 'label'
|
||||
}
|
||||
}
|
||||
|
||||
Cartodb.with_config(basemaps: { 'CARTO' => { class_name => dummy_options } }) do
|
||||
Rake::Task['carto:db:sync_basemaps_from_app_config'].invoke
|
||||
end
|
||||
|
||||
@table_visualization.reload
|
||||
bottom_layer, labels_layer = @table_visualization.user_layers.sort_by(&:order)
|
||||
expect(bottom_layer.options).to eq(dummy_options)
|
||||
expect(labels_layer.options).to eq('dummy' => 'label', 'name' => 'X Labels', 'type' => 'Tiled')
|
||||
|
||||
@user.reload
|
||||
expect(@user.viewer).to be_true
|
||||
end
|
||||
end
|
||||
|
||||
it 'doesn\'t touch unknown layers' do
|
||||
base_layer = @visualization.user_layers.first
|
||||
base_layer.options['className'] = 'something_custom'
|
||||
base_layer.save!
|
||||
|
||||
Rake::Task['carto:db:sync_basemaps_from_app_config'].invoke
|
||||
|
||||
expect { base_layer.reload }.not_to(change { base_layer.options })
|
||||
end
|
||||
|
||||
it 'doesn\'t touch non-tiled layers' do
|
||||
base_layer = @visualization.user_layers.first
|
||||
base_layer.kind = 'background'
|
||||
base_layer.save!
|
||||
|
||||
Rake::Task['carto:db:sync_basemaps_from_app_config'].invoke
|
||||
|
||||
expect { base_layer.reload }.not_to(change { base_layer.options })
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,112 @@
|
||||
require 'spec_helper_min'
|
||||
require 'rake'
|
||||
|
||||
describe 'oauth.rake' do
|
||||
before(:all) do
|
||||
Rake.application.rake_require('tasks/oauth')
|
||||
Rake::Task.define_task(:environment)
|
||||
|
||||
@sequel_developer = FactoryGirl.create(:valid_user)
|
||||
@developer = Carto::User.find(@sequel_developer.id)
|
||||
@user = FactoryGirl.create(:valid_user)
|
||||
@oauth_app = FactoryGirl.create(:oauth_app, user: @developer)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@oauth_app_user = @oauth_app.oauth_app_users.create!(user_id: @user.id)
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
@oauth_app_user.reload.destroy!
|
||||
Delorean.back_to_the_present
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@oauth_app.destroy!
|
||||
@user.destroy
|
||||
@sequel_developer.destroy
|
||||
end
|
||||
|
||||
describe '#destroy_expired_access_tokens' do
|
||||
before(:each) do
|
||||
Rake::Task['cartodb:oauth:destroy_expired_access_tokens'].reenable
|
||||
end
|
||||
|
||||
it 'does not delete just created access tokens' do
|
||||
access_token = @oauth_app_user.oauth_access_tokens.create!
|
||||
Rake::Task['cartodb:oauth:destroy_expired_access_tokens'].invoke
|
||||
expect(Carto::OauthAccessToken.exists?(access_token.id)).to(be_true)
|
||||
end
|
||||
|
||||
it 'deletes old access tokens' do
|
||||
access_token = @oauth_app_user.oauth_access_tokens.create!
|
||||
Delorean.jump(2.hours)
|
||||
Rake::Task['cartodb:oauth:destroy_expired_access_tokens'].invoke
|
||||
expect(Carto::OauthAccessToken.exists?(access_token.id)).to(be_false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy_expired_refresh_tokens' do
|
||||
before(:each) do
|
||||
Rake::Task['cartodb:oauth:destroy_expired_refresh_tokens'].reenable
|
||||
end
|
||||
|
||||
it 'does not delete just created access tokens' do
|
||||
refresh_token = @oauth_app_user.oauth_refresh_tokens.create!(scopes: ['offline'])
|
||||
Rake::Task['cartodb:oauth:destroy_expired_refresh_tokens'].invoke
|
||||
expect(Carto::OauthRefreshToken.exists?(refresh_token.id)).to(be_true)
|
||||
end
|
||||
|
||||
it 'deletes old access tokens' do
|
||||
refresh_token = @oauth_app_user.oauth_refresh_tokens.create!(scopes: ['offline'])
|
||||
Delorean.jump(1.year)
|
||||
Rake::Task['cartodb:oauth:destroy_expired_refresh_tokens'].invoke
|
||||
expect(Carto::OauthRefreshToken.exists?(refresh_token.id)).to(be_false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy_expired_authorization_codes' do
|
||||
before(:each) do
|
||||
Rake::Task['cartodb:oauth:destroy_expired_authorization_codes'].reenable
|
||||
end
|
||||
|
||||
it 'does not delete just created access tokens' do
|
||||
authorization_code = @oauth_app_user.oauth_authorization_codes.create!
|
||||
Rake::Task['cartodb:oauth:destroy_expired_authorization_codes'].invoke
|
||||
expect(Carto::OauthAuthorizationCode.exists?(authorization_code.id)).to(be_true)
|
||||
end
|
||||
|
||||
it 'deletes old access tokens' do
|
||||
authorization_code = @oauth_app_user.oauth_authorization_codes.create!
|
||||
Delorean.jump(2.minutes)
|
||||
Rake::Task['cartodb:oauth:destroy_expired_authorization_codes'].invoke
|
||||
expect(Carto::OauthAuthorizationCode.exists?(authorization_code.id)).to(be_false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_ownership_role' do
|
||||
before(:each) do
|
||||
Rake::Task['cartodb:oauth:create_ownership_role'].reenable
|
||||
end
|
||||
|
||||
it 'does nothing for a user with ownership role' do
|
||||
expect(@oauth_app_user.exists_ownership_role?).to(be_true)
|
||||
Carto::OauthAppUser.any_instance.expects(:create_ownership_role).never
|
||||
Carto::OauthAppUser.any_instance.expects(:grant_ownership_role_privileges).never
|
||||
Carto::ApiKey.any_instance.expects(:grant_ownership_role_privileges).never
|
||||
Carto::ApiKey.any_instance.expects(:save_cdb_conf_info).never
|
||||
Rake::Task['cartodb:oauth:create_ownership_role'].invoke
|
||||
end
|
||||
|
||||
it 'creates ownership roles + grants for oauth app users missing it' do
|
||||
drop_role_query = %{ DROP ROLE "#{@oauth_app_user.ownership_role_name}" }
|
||||
@developer.in_database(as: :superuser).execute(drop_role_query)
|
||||
expect(@oauth_app_user.exists_ownership_role?).to(be_false)
|
||||
|
||||
Carto::OauthAppUser.any_instance.expects(:grant_ownership_role_privileges).once
|
||||
Rake::Task['cartodb:oauth:create_ownership_role'].invoke
|
||||
|
||||
expect(@oauth_app_user.exists_ownership_role?).to(be_true)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
require 'spec_helper_min'
|
||||
require 'rake'
|
||||
|
||||
describe 'user' do
|
||||
before :all do
|
||||
Rake.application.rake_require "tasks/user_database_host_update"
|
||||
Rake::Task.define_task(:environment)
|
||||
end
|
||||
|
||||
describe 'cartodb:database_host' do
|
||||
describe 'update_dbm_and_redis' do
|
||||
it "should not accept wrong or incomplete input" do
|
||||
expect {
|
||||
Rake::Task["cartodb:database_host:update_dbm_and_redis"].reenable
|
||||
Rake.application.invoke_task "cartodb:database_host:update_dbm_and_redis"
|
||||
}.to raise_error(RuntimeError, "Origin IP parameter is mandatory")
|
||||
|
||||
expect {
|
||||
Rake::Task["cartodb:database_host:update_dbm_and_redis"].reenable
|
||||
Rake.application.invoke_task "cartodb:database_host:update_dbm_and_redis['127.0.0.1']"
|
||||
}.to raise_error(RuntimeError, "Destination IP parameter is mandatory")
|
||||
end
|
||||
|
||||
it "should change the database host for the users and also sync the metadata" do
|
||||
user = FactoryGirl.create(:valid_user)
|
||||
user.database_host = "localhost"
|
||||
user.save
|
||||
|
||||
user2 = FactoryGirl.create(:valid_user)
|
||||
user2.database_host = "127.0.0.1"
|
||||
user2.save
|
||||
|
||||
Rake::Task["cartodb:database_host:update_dbm_and_redis"].reenable
|
||||
Rake.application.invoke_task "cartodb:database_host:update_dbm_and_redis[localhost,127.0.0.1]"
|
||||
|
||||
user.reload.database_host.should eql '127.0.0.1'
|
||||
user2.reload.database_host.should eql '127.0.0.1'
|
||||
|
||||
metadata_user = $users_metadata.hgetall("rails:users:#{user.username}")
|
||||
metadata_user["database_host"].should eql '127.0.0.1'
|
||||
metadata_user2 = $users_metadata.hgetall("rails:users:#{user2.username}")
|
||||
metadata_user2["database_host"].should eql '127.0.0.1'
|
||||
|
||||
user.destroy
|
||||
user2.destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
require 'spec_helper_min'
|
||||
require 'rake'
|
||||
require 'factories/carto_visualizations'
|
||||
|
||||
describe 'user_migrator.rake' do
|
||||
include Carto::Factories::Visualizations
|
||||
|
||||
before(:all) do
|
||||
Rake.application.rake_require "tasks/user_migrator"
|
||||
Rake::Task.define_task(:environment)
|
||||
end
|
||||
|
||||
describe '#cartodb:user_migrator:cleanup:organization' do
|
||||
it 'does not remove organization assets from storage' do
|
||||
org = FactoryGirl.create(:organization_with_users)
|
||||
Asset.create(
|
||||
asset_file: (Rails.root + 'spec/support/data/cartofante_blue.png'),
|
||||
user: org.owner,
|
||||
organization_id: org.id
|
||||
)
|
||||
Asset.any_instance.stubs(:remove).raises("NOOOO!")
|
||||
Rake::Task['cartodb:user_migrator:cleanup:organization'].invoke(org.name)
|
||||
end
|
||||
|
||||
it 'does not remove user assets from storage' do
|
||||
user = FactoryGirl.create(:valid_user)
|
||||
Asset.create(
|
||||
asset_file: Rails.root + 'spec/support/data/cartofante_blue.png',
|
||||
user: user
|
||||
)
|
||||
Asset.any_instance.stubs(:remove).raises("NOOOO!")
|
||||
Rake::Task['cartodb:user_migrator:cleanup:user'].invoke(user.username)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,92 @@
|
||||
require 'spec_helper_min'
|
||||
require 'rake'
|
||||
|
||||
describe 'user' do
|
||||
before :all do
|
||||
Rake.application.rake_require "tasks/user"
|
||||
Rake::Task.define_task(:environment)
|
||||
end
|
||||
|
||||
describe 'notifications:add_by_field' do
|
||||
it "should not accept wrong or incomplete input" do
|
||||
expect {
|
||||
Rake::Task["user:notifications:add_by_field"].reenable
|
||||
Rake.application.invoke_task "user:notifications:add_by_field"
|
||||
}.to raise_error(RuntimeError, "Filter field and value are needed")
|
||||
|
||||
expect {
|
||||
Rake::Task["user:notifications:add_by_field"].reenable
|
||||
Rake.application.invoke_task "user:notifications:add_by_field[database_host]"
|
||||
}.to raise_error(RuntimeError, "Filter field and value are needed")
|
||||
|
||||
expect {
|
||||
Rake::Task["user:notifications:add_by_field"].reenable
|
||||
Rake.application.invoke_task "user:notifications:add_by_field[database_host,localhost]"
|
||||
}.to raise_error(RuntimeError, "Notification not provided. Please include it")
|
||||
|
||||
expect {
|
||||
Rake::Task["user:notifications:add_by_field"].reenable
|
||||
Rake.application.invoke_task "user:notifications:add_by_field[unknown_field,localhost,test notification!]"
|
||||
}.to raise_error(RuntimeError, "Unknown field unknown_field for filtering. Allowed fields are database_host")
|
||||
end
|
||||
|
||||
it "should change the notification field for the users with localhost database" do
|
||||
user = FactoryGirl.create(:valid_user)
|
||||
user.database_host = "localhost"
|
||||
user.save
|
||||
|
||||
user2 = FactoryGirl.create(:valid_user)
|
||||
user2.database_host = "127.0.0.1"
|
||||
user2.save
|
||||
|
||||
Rake::Task["user:notifications:add_by_field"].reenable
|
||||
Rake.application.invoke_task "user:notifications:add_by_field[database_host,localhost,test notification!]"
|
||||
|
||||
user.reload.notification.should eql 'test notification!'
|
||||
user2.reload.notification.should be_nil
|
||||
|
||||
user.destroy
|
||||
user2.destroy
|
||||
end
|
||||
end
|
||||
|
||||
describe 'notifications:clean_by_field' do
|
||||
it "should not accept wrong or incomplete input" do
|
||||
expect {
|
||||
Rake::Task["user:notifications:clean_by_field"].reenable
|
||||
Rake.application.invoke_task "user:notifications:clean_by_field"
|
||||
}.to raise_error(RuntimeError, "Filter field and value are needed")
|
||||
|
||||
expect {
|
||||
Rake::Task["user:notifications:clean_by_field"].reenable
|
||||
Rake.application.invoke_task "user:notifications:clean_by_field[database_host]"
|
||||
}.to raise_error(RuntimeError, "Filter field and value are needed")
|
||||
|
||||
expect {
|
||||
Rake::Task["user:notifications:clean_by_field"].reenable
|
||||
Rake.application.invoke_task "user:notifications:clean_by_field[unknown_field,localhost]"
|
||||
}.to raise_error(RuntimeError, "Unknown field unknown_field for filtering. Allowed fields are database_host")
|
||||
end
|
||||
|
||||
it "should clean the notification field for the users with localhost database" do
|
||||
user = FactoryGirl.create(:valid_user)
|
||||
user.database_host = "localhost"
|
||||
user.notification = 'test notification!'
|
||||
user.save
|
||||
|
||||
user2 = FactoryGirl.create(:valid_user)
|
||||
user2.database_host = "127.0.0.1"
|
||||
user2.notification = 'test notification!'
|
||||
user2.save
|
||||
|
||||
Rake::Task["user:notifications:clean_by_field"].reenable
|
||||
Rake.application.invoke_task "user:notifications:clean_by_field[database_host,localhost]"
|
||||
|
||||
user.reload.notification.should be_nil
|
||||
user2.reload.notification.should eql 'test notification!'
|
||||
|
||||
user.destroy
|
||||
user2.destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,102 @@
|
||||
require 'spec_helper'
|
||||
require_relative '../../lib/cartodb/trending_maps'
|
||||
|
||||
describe CartoDB::TrendingMaps do
|
||||
describe 'check for trending maps' do
|
||||
|
||||
before(:all) do
|
||||
@api_calls = CartoDB::Stats::APICalls.new
|
||||
@trending_maps = CartoDB::TrendingMaps.new
|
||||
@default_date_to = Date.today
|
||||
@default_date_from = Date.today - CartoDB::TrendingMaps::DAYS_TO_CHECK.days
|
||||
end
|
||||
|
||||
it "should mark the map as a trending one" do
|
||||
# @api_calls.stubs(:get_api_calls_from_redis).returns({@default_date_to.strftime("%Y%m%d")=>10})
|
||||
# @api_calls.stubs(:get_total_api_calls_from_redis).returns(490)
|
||||
@trending_maps.is_trending_map?(10, 500).should eq true
|
||||
end
|
||||
|
||||
it "should mark the map as a trending one with a greater factor" do
|
||||
@trending_maps.is_trending_map?(123, 8034).should eq true
|
||||
end
|
||||
|
||||
it "should mark the map as a trending one checking in the bounds" do
|
||||
@trending_maps.is_trending_map?(1, 2000).should eq true
|
||||
end
|
||||
|
||||
it "should not mark the map as a trending one" do
|
||||
@trending_maps.is_trending_map?(123, 7900).should eq false
|
||||
end
|
||||
|
||||
it "should not mark the map as a trending one checking the bounds" do
|
||||
@trending_maps.is_trending_map?(9, 8010).should eq false
|
||||
end
|
||||
|
||||
it "should return 4 trending maps" do
|
||||
date = Date.today - 1.days
|
||||
date_key = date.strftime("%Y%m%d")
|
||||
user = FactoryGirl.build(:user)
|
||||
visualization_1 = FactoryGirl.build(:derived_visualization, :user_id => user.id)
|
||||
stub_find_visualization(visualization_1, user)
|
||||
add_total_data(visualization_1.id, user.username, 490)
|
||||
add_date_data(visualization_1.id, user.username, date_key, 10)
|
||||
visualization_2 = FactoryGirl.build(:derived_visualization, :user_id => user.id)
|
||||
stub_find_visualization(visualization_2, user)
|
||||
add_total_data(visualization_2.id, user.username, 500)
|
||||
add_date_data(visualization_2.id, user.username, date_key, 10)
|
||||
visualization_3 = FactoryGirl.build(:derived_visualization, :user_id => user.id)
|
||||
stub_find_visualization(visualization_3, user)
|
||||
add_total_data(visualization_3.id, user.username, 1990)
|
||||
add_date_data(visualization_3.id, user.username, date_key, 10)
|
||||
visualization_4 = FactoryGirl.build(:derived_visualization, :user_id => user.id)
|
||||
stub_find_visualization(visualization_4, user)
|
||||
add_total_data(visualization_4.id, user.username, 8300)
|
||||
add_date_data(visualization_4.id, user.username, date_key, 20)
|
||||
visualization_5 = FactoryGirl.build(:derived_visualization, :user_id => user.id)
|
||||
stub_find_visualization(visualization_5, user)
|
||||
add_total_data(visualization_5.id, user.username, 15990)
|
||||
add_date_data(visualization_5.id, user.username, date_key, 200)
|
||||
visualization_6 = FactoryGirl.build(:derived_visualization, :user_id => user.id)
|
||||
stub_find_visualization(visualization_6, user)
|
||||
add_total_data(visualization_6.id, user.username, 7900)
|
||||
add_date_data(visualization_6.id, user.username, date_key, 15000)
|
||||
visualization_7 = FactoryGirl.build(:derived_visualization, :user_id => user.id)
|
||||
stub_find_visualization(visualization_7, user)
|
||||
add_total_data(visualization_7.id, user.username, 0)
|
||||
add_date_data(visualization_7.id, user.username, date_key, 0)
|
||||
visualization_8 = FactoryGirl.build(:derived_visualization, :user_id => user.id)
|
||||
stub_find_visualization(visualization_8, user)
|
||||
add_total_data(visualization_8.id, user.username, 10)
|
||||
add_date_data(visualization_8.id, user.username, date_key, 1)
|
||||
trending_maps = @trending_maps.get_trending_maps
|
||||
trending_maps.length.should eq 4
|
||||
trending_maps.keys.include?(visualization_1.id).should eq true
|
||||
trending_maps.keys.include?(visualization_3.id).should eq true
|
||||
trending_maps.keys.include?(visualization_5.id).should eq true
|
||||
trending_maps.keys.include?(visualization_6.id).should eq true
|
||||
end
|
||||
|
||||
def add_date_data(visualization_id, username, date, value)
|
||||
key = build_key(username, visualization_id)
|
||||
$users_metadata.ZADD(key, value, date).to_i
|
||||
updated_total = $users_metadata.ZSCORE(key, "total").to_f + value.to_f
|
||||
$users_metadata.ZADD(key, updated_total, "total").to_i
|
||||
end
|
||||
|
||||
def add_total_data(visualization_id, username, value)
|
||||
key = build_key(username, visualization_id)
|
||||
$users_metadata.ZADD(key, value, "total").to_i
|
||||
end
|
||||
|
||||
def build_key(username, visualization_id)
|
||||
@api_calls.redis_api_call_key(username, "mapviews", visualization_id)
|
||||
end
|
||||
|
||||
def stub_find_visualization(visualization, user)
|
||||
Carto::Visualization.stubs("find").with(visualization.id).returns(visualization)
|
||||
visualization.stubs("user").returns(user)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
require_relative '../simplecov_helper'
|
||||
require_relative '../../lib/url_signer'
|
||||
require_relative '../rspec_configuration.rb'
|
||||
|
||||
describe Carto::UrlSigner do
|
||||
describe '#sign_url' do
|
||||
it 'behaves like in the example provided' do
|
||||
url = 'http://maps.google.com/maps/api/geocode/json?address=New+York&sensor=false&client=clientID'
|
||||
private_key = 'vNIXE0xscrmjlyV-12Nj_BvUPaw='
|
||||
expected_signed_url = 'http://maps.google.com/maps/api/geocode/json?address=New+York&sensor=false&client=clientID&signature=KrU1TzVQM7Ur0i8i7K3huiw3MsA='
|
||||
Carto::UrlSigner.new(private_key).sign_url(url).should == expected_signed_url
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
require_relative '../spec_helper'
|
||||
require_relative '../../lib/user_account_creator'
|
||||
|
||||
describe CartoDB::UserAccountCreator do
|
||||
describe '#with_email_only' do
|
||||
USERNAME = "person"
|
||||
EMAIL = "#{USERNAME}@company.com"
|
||||
|
||||
it 'sets email, username and (random) password' do
|
||||
creator = CartoDB::UserAccountCreator.new(Carto::UserCreation::CREATED_VIA_ORG_SIGNUP)
|
||||
creator.with_email_only(EMAIL)
|
||||
|
||||
user = creator.build
|
||||
user.email.should == EMAIL
|
||||
user.username.should == USERNAME
|
||||
user.password.should_not be_empty
|
||||
|
||||
user_creation = creator.build_user_creation
|
||||
user_creation.email.should == EMAIL
|
||||
user_creation.username.should == USERNAME
|
||||
user_creation.crypted_password.should_not be_empty
|
||||
user_creation.organization_id.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
require_relative '../spec_helper'
|
||||
|
||||
describe "Varnish" do
|
||||
before(:all) do
|
||||
@first_config = Cartodb::config[:varnish_management]
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@telnet_mock = mock('Net::Telnet')
|
||||
Net::Telnet.stubs(:new).returns(@telnet_mock)
|
||||
@telnet_mock.stubs(:close)
|
||||
#@telnet_mock.stubs(:cmd).with("String" => "purge wadus", "Match" => /\n\n/).yields('200')
|
||||
@telnet_mock.stubs(:puts)
|
||||
@telnet_mock.stubs(:waitfor)
|
||||
|
||||
end
|
||||
|
||||
describe "with HTTP requests" do
|
||||
before(:all) do
|
||||
Cartodb::config.stubs(:[]).returns(@first_config.merge({'host' => 'wadus', 'http_port' => 6081}))
|
||||
end
|
||||
|
||||
it "goes over HTTP when http_port is specified" do
|
||||
@mock_request = mock('Typhoeus::Request')
|
||||
@mock_request.stubs(:code).returns(200)
|
||||
Typhoeus::Request.any_instance.stubs(:run).returns(@mock_request)
|
||||
CartoDB::Varnish.new.purge('/')
|
||||
end
|
||||
end
|
||||
describe "with Telnet requests" do
|
||||
before(:each) do
|
||||
Cartodb::config.stubs(:[]).returns(@first_config)
|
||||
end
|
||||
|
||||
describe "with any command" do
|
||||
describe "when connection is unsuccessful" do
|
||||
it "should raise an error on timeout" do
|
||||
@telnet_mock.stubs(:cmd).raises(Timeout::Error)
|
||||
lambda {
|
||||
CartoDB::Varnish.new.purge('/')
|
||||
}.should raise_error(RuntimeError)
|
||||
end
|
||||
|
||||
it "should retry on failure before erroring" do
|
||||
@telnet_mock.stubs(:cmd).raises(Timeout::Error)
|
||||
Net::Telnet.expects(:new).times(5)
|
||||
lambda {
|
||||
CartoDB::Varnish.new.purge('/')
|
||||
}.should raise_error(RuntimeError)
|
||||
end
|
||||
|
||||
it "should close the connection afterwards" do
|
||||
@telnet_mock.stubs(:cmd).with("String" => "purge obj.http.X-Cache-Channel ~ /", "Match" => /\n\n/).yields('200')
|
||||
@telnet_mock.expects(:close).times(1)
|
||||
CartoDB::Varnish.new.purge('/')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "when sending a purge command" do
|
||||
it "should return successfully" do
|
||||
@cdb_config = mock('Cartodb::config')
|
||||
@cdb_config.stubs('[]').returns({'host' => 'wadus'})
|
||||
@telnet_mock.stubs(:cmd).with("String" => "purge obj.http.X-Cache-Channel ~ wadus", "Match" => /\n\n/).yields('200')
|
||||
CartoDB::Varnish.new.purge('wadus').should == "200"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user