Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions
+180
View File
@@ -0,0 +1,180 @@
require 'spec_helper'
require_relative '../../lib/cartodb/stats/api_calls'
require 'helpers/unique_names_helper'
describe CartoDB::Stats::APICalls do
include UniqueNamesHelper
describe "Stats API Calls" do
before(:all) do
@api_calls = CartoDB::Stats::APICalls.new
@default_date_to = Date.today
@default_date_from = @default_date_to - 29.days
@dates = @default_date_to.downto(@default_date_from).map {|d| d.strftime("%Y%m%d")}
@dates_example_values = {}
@dates.each do |d|
@dates_example_values[d] = rand(50)
end
@redis_sources_count = CartoDB::Stats::APICalls::REDIS_SOURCES.length
end
it "should sum correctly api calls from all sources and return array without date" do
@api_calls.stubs(:get_old_api_calls).returns(@dates_example_values.values)
@api_calls.stubs(:get_api_calls_from_redis_source).returns(@dates_example_values)
expected_total_calls = @dates_example_values.values.map {|v| v * (@redis_sources_count + 1)}
@api_calls.get_api_calls_without_dates('wadus', {old_api_calls: true}).should == expected_total_calls
end
it "should sum correctly api calls from all redis sources and return hash with dates" do
@api_calls.stubs(:get_api_calls_from_redis_source).returns(@dates_example_values)
expected_total_calls = {}
@dates_example_values.each do |d, value|
expected_total_calls[d] = value * @redis_sources_count
end
@api_calls.get_api_calls_with_dates('wadus').should == expected_total_calls
end
it "should sum correctly api calls from all redis sources and return absolute value" do
api_calls_value = 3
@api_calls.stubs(:get_total_api_calls_from_redis_source).returns(api_calls_value)
expected_total_calls = @redis_sources_count * api_calls_value
@api_calls.get_total_api_calls('wadus', '123456').should == expected_total_calls
end
it "should sum correctly api calls with custom dates from all redis sources and return hash with dates" do
pending "Find a way to stub only redis call"
@api_calls.stubs(:get_api_calls_from_redis_source).returns(@dates_example_values)
expected_total_calls = {}
@dates_example_values.each do |d, value|
expected_total_calls[d] = value * @redis_sources_count
end
@api_calls.get_api_calls_with_dates(
'wadus',
{
from: (Date.today - 6.days),
to: Date.today
}
).should == expected_total_calls.to_a[0..6].to_h
end
end
describe 'get_api_calls_from_redis_source' do
before(:each) do
@api_calls = CartoDB::Stats::APICalls.new
@username = unique_name('user')
@type = unique_name('type')
@options = { stat_tag: '000d1206-1fed-11e5-9964-080027880ca6' }
end
it 'fetches 30 days in inverse order even if there is no data' do
today = Date.today
first_expected_day = (today - 29.days).strftime('%Y%m%d')
last_expected_day = today.strftime('%Y%m%d')
calls = @api_calls.get_api_calls_from_redis_source('nonexisting_user', 'nonexisting_api_call_type')
calls.count.should == 30
calls.each { |day, count|
count.should == 0
}
calls.first[0] = last_expected_day
calls[last_expected_day].should == 0
calls[first_expected_day].should == 0
end
it 'fetches data' do
key = @api_calls.redis_api_call_key(@username, @type, @options[:stat_tag])
today = Date.strptime('20180501', '%Y%m%d')
# Test data: 3 months, incremental count
date_to = today + 30.days
date_from = today - 60.days
score = 0
scores = {}
date_to.downto(date_from) do |date|
stat_date = date.strftime("%Y%m%d")
score += 1
$users_metadata.ZADD(key, score, stat_date).to_i
scores[stat_date] = score
end
Delorean.time_travel_to today
calls = @api_calls.get_api_calls_from_redis_source(@username, @type, @options)
Delorean.back_to_the_present
calls.count.should == 30
date_to = today
date_from = today - 29.days
date_to.downto(date_from) do |date|
stat_date = date.strftime("%Y%m%d")
calls[stat_date].should eq(scores[stat_date]), "Failed day #{stat_date}, it was #{calls[stat_date]} instead of #{scores[stat_date]}"
end
end
it 'fetches random data from 2 months' do
key = @api_calls.redis_api_call_key(@username, @type, @options[:stat_tag])
today = Date.strptime('20180501', '%Y%m%d')
random_data = {}
(0..365).each do |n|
$users_metadata.ZADD(key, 1, (today - n.days).strftime("%Y%m%d")).to_i
end
requested_days = 60
date_to = today
date_from = today - (requested_days - 1).days
calls = @api_calls.get_api_calls_from_redis_source(@username, @type, @options.merge({from: date_from, to: date_to}))
calls.count.should == requested_days
calls.values.reduce(:+).to_i.should == 60
end
it 'fetches data for one day' do
key = @api_calls.redis_api_call_key(@username, @type, @options[:stat_tag])
today = Date.today
random_data = {}
random_data[(today - 2.days).strftime("%Y%m%d")] = 7
random_data[(today - 1.days).strftime("%Y%m%d")] = 13
random_data.each do |date, score|
$users_metadata.ZADD(key, score, date).to_i
end
chose_date = Date.today - 1.days
calls = @api_calls.get_api_calls_from_redis_source(@username, @type, @options.merge({from: chose_date, to: chose_date}))
calls.count.should == 1
chose_date_key = chose_date.strftime("%Y%m%d")
calls[chose_date_key].should eq(random_data.fetch(chose_date_key, 0)), "Failed day #{chose_date_key}, it was #{calls[chose_date_key]} instead of #{random_data[chose_date_key]}"
end
describe 'should execute ZSCAN' do
it 'once for a single month' do
key = @api_calls.redis_api_call_key(@username, @type)
$users_metadata.stubs(:zscan_each).with(key, match: '201612*').once.returns([])
@api_calls.get_api_calls_from_redis_source(@username, @type, from: '20161201', to: '20161202')
end
it 'twice across month boundaries' do
key = @api_calls.redis_api_call_key(@username, @type)
$users_metadata.stubs(:zscan_each).with(key, match: '201611*').once.returns([])
$users_metadata.stubs(:zscan_each).with(key, match: '201612*').once.returns([])
@api_calls.get_api_calls_from_redis_source(@username, @type, from: '20161101', to: '20161211')
end
it 'twice across year boundaries' do
key = @api_calls.redis_api_call_key(@username, @type)
$users_metadata.stubs(:zscan_each).with(key, match: '201612*').once.returns([])
$users_metadata.stubs(:zscan_each).with(key, match: '201701*').once.returns([])
@api_calls.get_api_calls_from_redis_source(@username, @type, from: '20161231', to: '20170101')
end
end
end
end
@@ -0,0 +1,43 @@
require 'spec_helper_min'
require_relative '../../../../app/controllers/carto/controller_helper'
describe Carto::ImageAssetsService do
describe('#fetch_file') do
it 'reject files that are too big' do
max_size = Carto::ImageAssetsService.new.max_size_in_bytes
IO.stubs(:copy_stream).returns(max_size + 1)
expect {
Carto::ImageAssetsService.new.fetch_file(Tempfile.new(['manolo', '.png']))
}.to raise_error(Carto::UnprocesableEntityError, "resource is too big (> #{max_size} bytes)")
end
it 'validates file dimensions' do
file = File.new(Rails.root + 'spec/support/data/images/1025x1.jpg')
expect {
Carto::ImageAssetsService.new.fetch_file(file)
}.to raise_error(Carto::UnprocesableEntityError, "file is too big, 1024x1024 max")
end
it 'keeps original extension' do
file = Tempfile.new(['test', '.svg'])
file.write('wadus')
file.rewind
uploaded_file = Rack::Test::UploadedFile.new(file)
temp_file = Carto::ImageAssetsService.new.fetch_file(uploaded_file)
temp_file.path.should end_with '.svg'
end
it 'rejects invalid extensions' do
file = Tempfile.new(['test', '.exe'])
file.write('wadus')
file.rewind
uploaded_file = Rack::Test::UploadedFile.new(file)
expect {
Carto::ImageAssetsService.new.fetch_file(uploaded_file)
}.to raise_error(Carto::UnprocesableEntityError, "extension not accepted")
end
end
end
@@ -0,0 +1,36 @@
require 'fileutils'
require 'spec_helper_min'
require_relative '../../../../app/controllers/carto/controller_helper'
require_relative '../../../../lib/carto/configuration'
describe Carto::KuvizAssetsService do
after(:all) do
FileUtils.rmtree(Carto::Conf.new.public_uploads_path + '/kuviz_assets')
end
describe('#fetch_file') do
it 'rejects files that are too big' do
max_size = Carto::KuvizAssetsService.instance.max_size_in_bytes
IO.stubs(:copy_stream).returns(max_size + 1)
expect {
Carto::KuvizAssetsService.instance.fetch_file(StringIO.new('test'))
}.to raise_error(Carto::UnprocesableEntityError, "resource is too big (> #{max_size} bytes)")
end
it 'create a file has html extension' do
temp_file = Carto::KuvizAssetsService.instance.fetch_file(StringIO.new('test'))
temp_file.path.should end_with '.html'
end
end
describe('#upload') do
it 'uploads file and stores in the local system' do
visualization = FactoryGirl.create(:carto_visualization)
storage_info, url = Carto::KuvizAssetsService.instance.upload(visualization, StringIO.new('test'))
storage_info.present?.should be true
url.present?.should be true
end
end
end
@@ -0,0 +1,60 @@
require 'spec_helper_min'
describe Carto::OrganizationImageAssetsService do
describe('#location') do
after(:each) do
Carto::OrganizationImageAssetsService.instance.instance_variable_set(:@location, nil)
end
it 'uses bucket when available' do
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'bucket')
.returns('manolo_bucket')
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'location')
.returns('escobar_location')
Carto::OrganizationImageAssetsService.instance.location.should eq 'manolo_bucket'
end
it 'uses location when available if bucket is not' do
Cartodb.stubs(:get_config).with(:assets, 'organization', 'bucket')
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'location')
.returns('escobar_location')
Carto::OrganizationImageAssetsService.instance.location.should eq 'escobar_location'
end
it 'uses default when neither bucket nor location are available' do
Cartodb.stubs(:get_config).with(:assets, 'organization', 'bucket')
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'location')
default_location = Carto::OrganizationImageAssetsService::DEFAULT_LOCATION
Carto::OrganizationImageAssetsService.instance.location.should eq default_location
end
end
describe('#max_size_in_bytes') do
after(:each) do
Carto::OrganizationImageAssetsService.instance.instance_variable_set(:@max_size_in_bytes, nil)
end
it 'uses conf when available' do
Cartodb.stubs(:get_config)
.with(:assets, 'organization', 'max_size_in_bytes')
.returns(123456789)
Carto::OrganizationImageAssetsService.instance.max_size_in_bytes.should eq 123456789
end
it 'uses default when conf is not available' do
Cartodb.stubs(:get_config).with(:assets, 'organization', 'max_size_in_bytes')
default_max_size = Carto::ImageAssetsService.new.max_size_in_bytes
Carto::OrganizationImageAssetsService.instance.max_size_in_bytes.should eq default_max_size
end
end
end
+118
View File
@@ -0,0 +1,118 @@
require_relative '../../spec_helper_min.rb'
require_relative '../../../lib/carto/bolt.rb'
module Carto
describe Bolt do
before(:each) do
@bolt = Carto::Bolt.new('manolo_bolt_locked')
end
it 'should expect block' do
expect { @bolt.run_locked.should_raise }.to raise_error('no code block given')
end
it 'should allow access in block if locked and unlocked automatically' do
bolt = Carto::Bolt.new('manolo_bolt_locked', ttl_ms: 60000)
bolt.run_locked {}.should be_true
bolt.run_locked {}.should be_true
end
it 'should not allow access if locked' do
other_bolt = Carto::Bolt.new('manolo_bolt_locked')
@bolt.run_locked { other_bolt.run_locked {}.should be_false }.should be_true
end
it 'should wait for execution if we pass attempts parameters' do
main = Thread.new do
@bolt.run_locked {
sleep(2)
}.should be_true
end
sleep(0.5)
thr = Thread.new do
flag = false
Carto::Bolt.new('manolo_bolt_locked').run_locked(attempts: 5, timeout: 1000) { flag = true }
flag.should be_true
end
thr.join
main.join
end
it 'should wait for execution and exit without complete it if timeout and retries reach the limit' do
main = Thread.new do
@bolt.run_locked {
sleep(2)
}.should be_true
end
sleep(0.5)
thr = Thread.new do
flag = false
Carto::Bolt.new('manolo_bolt_locked').run_locked(attempts: 5, timeout: 1) { flag = true }
flag.should be_false
end
thr.join
main.join
end
it 'should retry an execution when other process tries to acquire bolt and has retriable flag set' do
main = Thread.new do
flag = 0
@bolt.run_locked(rerun_func: lambda { flag += 1 }) {
flag += 1
sleep(2)
}.should be_true
flag.should eq(2)
end
sleep(0.5)
thr = Thread.new do
Carto::Bolt.new('manolo_bolt_locked').run_locked {}.should be_false
end
thr.join
main.join
end
it 'should execute once the rerun_func part despite of the number of calls to acquire the lock' do
main = Thread.new do
flag = 0
rerun_func = lambda do
sleep(1)
flag += 1
end
@bolt.run_locked(rerun_func: rerun_func) {
flag += 1
sleep(2)
}.should be_true
flag.should > 2
end
sleep(0.5)
20.times do
t = Thread.new do
Carto::Bolt.new('manolo_bolt_locked').run_locked {}
sleep(0.25)
end
t.join
end
main.join
end
it 'should raise error if rerun_func is not a lambda' do
expect {
@bolt.run_locked(rerun_func: "lala") {}.should_raise
}.to raise_error('no proc/lambda passed as rerun_func')
end
it 'should expire a lock after ttl_ms' do
ttl_ms = 200
bolt = Carto::Bolt.new('manolo_bolt_locked', ttl_ms: ttl_ms)
bolt.run_locked do
sleep((ttl_ms * 2 / 1000.0).second)
Carto::Bolt.new('manolo_bolt_locked').run_locked {}.should be_true
end
end
end
end
+113
View File
@@ -0,0 +1,113 @@
require_relative '../../../../lib/carto/db/sanitize.rb'
module Carto
module DB
module Sanitize
describe '#sanitize_identifier' do
it 'should prepend table_ to identifiers starting with numbers' do
identifier = '13102016manolo'
sanitized_identifier = 'table_13102016manolo'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should prepend table_ to identifiers starting with _' do
identifier = '_manolo'
sanitized_identifier = 'table_manolo'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should strip whitespaces' do
identifier = ' manolo '
sanitized_identifier = 'manolo'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should remove repated _' do
identifier = 'manolo___________escobar'
sanitized_identifier = 'manolo_escobar'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should convert to lowercase' do
identifier = 'MaNoLo'
sanitized_identifier = 'manolo'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should replace whitespaces with _' do
identifier = 'manolo escobar'
sanitized_identifier = 'manolo_escobar'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should replace disallowed characters with _' do
identifier = 'mañolo!#^èscœbar'
sanitized_identifier = 'ma_olo_sc_bar'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should do all together' do
identifier = '!#_ where'
sanitized_identifier = 'table_where'
Carto::DB::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
it 'should append _t to identifiers that are RESERVED_WORDS' do
Carto::DB::Sanitize::RESERVED_WORDS.each do |reserved_word_identifier|
sanitized_identifier = reserved_word_identifier + '_t'
Carto::DB::Sanitize.sanitize_identifier(reserved_word_identifier).should eq sanitized_identifier
end
end
it 'should append _t to identifiers that are RESERVED_TABLE_NAMES' do
Carto::DB::Sanitize::RESERVED_TABLE_NAMES.each do |reserved_word_identifier|
sanitized_identifier = reserved_word_identifier + '_t'
Carto::DB::Sanitize.sanitize_identifier(reserved_word_identifier).should eq sanitized_identifier
end
end
it 'should append _t to identifiers that are SYSTEM_TABLE_NAMES' do
Carto::DB::Sanitize::SYSTEM_TABLE_NAMES.each do |reserved_word_identifier|
sanitized_identifier = reserved_word_identifier + '_t'
Carto::DB::Sanitize.sanitize_identifier(reserved_word_identifier).should eq sanitized_identifier
end
end
end
describe '#append_with_truncate_and_sanitize' do
it 'should append when length is normal' do
identifier = 'manolo'
suffix = '_escobar'
appended_and_truncated = 'manolo_escobar'
Carto::DB::Sanitize.append_with_truncate_and_sanitize(identifier, suffix).should eq appended_and_truncated
end
it 'should append when identifier is too long' do
identifier = 'manolo' * 12
suffix = '_escobar'
Carto::DB::Sanitize.append_with_truncate_and_sanitize(identifier, suffix).end_with?(suffix).should be_true
end
it 'should raise when suffix is too long' do
identifier = 'manolo' * 12
expect { Carto::DB::Sanitize.append_with_truncate_and_sanitize(identifier, identifier) }.to raise_error
end
end
end
end
end
+81
View File
@@ -0,0 +1,81 @@
require 'carto/db/sql_interface'
require_relative '../../../spec_helper'
module Carto
module Db
describe SqlInterface do
shared_examples 'run examples' do
it 'should work with run' do
db = Carto::Db::SqlInterface.new(@connection)
result = db.run("SELECT version()")
result.should be nil
end
it 'should raise an exception with run' do
expect {
db = Carto::Db::SqlInterface.new(@connection)
db.run("SELECT this_function_does_not_exist()")
}.to raise_error(Carto::Db::SqlInterface::Error,
/ERROR: function this_function_does_not_exist\(\) does not exist/)
end
end
shared_examples 'fetch examples' do
it 'should work with fetch/block' do
db = Carto::Db::SqlInterface.new(@connection)
db.fetch("SELECT version()") do |result|
result[:version].should match /PostgreSQL/
end
end
it 'should work with fetch/noblock' do
db = Carto::Db::SqlInterface.new(@connection)
result = db.fetch("SELECT version()")
result.length.should be 1
result[0][:version].should match /PostgreSQL/
end
it 'should raise an exception with fetch/block' do
db = Carto::Db::SqlInterface.new(@connection)
expect {
db.fetch("SELECT this_function_does_not_exist()") do
nil
end
}.to raise_error(Carto::Db::SqlInterface::Error,
/ERROR: function this_function_does_not_exist\(\) does not exist/)
end
it 'should raise an exception with fetch/noblock' do
db = Carto::Db::SqlInterface.new(@connection)
expect {
db.fetch("SELECT this_function_does_not_exist()")
}.to raise_error(Carto::Db::SqlInterface::Error,
/ERROR: function this_function_does_not_exist\(\) does not exist/)
end
end
context 'with sequel connection' do
before do
options = ::SequelRails.configuration.environment_for(Rails.env)
@connection = ::Sequel.connect(options)
end
after do
@connection.disconnect
end
include_examples 'run examples'
include_examples 'fetch examples'
end
context 'with active record connection' do
before do
@connection = ::ActiveRecord::Base.connection
end
include_examples 'run examples'
include_examples 'fetch examples'
end
end
end
end
+50
View File
@@ -0,0 +1,50 @@
require_relative '../../../spec_helper_min.rb'
require 'carto/db/user_schema'
module Carto
module Db
describe UserSchema do
before(:all) do
@user = ::User[FactoryGirl.create(:carto_user).id]
@user_schema = Carto::Db::UserSchema.new(@user)
end
before(:each) do
bypass_named_maps
end
after(:all) do
@user.destroy
end
def run_in_user_database(query)
@user.in_database.run(query)
end
describe '#table_names' do
it 'Regression for CDB-3446' do
new_name = 'table_'
run_in_user_database(%{
CREATE TABLE #{new_name} ("description" text);
})
@user_schema.table_names.should include(new_name)
end
it 'should find unused names' do
run_in_user_database(%{
CREATE TABLE table_m_nolo_es_co_bar_ ("description" text);
CREATE TABLE table_m_nolo_es_co_bar_1 ("description" text);
CREATE TABLE table_m_nolo_es_co_bar_3 ("description" text);
})
table_names = @user_schema.table_names
table_names.should include('table_m_nolo_es_co_bar_')
table_names.should include('table_m_nolo_es_co_bar_1')
table_names.should include('table_m_nolo_es_co_bar_3')
end
end
end
end
end
+24
View File
@@ -0,0 +1,24 @@
require 'spec_helper_min'
module Carto
describe Definition do
before (:all) { @definition = Carto::Definition.instance }
after (:all) { @definition = nil }
it 'handles inexesitent file paths' do
expect { @definition.load_from_file('/fake/path.json') }.to raise_error do
'Carto::Definition: Couldn\'t read from file'
end
end
it 'doesn\'t read the same file twice' do
file_path = 'lib/assets/javascripts/builder/data/default-cartography.json'
File.expects(:read).with(file_path).returns('{}').at_most(1)
2.times do
@definition.load_from_file(file_path)
end
end
end
end
@@ -0,0 +1,40 @@
require_relative '../../../lib/carto/email_domain_validator.rb'
module Carto
describe EmailDomainValidator do
describe '#validate email domains against whitelist' do
it 'validate a non-wildcard domain' do
Carto::EmailDomainValidator.validate_domain('test@carto.com', ['carto.com']).should == true
end
it 'Error on not whitelisted domain' do
Carto::EmailDomainValidator.validate_domain('test@test.com', ['carto.com']).should == false
end
it 'validate wildcard domain' do
Carto::EmailDomainValidator.validate_domain('test@a.carto.com', ['*.carto.com']).should == true
Carto::EmailDomainValidator.validate_domain('test@a.b.carto.com', ['*.carto.com']).should == true
Carto::EmailDomainValidator.validate_domain('test@a.b.c.carto.com', ['*.carto.com']).should == true
end
it 'validate subdomains wildcard' do
Carto::EmailDomainValidator.validate_domain('test@b.a.carto.com', ['*.a.carto.com']).should == true
Carto::EmailDomainValidator.validate_domain('test@b.b.carto.com', ['*.a.carto.com']).should == false
end
it 'validate multiple domains' do
whitelisted_domains = ['carto.com', '*.mit.edu', '*.cs.harvard.edu']
Carto::EmailDomainValidator.validate_domain('test@carto.com', whitelisted_domains).should == true
Carto::EmailDomainValidator.validate_domain('test@a.b.carto.com', whitelisted_domains).should == false
Carto::EmailDomainValidator.validate_domain('test@cs.mit.edu', whitelisted_domains).should == true
Carto::EmailDomainValidator.validate_domain('test@a.cs.harvard.edu', whitelisted_domains).should == true
Carto::EmailDomainValidator.validate_domain('test@a.bio.harvard.edu', whitelisted_domains).should == false
Carto::EmailDomainValidator.validate_domain('test@nonwhite.com', whitelisted_domains).should == false
end
it 'does not fail validating empty emails' do
Carto::EmailDomainValidator.validate_domain('', []).should == false
end
end
end
end
@@ -0,0 +1,22 @@
require_relative '../../../../lib/carto/file_system/sanitize.rb'
module Carto
module FileSystem
module Sanitize
describe '#sanitize_identifier' do
it 'should allow for non ascii characters' do
identifier = 'MæđrÏD, ĘŠPªÑÅ'
Carto::FileSystem::Sanitize.sanitize_identifier(identifier).should eq identifier
end
it 'should sanitize disallowed characters' do
identifier = "M\x00æđ/rÏD,\\:* : ! ? \" ' >Ę<ŠP|ªÑÅ"
sanitized_identifier = 'M_æđ_rÏD,___ _ ! _ _ \' _Ę_ŠP_ªÑÅ'
Carto::FileSystem::Sanitize.sanitize_identifier(identifier).should eq sanitized_identifier
end
end
end
end
end
+53
View File
@@ -0,0 +1,53 @@
require 'active_support'
require 'active_support/core_ext/object/blank'
require_relative '../../../lib/carto/filename_generator'
describe Carto::FilenameGenerator do
class TestFilenameGenerator
include Carto::FilenameGenerator
end
describe '#filename_from_url' do
let(:generator) { TestFilenameGenerator.new }
let(:supported_extensions) { %w(.csv .carto .zip) }
it 'returns the full url for just filenames' do
['a_name.csv', 'a_name.carto'].each do |filename|
generator.filename_from_url(filename, supported_extensions).should eq filename
end
end
it 'returns nil for unsupported extensions' do
['a_name.wadus', 'a_name'].each do |filename|
generator.filename_from_url(filename, supported_extensions).should be_nil
end
end
it 'returns the filename for normal urls' do
[%w(http://carto.com/name.csv name.csv), %w(http://carto.com/sub/file.carto file.carto)].each do |url, filename|
generator.filename_from_url(url, supported_extensions).should eq filename
end
end
it 'returns the filename for filenames with dots and other characters' do
name1 = 'name.something (one - two : three).carto'
[
%w(http://carto.com/name.something.csv name.something.csv),
["http://carto.com/sub/#{name1.gsub(' ', '%20')}", name1]
].each do |url, filename|
generator.filename_from_url(url, supported_extensions).should eq filename
end
end
it 'returns the filename for weirds URLs with filenames within the parameters (see #5704)' do
inner_filename = '1259030001_MB_2011_ASGC_NSW_csv.zip'
weird_url = "http://www.abs.gov.au/ausstats/subscriber.nsf/log?openagent&"\
"#{inner_filename}"\
"&1259.0.30.001&Data%20Cubes&2787F2FFF3F6E607CA2578CC001268ED&0&July%202011&05.10.2011&Latest"
[[weird_url, inner_filename]].each do |url, filename|
generator.filename_from_url(url, supported_extensions).should eq filename
end
end
end
end
+171
View File
@@ -0,0 +1,171 @@
require 'spec_helper_min'
module Carto
describe Form do
let(:point_form) do
{
"fill" => {
"size" => {
"fixed" => 7
},
"color" => {
"fixed" => "#EE4D5A",
"opacity" => 0.9
}
},
"stroke" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"blending" => "none",
"aggregation" => {},
"labels" => {
"enabled" => false,
"attribute" => nil,
"font" => "DejaVu Sans Book",
"fill" => {
"size" => {
"fixed" => 10
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"halo" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#6F808D",
"opacity" => 1
}
},
"offset" => -10,
"overlap" => true,
"placement" => "point"
}
}
end
let(:polygon_form) do
{
"fill" => {
"color" => {
"fixed" => "#826DBA",
"opacity" => 0.9
}
},
"stroke" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 0.5
}
},
"blending" => "none",
"aggregation" => {},
"labels" => {
"enabled" => false,
"attribute" => nil,
"font" => "DejaVu Sans Book",
"fill" => {
"size" => {
"fixed" => 10
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"halo" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#6F808D",
"opacity" => 1
}
},
"offset" => -10,
"overlap" => true,
"placement" => "point"
}
}
end
let(:line_form) do
{
"fill" => {},
"stroke" => {
"size" => {
"fixed" => 1.5
},
"color" => {
"fixed" => "#4CC8A3",
"opacity" => 1
}
},
"blending" => "none",
"aggregation" => {},
"labels" => {
"enabled" => false,
"attribute" => nil,
"font" => "DejaVu Sans Book",
"fill" => {
"size" => {
"fixed" => 10
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"halo" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#6F808D",
"opacity" => 1
}
},
"offset" => -10,
"overlap" => true,
"placement" => "point"
}
}
end
it 'return point form if inexisting type is specified' do
form = Carto::Form.new('geometry')
form.to_hash.should eq point_form
end
it 'returns a correct line form' do
form = Carto::Form.new('line')
form.to_hash.should eq line_form
end
it 'returns a correct polygon form' do
form = Carto::Form.new('polygon')
form.to_hash.should eq polygon_form
end
it 'returns a correct point form' do
form = Carto::Form.new('point')
form.to_hash.should eq point_form
end
end
end
+62
View File
@@ -0,0 +1,62 @@
require 'spec_helper_min'
module Carto
describe 'Forms definition' do
let(:production_forms_definition) do
{
"fill" => {
"size" => {
"fixed" => 7
},
"color" => {
"fixed" => "#EE4D5A",
"opacity" => 0.9
}
},
"stroke" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"blending" => "none",
"aggregation" => {},
"labels" => {
"enabled" => false,
"attribute" => nil,
"font" => "DejaVu Sans Book",
"fill" => {
"size" => {
"fixed" => 10
},
"color" => {
"fixed" => "#FFFFFF",
"opacity" => 1
}
},
"halo" => {
"size" => {
"fixed" => 1
},
"color" => {
"fixed" => "#6F808D",
"opacity" => 1
}
},
"offset" => -10,
"overlap" => true,
"placement" => "point"
}
}
end
it 'default has stayed the same' do
file_path = Carto::Form::DEFAULT_FORMS_DEFINITION_LOCATION
Carto::Definition.instance.load_from_file(file_path).should eq production_forms_definition
end
end
end
+403
View File
@@ -0,0 +1,403 @@
require_relative '../../spec_helper_min.rb'
require_relative '../../../lib/carto/ghost_tables_manager'
require 'helpers/database_connection_helper'
module Carto
describe GhostTablesManager do
include DatabaseConnectionHelper
before(:all) do
@sequel_user = FactoryGirl.create(:valid_user)
@user = Carto::User.find(@sequel_user.id)
@ghost_tables_manager = Carto::GhostTablesManager.new(@user.id)
end
before(:each) do
bypass_named_maps
CartoDB::Logger.expects(:error).never
end
after(:all) do
::User[@user.id].destroy
end
def run_in_user_database(query)
::User[@user.id].in_database.run(query)
end
it 'should be consistent when no new/renamed/dropped tables' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should not run sync when more than MAX_TABLES_FOR_SYNC_RUN need to be linked' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns([*1..Carto::GhostTablesManager::MAX_TABLES_FOR_SYNC_RUN])
@ghost_tables_manager.send(:should_run_synchronously?).should be_false
end
it 'should not run sync when more than MAX_TABLES_FOR_SYNC_RUN need to be linked including new tables' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns(['manolo'])
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns([*1..Carto::GhostTablesManager::MAX_TABLES_FOR_SYNC_RUN])
@ghost_tables_manager.send(:should_run_synchronously?).should be_false
end
it 'should run sync when more than 0 but less than MAX_TABLES_FOR_SYNC_RUN need to be linked' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns([*1..(Carto::GhostTablesManager::MAX_TABLES_FOR_SYNC_RUN - 1)])
@ghost_tables_manager.send(:should_run_synchronously?).should be_true
end
it 'should not run sync when no tables are stale or dropped' do
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.stubs(:find_dropped_tables)
.returns([])
@ghost_tables_manager.stubs(:find_new_tables)
.returns([*1..Carto::GhostTablesManager::MAX_TABLES_FOR_SYNC_RUN])
@ghost_tables_manager.send(:should_run_synchronously?).should be_false
end
it 'should not run when no tables are changed with tables detected as raster and non-raster' do
raster_tables = [Carto::TableFacade.new(123, 'manolito', @user.id)]
@ghost_tables_manager.stubs(:fetch_non_raster_cartodbfied_tables).returns(raster_tables)
@ghost_tables_manager.stubs(:fetch_raster_tables).returns(raster_tables)
@ghost_tables_manager.stubs(:fetch_user_tables).returns(raster_tables)
@ghost_tables_manager.send(:user_tables_synced_with_db?).should be_true
end
it 'should link sql created table, relink sql renamed tables and unlink sql dropped tables' do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'manoloescobar'
run_in_user_database(%{
ALTER TABLE manoloescobar RENAME TO escobar;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'escobar'
run_in_user_database(%{
DROP TABLE escobar;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should link sql created table using regular api key with create permissions' do
grants = [
{
type: 'apis',
apis: ['maps', 'sql']
},
{
type: "database",
schemas: [
{
name: "#{@user.database_schema}",
permissions: ['create']
}
]
}
]
api_key = @user.api_keys.create_regular_key!(name: 'ghost_tables', grants: grants)
with_connection_from_api_key(api_key) do |connection|
sql = %{
CREATE TABLE test_table ("description" text);
SELECT * FROM CDB_CartodbfyTable('test_table');
}
connection.execute(sql)
end
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'test_table'
with_connection_from_api_key(api_key) do |connection|
connection.execute('DROP TABLE test_table')
end
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
api_key.destroy
end
it 'should link sql created table using oauth_app api key with create permissions' do
scopes = ['offline', 'user:profile', 'schemas:c']
app = FactoryGirl.create(:oauth_app, user: @user)
oau = OauthAppUser.create!(user: @user, oauth_app: app, scopes: scopes)
refresh_token = oau.oauth_refresh_tokens.create!(scopes: scopes)
access_token = refresh_token.exchange!(requested_scopes: scopes)[0]
with_connection_from_api_key(access_token.api_key) do |connection|
sql = %{
CREATE TABLE test_table ("description" text);
SELECT * FROM CDB_CartodbfyTable('test_table');
}
connection.execute(sql)
end
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'test_table'
with_connection_from_api_key(access_token.api_key) do |connection|
connection.execute('DROP TABLE test_table')
end
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
oau.destroy
app.destroy
end
it 'should not link non cartodbyfied tables' do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@ghost_tables_manager.link_ghost_tables_synchronously
run_in_user_database(%{
DROP TABLE manoloescobar;
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should link raster tables' do
run_in_user_database(%{
CREATE TABLE manolo_raster ("cartodb_id" uuid, "the_raster_webmercator" raster);
CREATE TRIGGER test_quota_per_row
BEFORE INSERT OR UPDATE
ON manolo_raster
FOR EACH ROW
EXECUTE PROCEDURE cdb_checkquota('0.001', '-1', 'public');
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 1
@user.tables.first.name.should == 'manolo_raster'
run_in_user_database(%{
DROP TABLE manolo_raster;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should regenerate user tables with bad table_ids' do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'manoloescobar'
user_table = @user.tables.first
original_oid = user_table.table_id
user_table.table_id = original_oid + 1
user_table.save
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.name.should == 'manoloescobar'
@user.tables.first.table_id.should == original_oid
run_in_user_database(%{
DROP TABLE manoloescobar;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'should preseve maps in drop create scenarios' do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
::Resque.expects(:enqueue).with(::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTables, @user.id).never
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
original_user_table = @user.tables.first
original_user_table.name.should == 'manoloescobar'
original_user_table_id = original_user_table.id
original_map_id = original_user_table.map.id
run_in_user_database(%{
DROP TABLE manoloescobar;
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 1
@user.tables.first.id.should == original_user_table_id
@user.tables.first.map.id.should == original_map_id
run_in_user_database(%{
DROP TABLE manoloescobar;
})
@user.tables.count.should eq 1
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
it 'perform a successfully ghost tables execution when is called from LinkGhostTablesByUsername' do
Carto::GhostTablesManager.expects(:new).with(@user.id).returns(@ghost_tables_manager).once
@ghost_tables_manager.expects(:link_ghost_tables_synchronously).once
::Resque::UserDBJobs::UserDBMaintenance::LinkGhostTablesByUsername.perform(@user.username)
end
it 'should call the rerun_func and execute sync twice becuase other worker tried to get the lock' do
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
main = Thread.new do
run_in_user_database(%{
CREATE TABLE manoloescobar ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar');
})
rerun_func = lambda do
Carto::GhostTablesManager.new(@user.id).send(:sync)
end
gtm = Carto::GhostTablesManager.new(@user.id)
gtm.get_bolt.run_locked(rerun_func: rerun_func) do
sleep(1)
Carto::GhostTablesManager.new(@user.id).send(:sync)
end
end
thr = Thread.new do
run_in_user_database(%{
CREATE TABLE manoloescobar2 ("description" text);
SELECT * FROM CDB_CartodbfyTable('manoloescobar2');
})
Carto::GhostTablesManager.new(@user.id).get_bolt.run_locked {}
end
thr.join
main.join
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
@user.tables.count.should eq 2
run_in_user_database(%{
DROP TABLE manoloescobar;
DROP TABLE manoloescobar2;
})
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_false
@ghost_tables_manager.link_ghost_tables_synchronously
@user.tables.count.should eq 0
@ghost_tables_manager.instance_eval { user_tables_synced_with_db? }.should be_true
end
end
end
+33
View File
@@ -0,0 +1,33 @@
require_relative '../../spec_helper_min.rb'
require 'carto/google_maps_api'
module Carto
describe GoogleMapsApi do
let(:url) { 'https://maps.googleapis.com/maps/api/staticmap?center=40.714%2c-73.998&zoom=12&size=400x400' }
describe '#sign' do
describe 'with client_id' do
# You can generate test signature with https://developers.google.com/maps/documentation/static-maps/get-api-key
let(:user) do
FactoryGirl.build(:user, google_maps_key: 'client=wds', google_maps_private_key: 'MjM0MzJk-3N_czQzJmFkc2Rhc2Q=')
end
it 'signs google maps urls' do
gmas = GoogleMapsApi.new
expect(gmas.sign_url(user, url)).to eq "#{url}&client=wds&signature=fK5mHV9MLROw4xZnBPcrCOUdjio="
end
end
describe 'with api_key' do
let(:user) do
FactoryGirl.build(:user, google_maps_key: 'key=xxx')
end
it 'signs google maps urls' do
gmas = GoogleMapsApi.new
expect(gmas.sign_url(user, url)).to eq "#{url}&key=xxx"
end
end
end
end
end
+97
View File
@@ -0,0 +1,97 @@
require_relative '../../../spec_helper'
require 'rspec/core'
require 'rspec/expectations'
require 'rspec/mocks'
require 'mocha'
require_relative '../../../../lib/carto/http/client'
require_relative '../../../../lib/carto/http/request'
describe Carto::Http::Client do
before(:each) do
http_client_tag = 'test'
@client = Carto::Http::Client.get(http_client_tag, log_requests: true)
end
describe '#request' do
it 'wraps creation of typhoeus request objects' do
expected_url = 'www.example.com'
expected_options = {
method: :post,
body: "this is a request body",
params: { field1: "a field" },
headers: { Accept: "text/html",
"User-Agent" => Carto::Http::Request::DEFAULT_USER_AGENT
}
}
Typhoeus::Request.expects(:new).once.with(expected_url, expected_options)
@client.request(
"www.example.com",
method: :post,
body: "this is a request body",
params: { field1: "a field" },
headers: { Accept: "text/html", "User-Agent" => Carto::Http::Request::DEFAULT_USER_AGENT}
)
end
end
describe 'Request#run' do
it 'performs a request through its typhoeus request object' do
expected_response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
Typhoeus.stub('www.example.com').and_return(expected_response)
request = @client.request(
"www.example.com",
method: :post,
body: "this is a request body",
params: { field1: "a field" },
headers: { Accept: "text/html", "User-Agent" => Carto::Http::Request::DEFAULT_USER_AGENT}
)
request.run.should eq expected_response
end
end
describe '#get' do
it 'creates a wrapped GET request and runs it' do
expected_response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
Typhoeus.stub("www.example.com").and_return(expected_response)
@client.get("www.example.com").should eq expected_response
end
end
describe '#post' do
it 'creates a wrapped POST request and runs it' do
expected_response = Typhoeus::Response.new(code: 200, body: "{'success': 'true'}")
Typhoeus.stub("www.example.com/posts").and_return(expected_response)
@client.post("www.example.com/posts", body: { title: "test post", content: "this is my test"})
.should eq expected_response
end
end
describe '#head' do
it 'creates a wrapped HEAD request and runs it' do
expected_response = Typhoeus::Response.new(code: 200)
Typhoeus.stub("www.example.com").and_return(expected_response)
@client.head("www.example.com").should eq expected_response
end
end
describe '#put' do
it 'creates a wrapped PUT request and runs it' do
expected_response = Typhoeus::Response.new(code: 200, body: "{'success': 'true'}")
Typhoeus.stub("www.example.com/posts/1").and_return(expected_response)
@client.put("www.example.com/posts/1", body: "whoo, a body").should eq expected_response
end
end
describe '#delete' do
it 'creates a wrapped DELETE request and runs it' do
expected_response = Typhoeus::Response.new(code: 204)
Typhoeus.stub("www.example.com/posts/1").and_return(expected_response)
@client.delete("www.example.com/posts/1").should eq expected_response
end
end
end
@@ -0,0 +1,139 @@
require 'ostruct'
require 'uuidtools'
require_relative '../../spec_helper'
require_relative '../../../lib/carto/http_header_authentication'
require_relative '../../requests/http_authentication_helper'
describe Carto::HttpHeaderAuthentication do
include HttpAuthenticationHelper
EMAIL = "user@carto.com"
USERNAME = "user"
ID = UUIDTools::UUID.timestamp_create.to_s
let(:mock_unauthenticated_request) do
OpenStruct.new(headers: {})
end
let(:mock_email_request) { OpenStruct.new(headers: { "#{authenticated_header}" => EMAIL }) }
let(:mock_username_request) { OpenStruct.new(headers: { "#{authenticated_header}" => USERNAME }) }
let(:mock_id_request) { OpenStruct.new(headers: { "#{authenticated_header}" => ID }) }
let(:mock_user) do
OpenStruct.new(
email: EMAIL
)
end
let(:mock_user_search) { OpenStruct.new(first: mock_user) }
describe '#valid?' do
it 'returns false without configuration' do
Carto::HttpHeaderAuthentication.new.valid?(mock_email_request).should == false
end
it 'returns false with configuration without header' do
stub_http_header_authentication_configuration(field: 'email')
Carto::HttpHeaderAuthentication.new.valid?(mock_unauthenticated_request).should == false
end
it 'returns true with configuration and header' do
stub_http_header_authentication_configuration(field: 'email')
Carto::HttpHeaderAuthentication.new.valid?(mock_email_request).should == true
end
end
describe '#get_user?' do
before(:each) do
stub_http_header_authentication_configuration(field: 'email')
end
it 'returns nil without header' do
Carto::HttpHeaderAuthentication.new.get_user(mock_unauthenticated_request).should be_nil
end
it 'searches by email with header' do
User.expects(:where).with("email = ?", mock_email_request.headers[authenticated_header]).returns mock_user_search
Carto::HttpHeaderAuthentication.new.get_user(mock_email_request).should == mock_user
end
describe 'auto field' do
before(:each) do
stub_http_header_authentication_configuration(field: 'auto')
end
it 'searches by field depending on header' do
User.expects(:where).with("email = ?", mock_email_request.headers[authenticated_header]).once.returns mock_user_search
Carto::HttpHeaderAuthentication.new.get_user(mock_email_request).should == mock_user
User.expects(:where).with("username = ?", mock_username_request.headers[authenticated_header]).once.returns mock_user_search
Carto::HttpHeaderAuthentication.new.get_user(mock_username_request).should == mock_user
User.expects(:where).with("id = ?", mock_id_request.headers[authenticated_header]).once.returns mock_user_search
Carto::HttpHeaderAuthentication.new.get_user(mock_id_request).should == mock_user
end
end
end
describe '#autocreation_enabled?' do
it 'returns autocreation configuration' do
stub_http_header_authentication_configuration(field: 'auto', autocreation: true)
Carto::HttpHeaderAuthentication.new.autocreation_enabled?.should be_true
stub_http_header_authentication_configuration(field: 'auto', autocreation: false)
Carto::HttpHeaderAuthentication.new.autocreation_enabled?.should be_false
end
end
describe '#email' do
let(:authenticator) { Carto::HttpHeaderAuthentication.new }
it 'returns email for email requests' do
stub_http_header_authentication_configuration(field: 'email')
authenticator.email(mock_email_request).should == EMAIL
end
it 'raises error if configuration is not email (or auto with an email in the request)' do
stub_http_header_authentication_configuration(field: 'id')
expect { authenticator.email(mock_email_request) }.to raise_error
stub_http_header_authentication_configuration(field: 'username')
expect { authenticator.email(mock_email_request) }.to raise_error
stub_http_header_authentication_configuration(field: 'auto')
expect { authenticator.email(mock_username_request) }.to raise_error
end
it 'returns email if configuration is auto and request contains an email' do
stub_http_header_authentication_configuration(field: 'auto')
expect { authenticator.email(mock_email_request) }.not_to raise_error
end
end
describe '#creation_in_progress?' do
let(:authenticator) { Carto::HttpHeaderAuthentication.new }
it 'returns true if there is a matching creation in progress by (user) id' do
stub_http_header_authentication_configuration(field: 'id')
uc = FactoryGirl.create(:user_creation, state: 'enqueuing', user_id: ID)
authenticator.creation_in_progress?(mock_id_request).should be_true
uc.destroy
end
it 'returns true if there is a matching creation in progress by username' do
stub_http_header_authentication_configuration(field: 'username')
uc = FactoryGirl.create(:user_creation, state: 'enqueuing', username: USERNAME)
authenticator.creation_in_progress?(mock_username_request).should be_true
uc.destroy
end
it 'returns true if there is a matching creation in progress by email' do
stub_http_header_authentication_configuration(field: 'email')
uc = FactoryGirl.create(:user_creation, state: 'enqueuing', email: EMAIL)
authenticator.creation_in_progress?(mock_email_request).should be_true
uc.destroy
end
it 'returns false if there is not a matching creation in progress' do
stub_http_header_authentication_configuration(field: 'auto')
authenticator.creation_in_progress?(mock_email_request).should be_false
end
end
end
@@ -0,0 +1,259 @@
require 'spec_helper_min'
module Carto
describe LegendDefinitionValidator do
let(:random_definition) do
{
hey: 'teacher',
leave: {
the: 'kids alone'
}
}.with_indifferent_access
end
let(:bubble_definition) do
definition_location = "#{Rails.root}/lib/formats/legends/definitions/bubble.json"
Carto::Definition.instance
.load_from_file(definition_location)
end
describe '#bubble' do
let(:definition) do
{
color: '#fff'
}
end
it 'accepts a correct definition' do
validator = LegendDefinitionValidator.new(:bubble, definition)
validator.errors.should be_empty
end
it 'rejects spammy definitions' do
spammed_definition = definition.merge(spam: 'the ham')
validator = LegendDefinitionValidator.new(:bubble, spammed_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
it 'rejects incomplete definitions' do
incomplete_definition = definition.except(:color)
validator = LegendDefinitionValidator.new(:bubble, incomplete_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not contain a required property')
end
it 'rejects wrongly formatted colors' do
definition[:color] = '#notcool'
validator = LegendDefinitionValidator.new(:bubble, definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not match the regex')
end
end
describe '#choropleth' do
let(:definition) do
{
prefix: "123",
suffix: "foo"
}
end
it 'accepts a correct definition' do
validator = LegendDefinitionValidator.new(:choropleth, definition)
validator.errors.should be_empty
end
it 'rejects spammy definitions' do
spammed_definition = definition.merge(spam: 'the ham')
validator = LegendDefinitionValidator.new(:choropleth,
spammed_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
it 'doesn\'t reject incomplete definitions' do
incomplete_definition = definition.except(:prefix)
validator = LegendDefinitionValidator.new(:choropleth,
incomplete_definition)
validator.errors.should be_empty
end
end
describe '#custom' do
let(:definition) do
{
categories: [
{ title: 'Manolo Escobar' },
{ title: 'Manolo Escobar', color: '#fff' },
{ title: 'Manolo Escobar', icon: 'super.png' },
{ title: 'Manolo Escobar', icon: 'super.png', color: '#fff' }
]
}
end
it 'accepts a correct definition' do
validator = LegendDefinitionValidator.new(:custom, definition)
validator.errors.should be_empty
end
it 'rejects spammy definitions' do
spammed_definition = definition.merge(spam: 'the ham')
validator = LegendDefinitionValidator.new(:custom,
spammed_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
it 'rejects incomplete definitions' do
incomplete_definition = definition.except(:categories)
validator = LegendDefinitionValidator.new(:custom,
incomplete_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not contain a required property')
end
describe 'categories' do
it 'rejects spammy definitions' do
definition[:categories] << { title: 'foo', spam: 'the ham' }
validator = LegendDefinitionValidator.new(:custom, definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
it 'rejects incomplete definitions' do
definition[:categories] << {}
validator = LegendDefinitionValidator.new(:custom, definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not contain a required property')
end
it 'rejects wrongly formatted colors' do
definition[:categories] << { color: '#notcool' }
validator = LegendDefinitionValidator.new(:custom, definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('did not match the regex')
end
end
end
describe '#category' do
let(:definition) do
{}
end
it 'accepts a correct definition' do
validator = LegendDefinitionValidator.new(:category, definition)
validator.errors.should be_empty
end
it 'rejects spammy definitions' do
spammed_definition = definition.merge(spam: 'the ham')
validator = LegendDefinitionValidator.new(:category, spammed_definition)
validator.errors.should_not be_empty
joint_errors = validator.errors.join(', ')
joint_errors.should include('additional properties')
end
end
describe '#errors' do
it 'handles non defined schemas' do
validator = LegendDefinitionValidator.new(nil, random_definition)
validator.errors.should eq ['could not be validated']
end
it 'handles invalid definitions' do
validator = LegendDefinitionValidator.new(:bubble, nil)
validator.errors.should eq ['could not be validated']
end
it 'performs validations' do
validator = LegendDefinitionValidator
.new(:bubble, random_definition)
validation_errors = validator.errors
validation_errors.should_not be_empty
validation_errors.should_not include('could not be validated')
end
it 'is memoized' do
validator = LegendDefinitionValidator.new('bubble', random_definition)
JSON::Validator.expects(:fully_validate)
.with(bubble_definition, random_definition)
.returns(['Cool validation error'])
.once
2.times { validator.errors.should_not be_empty }
end
end
describe '#location' do
it 'returns nil for inexistent locations' do
validator = LegendDefinitionValidator.new(:foo, nil)
validator.send(:location).should be_nil
end
it 'returns location for existent locations' do
validator = LegendDefinitionValidator.new(:bubble, nil)
expected_location = "#{Rails.root}/lib/formats/legends/definitions/bubble.json"
validator.send(:location).should eq expected_location
end
it 'returns location for existent locations when type is string' do
validator = LegendDefinitionValidator.new('bubble', nil)
expected_location = "#{Rails.root}/lib/formats/legends/definitions/bubble.json"
validator.send(:location).should eq expected_location
end
it 'is memoized' do
expected_location = "#{Rails.root}/lib/formats/legends/definitions/bubble.json"
validator = LegendDefinitionValidator.new('bubble', nil)
File.expects(:exists?)
.with(expected_location)
.returns(true)
.once
validator.send(:location).should eq expected_location
validator.send(:location).should eq expected_location
end
end
end
end
+507
View File
@@ -0,0 +1,507 @@
require 'spec_helper_min'
require 'carto/legend_migrator'
module Carto
describe LegendMigrator do
before(:all) do
@layer = Carto::Layer.create(kind: 'carto')
end
after(:all) do
@layer.destroy
end
describe('#with templates') do
let(:old_legend_with_template) do
{
"show_title" => false,
"title" => "",
"template" => "<h1>Manolo Escobar</h1>",
"visible" => true,
"items" => []
}
end
it 'should migrate to custom for type custom' do
@old_legend = old_legend_with_template.merge(type: 'custom')
end
it 'should migrate to custom for type category' do
@old_legend = old_legend_with_template.merge(type: 'category')
end
it 'should migrate to custom for type bubble' do
@old_legend = old_legend_with_template.merge(type: 'bubble')
end
it 'should migrate to custom for type choropleth' do
@old_legend = old_legend_with_template.merge(type: 'choropleth')
end
it 'should migrate to custom for type intensity' do
@old_legend = old_legend_with_template.merge(type: 'intensity')
end
it 'should migrate to custom for type density' do
@old_legend = old_legend_with_template.merge(type: 'density')
end
after(:each) do
new_legend = Carto::LegendMigrator.new(@layer.id, @old_legend).build
new_legend.definition[:html].should include('<h1>Manolo Escobar</h1>')
new_legend.type.should eq 'custom'
end
end
describe('#custom types') do
describe('#with categories') do
let(:old_category) do
{
"type" => "category",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => 0,
"visible" => true,
"value" => "super.png"
},
{
"name" => 1350,
"visible" => true,
"value" => "#1F78B4"
},
{
"name" => 1440,
"visible" => true,
"value" => "#B2DF8A"
},
{
"name" => 1800,
"visible" => true,
"value" => "#33A02C"
},
{
"name" => 2250,
"visible" => true,
"value" => "#FB9A99"
},
{
"name" => 2700,
"visible" => true,
"value" => "#E31A1C"
},
{
"name" => 4500,
"visible" => true,
"value" => "#FDBF6F"
},
{
"name" => 450000,
"visible" => true,
"value" => "#FF7F00"
},
{
"name" => 900,
"visible" => true,
"value" => "duper.png"
},
{
"name" => 90000,
"visible" => true,
"value" => "#6A3D9A"
},
{
"name" => "Others",
"visible" => true,
"value" => "#DDDDDD"
}
]
}
end
let(:old_custom) do
{
"type" => "custom",
"show_title" => true,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "preta",
"visible" => true,
"value" => "#41006D",
"sync" => true
},
{
"name" => "Untitled",
"visible" => true,
"value" => "url(http://com.cartodb.users-assets.production.s3.amazonaws.com/superduper.png)",
"sync" => true
},
{
"name" => "patata",
"visible" => true,
"value" => "#cccccc",
"sync" => true
},
{
"name" => "Untitled",
"visible" => true,
"value" => "#cccccc",
"sync" => true
}
]
}
end
it 'omits badly formatted colors' do
truncated = old_category.dup
truncated['items'].first['value'] = '#fatal#fatal#fatal'
@old_legend = truncated
end
it 'migrates old category to new custom' do
@old_legend = old_category
end
it 'migrates old custom to new custom with no template' do
@old_legend = old_custom
end
after(:each) do
new_legend = Carto::LegendMigrator.new(@layer.id, @old_legend).build
new_legend.type.should eq 'custom'
new_legend.valid?.should be_true
category_keys = new_legend.definition[:categories]
.map(&:keys)
.flatten
.uniq
category_keys.should include(:title)
category_keys.should include(:color)
category_keys.should include(:icon)
if @old_legend['type'] == 'custom'
new_legend.definition[:categories].map { |category| category[:icon] }.each do |icon|
unless icon.blank?
icon.should include("https://s3.amazonaws.com/com.cartodb.users-assets.production/superduper.png")
icon.should_not include("url(")
end
end
end
end
end
describe('#with html') do
let(:old_custom) do
{
"type" => "custom",
"show_title" => true,
"title" => "",
"template" => "<h1>Manolo Escobar</h1>",
"visible" => true,
"items" => []
}
end
let(:old_bubble) do
{
"type" => "bubble",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Left label",
"visible" => true,
"value" => 787.5,
"legend_type" => "bubble",
"type" => "text",
"sync" => false
},
{
"name" => "Right Label",
"visible" => true,
"value" => 6273765,
"legend_type" => "bubble",
"type" => "text",
"sync" => false
},
{
"name" => "Color",
"visible" => true,
"value" => "#FF5C00",
"type" => "color"
}
]
}
end
let(:old_bubble_with_custom_labels) do
{
"type" => "bubble",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Left label",
"visible" => true,
"value" => "few",
"legend_type" => "bubble",
"type" => "text",
"sync" => false
},
{
"name" => "Right Label",
"visible" => true,
"value" => "many",
"legend_type" => "bubble",
"type" => "text",
"sync" => false
},
{
"name" => "Color",
"visible" => true,
"value" => "#FF5C00",
"type" => "color"
}
]
}
end
let(:old_choropleth) do
{
"type" => "choropleth",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Left label",
"visible" => true, "value" => "1350.00",
"type" => "text"
},
{
"name" => "Right label",
"visible" => true,
"value" => "6273765.00",
"type" => "text"
},
{
"name" => "Color",
"visible" => true, "value" => "#FFFFB2",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FED976",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FEB24C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FD8D3C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FC4E2A",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#E31A1C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#B10026",
"type" => "color"
}
]
}
end
let(:old_density) do
{
"type" => "density",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Less",
"visible" => true,
"value" => "less",
"legend_type" => "density",
"type" => "text",
"sync" => true
},
{
"name" => "More",
"visible" => true,
"value" => "more",
"legend_type" => "density",
"type" => "text",
"sync" => true
},
{
"name" => "Color",
"visible" => true, "value" => "#FFFFB2",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FECC5C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#FD8D3C",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#F03B20",
"type" => "color"
},
{
"name" => "Color",
"visible" => true, "value" => "#BD0026",
"type" => "color"
}
]
}
end
let(:old_intensity) do
{
"type" => "intensity",
"show_title" => false,
"title" => "",
"template" => "",
"visible" => true,
"items" => [
{
"name" => "Left label",
"visible" => true,
"value" => "Less",
"legend_type" => "intensity",
"type" => "text",
"sync" => true
},
{
"name" => "Right label",
"visible" => true,
"value" => "More",
"legend_type" => "intensity",
"type" => "text",
"sync" => true
},
{
"name" => "Color",
"visible" => true, "value" => "#FFCC00",
"type" => "color"
}
]
}
end
it 'migrates old bubble with custom labels to new custom' do
@old_legend = old_bubble_with_custom_labels
end
it 'migrates old custom with template to new custom' do
@old_legend = old_custom
end
it 'migrates old bubble to new custom' do
@old_legend = old_bubble
end
it 'migrates old choropleth to new custom' do
@old_legend = old_choropleth
end
it 'migrates old density to new custom' do
@old_legend = old_density
end
it 'migrates old density without labels to new custom' do
truncated = old_density.dup
truncated['items'].delete_at(0)
truncated['items'].delete_at(0)
@old_legend = truncated
end
it 'migrates old intensity to new custom' do
@old_legend = old_intensity
end
it 'migrates old intensity without labels to new custom' do
truncated = old_intensity.dup
truncated['items'].delete_at(0)
truncated['items'].delete_at(0)
@old_legend = truncated
end
after(:each) do
new_legend = Carto::LegendMigrator.new(@layer.id, @old_legend).build
new_legend.type.should eq 'custom'
new_legend.valid?.should be_true
unless @old_legend['type'] == 'bubble' || @old_legend['type'] == 'custom'
html = new_legend.definition[:html]
# Result should have labels, colors, and icons somewhere in generated
# html
@old_legend['items'].map { |item| item['value'] }.each do |value|
html.downcase.should include(value.to_s.downcase)
end
# No incomplete gradients allowed
html.scan(/#(?:[0-9a-fA-F]{3}){1,2}/).size.should be >= 2
end
@old_legend = nil
end
end
end
describe('#bad legends') do
let(:bad_legend) do
{
patata_pochada: 'buena',
pero: 'mejor que pochada',
frita: 'a que si!'
}
end
it 'returns invalid legends for invalid definitions' do
migrator = Carto::LegendMigrator.new(@layer.id, bad_legend)
migrator.build.should_not be_valid
end
end
end
end
@@ -0,0 +1,55 @@
require 'spec_helper_min'
require 'carto/mapcapped_visualization_updater'
module Carto
describe MapcappedVisualizationUpdater do
include Factories::Visualizations
include MapcappedVisualizationUpdater
before(:all) do
@user = FactoryGirl.create(:carto_user)
@map, @table, @table_visualization, @visualization = create_full_visualization(@user)
end
after(:all) do
destroy_full_visualization(@map, @table, @table_visualization, @visualization)
@user.destroy
end
it 'updates a non-mapcapped visualization' do
Map.any_instance.stubs(:notify_map_change).once
success = update_visualization_and_mapcap(@visualization) do |visualization, persisted|
layer = visualization.layers.first
layer.options[:wadus] = 'wadus!'
layer.save if persisted
end
success.should be_true
@visualization.reload
@visualization.mapcapped?.should be_false
@visualization.layers.first.options[:wadus].should eq 'wadus!'
end
it 'updates a mapcapped visualization' do
@visualization.create_mapcap!
@visualization.layers.first.update_attribute(:tooltip, do_not_publish_this: true)
Map.any_instance.stubs(:notify_map_change).once
success = update_visualization_and_mapcap(@visualization) do |visualization, persisted|
layer = visualization.layers.first
layer.options[:something] = 'else'
layer.save if persisted
end
success.should be_true
@visualization.reload
@visualization.mapcapped?.should be_true
@visualization.layers.first.options[:something].should eq 'else'
mapcap_visualization = @visualization.latest_mapcap.regenerate_visualization
mapcap_visualization.layers.first.options[:something].should eq 'else'
mapcap_visualization.layers.first.tooltip.should be_nil
end
end
end
+804
View File
@@ -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
+59
View File
@@ -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
+143
View File
@@ -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
+81
View File
@@ -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
+70
View File
@@ -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
+43
View File
@@ -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
+23
View File
@@ -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
+29
View File
@@ -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
+27
View File
@@ -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
+62
View File
@@ -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
+86
View File
@@ -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
+149
View File
@@ -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
+14
View File
@@ -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
+106
View File
@@ -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
+50
View File
@@ -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
+17
View File
@@ -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
+80
View File
@@ -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
+214
View File
@@ -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
+20
View File
@@ -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
+59
View File
@@ -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
+54
View File
@@ -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
+134
View File
@@ -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
+112
View File
@@ -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
+35
View File
@@ -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
+92
View File
@@ -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
+102
View File
@@ -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
+14
View File
@@ -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
+25
View File
@@ -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
+70
View File
@@ -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