cartodb/spec/requests/carto/api/visualizations_controller_spec.rb
2020-06-15 10:58:47 +08:00

3606 lines
151 KiB
Ruby

require_relative '../../../spec_helper'
require_relative '../../../factories/users_helper'
require_relative '../../../../app/controllers/carto/api/visualizations_controller'
# TODO: Remove once Carto::Visualization is complete enough
require_relative '../../../../app/models/visualization/member'
require_relative './vizjson_shared_examples'
require 'helpers/unique_names_helper'
require_dependency 'carto/uuidhelper'
require 'factories/carto_visualizations'
require 'helpers/visualization_destruction_helper'
require 'helpers/feature_flag_helper'
include Carto::UUIDHelper
describe Carto::Api::VisualizationsController do
include UniqueNamesHelper
include Carto::Factories::Visualizations
include VisualizationDestructionHelper
include FeatureFlagHelper
before(:all) do
create_account_type_fg('ORGANIZATION USER')
end
describe 'vizjson2 generator' do
it_behaves_like 'vizjson generator' do
def api_vx_visualizations_vizjson_url(options)
api_v2_visualizations_vizjson_url(options)
end
def vizjson_vx_version
'0.1.0'
end
def attributions_from_vizjson(visualization)
visualization['layers'][1]['options']['attribution'].split(',').map(&:strip)
end
before(:each) do
bypass_named_maps
end
it 'marks visualizations as using vizjson2' do
visualization = FactoryGirl.create(:carto_visualization)
Carto::Api::VisualizationsController.any_instance.stubs(:generate_vizjson2).returns({})
get(
api_v2_visualizations_vizjson_url(user_domain: visualization.user.username, id: visualization.id),
{},
'HTTP_REFERER' => 'http://wadus.com'
)
visualization.uses_vizjson2?.should be_true
end
it 'marks visualizations as using vizjson2 with invalid referer' do
visualization = FactoryGirl.create(:carto_visualization)
Carto::Api::VisualizationsController.any_instance.stubs(:generate_vizjson2).returns({})
get(
api_v2_visualizations_vizjson_url(user_domain: visualization.user.username, id: visualization.id),
{},
'HTTP_REFERER' => 'invalid'
)
visualization.uses_vizjson2?.should be_true
end
it 'marks visualizations as using vizjson2 without referer' do
visualization = FactoryGirl.create(:carto_visualization)
Carto::Api::VisualizationsController.any_instance.stubs(:generate_vizjson2).returns({})
get api_v2_visualizations_vizjson_url(user_domain: visualization.user.username, id: visualization.id)
visualization.uses_vizjson2?.should be_true
end
it 'does not mark visualizations as using vizjson2 with carto referer' do
visualization = FactoryGirl.create(:carto_visualization)
Carto::Api::VisualizationsController.any_instance.stubs(:generate_vizjson2).returns({})
get(
api_v2_visualizations_vizjson_url(user_domain: visualization.user.username, id: visualization.id),
{},
'HTTP_REFERER' => 'https://carto.com/wadus'
)
visualization.uses_vizjson2?.should be_false
end
end
end
describe 'vizjson3 generator' do
it_behaves_like 'vizjson generator' do
def api_vx_visualizations_vizjson_url(options)
api_v3_visualizations_vizjson_url(options)
end
def vizjson_vx_version
'3.0.0'
end
def attributions_from_vizjson(visualization)
visualization['layers'].select { |l| l['type'] == 'CartoDB' }
.map { |l| l['options']['attribution'] }
.select(&:present?)
end
end
end
TEST_UUID = '00000000-0000-0000-0000-000000000000'.freeze
DATE_ATTRIBUTES = %w{ created_at updated_at }.freeze
NORMALIZED_ASSOCIATION_ATTRIBUTES = {
attributes: DATE_ATTRIBUTES,
associations: {
'permission' => {
attributes: DATE_ATTRIBUTES,
associations: {}
},
'table' => {
attributes: DATE_ATTRIBUTES,
associations: {}
}
}
}.freeze
NEW_ATTRIBUTES = {
attributes: [],
associations: {
'table' => {
attributes: [],
associations: {
'permission' => {
attributes: [],
associations: {
'owner' => {
attributes: ['email', 'quota_in_bytes', 'db_size_in_bytes', 'public_visualization_count',
'all_visualization_count', 'table_count'],
associations: {}
}
}
}
}
},
'permission' => {
attributes: [],
associations: {
'owner' => {
attributes: ['email', 'quota_in_bytes', 'db_size_in_bytes', 'public_visualization_count',
'all_visualization_count', 'table_count'],
associations: {}
}
}
}
}
}.freeze
BBOX_GEOM = '{"type":"MultiPolygon","coordinates":[[[[-75.234375,54.57206166],[4.921875,54.36775852],[7.03125,-0.35156029],[-71.71875,1.75753681],[-75.234375,54.57206166]]]]}'.freeze
OUTSIDE_BBOX_GEOM = '{"type":"MultiPolygon","coordinates":[[[[-149.4140625,79.74993208],[-139.921875,79.74993208],[-136.0546875,78.13449318],[-148.7109375,78.06198919],[-149.4140625,79.74993208]]]]}'.freeze
describe 'static_map' do
include_context 'visualization creation helpers'
include_context 'users helper'
before(:all) do
Carto::NamedMaps::Api.any_instance.stubs(get: nil, create: true, update: true)
@user_1 = FactoryGirl.create(:valid_user, private_tables_enabled: false)
@table1 = create_random_table(@user_1)
@headers = { 'CONTENT_TYPE' => 'application/json' }
host! "#{@user_1.subdomain}.localhost.lan"
end
after(:all) do
@user_1.destroy
end
it 'tests with non-existing cdn config, which uses maps_api_template url' do
width = 123
height = 456
Carto::StaticMapsURLHelper.any_instance
.stubs(:get_cdn_config)
.returns(nil)
ApplicationHelper.stubs(:maps_api_template)
.returns("http://#{@user_1.username}.localhost.lan:8181")
get api_v2_visualizations_static_map_url(
user_domain: @user_1.username,
id: @table1.table_visualization.id,
width: width,
height: height), @headers
last_response.status.should == 302
tpl_id = Carto::NamedMaps::Template.new(Carto::Visualization.find(@table1.table_visualization.id)).name
last_response.location.should == "http://#{@user_1.username}.localhost.lan:8181/api/v1/map/static/named/#{tpl_id}/#{width}/#{height}.png"
end
it 'tests with existing cdn config' do
width = 123
height = 456
Carto::StaticMapsURLHelper.any_instance
.stubs(:get_cdn_config)
.returns("http" => "cdn.local.lan")
get api_v2_visualizations_static_map_url(
user_domain: @user_1.username,
id: @table1.table_visualization.id,
width: width,
height: height
), @headers
last_response.status.should == 302
tpl_id = Carto::NamedMaps::Template.new(Carto::Visualization.find(@table1.table_visualization.id)).name
last_response.location.should == "http://cdn.local.lan/#{@user_1.username}/api/v1/map/static/named/#{tpl_id}/#{width}/#{height}.png"
end
it 'tests privacy of static_maps calls' do
# As privacy is equal to other visualizations controller methods, no need to test every option, just generally
width = 123
height = 456
# By default no private tables so all are created public
@user_1.private_tables_enabled = true
@user_1.save
private_table = create_random_table(@user_1)
@user_1.private_tables_enabled = false
@user_1.save
Carto::StaticMapsURLHelper.any_instance
.stubs(:get_cdn_config)
.returns(nil)
ApplicationHelper.stubs(:maps_api_template)
.returns("http://#{@user_1.username}.localhost.lan:8181")
get api_v2_visualizations_static_map_url(
user_domain: @user_1.username,
id: @table1.table_visualization.id,
width: width,
height: height
), @headers
last_response.status.should == 302
template_name = Carto::NamedMaps::Template.new(Carto::Visualization.find(@table1.table_visualization.id)).name
last_response.location.should == "http://#{@user_1.username}.localhost.lan:8181/api/v1/map/static/named/#{template_name}/#{width}/#{height}.png"
get api_v2_visualizations_static_map_url(
user_domain: @user_1.username,
id: private_table.table_visualization.id,
width: width,
height: height
), @headers
last_response.status.should == 403
get api_v2_visualizations_static_map_url(
user_domain: @user_1.username,
api_key: @user_1.api_key,
id: private_table.table_visualization.id,
width: width,
height: height
), @headers
last_response.status.should == 302
visualization_id = private_table.table_visualization.id
template_name = Carto::NamedMaps::Template.new(Carto::Visualization.find(visualization_id)).name
last_response.location.should == "http://#{@user_1.username}.localhost.lan:8181/api/v1/map/static/named/#{template_name}/#{width}/#{height}.png"
end
it 'tests varnish keys' do
width = 123
height = 456
Carto::StaticMapsURLHelper.any_instance
.stubs(:get_cdn_config)
.returns("http" => "cdn.local.lan")
get api_v2_visualizations_static_map_url(
user_domain: @user_1.username,
# api_key: @user_1.api_key,
id: @table1.table_visualization.id,
width: width,
height: height
), @headers
last_response.status.should == 302
last_response.headers["X-Cache-Channel"].should include(@table1.name)
last_response.headers["X-Cache-Channel"].should include(@table1.table_visualization.varnish_key)
last_response.headers["Surrogate-Key"].should_not be_empty
last_response.headers["Surrogate-Key"].should include(CartoDB::SURROGATE_NAMESPACE_VIZJSON)
last_response.headers["Surrogate-Key"].should include(@table1.table_visualization.surrogate_key)
end
end
describe 'index' do
include_context 'visualization creation helpers'
include_context 'users helper'
before(:all) do
Carto::NamedMaps::Api.any_instance.stubs(get: nil, create: true, update: true)
@user_1 = FactoryGirl.create(:valid_user)
end
before(:each) do
login(@user_1)
@headers = { 'CONTENT_TYPE' => 'application/json' }
host! "#{@user_1.subdomain}.localhost.lan"
end
after(:each) do
delete_user_data @user_1
end
after(:all) do
@user_1.destroy
end
def compare_with_dates(a, b)
# Compares two hashes, ignoring differences in timezones
a.keys.sort.should eq b.keys.sort
a.each do |k, v|
v2 = b[k]
if k.ends_with?('_at')
# DateTime.parse(v).should eq DateTime.parse(v2) unless v.blank? && v2.blank?
elsif v.is_a?(Hash)
compare_with_dates(v, v2)
else
v.should eq v2
end
end
end
it 'returns success, empty response for empty user' do
expected_response = {
'visualizations' => [],
'total_entries' => 0,
'total_likes' => 0,
'total_user_entries' => 0,
'total_shared' => 0,
'total_locked' => 0
}
response_body.should == expected_response
end
it 'returns 400 if wrong page parameter is passed' do
get base_url, { page: '%00' }, @headers
last_response.status.should == 400
end
it 'returns 400 if wrong per_page parameter is passed' do
get base_url, { per_page: '%00' }, @headers
last_response.status.should == 400
end
it 'returns valid information for a user with one table' do
table1 = create_random_table(@user_1)
table1_visualization = CartoDB::Visualization::Member.new(id: table1.table_visualization.id).fetch
table1_visualization_hash = table1_visualization.to_hash(
related: false,
table_data: true,
user: @user_1,
table: table1,
synchronization: nil)
table1_visualization_hash[:permission][:owner].delete(:groups)
table1_visualization_hash[:table][:permission][:owner].delete(:groups)
expected_visualization = JSON.parse(table1_visualization_hash.to_json)
expected_visualization = normalize_hash(expected_visualization)
# This is only in the Carto::Visualization presenter (not in old member presenter)
expected_visualization['uses_builder_features'] = false
expected_visualization['auth_tokens'] = nil # normalize_hash converts [] to nil
expected_visualization['version'] = 2
response = response_body(type: CartoDB::Visualization::Member::TYPE_CANONICAL)
# INFO: old API won't support server side generated urls for visualizations. See #5250 and #5279
response['visualizations'][0].delete('url')
response['visualizations'][0]['synchronization'] = {}
compare_with_dates(response['visualizations'][0], expected_visualization)
response['total_entries'].should eq 1
response['total_user_entries'].should eq 1
response['total_shared'].should eq 0
response['total_locked'].should eq 0
end
it 'returns locked count' do
FactoryGirl.build(:derived_visualization, user_id: @user_1.id, locked: false).store
FactoryGirl.build(:derived_visualization, user_id: @user_1.id, locked: true).store
user2 = FactoryGirl.create(:valid_user)
FactoryGirl.build(:derived_visualization, user_id: user2.id, locked: true).store
response_body(type: CartoDB::Visualization::Member::TYPE_DERIVED)['total_locked'].should == 1
end
it 'does a partial match search' do
FactoryGirl.build(:derived_visualization, user_id: @user_1.id, name: 'foo').store
FactoryGirl.build(:derived_visualization, user_id: @user_1.id, name: 'bar').store
FactoryGirl.build(:derived_visualization, user_id: @user_1.id, name: 'foo_patata_bar').store
FactoryGirl.build(:derived_visualization, user_id: @user_1.id, name: 'foo_patata_baz').store
body = response_body(q: 'patata', type: CartoDB::Visualization::Member::TYPE_DERIVED)
body['total_entries'].should == 2
body['total_user_entries'].should == 4
body = response_body(q: '_atata', type: CartoDB::Visualization::Member::TYPE_DERIVED)
body['total_entries'].should == 0
body['total_user_entries'].should == 4
end
it 'allows to search datasets including dependent visualizations' do
FactoryGirl.create(:table_visualization, user_id: @user_1.id, name: 'foo')
body = response_body(q: 'foo', type: CartoDB::Visualization::Member::TYPE_CANONICAL,
with_dependent_visualizations: 10)
body['total_entries'].should == 1
body['total_user_entries'].should == 1
body['visualizations'][0]['dependent_visualizations_count'].should be_zero
end
describe 'performance with many tables' do
# The bigger the number the better the improvement, but test gets too slow
VIZS_N = 20
before(:all) do
@visualizations = (1..VIZS_N).map { FactoryGirl.create(:carto_user_table_with_canonical, user_id: @user_1.id) }
end
LIST_NAMES_PARAMS = {
type: CartoDB::Visualization::Member::TYPE_CANONICAL,
order: :updated_at,
page: 1,
per_page: 3000,
exclude_shared: false,
exclude_raster: true
}.freeze
NO_FETCHING_PARAMS = {
show_liked: false,
show_stats: false,
show_table: false,
show_permission: false,
show_synchronization: false,
show_uses_builder_features: false,
show_auth_tokens: false,
load_totals: false
}.freeze
it 'should improve with reduced fetching (see #12058)' do
no_fetching_params = LIST_NAMES_PARAMS.merge(NO_FETCHING_PARAMS)
beginning = Time.now
get base_url, LIST_NAMES_PARAMS.dup, @headers
body1 = last_response.body
full_time = Time.now
get base_url, no_fetching_params.dup, @headers
body2 = last_response.body
no_fetch_time = Time.now
((full_time - beginning) / (no_fetch_time - full_time)).should be >= 3
body1 = JSON.parse(body1)
body1['visualizations'].count.should eq VIZS_N
body1['total_entries'].should eq VIZS_N
body1['total_shared'].should_not be_nil
body2 = JSON.parse(body2)
body2['visualizations'].count.should eq VIZS_N
body2['total_entries'].should eq VIZS_N
body2['total_shared'].should be_nil
end
end
end
describe 'main behaviour' do
before(:all) do
CartoDB::Varnish.any_instance.stubs(:send_command).returns(true)
Carto::NamedMaps::Api.any_instance.stubs(get: nil, create: true, update: true)
@user_1 = FactoryGirl.create(:valid_user)
@carto_user1 = Carto::User.find(@user_1.id)
@user_2 = FactoryGirl.create(:valid_user, private_maps_enabled: true)
@carto_user2 = Carto::User.find(@user_2.id)
@api_key = @user_1.api_key
end
before(:each) do
begin
delete_user_data @user_1
rescue => exception
# Silence named maps problems only here upon data cleaning, not in specs
raise unless exception.class.to_s == 'CartoDB::NamedMapsWrapper::HTTPResponseError'
end
@headers = {
'CONTENT_TYPE' => 'application/json'
}
host! "#{@user_1.username}.localhost.lan"
end
after(:all) do
@user_1.destroy
@user_2.destroy
end
it 'tests exclude_shared and only_shared filters' do
Carto::NamedMaps::Api.any_instance.stubs(get: nil, create: true, update: true)
user_1 = create_user(
username: unique_name('user'),
email: unique_email,
password: 'clientex',
private_tables_enabled: false
)
user_2 = create_user(
username: unique_name('user'),
email: unique_email,
password: 'clientex',
private_tables_enabled: false
)
organization = test_organization.save
user_org = CartoDB::UserOrganization.new(organization.id, user_1.id)
user_org.promote_user_to_admin
organization.reload
user_1.reload
user_2.organization_id = organization.id
user_2.save.reload
organization.reload
table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: unique_name('table'), user_id: user_1.id)
u1_t_1_id = table.table_visualization.id
u1_t_1_perm_id = table.table_visualization.permission.id
table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: unique_name('table'), user_id: user_2.id)
u2_t_1_id = table.table_visualization.id
post api_v1_visualizations_create_url(user_domain: user_1.username, api_key: user_1.api_key),
factory(user_1).to_json, @headers
last_response.status.should == 200
u1_vis_1_id = JSON.parse(last_response.body).fetch('id')
u1_vis_1_perm_id = JSON.parse(last_response.body).fetch('permission').fetch('id')
post api_v1_visualizations_create_url(user_domain: user_2.username, api_key: user_2.api_key),
factory(user_2).to_json, @headers
last_response.status.should == 200
u2_vis_1_id = JSON.parse(last_response.body).fetch('id')
get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
vis = body['visualizations'].first
vis['id'].should eq u1_t_1_id
get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
vis = body['visualizations'].first
vis['id'].should eq u1_vis_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
vis = body['visualizations'].first
vis['id'].should eq u2_t_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
vis = body['visualizations'].first
vis['id'].should eq u2_vis_1_id
# Share u1 vis with u2
put api_v1_permissions_update_url(user_domain:user_1.username, api_key: user_1.api_key, id: u1_vis_1_perm_id),
{acl: [{
type: CartoDB::Permission::TYPE_USER,
entity: {
id: user_2.id,
},
access: CartoDB::Permission::ACCESS_READONLY
}]}.to_json, @headers
last_response.status.should == 200
# Vis listing checks
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
# Permissions don't change updated_at
body['visualizations'][0]['id'].should eq u2_vis_1_id
body['visualizations'][1]['id'].should eq u1_vis_1_id
get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['visualizations'][0]['id'].should eq u1_vis_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at',
exclude_shared: false), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
body['visualizations'][0]['id'].should eq u2_vis_1_id
body['visualizations'][1]['id'].should eq u1_vis_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at',
only_shared: false), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
body['visualizations'][0]['id'].should eq u2_vis_1_id
body['visualizations'][1]['id'].should eq u1_vis_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at',
exclude_shared: true), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['visualizations'][0]['id'].should eq u2_vis_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at',
only_shared: true), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['visualizations'][0]['id'].should eq u1_vis_1_id
# Same with 'shared' filter (convenience alias for not handling both exclude_shared and only_shared)
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at',
shared: CartoDB::Visualization::Collection::FILTER_SHARED_YES), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
body['visualizations'][0]['id'].should eq u2_vis_1_id
body['visualizations'][1]['id'].should eq u1_vis_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at',
shared: CartoDB::Visualization::Collection::FILTER_SHARED_NO), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['visualizations'][0]['id'].should eq u2_vis_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at',
shared: CartoDB::Visualization::Collection::FILTER_SHARED_ONLY), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['visualizations'][0]['id'].should eq u1_vis_1_id
# Share u1 table with u2
put api_v1_permissions_update_url(user_domain:user_1.username, api_key: user_1.api_key, id: u1_t_1_perm_id),
{acl: [{
type: CartoDB::Permission::TYPE_USER,
entity: {
id: user_2.id,
},
access: CartoDB::Permission::ACCESS_READONLY
}]}.to_json, @headers
last_response.status.should == 200
# Dunno why (rack test error?) but this call seems to cache previous params, so just call it to "flush" them
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at',
shared: 'wadus',
exclude_shared: false,
only_shared: false),
@headers
# -------------
# Table listing checks
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
body['visualizations'][0]['id'].should eq u2_t_1_id
body['visualizations'][1]['id'].should eq u1_t_1_id
get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['visualizations'][0]['id'].should eq u1_t_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at',
exclude_shared: false), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
body['visualizations'][0]['id'].should eq u2_t_1_id
body['visualizations'][1]['id'].should eq u1_t_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at',
only_shared: false), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
body['visualizations'][0]['id'].should eq u2_t_1_id
body['visualizations'][1]['id'].should eq u1_t_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at',
exclude_shared: true), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['visualizations'][0]['id'].should eq u2_t_1_id
get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at',
only_shared: true), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['visualizations'][0]['id'].should eq u1_t_1_id
end
context 'visualization likes endpoints' do
before(:each) do
@map, @table, @table_visualization, @map_visualization = create_full_visualization(@carto_user1, visualization_attributes: { version: nil, privacy: Carto::Visualization::PRIVACY_PUBLIC })
@vis = FactoryGirl.create(:carto_visualization, user: @carto_user1)
@vis_user2 = FactoryGirl.create(:carto_visualization, user: @carto_user2)
@user_domain = @carto_user1.username
@user_domain2 = @carto_user2.username
end
after(:each) do
destroy_full_visualization(@map, @table, @table_visualization, @map_visualization)
end
describe 'POST add_like' do
it 'triggers error 403 if not authenticated' do
post api_v1_visualizations_add_like_url(user_domain: @user_domain, id: @vis.id, api_key: 'foo')
expect(last_response.status).to eq(403)
end
it 'triggers error 403 if dont have at least read permission' do
post api_v1_visualizations_add_like_url(user_domain: @user_domain, id: @vis_user2.id, api_key: 'foo')
expect(last_response.status).to eq(403)
end
it 'add likes to a given visualization' do
post api_v1_visualizations_add_like_url(user_domain: @user_domain, id: @vis.id, api_key: @carto_user1.api_key)
expect(last_response.status).to eq(200)
end
it 'returns an error if you try to like twice a visualization' do
post api_v1_visualizations_add_like_url(user_domain: @user_domain, id: @vis.id, api_key: @carto_user1.api_key)
expect(last_response.status).to eq(200)
post api_v1_visualizations_add_like_url(user_domain: @user_domain, id: @vis.id, api_key: @carto_user1.api_key)
expect(last_response.status).to eq(400)
expect(JSON.parse(last_response.body)['text']).to eq("You've already favorited this visualization")
end
end
describe 'POST remove_like' do
it 'triggers error 403 if not authenticated' do
delete api_v1_visualizations_remove_like_url(user_domain: @user_domain, id: @vis.id, api_key: 'foo')
expect(last_response.status).to eq(403)
end
it 'triggers error 403 if you dont have at least read permission' do
delete api_v1_visualizations_remove_like_url(user_domain: @user_domain, id: @vis_user2.id, api_key: 'foo')
expect(last_response.status).to eq(403)
end
it 'removes a like from a given visualization' do
@vis.add_like_from(@carto_user1)
delete api_v1_visualizations_remove_like_url(user_domain: @user_domain, id: @vis.id, api_key: @carto_user1.api_key)
expect(last_response.status).to eq(200)
get api_v1_visualizations_show_url(user_domain: @user_domain, id: @vis.id, api_key: @carto_user1.api_key, show_liked: true)
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body).fetch('liked')).to eq(false)
end
it 'does not returns error if you try to remove a non-existent like' do
delete api_v1_visualizations_remove_like_url(user_domain: @user_domain, id: @vis.id, api_key: @carto_user1.api_key, show_liked: true)
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body).fetch('liked')).to eq(false)
end
end
end
describe 'tests visualization likes endpoints in organizations' do
include_context 'organization with users helper'
describe 'PUT notify_watching' do
it 'adds the user to the watching list' do
vis = FactoryGirl.create(:carto_visualization, user: @carto_org_user_1)
user_domain = @carto_org_user_1.username
put api_v1_visualizations_notify_watching_url(user_domain: user_domain, id: vis.id, api_key: @carto_org_user_1.api_key)
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)).to eq([@carto_org_user_1.username])
end
it 'returns 403 if user does not have read permissions on the visualization' do
private_vis = FactoryGirl.create(:carto_visualization, user: @carto_org_user_1, privacy: Carto::Visualization::PRIVACY_PRIVATE)
put api_v1_visualizations_notify_watching_url(user_domain: @carto_org_user_2.username, id: private_vis.id, api_key: @carto_org_user_2.api_key)
expect(last_response.status).to eq(403)
end
end
describe 'GET list_watching' do
it 'returns the users currently on the watching list' do
vis = FactoryGirl.create(:carto_visualization, user: @carto_org_user_1)
user_domain = @carto_org_user_1.username
get api_v1_visualizations_notify_watching_url(user_domain: user_domain, id: vis.id, api_key: @carto_org_user_1.api_key)
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)).to eq([])
put api_v1_visualizations_notify_watching_url(user_domain: user_domain, id: vis.id, api_key: @carto_org_user_1.api_key)
expect(last_response.status).to eq(200)
get api_v1_visualizations_notify_watching_url(user_domain: user_domain, id: vis.id, api_key: @carto_org_user_1.api_key)
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)).to eq([@carto_org_user_1.username])
end
it 'returns 403 if user does not have read permissions on the visualization' do
private_vis = FactoryGirl.create(:carto_visualization, user: @carto_org_user_1, privacy: Carto::Visualization::PRIVACY_PRIVATE)
get api_v1_visualizations_notify_watching_url(user_domain: @carto_org_user_2.username, id: private_vis.id, api_key: @carto_org_user_2.api_key)
expect(last_response.status).to eq(403)
end
end
it 'tests totals calculations' do
bypass_named_maps
# user 1 will have 1 table and 1 vis
# user 2 will have 2 of each
# user 2 will share 1 table and 1 vis with the org
# user 2 will share the other table and other vis with user 1
table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: unique_name('table'), user_id: @org_user_1.id)
u1_t_1_id = table.table_visualization.id
table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: unique_name('table'), user_id: @org_user_2.id)
u2_t_1_id = table.table_visualization.id
u2_t_1_perm_id = table.table_visualization.permission.id
table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: unique_name('table'), user_id: @org_user_2.id)
u2_t_2 = table
u2_t_2_id = table.table_visualization.id
u2_t_2_perm_id = table.table_visualization.permission.id
post api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
factory(@org_user_1).to_json, @headers
last_response.status.should == 200
u1_vis_1_id = JSON.parse(last_response.body).fetch('id')
post api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key),
factory(@org_user_2).to_json, @headers
last_response.status.should == 200
u2_vis_1_id = JSON.parse(last_response.body).fetch('id')
u2_vis_1_perm_id = JSON.parse(last_response.body).fetch('permission').fetch('id')
post api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key),
factory(@org_user_2).to_json, @headers
last_response.status.should == 200
u2_vis_2_id = JSON.parse(last_response.body).fetch('id')
u2_vis_2_perm_id = JSON.parse(last_response.body).fetch('permission').fetch('id')
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['total_shared'].should eq 0
body['total_locked'].should eq 0
vis = body['visualizations'].first
vis['id'].should eq u1_t_1_id
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
body['total_shared'].should eq 0
body['total_locked'].should eq 0
vis = body['visualizations'].first
vis['id'].should eq u1_vis_1_id
# Share u2 vis1 with organization
put api_v1_permissions_update_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key, id: u2_vis_1_perm_id),
{acl: [{
type: CartoDB::Permission::TYPE_ORGANIZATION,
entity: {
id: @organization.id,
},
access: CartoDB::Permission::ACCESS_READONLY
}]}.to_json, @headers
last_response.status.should == 200
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
body['total_shared'].should eq 1
body['total_locked'].should eq 0
# Share u2 vis2 with u1
put api_v1_permissions_update_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key, id: u2_vis_2_perm_id),
{acl: [{
type: CartoDB::Permission::TYPE_USER,
entity: {
id: @org_user_1.id,
},
access: CartoDB::Permission::ACCESS_READONLY
}]}.to_json, @headers
last_response.status.should == 200
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 3
body['total_shared'].should eq 2
body['total_locked'].should eq 0
post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_vis_1_id, api_key: @org_user_1.api_key)
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 3
body['total_shared'].should eq 2
body['total_locked'].should eq 0
# Multiple likes to same vis shouldn't increment total as is per vis
post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_vis_1_id, api_key: @org_user_1.api_key)
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 3
body['total_shared'].should eq 2
body['total_locked'].should eq 0
# Share u2 table1 with org
put api_v1_permissions_update_url(user_domain:@org_user_2.username, api_key: @org_user_2.api_key, id: u2_t_1_perm_id),
{acl: [{
type: CartoDB::Permission::TYPE_ORGANIZATION,
entity: {
id: @organization.id,
},
access: CartoDB::Permission::ACCESS_READONLY
}]}.to_json, @headers
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
body['total_shared'].should eq 1
body['total_locked'].should eq 0
# Share u2 table2 with org
put api_v1_permissions_update_url(user_domain:@org_user_2.username, api_key: @org_user_2.api_key, id: u2_t_2_perm_id),
{acl: [{
type: CartoDB::Permission::TYPE_USER,
entity: {
id: @org_user_1.id,
},
access: CartoDB::Permission::ACCESS_READONLY
}]}.to_json, @headers
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 3
body['total_shared'].should eq 2
body['total_locked'].should eq 0
body['visualizations'][0]['table']['name'].should == "\"#{@org_user_2.database_schema}\".#{u2_t_2.name}"
post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_t_1_id, api_key: @org_user_1.api_key)
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 3
body['total_shared'].should eq 2
body['total_locked'].should eq 0
# Multiple likes to same table shouldn't increment total as is per vis
post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_t_1_id, api_key: @org_user_2.api_key)
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 3
body['total_shared'].should eq 2
body['total_locked'].should eq 0
end
end
describe 'index endpoint' do
it 'tests normal users authenticated and unauthenticated calls' do
bypass_named_maps
collection = CartoDB::Visualization::Collection.new.fetch(user_id: @user_2.id)
collection.map(&:delete)
post api_v1_visualizations_create_url(user_domain: @user_2.username, api_key: @user_2.api_key),
factory(@user_2).to_json, @headers
last_response.status.should == 200
pub_vis_id = JSON.parse(last_response.body).fetch('id')
put api_v1_visualizations_update_url(user_domain: @user_2.username, api_key: @user_2.api_key, id: pub_vis_id),
{ id: pub_vis_id, privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC }.to_json,
@headers
last_response.status.should == 200
post api_v1_visualizations_create_url(user_domain: @user_2.username, api_key: @user_2.api_key),
factory(@user_2).to_json, @headers
last_response.status.should == 200
priv_vis_id = JSON.parse(last_response.body).fetch('id')
put api_v1_visualizations_update_url(user_domain: @user_2.username, api_key: @user_2.api_key, id: priv_vis_id),
{ id: priv_vis_id, privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE }.to_json,
@headers
last_response.status.should == 200
get api_v1_visualizations_index_url(user_domain: @user_2.username, type: 'derived'), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
vis = body['visualizations'].first
vis['id'].should eq pub_vis_id
vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase
get api_v1_visualizations_index_url(user_domain: @user_2.username, type: 'derived', api_key: @user_2.api_key,
order: 'updated_at'),
{}, @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
vis = body['visualizations'][0]
vis['id'].should eq priv_vis_id
vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PRIVATE.upcase
vis = body['visualizations'][1]
vis['id'].should eq pub_vis_id
vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase
end
it 'tests organization users authenticated and unauthenticated calls' do
bypass_named_maps
organization = test_organization.save
user_2 = create_user(
username: unique_name('user'),
email: unique_email,
password: 'clientex'
)
user_org = CartoDB::UserOrganization.new(organization.id, user_2.id)
user_org.promote_user_to_admin
organization.reload
user_2.reload
post "http://#{organization.name}.cartodb.test#{api_v1_visualizations_create_path(user_domain: user_2.username,
api_key: user_2.api_key)}",
factory(user_2).to_json, @headers
last_response.status.should == 200
pub_vis_id = JSON.parse(last_response.body).fetch('id')
put "http://#{organization.name}.cartodb.test#{api_v1_visualizations_update_path(user_domain: user_2.username,
api_key: user_2.api_key,
id: pub_vis_id)}",
{
id: pub_vis_id,
privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC
}.to_json, @headers
last_response.status.should == 200
post "http://#{organization.name}.cartodb.test#{api_v1_visualizations_create_path(user_domain: user_2.username,
api_key: user_2.api_key)}",
factory(user_2).to_json, @headers
last_response.status.should == 200
priv_vis_id = JSON.parse(last_response.body).fetch('id')
put "http://#{organization.name}.cartodb.test#{api_v1_visualizations_update_path(user_domain: user_2.username,
api_key: user_2.api_key,
id: priv_vis_id)}",
{
id: priv_vis_id,
privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE
}.to_json, @headers
last_response.status.should == 200
get "http://#{organization.name}.cartodb.test#{api_v1_visualizations_index_path(user_domain: user_2.username,
type: 'derived')}", @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 1
vis = body['visualizations'].first
vis['id'].should eq pub_vis_id
vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase
get "http://#{organization.name}.cartodb.test#{api_v1_visualizations_index_path(user_domain: user_2.username,
api_key: user_2.api_key,
type: 'derived',
order: 'updated_at')}", {}, @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 2
vis = body['visualizations'][0]
vis['id'].should eq priv_vis_id
vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PRIVATE.upcase
vis = body['visualizations'][1]
vis['id'].should eq pub_vis_id
vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase
user_2.destroy
end
end
describe 'GET /api/v1/viz' do
before(:each) do
bypass_named_maps
delete_user_data(@user_1)
end
it 'retrieves a collection of visualizations' do
payload = factory(@user_1)
post api_v1_visualizations_create_url(api_key: @api_key), payload.to_json, @headers
id = JSON.parse(last_response.body).fetch('id')
get api_v1_visualizations_index_url(api_key: @api_key),
{}, @headers
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.first.fetch('id').should == id
end
it 'is updated after creating a visualization' do
payload = factory(@user_1)
post api_v1_visualizations_create_url(api_key: @api_key),
payload.to_json, @headers
get api_v1_visualizations_index_url(api_key: @api_key),
{}, @headers
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.size.should == 1
payload = factory(@user_1).merge('name' => 'another one')
post api_v1_visualizations_create_url(api_key: @api_key),
payload.to_json, @headers
get api_v1_visualizations_index_url(api_key: @api_key),
{}, @headers
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.size.should == 2
end
it 'is updated after deleting a visualization' do
payload = factory(@user_1)
post api_v1_visualizations_create_url(api_key: @api_key),
payload.to_json, @headers
id = JSON.parse(last_response.body).fetch('id')
get api_v1_visualizations_index_url(api_key: @api_key),
{}, @headers
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.should_not be_empty
delete api_v1_visualizations_destroy_url(id: id, api_key: @api_key),
{ id: id }.to_json,
@headers
get api_v1_visualizations_index_url(api_key: @api_key),
{}, @headers
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.should be_empty
end
it 'paginates results' do
per_page = 10
total_entries = 20
total_entries.times do
post api_v1_visualizations_index_url(api_key: @api_key),
factory(@user_1).to_json, @headers
end
get api_v1_visualizations_index_url(api_key: @api_key, page: 1, per_page: per_page), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should == per_page
response.fetch('total_entries').should == total_entries
end
it 'returns filtered results' do
post api_v1_visualizations_create_url(api_key: @api_key),
factory(@user_1).to_json, @headers
get api_v1_visualizations_index_url(api_key: @api_key, type: 'table'),
{}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.should be_empty
post api_v1_visualizations_create_url(api_key: @api_key),
factory(@user_1).to_json, @headers
post api_v1_visualizations_create_url(api_key: @api_key),
factory(@user_1).merge(type: 'table').to_json, @headers
get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived'),
{}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.size.should == 2
end
it 'creates a visualization from a list of tables' do
bypass_named_maps
table1 = table_factory
table2 = table_factory
table3 = table_factory
payload = {
name: 'new visualization',
tables: [
table1.fetch('name'),
table2.fetch('name'),
table3.fetch('name')
],
privacy: 'public'
}
post api_v1_visualizations_create_url(api_key: @api_key),
payload.to_json, @headers
last_response.status.should == 200
visualization = JSON.parse(last_response.body)
# TODO: this endpoint doesn't exist now. Current replacement?
#get "/api/v1/viz/#{visualization.fetch('id')}/viz?api_key=#{@api_key}",
# {}, @headers
#last_response.status.should == 403
get api_v2_visualizations_vizjson_url(id: visualization.fetch('id'), api_key: @api_key),
{}, @headers
last_response.status.should == 200
# include overlays
get overlays_url(visualization_id: visualization.fetch('id'), api_key: @api_key),
{}, @headers
last_response.status.should == 200
overlays = JSON.parse(last_response.body)
overlays.length.should == 5
end
end
describe 'GET /api/v1/viz/:id' do
before(:each) do
@carto_user1.private_maps_enabled = true
@carto_user1.save
bypass_named_maps
delete_user_data(@user_1)
@map, @table, @table_visualization, @visualization = create_full_builder_vis(@carto_user1,
visualization_attributes:
{
tags: ['foo'],
description: 'dull desc'
})
end
after(:each) do
destroy_full_visualization(@map, @table, @table_visualization, @visualization)
end
it 'returns a visualization' do
get api_v1_visualizations_show_url(id: @visualization.id, api_key: @api_key), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('id') .should_not be_nil
response.fetch('map_id') .should_not be_nil
response.fetch('tags') .should_not be_empty
response.fetch('description') .should_not be_nil
response.fetch('related_tables') .should_not be_nil
# Optional information
response['likes'].should eq nil
end
it 'returns a specific error for password-protected visualizations and required, public information' do
@visualization.privacy = Carto::Visualization::PRIVACY_PROTECTED
@visualization.password = 'wadus'
@visualization.save!
@visualization.user.update_attribute(:google_maps_key, 'waaaaadus')
expected_visualization_info = {
privacy: @visualization.privacy,
user: {
google_maps_query_string: @visualization.user.google_maps_query_string
}
}
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 403
response.body[:errors].should eq "Visualization not viewable"
response.body[:errors_cause].should eq "privacy_password"
response.body[:visualization].should eq expected_visualization_info
end
end
it 'doesn\'t return a specific error for private visualizations' do
@visualization.privacy = Carto::Visualization::PRIVACY_PRIVATE
@visualization.save!
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 403
response.body[:errors].should eq "Visualization not viewable"
response.body[:errors_cause].should be_nil
end
end
it 'returns a visualization with optional information if requested' do
url = api_v1_visualizations_show_url(
id: @visualization.id,
api_key: @api_key,
show_liked: true
)
get url, {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response['liked'].should eq false
end
it 'returns related_canonical_visualizations if requested' do
get_json api_v1_visualizations_show_url(id: @visualization.id, api_key: @carto_user1.api_key) do |response|
response.status.should eq 200
response.body[:related_canonical_visualizations].should be_nil
end
get_json api_v1_visualizations_show_url(id: @visualization.id,
api_key: @carto_user1.api_key,
fetch_related_canonical_visualizations: true) do |response|
response.status.should eq 200
related_canonical = response.body[:related_canonical_visualizations]
related_canonical.should_not be_nil
related_canonical.count.should eq 1
related_canonical[0][:id].should eq @table_visualization.id
end
end
it 'returns private related_canonical_visualizations for users that can see it' do
related_canonical_visualizations = @visualization.related_canonical_visualizations
related_canonical_visualizations.should_not be_empty
old_privacies = related_canonical_visualizations.map(&:privacy)
related_canonical_visualizations.each do |v|
v.update_attribute(:privacy, Carto::Visualization::PRIVACY_PRIVATE)
end
get_json api_v1_visualizations_show_url(id: @visualization.id,
api_key: @carto_user1.api_key,
fetch_related_canonical_visualizations: true) do |response|
response.status.should eq 200
related_canonical = response.body[:related_canonical_visualizations]
related_canonical.count.should eq related_canonical_visualizations.count
response.body[:related_canonical_visualizations_count].should eq related_canonical_visualizations.count
end
related_canonical_visualizations.zip(old_privacies).each do |v, p|
v.update_attribute(:privacy, p)
end
end
it 'returns private information about the user if requested' do
url = api_v1_visualizations_show_url(id: @visualization.id, api_key: @carto_user1.api_key)
get_json url do |response|
response.status.should eq 200
response.body[:user].should be_nil
end
get_json url, fetch_user: true do |response|
response.status.should eq 200
user = response.body[:user]
user.should_not be_nil
user[:avatar_url].should_not be_nil
user[:quota_in_bytes].should_not be_nil
end
end
# This is a contrast to the anonymous use case. A public endpoint shouldn't hit the user DB to avoid
# workers locks if user DB is under heavy load. Nevertheless, the private one can (and needs).
describe 'user db connectivity issues' do
before(:each) do
@actual_database_name = @visualization.user.database_name
@visualization.user.update_attribute(:database_name, 'wadus')
end
after(:each) do
@visualization.user.update_attribute(:database_name, @actual_database_name)
end
it 'needs connection to the user db if the viewer is the owner' do
CartoDB::Logger.expects(:error).once.with do |e|
e[:exception].message.should eq "FATAL: database \"#{@visualization.user.database_name}\" does not exist\n"
end
get_json api_v1_visualizations_show_url(id: @visualization.id),
api_key: @visualization.user.api_key,
fetch_related_canonical_visualizations: true,
fetch_user: true,
show_liked: true,
show_permission: true,
show_auth_tokens: true,
show_stats: true do |response|
# We currently log 404 on errors. Maybe something that we should change in the future...
response.status.should == 404
end
end
end
describe 'to anonymous users' do
it 'returns a 403 on private visualizations' do
@visualization.privacy = Carto::Visualization::PRIVACY_PRIVATE
@visualization.save!
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 403
expect(response.body[:errors]).to eq('Visualization not viewable')
end
end
describe 'publically accessible visualizations' do
before(:each) do
@visualization.privacy = Carto::Visualization::PRIVACY_LINK
@visualization.save!
end
it 'returns 403 for unpublished visualizations' do
@visualization.published?.should eq false
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 403
expect(response.body[:errors]).to eq('Visualization not viewable')
end
end
it 'returns 403 for unpublished and password protected visualizations' do
@visualization.published?.should eq false
@visualization.privacy = Carto::Visualization::PRIVACY_PROTECTED
@visualization.password = 'wadus'
@visualization.save!
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 403
expect(response.body[:errors]).to eq('Visualization not viewable')
end
end
describe 'published visualizations' do
before(:each) do
@visualization.create_mapcap!
@visualization.published?.should eq true
end
it 'only returns public information' do
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 200
response.body[:name].should_not be_nil
response.body[:updated_at].should_not be_nil
response.body[:tags].should_not be_nil
response.body[:title].should_not be_nil
response.body[:description].should_not be_nil
# Optional information requiring parameters
response.body[:user].should eq nil
response.body[:liked].should eq nil
response.body[:stats].should be_empty
response.body[:auth_tokens].should be_empty
response.body[:permission].should eq nil
end
end
it 'only returns public information, including optional if requested' do
url = api_v1_visualizations_show_url(
id: @visualization.id,
show_liked: true,
show_permission: true,
show_auth_tokens: true,
show_stats: true,
fetch_user: true,
show_user_basemaps: true
)
get_json url do |response|
response.status.should eq 200
response.body[:user].should_not be_nil
response.body[:liked].should eq false
response.body[:stats].should_not be_empty
response_user = response.body[:user]
response_user[:basemaps].should be_nil # Even if requested, because it's not public
permission = response.body[:permission]
permission.should_not eq nil
permission[:owner].should_not eq nil
owner = permission[:owner]
user = @visualization.permission.owner
owner[:name].should eq user.name
owner[:last_name].should eq user.last_name
owner[:username].should eq user.username
owner[:avatar_url].should eq user.avatar_url
owner[:base_url].should eq user.public_url
owner[:org_admin].should eq user.org_admin
owner[:org_user].should eq user.organization_id.present?
end
end
it 'not only returns public information for authenticated requests' do
url = api_v1_visualizations_show_url(
id: @visualization.id,
fetch_user: true,
show_user_basemaps: true,
api_key: @visualization.user.api_key
)
get_json url do |response|
response.status.should eq 200
response.body[:user].should_not be_nil
response_user = response.body[:user]
response_user[:basemaps].should_not be_empty
end
end
it 'returns auth_tokens for password protected visualizations if correct password is provided' do
password = 'wadus'
@visualization.privacy = Carto::Visualization::PRIVACY_PROTECTED
@visualization.password = password
@visualization.save!
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 403
expect(response.body[:errors]).to eq('Visualization not viewable')
end
get_json api_v1_visualizations_show_url(id: @visualization.id, password: password * 2) do |response|
response.status.should eq 403
expect(response.body[:errors]).to eq('Visualization not viewable')
end
get_json api_v1_visualizations_show_url(id: @visualization.id, password: password) do |response|
response.status.should eq 200
response.body[:auth_tokens].should_not be_nil
end
end
it 'returns auth_tokens for password protected visualizations if requested by the owner' do
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 200
response.body[:auth_tokens].should_not be_nil
end
end
it 'returns public information about the user if requested' do
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 200
response.body[:user].should be_nil
end
get_json api_v1_visualizations_show_url(id: @visualization.id), fetch_user: true do |response|
@visualization.user.update_attribute(:google_maps_key, 'waaaaadus')
response.status.should eq 200
user = response.body[:user]
user.should_not be_nil
user[:avatar_url].should_not be_nil
user[:quota_in_bytes].should be_nil
user[:google_maps_query_string].should_not be_nil
user[:google_maps_query_string].should eq @visualization.user.google_maps_query_string
end
end
it 'returns related_canonical_visualizations if requested' do
get_json api_v1_visualizations_show_url(id: @visualization.id) do |response|
response.status.should eq 200
response.body[:related_canonical_visualizations].should be_nil
end
get_json api_v1_visualizations_show_url(id: @visualization.id,
fetch_related_canonical_visualizations: true) do |response|
response.status.should eq 200
related_canonical = response.body[:related_canonical_visualizations]
related_canonical.should_not be_nil
related_canonical.count.should eq 1
related_canonical[0][:id].should eq @table_visualization.id
end
end
it 'doesn\'t return private related_canonical_visualizations' do
related_canonical_visualizations = @visualization.related_canonical_visualizations
related_canonical_visualizations.should_not be_empty
old_privacies = related_canonical_visualizations.map(&:privacy)
related_canonical_visualizations.each do |v|
v.update_attribute(:privacy, Carto::Visualization::PRIVACY_PRIVATE)
end
get_json api_v1_visualizations_show_url(id: @visualization.id,
fetch_related_canonical_visualizations: true) do |response|
response.status.should eq 200
related_canonical = response.body[:related_canonical_visualizations]
related_canonical.count.should eq 0
response.body[:related_canonical_visualizations_count].should eq related_canonical_visualizations.count
end
related_canonical_visualizations.zip(old_privacies).each do |v, p|
v.update_attribute(:privacy, p)
end
end
describe 'user db connectivity issues' do
before(:each) do
@actual_database_name = @visualization.user.database_name
@visualization.user.update_attribute(:database_name, 'wadus')
end
after(:each) do
@visualization.user.update_attribute(:database_name, @actual_database_name)
end
it 'does not need connection to the user db if viewer is anonymous' do
CartoDB::Logger.expects(:warning).never
CartoDB::Logger.expects(:error).never
get_json api_v1_visualizations_show_url(id: @visualization.id),
fetch_related_canonical_visualizations: true,
fetch_user: true,
show_liked: true,
show_permission: true,
show_stats: true do |response|
response.status.should == 200
end
end
end
end
end
end
end
# Specific tests for vizjson 3. Common are at `vizjson_shared_examples`
describe '#vizjson3' do
include Fixtures::Layers::Infowindows
include Fixtures::Layers::Tooltips
include Carto::Factories::Visualizations
include_context 'visualization creation helpers'
def get_vizjson3_url(user, visualization)
args = { user_domain: user.username, id: visualization.id, api_key: user.api_key }
api_v3_visualizations_vizjson_url(args)
end
def first_layer_definition_from_response(response)
index = response.body[:layers].index { |l| l[:options] && l[:options][:layer_definition] }
response.body[:layers][index][:options][:layer_definition]
end
def first_layer_named_map_from_response(response)
index = response.body[:layers].index { |l| l[:options] && l[:options][:named_map] }
response.body[:layers][index][:options][:named_map]
end
def first_data_layer_from_response(response)
index = response.body[:layers].index { |l| l[:type] == 'CartoDB' }
response.body[:layers][index]
end
let(:infowindow) do
FactoryGirl.build_stubbed(:carto_layer_with_infowindow).infowindow
end
let(:tooltip) do
FactoryGirl.build_stubbed(:carto_layer_with_tooltip).tooltip
end
before(:each) do
@map, @table, @table_visualization, @visualization = create_full_visualization(Carto::User.find(@user_1.id))
@table.privacy = UserTable::PRIVACY_PUBLIC
@table.save
layer = @visualization.data_layers.first
layer.infowindow = infowindow
layer.tooltip = tooltip
layer.options[:table_name] = @table.name
layer.options[:query] = "select * from #{@table.name}"
layer.save
end
after(:each) do
destroy_full_visualization(@map, @table, @table_visualization, @visualization)
end
describe 'layer templates' do
describe 'anonymous maps' do
before(:each) do
@table.privacy = UserTable::PRIVACY_PUBLIC
@table.save
end
it 'uses v3 infowindows and tooltips templates removing "table/views/" from template_name' do
# vizjson v2 doesn't change
get_json api_v2_visualizations_vizjson_url(user_domain: @user_1.username,
id: @visualization.id,
api_key: @user_1.api_key), @headers do |response|
response.status.should eq 200
layer_definition = first_layer_definition_from_response(response)
response_infowindow = layer_definition[:layers][0][:infowindow]
response_infowindow[:template_name].should eq infowindow[:template_name]
response_infowindow[:template].should include(v2_infowindow_light_template_fragment)
response_infowindow[:template].should_not include(v3_infowindow_light_template_fragment)
response_tooltip = layer_definition[:layers][0][:tooltip]
response_tooltip[:template_name].should eq tooltip[:template_name]
response_tooltip[:template].should include(v2_tooltip_light_template_fragment)
response_tooltip[:template].should_not include(v3_tooltip_light_template_fragment)
end
get_json get_vizjson3_url(@user_1, @visualization), @headers do |response|
response.status.should eq 200
layer = first_data_layer_from_response(response)
response_infowindow = layer[:infowindow]
infowindow[:template_name].should eq "table/views/infowindow_light"
response_infowindow[:template_name].should eq "infowindow_light"
response_infowindow[:template].should include(v3_infowindow_light_template_fragment)
response_infowindow[:template].should_not include(v2_infowindow_light_template_fragment)
response_tooltip = layer[:tooltip]
response_tooltip[:template_name].should eq tooltip[:template_name]
response_tooltip[:template].should include(v3_tooltip_light_template_fragment)
response_tooltip[:template].should_not include(v2_tooltip_light_template_fragment)
end
end
end
describe 'named maps' do
before(:each) do
@user_1.private_tables_enabled = true
@user_1.save
@table.user.reload
@table.privacy = UserTable::PRIVACY_PRIVATE
@table.save!
end
it 'uses v3 infowindows templates at named maps removing "table/views/" from template_name' do
# vizjson v2 doesn't change
get_json api_v2_visualizations_vizjson_url(user_domain: @user_1.username,
id: @visualization.id,
api_key: @user_1.api_key), @headers do |response|
response.status.should eq 200
layer_named_map = first_layer_named_map_from_response(response)
response_infowindow = layer_named_map[:layers][0][:infowindow]
response_infowindow[:template_name].should eq infowindow[:template_name]
response_infowindow[:template].should include(v2_infowindow_light_template_fragment)
response_infowindow[:template].should_not include(v3_infowindow_light_template_fragment)
response_tooltip = layer_named_map[:layers][0][:tooltip]
response_tooltip[:template_name].should eq tooltip[:template_name]
response_tooltip[:template].should include(v2_tooltip_light_template_fragment)
response_tooltip[:template].should_not include(v3_tooltip_light_template_fragment)
end
get_json get_vizjson3_url(@user_1, @visualization), @headers do |response|
response.status.should eq 200
layer = first_data_layer_from_response(response)
response_infowindow = layer[:infowindow]
infowindow[:template_name].should eq "table/views/infowindow_light"
response_infowindow[:template_name].should eq 'infowindow_light'
response_infowindow[:template].should include(v3_infowindow_light_template_fragment)
response_infowindow[:template].should_not include(v2_infowindow_light_template_fragment)
response_tooltip = layer[:tooltip]
response_tooltip[:template_name].should eq tooltip[:template_name]
response_tooltip[:template].should include(v3_tooltip_light_template_fragment)
response_tooltip[:template].should_not include(v2_tooltip_light_template_fragment)
end
end
end
end
describe 'layer custom infowindows and tooltips' do
before(:each) do
layer = @visualization.data_layers.first
layer.infowindow = custom_infowindow
layer.tooltip = custom_tooltip
layer.save
end
describe 'anonymous maps' do
before(:each) do
@table.privacy = UserTable::PRIVACY_PUBLIC
@table.save
end
it 'uses v3 infowindows and tooltips templates' do
# vizjson v2 doesn't change
get_json api_v2_visualizations_vizjson_url(user_domain: @user_1.username,
id: @visualization.id,
api_key: @user_1.api_key), @headers do |response|
response.status.should eq 200
layer_definition = first_layer_definition_from_response(response)
response_infowindow = layer_definition[:layers][0][:infowindow]
response_infowindow[:template_name].should eq ''
response_infowindow[:template].should eq custom_infowindow[:template]
response_tooltip = layer_definition[:layers][0][:tooltip]
response_tooltip[:template_name].should eq ''
response_tooltip[:template].should eq custom_tooltip[:template]
end
get_json get_vizjson3_url(@user_1, @visualization), @headers do |response|
response.status.should eq 200
layer = first_data_layer_from_response(response)
response_infowindow = layer[:infowindow]
response_infowindow[:template_name].should eq ''
response_infowindow[:template].should eq custom_infowindow[:template]
response_tooltip = layer[:tooltip]
response_tooltip[:template_name].should eq ''
response_tooltip[:template].should eq custom_tooltip[:template]
end
end
end
describe 'named maps' do
before(:each) do
@user_1.private_tables_enabled = true
@user_1.save
@table.user.reload
@table.privacy = UserTable::PRIVACY_PRIVATE
@table.save
end
it 'uses v3 infowindows templates at named maps' do
# vizjson v2 doesn't change
get_json api_v2_visualizations_vizjson_url(user_domain: @user_1.username,
id: @visualization.id,
api_key: @user_1.api_key), @headers do |response|
response.status.should eq 200
layer_named_map = first_layer_named_map_from_response(response)
response_infowindow = layer_named_map[:layers][0][:infowindow]
response_infowindow[:template_name].should eq ''
response_infowindow[:template].should eq custom_infowindow[:template]
response_tooltip = layer_named_map[:layers][0][:tooltip]
response_tooltip[:template_name].should eq ''
response_tooltip[:template].should eq custom_tooltip[:template]
end
get_json get_vizjson3_url(@user_1, @visualization), @headers do |response|
response.status.should eq 200
layer = first_data_layer_from_response(response)
response_infowindow = layer[:infowindow]
response_infowindow[:template_name].should eq ''
response_infowindow[:template].should eq custom_infowindow[:template]
response_tooltip = layer[:tooltip]
response_tooltip[:template_name].should eq ''
response_tooltip[:template].should eq custom_tooltip[:template]
end
end
end
end
it 'returns a vizjson with empty widgets array for visualizations without widgets' do
get_json get_vizjson3_url(@user_1, @visualization), @headers do |response|
response.status.should == 200
vizjson3 = response.body
vizjson3.keys.should include(:widgets)
vizjson3[:widgets].should == []
end
end
it 'returns visualization widgets' do
layer = @visualization.layers.first
widget = FactoryGirl.create(:widget, layer: layer)
widget2 = FactoryGirl.create(:widget_with_layer, type: 'fake')
get_json get_vizjson3_url(@user_1, @visualization), @headers do |response|
response.status.should == 200
vizjson3 = response.body
vizjson3[:widgets].length.should == 1
vizjson3[:widgets].map { |w| w[:type] }.should include(widget.type)
vizjson3[:widgets].map { |w| w[:layer_id] }.should include(layer.id)
widget2.destroy
widget.destroy
end
end
it 'returns datasource' do
get_json get_vizjson3_url(@user_1, @visualization), @headers do |response|
response.status.should == 200
vizjson3 = response.body
vizjson3[:datasource][:user_name].should == @user_1.username
vizjson3[:datasource][:maps_api_template].should_not be_nil
vizjson3[:datasource][:stat_tag].should_not be_nil
vizjson3[:user][:fullname].should == (@user_1.name.nil? ? @user_1.username : @user_1.name)
vizjson3[:user][:avatar_url].should_not be_nil
end
end
it 'returns datasource.template_name for visualizations with retrieve_named_map? true' do
Carto::Visualization.any_instance.stubs(:retrieve_named_map?).returns(true)
get_json get_vizjson3_url(@user_1, @visualization), @headers do |response|
response.status.should == 200
vizjson3 = response.body
vizjson3[:datasource][:template_name].should_not be_nil
end
end
it 'returns nil datasource.template_name for visualizations with retrieve_named_map? false' do
Carto::Visualization.any_instance.stubs(:retrieve_named_map?).returns(false)
get_json get_vizjson3_url(@user_1, @visualization), @headers do |response|
response.status.should == 200
vizjson3 = response.body
vizjson3[:datasource].key?(:template_name).should be_false
end
end
end
describe 'filters' do
before(:each) do
bypass_named_maps
delete_user_data(@user_1)
end
it 'uses locked filter' do
bypass_named_maps
post api_v1_visualizations_create_url(api_key: @api_key), factory(@user_1, locked: true).to_json, @headers
vis_1_id = JSON.parse(last_response.body).fetch('id')
post api_v1_visualizations_create_url(api_key: @api_key), factory(@user_1, locked: false).to_json, @headers
vis_2_id = JSON.parse(last_response.body).fetch('id')
get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived', locked: true), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 1
collection.first.fetch('id').should eq vis_1_id
get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived', locked: false), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 1
collection.first.fetch('id').should eq vis_2_id
end
it 'searches by tag' do
post api_v1_visualizations_create_url(api_key: @api_key), factory(@user_1, locked: true, tags: ['test1']).to_json, @headers
vis_1_id = JSON.parse(last_response.body).fetch('id')
post api_v1_visualizations_create_url(api_key: @api_key), factory(@user_1, locked: false, tags: ['test2']).to_json, @headers
get api_v1_visualizations_index_url(api_key: @api_key, tags: 'test1'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 1
collection.first['id'].should == vis_1_id
end
context 'only_published' do
before(:each) do
carto_user = Carto::User.find(@user_1.id)
unpublished_attrs = { privacy: Carto::Visualization::PRIVACY_PUBLIC, version: 3 }
_, _, _, @unpublished_viz = create_full_visualization(carto_user, visualization_attributes: unpublished_attrs)
published_attrs = { privacy: Carto::Visualization::PRIVACY_PUBLIC }
_, _, _, @published_viz = create_full_visualization(carto_user, visualization_attributes: published_attrs)
end
after(:each) do
@unpublished_viz.destroy
@published_viz.destroy
end
it 'filters with only_published = true' do
get api_v1_visualizations_index_url(api_key: @api_key, only_published: true), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 1
collection.first['id'].should == @published_viz.id
end
it 'does not filter without it' do
get api_v1_visualizations_index_url(api_key: @api_key), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
end
end
end
describe 'non existent visualization' do
it 'returns 404' do
get api_v1_visualizations_show_url(id: TEST_UUID, api_key: @api_key), {}, @headers
last_response.status.should == 404
put api_v1_visualizations_update_url(id: TEST_UUID, api_key: @api_key), { id: TEST_UUID }.to_json, @headers
last_response.status.should == 404
delete api_v1_visualizations_destroy_url(id: TEST_UUID, api_key: @api_key), { id: TEST_UUID }.to_json, @headers
last_response.status.should == 404
end
end
describe '/api/v1/viz/:id/watching' do
before(:all) do
@user_1_1 = create_test_user
@user_1_2 = create_test_user
organization = test_organization.save
user_org = CartoDB::UserOrganization.new(organization.id, @user_1_1.id)
user_org.promote_user_to_admin
@user_1_1.reload
@user_1_2.organization_id = organization.id
@user_1_2.save.reload
end
it 'returns an empty array if no other user is watching' do
Carto::Visualization::Watcher.any_instance.stubs(:list).returns([])
bypass_named_maps
login(@user_1_1)
post api_v1_visualizations_create_url(api_key: @user_1_1.api_key), factory(@user_1_1, locked: true).to_json, @headers
id = JSON.parse(last_response.body).fetch('id')
login(@user_1_1)
get api_v1_visualizations_notify_watching_url(id: id, api_key: @user_1_1.api_key)
body = JSON.parse(last_response.body)
expect(body).to eq([])
end
end
describe 'legacy controller migration' do
before(:all) do
@user = create_user
end
after(:all) do
bypass_named_maps
@user.destroy
end
before(:each) do
bypass_named_maps
# bypass_metrics
host! "#{@user.username}.localhost.lan"
end
after(:each) do
bypass_named_maps
delete_user_data @user
end
describe '#create' do
describe '#duplicate map' do
before(:all) do
@other_user = create_user
end
before(:each) do
bypass_named_maps
@map = Map.create(user_id: @user.id)
@visualization = FactoryGirl.create(:derived_visualization,
map_id: @map.id,
user_id: @user.id,
privacy: Visualization::Member::PRIVACY_PRIVATE)
end
after(:each) do
@map.destroy
end
after(:all) do
@other_user.destroy
end
it 'duplicates a map' do
new_name = @visualization.name + ' patatas'
post_json api_v1_visualizations_create_url(api_key: @user.api_key),
source_visualization_id: @visualization.id,
name: new_name
last_response.status.should be_success
Carto::Visualization.exists?(user_id: @user.id, type: 'derived', name: new_name).should be_true
end
it 'registers table dependencies for duplicated maps' do
map, table, table_visualization, visualization = create_full_visualization(Carto::User.find(@user.id))
new_name = visualization.name + ' registered'
post_json api_v1_visualizations_create_url(api_key: @user.api_key),
source_visualization_id: visualization.id,
name: new_name
last_response.status.should be_success
visualization = Carto::Visualization.where(user_id: @user.id, type: 'derived', name: new_name).first
visualization.should be
visualization.data_layers.first.user_tables.count.should eq 1
destroy_full_visualization(map, table, table_visualization, visualization)
end
it "duplicates someone else's map if has at least read permission to it" do
new_name = @visualization.name + ' patatas'
Carto::Visualization.any_instance.stubs(:is_viewable_by_user?).returns(true)
post_json api_v1_visualizations_create_url(user_domain: @other_user.username, api_key: @other_user.api_key),
source_visualization_id: @visualization.id,
name: new_name
last_response.status.should be_success
Carto::Visualization.exists?(user_id: @other_user.id, type: 'derived', name: new_name).should be_true
end
it "doesn't duplicate someone else's map without permission" do
new_name = @visualization.name + ' patatatosky'
post_json api_v1_visualizations_create_url(user_domain: @other_user.username, api_key: @other_user.api_key),
source_visualization_id: @visualization.id,
name: new_name
last_response.status.should == 403
Carto::Visualization.exists?(user_id: @other_user.id, type: 'derived', name: new_name).should be_false
end
end
describe 'map creation from datasets' do
include_context 'organization with users helper'
include TableSharing
it 'creates a visualization from a dataset given the viz id' do
table1 = create_table(user_id: @org_user_1.id)
payload = {
source_visualization_id: table1.table_visualization.id,
visChanges: 0,
name: "untitled_table_XXX_map"
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
v.user_id.should eq @org_user_1.id
v.map.user_id.should eq @org_user_1.id
end
end
it 'does not create visualizations if user is viewer' do
table1 = create_table(user_id: @org_user_1.id)
payload = {
source_visualization_id: table1.table_visualization.id,
visChanges: 0,
name: "untitled_table_XXX_map"
}
@org_user_1.viewer = true
@org_user_1.save
post_json(api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
payload) do |response|
response.status.should eq 403
end
@org_user_1.viewer = false
@org_user_1.save
end
it 'creates a visualization from a dataset given the table id' do
table1 = create_table(user_id: @org_user_1.id)
payload = {
tables: [table1.name]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
v.user_id.should eq @org_user_1.id
v.map.user_id.should eq @org_user_1.id
end
end
it 'correctly creates a visualization from two dataset of different users' do
table1 = create_table(user_id: @org_user_1.id)
table2 = create_table(user_id: @org_user_2.id)
share_table_with_user(table1, @org_user_2)
payload = {
type: 'derived',
tables: ["#{@org_user_1.username}.#{table1.name}", table2.name]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
v.user_id.should eq @org_user_2.id
v.map.user_id.should eq @org_user_2.id
end
end
describe 'builder and editor behaviour' do
before(:all) do
@old_builder_enabled = @org_user_1.builder_enabled
end
after(:all) do
@org_user_1.builder_enabled = @old_builder_enabled
@org_user_1.save
end
describe 'for editor users' do
before(:all) do
@org_user_1.builder_enabled = false
@org_user_1.save
end
it 'copies the styles' do
table1 = create_table(user_id: @org_user_1.id)
payload = {
tables: [table1.name]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
original_layer = table1.map.data_layers.first
layer = v.map.data_layers.first
layer.options['tile_style'].should eq original_layer.options['tile_style']
end
end
it 'doesn\'t add style properties' do
table1 = create_table(user_id: @org_user_1.id)
payload = {
tables: [table1.name]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
layer = v.map.data_layers.first
layer.options['style_properties'].should be_nil
end
end
end
describe 'for builder users' do
before(:all) do
@org_user_1.builder_enabled = true
@org_user_1.save
end
it 'resets the styles' do
table1 = create_table(user_id: @org_user_1.id)
Table.any_instance.stubs(:geometry_types).returns(['ST_Point'])
payload = {
tables: [table1.name]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
original_layer = table1.map.data_layers.first
layer = v.map.data_layers.first
layer.options['tile_style'].should_not eq original_layer.options['tile_style']
end
end
it 'adds style properties' do
table1 = create_table(user_id: @org_user_1.id)
Table.any_instance.stubs(:geometry_types).returns(['ST_Point'])
payload = {
tables: [table1.name]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
layer = v.map.data_layers.first
layer.options['style_properties'].should_not be_nil
end
end
end
end
it 'rewrites queries for other user datasets' do
table1 = create_table(user_id: @org_user_1.id)
layer = table1.map.data_layers.first
layer.options['query'] = "SELECT * FROM #{table1.name} LIMIT 1"
layer.save
share_table_with_user(table1, @org_user_2)
payload = {
type: 'derived',
tables: ["#{@org_user_1.username}.#{table1.name}"]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
layer = v.map.data_layers.first
layer.options['query'].should eq "SELECT * FROM #{@org_user_1.username}.#{table1.name} LIMIT 1"
end
end
it 'does not rewrite queries for same user datasets' do
table1 = create_table(user_id: @org_user_1.id)
layer = table1.map.data_layers.first
layer.options['query'] = "SELECT * FROM #{table1.name} LIMIT 1"
layer.save
share_table_with_user(table1, @org_user_1)
payload = {
type: 'derived',
tables: ["#{@org_user_1.username}.#{table1.name}"]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
new_layer = v.map.data_layers.first
new_layer.options['query'].should eq layer.options['query']
end
end
it 'sets table privacy if the user has private_maps' do
table1 = create_table(user_id: @org_user_1.id)
payload = {
tables: [table1.name]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
v.privacy.should eq CartoDB::Visualization::Member::PRIVACY_PRIVATE
end
end
it 'sets PUBLIC privacy if the user doesn\'t have private_maps' do
@carto_org_user_2.update_column(:private_maps_enabled, false) # Direct to DB to skip validations
table1 = create_table(user_id: @org_user_2.id)
payload = {
tables: [table1.name]
}
post_json(api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key),
payload) do |response|
response.status.should eq 200
vid = response.body[:id]
v = CartoDB::Visualization::Member.new(id: vid).fetch
v.privacy.should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC
end
end
it 'enables scrollwheel zoom by default' do
table1 = create_table(user_id: @org_user_2.id)
table1.map.scrollwheel = false
table1.map.options[:scrollwheel] = false
table1.map.save
post_json(api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key),
tables: [table1.name]) do |response|
response.status.should eq 200
vid = response.body[:id]
v = Carto::Visualization.find(vid)
v.map.scrollwheel.should eq true
v.map.options[:scrollwheel].should eq true
end
end
end
end
describe "#update" do
before(:each) do
login(@user)
end
it "Does not update visualizations if user is viewer" do
table = new_table(user_id: @user.id, privacy: ::UserTable::PRIVACY_PUBLIC).save.reload
@user.viewer = true
@user.save
payload = { id: table.table_visualization.id, privacy: Carto::Visualization::PRIVACY_PRIVATE }
put_json api_v1_visualizations_update_url(id: table.table_visualization.id), payload do |response|
response.status.should eq 403
end
@user.viewer = false
@user.save
table.destroy
end
it "Updates changes even if named maps communication fails" do
@user.private_tables_enabled = true
@user.save
table = new_table(user_id: @user.id, privacy: ::UserTable::PRIVACY_PUBLIC).save.reload
Carto::NamedMaps::Api.any_instance.stubs(:create).raises('fake named maps failure')
payload = { id: table.table_visualization.id, privacy: Carto::Visualization::PRIVACY_PRIVATE }
put_json api_v1_visualizations_update_url(id: table.table_visualization.id), payload do |response|
response.status.should be_success
end
table.reload
table.privacy.should eq ::UserTable::PRIVACY_PRIVATE
table.destroy
@user.private_tables_enabled = false
@user.save
end
it 'filters attributes' do
table = new_table(user_id: @user.id, privacy: ::UserTable::PRIVACY_PUBLIC).save.reload
table.table_visualization.description.should_not eq "something"
payload = { id: table.table_visualization.id, description: "something", fake: "NO!" }
put_json api_v1_visualizations_update_url(id: table.table_visualization.id), payload do |response|
response.status.should be_success
end
table.reload
table.table_visualization.description.should eq "something"
table.destroy
end
it "renames datasets" do
table = new_table(user_id: @user.id).save.reload
payload = { id: table.table_visualization.id, name: 'vis_rename_test1' }
put_json api_v1_visualizations_update_url(id: table.table_visualization.id), payload do |response|
response.status.should be_success
end
table.reload
table.name.should eq 'vis_rename_test1'
table.destroy
end
it 'sets password protection' do
visualization = FactoryGirl.create(:carto_visualization, user_id: @user.id)
visualization.password_protected?.should be_false
payload = {
id: visualization.id,
password: 'the_pass',
privacy: Carto::Visualization::PRIVACY_PROTECTED
}
put_json api_v1_visualizations_update_url(id: visualization.id), payload do |response|
response.status.should be_success
end
visualization.reload
visualization.password_protected?.should be_true
visualization.password_valid?('the_pass').should be_true
visualization.destroy
end
it 'migrates visualizations to v3' do
_, _, _, visualization = create_full_visualization(@user)
visualization.update_attributes!(version: 2)
visualization.analyses.each(&:destroy)
payload = {
id: visualization.id,
version: 3
}
put_json api_v1_visualizations_update_url(id: visualization.id), payload do |response|
response.status.should be_success
expect(response.body[:version]).to eq 3
end
visualization.reload
expect(visualization.analyses.any?).to be_true
visualization.destroy
end
end
end
describe '#destroy' do
include_context 'organization with users helper'
include TableSharing
def destroy_url(user, vis_id)
api_v1_visualizations_destroy_url(id: vis_id, user_domain: user.username, api_key: user.api_key)
end
it 'returns 404 for nonexisting visualizations' do
delete_json(destroy_url(@carto_org_user_1, random_uuid)) do |response|
expect(response.status).to eq 404
end
end
it 'returns 404 for not-accesible visualizations' do
other_visualization = FactoryGirl.create(:carto_visualization, user: @carto_org_user_2)
delete_json(destroy_url(@carto_org_user_1, other_visualization.id)) do |response|
expect(response.status).to eq 404
end
end
it 'returns 403 for not-owned visualizations' do
other_visualization = FactoryGirl.create(:carto_visualization, user: @carto_org_user_2)
share_visualization_with_user(other_visualization, @carto_org_user_1)
delete_json(destroy_url(@carto_org_user_1, other_visualization.id)) do |response|
expect(response.status).to eq 403
end
end
it 'returns 403 for viewer users' do
visualization = FactoryGirl.create(:carto_visualization, user: @carto_org_user_1)
@carto_org_user_1.update_attribute(:viewer, true)
delete_json(destroy_url(@carto_org_user_1, visualization.id)) do |response|
expect(response.status).to eq 403
end
@carto_org_user_1.update_attribute(:viewer, false)
end
it 'destroys a visualization by id' do
visualization = FactoryGirl.create(:carto_visualization, user: @carto_org_user_1)
delete_json(destroy_url(@carto_org_user_1, visualization.id)) do |response|
expect(response.status).to eq 204
end
end
it 'destroys a visualization by name' do
visualization = FactoryGirl.create(:carto_visualization, user: @carto_org_user_1)
delete_json(destroy_url(@carto_org_user_1, visualization.name)) do |response|
expect(response.status).to eq 204
end
end
it 'destroys a visualization and all of its dependencies (fully dependent)' do
_, _, table_visualization, visualization = create_full_visualization(@carto_org_user_1)
expect_visualization_to_be_destroyed(visualization) do
expect_visualization_to_be_destroyed(table_visualization) do
delete_json(destroy_url(@carto_org_user_1, table_visualization.id)) do |response|
expect(response.status).to eq 204
end
end
end
end
it 'destroys a visualization and affected layers (partially dependent)' do
_, _, table_visualization, visualization = create_full_visualization(@carto_org_user_1)
visualization.layers << FactoryGirl.create(:carto_layer)
visualization.data_layers.count.should eq 2
expect_visualization_to_be_destroyed(table_visualization) do
delete_json(destroy_url(@carto_org_user_1, table_visualization.id)) do |response|
expect(response.status).to eq 204
end
end
expect(Carto::Visualization.exists?(visualization.id)).to be_true
visualization.reload
visualization.data_layers.count.should eq 1
end
end
describe '#update' do
after(:all) do
@carto_user1.private_maps_enabled = false
@carto_user1.public_map_quota = nil
@carto_user1.save
end
it 'returns a 200 response when making a map public with enough quota' do
@carto_user1.private_maps_enabled = true
@carto_user1.public_map_quota = nil
@carto_user1.save
visualization = FactoryGirl.create(:carto_visualization, user: @carto_user1,
privacy: Carto::Visualization::PRIVACY_PRIVATE)
request_params = { user_domain: @carto_user1.username, api_key: @carto_user1.api_key, id: visualization.id }
put api_v1_visualizations_update_url(request_params),
{ id: visualization.id, privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC }.to_json,
@headers
last_response.status.should == 200
visualization.destroy!
end
it 'returns a 403 response when making a map public without enough quota' do
@carto_user1.private_maps_enabled = true
@carto_user1.public_map_quota = 0
@carto_user1.save
visualization = FactoryGirl.create(:carto_visualization, user: @carto_user1,
privacy: Carto::Visualization::PRIVACY_PRIVATE)
request_params = { user_domain: @carto_user1.username, api_key: @carto_user1.api_key, id: visualization.id }
put api_v1_visualizations_update_url(request_params),
{ id: visualization.id, privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC }.to_json,
@headers
last_response.status.should == 403
last_response.body.should =~ /public map quota/
end
it 'returns a 200 response when making a table public without enough map quota' do
@carto_user1.private_maps_enabled = true
@carto_user1.public_map_quota = 0
@carto_user1.save
user_table = FactoryGirl.create(:carto_user_table, :with_db_table, user_id: @carto_user1.id)
map = FactoryGirl.create(:carto_map)
user_table.map = map
user_table.save!
visualization = FactoryGirl.create(:carto_visualization,
type: Carto::Visualization::TYPE_CANONICAL,
map: map,
user: @carto_user1,
privacy: Carto::Visualization::PRIVACY_PRIVATE)
request_params = { user_domain: @carto_user1.username, api_key: @carto_user1.api_key, id: visualization.id }
put api_v1_visualizations_update_url(request_params),
{ id: visualization.id, privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC }.to_json,
@headers
last_response.status.should == 200
visualization.destroy!
end
end
end
describe 'index' do
include_context 'visualization creation helpers'
before(:all) do
@headers = {'CONTENT_TYPE' => 'application/json'}
end
before(:each) do
@user = FactoryGirl.create(:valid_user)
login(@user)
end
after(:each) do
@user.destroy
end
it 'mixed types search should filter only remote without display name' do
post api_v1_visualizations_create_url(api_key: @user.api_key),
factory(@user, locked: true, type: 'table').to_json, @headers
vis_1_id = JSON.parse(last_response.body).fetch('id')
post api_v1_visualizations_create_url(api_key: @user.api_key),
factory(@user, locked: true, type: 'remote', name: 'visu2', display_name: 'visu2').to_json, @headers
vis_2_id = JSON.parse(last_response.body).fetch('id')
Carto::ExternalSource.new(
visualization_id: vis_2_id,
import_url: 'http://www.fake.com',
rows_counted: 1,
size: 200).save
post api_v1_visualizations_create_url(api_key: @user.api_key),
factory(@user, locked: true, type: 'remote', name: 'visu3').to_json, @headers
vis_3_id = JSON.parse(last_response.body).fetch('id')
Carto::ExternalSource.new(
visualization_id: vis_3_id,
import_url: 'http://www.fake.com',
rows_counted: 1,
size: 200).save
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'remote,table'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
[vis_1_id, vis_2_id].include?(collection[0]['id']).should eq true
[vis_1_id, vis_2_id].include?(collection[1]['id']).should eq true
end
context 'with_dependent_visualizations' do
before(:each) do
table_a = create_random_table(@user)
@visualization_a = table_a.table_visualization
Delorean.time_travel_to "2018/01/01 00:00:00" do
@dependencies = Array.new(3) do
Delorean.jump(1.day)
FactoryGirl.create(:carto_visualization, user_id: @user.id)
end
end
Carto::Visualization.any_instance.stubs(:dependent_visualizations).returns(@dependencies)
end
it 'does not return the dependent visualizations by default' do
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'table',
order: 'dependent_visualizations'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 1
collection[0]['dependent_visualizations_count'].should be_nil
collection[0]['dependent_visualizations'].should be_nil
end
it 'does not return the dependent visualizations if with_dependent_visualizations = 0' do
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'table',
with_dependent_visualizations: 0), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 1
collection[0]['dependent_visualizations_count'].should be_nil
collection[0]['dependent_visualizations'].should be_nil
end
it 'returns the 2 most recent dependent visualizations when with_dependent_visualizations = 2' do
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'table',
with_dependent_visualizations: 2), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 1
collection[0]['dependent_visualizations_count'].should eql 3
collection[0]['dependent_visualizations'].length.should eq 2
collection[0]['dependent_visualizations'].first['id'].should eql @dependencies[2].id
collection[0]['dependent_visualizations'].first['name'].should_not be_nil
collection[0]['dependent_visualizations'].second['id'].should eql @dependencies[1].id
collection[0]['dependent_visualizations'].second['name'].should_not be_nil
end
end
context 'ordering' do
it 'returns the expected status' do
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'derived', order: ''), {}, @headers
last_response.status.should == 200
get api_v1_visualizations_index_url(
api_key: @user.api_key,
types: 'derived',
order: '',
page: '',
per_page: ''
), {}, @headers
last_response.status.should == 200
['derived', 'slide'].each do |type|
get api_v1_visualizations_index_url(api_key: @user.api_key, types: type, order: :mapviews), {}, @headers
last_response.status.should == 200
end
['remote', 'table'].each do |type|
get api_v1_visualizations_index_url(api_key: @user.api_key, types: type, order: :size), {}, @headers
last_response.status.should == 200
end
['derived', 'remote', 'slide', 'table'].each do |type|
get api_v1_visualizations_index_url(api_key: @user.api_key, types: type, order: :whatever), {}, @headers
last_response.status.should == 400
JSON.parse(last_response.body).fetch('error').should_not be_nil
end
end
it 'orders descending by default' do
visualization_a = FactoryGirl.create(:carto_visualization, name: 'Visualization A', user_id: @user.id).store
visualization_b = FactoryGirl.create(:carto_visualization, name: 'Visualization B', user_id: @user.id).store
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'derived', order: 'name'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
collection[0]['id'].should eq visualization_b.id
collection[1]['id'].should eq visualization_a.id
end
it 'orders by name' do
visualization_a = FactoryGirl.create(:carto_visualization, name: 'Visualization A', user_id: @user.id).store
visualization_b = FactoryGirl.create(:carto_visualization, name: 'Visualization B', user_id: @user.id).store
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'derived', order: 'name',
order_direction: 'asc'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
collection[0]['id'].should eq visualization_a.id
collection[1]['id'].should eq visualization_b.id
end
it 'orders by favorited' do
visualization_a = FactoryGirl.create(:carto_visualization, user_id: @user.id).store
visualization_b = FactoryGirl.create(:carto_visualization, user_id: @user.id).store
visualization_a.add_like_from(@user)
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'derived', with_dependent_visualizations: 10,
order: 'favorited', order_direction: 'desc'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
collection[0]['id'].should eq visualization_a.id
collection[1]['id'].should eq visualization_b.id
end
it 'orders by size' do
vis1 = factory(@user, locked: true, type: 'remote', display_name: 'visu1')
post api_v1_visualizations_create_url(api_key: @user.api_key), vis1.to_json, @headers
vis1_id = JSON.parse(last_response.body).fetch('id')
Carto::ExternalSource.new(
visualization_id: vis1_id,
import_url: 'http://www.fake.com',
rows_counted: 1,
size: 100
).save
vis2 = factory(@user, locked: true, type: 'remote', display_name: 'visu2')
post api_v1_visualizations_create_url(api_key: @user.api_key), vis2.to_json, @headers
vis2_id = JSON.parse(last_response.body).fetch('id')
Carto::ExternalSource.new(
visualization_id: vis2_id,
import_url: 'http://www.fake.com',
rows_counted: 1,
size: 200
).save
vis3 = factory(@user, locked: true, type: 'remote', display_name: 'visu3')
post api_v1_visualizations_create_url(api_key: @user.api_key), vis3.to_json, @headers
vis3_id = JSON.parse(last_response.body).fetch('id')
Carto::ExternalSource.new(
visualization_id: vis3_id,
import_url: 'http://www.fake.com',
rows_counted: 1, size: 10
).save
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'remote', order: 'size'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 3
collection[0]['id'].should == vis2_id
collection[1]['id'].should == vis1_id
collection[2]['id'].should == vis3_id
end
xit 'orders by estimated row count' do
visualization_a = FactoryGirl.create(:carto_visualization, user_id: @user.id)
table = FactoryGirl.create(:table, user_id: @user.id)
table.insert_row!(name: 'name1')
table.update_table_pg_stats
visualization_b = FactoryGirl.create(:carto_visualization, user_id: @user.id, map_id: table.map_id)
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'derived', order: 'estimated_row_count',
order_direction: 'desc'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
collection[0]['id'].should eq visualization_b.id
collection[1]['id'].should eq visualization_a.id
end
it 'orders by privacy' do
link_privacy = Carto::Visualization::PRIVACY_LINK
public_privacy = Carto::Visualization::PRIVACY_PUBLIC
visualization_a = FactoryGirl.create(:carto_visualization, user_id: @user.id, privacy: link_privacy).store
visualization_b = FactoryGirl.create(:carto_visualization, user_id: @user.id, privacy: public_privacy).store
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'derived', order: 'privacy',
order_direction: 'desc'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
collection[0]['id'].should eq visualization_b.id
collection[1]['id'].should eq visualization_a.id
end
it 'orders by dependent visualizations' do
table_a = create_random_table(@user)
visualization_a = table_a.table_visualization
dependent_visualization = FactoryGirl.create(:carto_visualization, user_id: @user.id)
dependent_visualization.map = FactoryGirl.create(:carto_map, user_id: @user.id)
dependent_visualization.save!
layer = FactoryGirl.build(:carto_layer)
layer.options[:table_name] = table_a.name
layer.save!
dependent_visualization.layers << layer
table_b = create_random_table(@user)
visualization_b = table_b.table_visualization
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'table', order: 'dependent_visualizations',
with_dependent_visualizations: 10,
order_direction: 'desc'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
collection[0]['id'].should eq visualization_a.id
collection[1]['id'].should eq visualization_b.id
end
context 'by search rank' do
before(:each) do
@visualization_a = FactoryGirl.create(:carto_visualization, name: 'Best rank', user_id: @user.id).store
@visualization_b = FactoryGirl.create(:carto_visualization, name: 'Another rank, but not the best',
user_id: @user.id).store
end
it 'orders results by search rank when searching' do
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'derived',
q: 'Best rank'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
collection[0]['id'].should eq @visualization_a.id
collection[1]['id'].should eq @visualization_b.id
end
it 'ignores other ordering parameters' do
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'derived', q: 'Best rank',
order: 'name', order_direction: 'asc'), {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
collection = response.fetch('visualizations')
collection.length.should eq 2
collection[0]['id'].should eq @visualization_a.id
collection[1]['id'].should eq @visualization_b.id
end
end
context 'error handling' do
before(:each) do
@valid_order = 'updated_at'
@invalid_order = 'invalid_order'
@valid_order_direction = 'asc'
@invalid_order_direction = 'invalid_order_direction'
@valid_order_combination = 'name,updated_at'
@invalid_order_combination = 'size,updated_at'
@valid_direction_combination = 'asc,desc'
@invalid_direction_combination = 'asc,invalid'
end
it 'returns an error if an invalid :order is given' do
get api_v1_visualizations_index_url(order: @valid_order, api_key: @user.api_key,
types: 'derived'), {}, @headers
last_response.status.should == 200
get api_v1_visualizations_index_url(order: @invalid_order, api_key: @user.api_key,
types: 'derived'), {}, @headers
last_response.status.should == 400
last_response.body.should include "Wrong 'order' parameter value"
end
it 'returns an error if an invalid :order_direction is given' do
get api_v1_visualizations_index_url(order_direction: @valid_order_direction, order: @valid_order,
api_key: @user.api_key, types: 'derived'), {}, @headers
last_response.status.should == 200
get api_v1_visualizations_index_url(order_direction: @invalid_order_direction, order: @valid_order,
api_key: @user.api_key, types: 'derived'), {}, @headers
last_response.status.should == 400
last_response.body.should include "Wrong 'order_direction' parameter value"
end
it 'returns an error if an invalid :order combination is given' do
get api_v1_visualizations_index_url(order: @valid_order_combination, api_key: @user.api_key,
types: 'derived'), {}, @headers
last_response.status.should == 200
get api_v1_visualizations_index_url(order: @invalid_order_combination, api_key: @user.api_key,
types: 'derived'), {}, @headers
last_response.status.should == 400
last_response.body.should include "Wrong 'order' parameter combination"
end
it 'returns an error if an invalid :order_direction combination is given' do
get api_v1_visualizations_index_url(order: @valid_order_combination,
order_direction: @valid_direction_combination,
api_key: @user.api_key, types: 'derived'), {}, @headers
last_response.status.should == 200
get api_v1_visualizations_index_url(order: @valid_order_combination,
order_direction: @invalid_direction_combination,
api_key: @user.api_key, types: 'derived'), {}, @headers
last_response.status.should == 400
last_response.body.should include "Wrong 'order_direction' parameter combination"
end
end
end
end
describe 'index' do
include_context 'organization with users helper'
include_context 'visualization creation helpers'
describe 'shared_only' do
it 'should not display nor count the shared visualizations you own' do
table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: unique_name('table'), user_id: @org_user_1.id)
u1_t_1_id = table.table_visualization.id
u1_t_1_perm_id = table.table_visualization.permission.id
share_table_with_organization(table, @org_user_1, @organization)
get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at',
shared: CartoDB::Visualization::Collection::FILTER_SHARED_ONLY), @headers
body = JSON.parse(last_response.body)
body['total_entries'].should eq 0
body['visualizations'].count.should eq 0
end
end
it 'returns auth tokens for password protected viz for the owner but not for users that have them shared' do
@map, @table, @table_visualization, @visualization = create_full_visualization(@carto_org_user_1)
@visualization.privacy = Carto::Visualization::PRIVACY_PROTECTED
@visualization.password = 'wontbeused'
@visualization.save!
share_visualization(@visualization, @org_user_2)
get_json api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key,
type: Carto::Visualization::TYPE_DERIVED,
shared: CartoDB::Visualization::Collection::FILTER_SHARED_YES), @headers do |response|
response.status.should eq 200
response.body[:visualizations][0][:id].should_not be_empty
response.body[:visualizations][0][:auth_tokens].should_not be_empty
end
get_json api_v1_visualizations_index_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key,
type: Carto::Visualization::TYPE_DERIVED,
shared: CartoDB::Visualization::Collection::FILTER_SHARED_YES), @headers do |response|
response.status.should eq 200
response.body[:visualizations][0][:id].should_not be_empty
response.body[:visualizations][0][:auth_tokens].should be_empty
end
destroy_full_visualization(@map, @table, @table_visualization, @visualization)
end
end
describe 'visualization url generation' do
include_context 'visualization creation helpers'
include_context 'organization with users helper'
before(:all) do
@user = FactoryGirl.create(:valid_user)
end
after(:all) do
@user.destroy
end
it 'generates a user table visualization url' do
table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: unique_name('table'), user_id: @user.id)
vis_id = table.table_visualization.id
get_json api_v1_visualizations_show_url(user_domain: @user.username, id: vis_id, api_key: @user.api_key), {}, http_json_headers do |response|
response.status.should == 200
response.body[:url].should == "http://#{@user.username}#{Cartodb.config[:session_domain]}:#{Cartodb.config[:http_port]}/tables/#{table.name}"
end
end
it 'generates a user map url' do
visualization = api_visualization_creation(@user, http_json_headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_DERIVED })
get_json api_v1_visualizations_show_url(user_domain: @user.username, id: visualization.id, api_key: @user.api_key), {}, http_json_headers do |response|
response.status.should == 200
response.body[:url].should == "http://#{@user.username}#{Cartodb.config[:session_domain]}:#{Cartodb.config[:http_port]}/viz/#{visualization.id}/map"
end
end
it 'generates a org user table visualization url' do
table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: unique_name('table'), user_id: @org_user_1.id)
vis_id = table.table_visualization.id
get_json api_v1_visualizations_show_url(user_domain: @org_user_1.username, id: vis_id, api_key: @org_user_1.api_key), {}, http_json_headers do |response|
response.status.should == 200
response.body[:url].should == "http://#{@org_user_1.organization.name}#{Cartodb.config[:session_domain]}:#{Cartodb.config[:http_port]}/u/#{@org_user_1.username}/tables/#{table.name}"
end
end
it 'generates a organization user map url' do
visualization = api_visualization_creation(@org_user_1, http_json_headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_DERIVED })
get_json api_v1_visualizations_show_url(user_domain: @org_user_1.username, id: visualization.id, api_key: @org_user_1.api_key), {}, http_json_headers do |response|
response.status.should == 200
response.body[:url].should == "http://#{@org_user_1.organization.name}#{Cartodb.config[:session_domain]}:#{Cartodb.config[:http_port]}/u/#{@org_user_1.username}/viz/#{visualization.id}/map"
end
end
it 'generates the URL for tables shared by another user with hyphens in his username' do
user_with_hyphen = FactoryGirl.create(:user, username: 'fulano-de-tal', organization: @organization)
table = create_random_table(user_with_hyphen, 'tabluca', UserTable::PRIVACY_PRIVATE)
shared_table = table.table_visualization
share_visualization(shared_table, @org_user_1)
request_url = api_v1_visualizations_show_url(user_domain: @org_user_1.username,
id: shared_table.id, api_key: @org_user_1.api_key)
get_json request_url, {}, http_json_headers do |response|
response.status.should == 200
response.body[:url].should include("/tables/fulano-de-tal.tabluca")
end
end
end
describe 'filter canonical viz by bounding box' do
include_context 'visualization creation helpers'
before(:all) do
bypass_named_maps
@user = FactoryGirl.create(:valid_user)
@table_inside_bbox = create_geometry_table(@user, BBOX_GEOM)
@table_outside_bbox = create_geometry_table(@user, OUTSIDE_BBOX_GEOM)
end
after(:all) do
@user.destroy
end
it 'should show return only visualizations that intersect with the bbox' do
get api_v1_visualizations_index_url(user_domain: @user.username,
types: CartoDB::Visualization::Member::TYPE_CANONICAL, bbox: '-18.166667,27.633333,4.333333,43.916667'), @headers
body = JSON.parse(last_response.body)
body["visualizations"].length.should eq 1
body["visualizations"][0]["id"].should eq @table_inside_bbox.table_visualization.id
end
it 'should return 400 when try to filter by bbox and not canonical visualizations' do
get api_v1_visualizations_index_url(user_domain: @user.username,
types: CartoDB::Visualization::Member::TYPE_DERIVED, bbox: '-18.166667,27.633333,4.333333,43.916667'), @headers
last_response.status.should eq 400
end
it 'should return 400 when try to filter by bbox and with more than only canonical visualizations' do
get api_v1_visualizations_index_url(user_domain: @user.username,
types: "#{CartoDB::Visualization::Member::TYPE_DERIVED}, #{CartoDB::Visualization::Member::TYPE_CANONICAL}", bbox: '-18.166667,27.633333,4.333333,43.916667'), @headers
last_response.status.should eq 400
end
it 'should return 400 when try to filter by bbox with less than 4 coordinates' do
get api_v1_visualizations_index_url(user_domain: @user.username,
types: CartoDB::Visualization::Member::TYPE_DERIVED, bbox: '27.633333,4.333333,43.916667'), @headers
last_response.status.should eq 400
end
it 'should return 400 when try to filter by bbox with wrong typed coordinates' do
get api_v1_visualizations_index_url(user_domain: @user.username,
types: CartoDB::Visualization::Member::TYPE_CANONICAL, bbox: '18.323232,alal,4.333333,43.916667'), @headers
last_response.status.should eq 400
get api_v1_visualizations_index_url(user_domain: @user.username,
types: CartoDB::Visualization::Member::TYPE_CANONICAL, bbox: 'true,2.393939,4.333333,43.916667'), @headers
last_response.status.should eq 400
end
end
# See #5591
describe 'error with wrong visualization url' do
def url(user_domain, visualization_id, api_key, host = @host)
api_v1_visualizations_show_url(user_domain: user_domain, id: visualization_id, api_key: api_key).
gsub('www.example.com', host)
end
describe 'normal user urls' do
before(:all) do
bypass_named_maps
@vis_owner = FactoryGirl.create(:valid_user, private_tables_enabled: true)
@other_user = FactoryGirl.create(:valid_user, private_tables_enabled: true)
@table = create_random_table(@vis_owner, unique_name('viz'), UserTable::PRIVACY_PRIVATE)
@vis = @table.table_visualization
@vis.private?.should == true
@host = "#{@vis_owner.username}.localhost.lan"
@headers = http_json_headers
end
after(:all) do
@table.destroy
end
it 'returns 200 with owner user_domain' do
get_json url(@vis_owner.username, @vis.id, @vis_owner.api_key), {}, @headers do |response|
response.status.should == 200
end
end
it 'returns 404 if visualization does not exist' do
get_json url(@vis_owner.username, random_uuid, @vis_owner.api_key), {}, @headers do |response|
response.status.should == 404
response.body[:errors].should == 'Visualization does not exist'
end
end
it 'returns 403 under other user domain if visualization is private' do
get_json url(@other_user.username, @vis.id, @other_user.api_key), {}, @headers do |response|
response.status.should == 403
response.body[:errors].should == 'Visualization not viewable'
end
end
it 'returns 403 if visualization is private' do
get_json url(@vis_owner.username, @vis.id, @other_user.api_key), {}, @headers do |response|
response.status.should == 403
end
end
it 'returns 200 if user at url is empty' do
ApplicationController.any_instance.stubs(:current_viewer).returns(@vis_owner)
login_as(@vis_owner, scope: @vis_owner.username)
get_json url(nil, @vis.id, @vis_owner.api_key), {}, @headers do |response|
response.status.should == 200
end
end
it 'returns 404 if user at url does not match visualization owner' do
app = ApplicationController.any_instance
app.stubs(:current_user).returns(@vis_owner)
app.stubs(:current_viewer).returns(@vis_owner)
app.stubs(:api_authorization_required).returns(true)
get_json url(@other_user.username, @vis.id, @vis_owner.api_key), {}, @headers do |response|
response.status.should == 404
end
end
it 'returns 404 if user subdomain does not match visualization owner' do
app = ApplicationController.any_instance
app.stubs(:current_user).returns(@vis_owner)
app.stubs(:current_viewer).returns(@vis_owner)
app.stubs(:api_authorization_required).returns(true)
host = "#{@other_user.username}.localhost.lan"
get_json url(nil, @vis.id, @vis_owner.api_key, host), {}, @headers do |response|
response.status.should == 404
response.body[:errors].should == 'Visualization of that user does not exist'
end
end
end
describe 'organization urls' do
include_context 'organization with users helper'
before(:each) do
bypass_named_maps
@vis_owner = @org_user_1
@shared_vis = FactoryGirl.build(:derived_visualization,
user_id: @vis_owner.id,
name: unique_name('viz'),
description: 'wadus desc',
privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE).store
@shared_user = @org_user_2
@not_shared_user = @org_user_owner
share_visualization(@shared_vis, @shared_user)
@host = "#{@vis_owner.organization.name}.localhost.lan"
@headers = http_json_headers
end
after(:each) do
@shared_vis.delete
end
it 'returns 200 with owner user_domain' do
get_json url(@vis_owner.username, @shared_vis.id, @vis_owner.api_key), {}, @headers do |response|
response.status.should == 200
end
end
it 'returns 200 with valid (shared user) user_domain' do
get_json url(@shared_user.username, @shared_vis.id, @shared_user.api_key), {}, @headers do |response|
response.status.should == 200
end
end
it 'returns 200 with valid shared user (current_user) user_domain, with current_viewer being the owner' do
ApplicationController.any_instance.stubs(:current_viewer).returns(@vis_owner)
ApplicationController.any_instance.stubs(:current_user).returns(@shared_user)
get_json url(@shared_user.username, @shared_vis.id, @shared_user.api_key), {}, @headers do |response|
response.status.should == 200
end
end
it 'returns 200 and private info with valid shared user user_domain' do
get_json url(@shared_user.username, @shared_vis.id, @shared_user.api_key), {}, @headers do |response|
response.status.should == 200
response.body[:description].should_not be_nil
response.body[:auth_tokens].should_not be_nil
end
end
it 'returns 404 if visualization does not exist' do
get_json url(@vis_owner.username, random_uuid, @vis_owner.api_key), {}, @headers do |response|
response.status.should == 404
response.body[:errors].should == 'Visualization does not exist'
end
end
it 'returns 403 if visualization is not shared with the domain user' do
get_json url(@not_shared_user.username, @shared_vis.id, @not_shared_user.api_key), {}, @headers do |response|
response.status.should == 403
response.body[:errors].should == 'Visualization not viewable'
end
end
it 'returns 403 if visualization is not shared with the apikey user' do
get_json url(@shared_user.username, @shared_vis.id, @not_shared_user.api_key), {}, @headers do |response|
response.status.should == 403
end
end
it 'returns 404 if user at url is empty' do
ApplicationController.any_instance.stubs(:current_viewer).returns(@shared_user)
login_as(@shared_user, scope: @shared_user.organization.name)
get_json url(nil, @shared_vis.id, @shared_user.api_key), {}, @headers do |response|
response.status.should == 404
response.body[:errors].should == 'Visualization of that user does not exist'
end
end
it 'returns 404 if user at url is empty, current_user is the owner and current_viewer has permission' do
ApplicationController.any_instance.stubs(:current_user).returns(@vis_owner)
ApplicationController.any_instance.stubs(:current_viewer).returns(@shared_user)
login_as(@shared_user, scope: @shared_user.organization.name)
get_json url(nil, @shared_vis.id, @shared_user.api_key), {}, @headers do |response|
response.status.should == 404
response.body[:errors].should == 'Visualization of that user does not exist'
end
end
it 'returns 404 if user at url does not match visualization owner' do
app = ApplicationController.any_instance
app.stubs(:current_user).returns(@shared_user)
app.stubs(:current_viewer).returns(@shared_user)
app.stubs(:api_authorization_required).returns(true)
login_as(@shared_user, scope: @shared_user.organization.name)
get_json url(@not_shared_user.username, @shared_vis.id, @shared_user.api_key), {}, @headers do |response|
response.status.should == 404
response.body[:errors].should == 'Visualization of that user does not exist'
end
end
it 'returns 404 if user at url does not match visualization owner with current_user being the owner and current_viewer the shared to' do
app = ApplicationController.any_instance
app.stubs(:current_user).returns(@vis_owner)
app.stubs(:current_viewer).returns(@shared_user)
app.stubs(:api_authorization_required).returns(true)
login_as(@shared_user, scope: @shared_user.organization.name)
get_json url(@not_shared_user.username, @shared_vis.id, @shared_user.api_key), {}, @headers do |response|
response.status.should == 404
response.body[:errors].should == 'Visualization of that user does not exist'
end
end
end
end
describe '#google_maps_static_image' do
before(:all) do
@user = FactoryGirl.create(:carto_user)
@map, @table, @table_visualization, @visualization = create_full_visualization(@user)
base_layer = @visualization.base_layers.first
base_layer.options[:baseType] = 'roadmap'
base_layer.options[:style] = '[]'
base_layer.save
end
before(:each) do
host! "#{@user.username}.localhost.lan"
login_as(@user, scope: @user.username)
end
after(:all) do
destroy_full_visualization(@map, @table, @table_visualization, @visualization)
@user.destroy
end
let(:params) do
{
size: '300x200',
zoom: 14,
center: '0.12,-7.56'
}
end
it 'returns error if user does not have Google configured' do
@user.google_maps_key = nil
@user.save
get_json api_v1_google_maps_static_image_url(params.merge(id: @visualization.id)) do |response|
expect(response.status).to eq 400
expect(response.body[:errors]).to be
end
end
it 'returns signed google maps URL (key)' do
@user.google_maps_key = 'key=GAdhfasjkd'
@user.save
get_json api_v1_google_maps_static_image_url(params.merge(id: @visualization.id)) do |response|
response.status.should be_success
response.body[:url].should eq 'https://maps.googleapis.com/maps/api/staticmap?center=0.12,-7.56&mapType=roadmap&size=300x200&zoom=14&key=GAdhfasjkd'
end
end
it 'returns signed google maps URL (client + signature)' do
@user.google_maps_key = 'client=GAdhfasjkd'
@user.google_maps_private_key = 'MjM0MzJk-3N_czQzJmFkc2Rhc2Q='
@user.save
get_json api_v1_google_maps_static_image_url(params.merge(id: @visualization.id)) do |response|
response.status.should be_success
response.body[:url].should eq 'https://maps.googleapis.com/maps/api/staticmap?center=0.12,-7.56&mapType=roadmap&size=300x200&zoom=14&client=GAdhfasjkd&signature=q3E0WXgV1XlglotqoRXUZ4O8d10='
end
end
end
include Rack::Test::Methods
include Warden::Test::Helpers
include CacheHelper
private
# Custom hash comparison, since in the ActiveModel-based controllers
# we allow some differences:
# - x to many associations can return [] instead of nil
def normalize_hash(h, normalized_attributes = NORMALIZED_ASSOCIATION_ATTRIBUTES)
h.each { |k, v|
h[k] = nil if v == []
h[k] = '' if normalized_attributes[:attributes].include?(k)
if normalized_attributes[:associations].keys.include?(k)
normalize_hash(v, normalized_attributes[:associations][k])
end
}
end
# INFO: this test uses comparison against old data structures to check validity.
# You can use this method to remove that new data so next comparisons will work.
def remove_data_only_in_new_controllers(visualization_hash, new_attributes = NEW_ATTRIBUTES)
visualization_hash.each { |k, v|
if new_attributes[:attributes].include?(k)
removed = visualization_hash.delete(k)
elsif new_attributes[:associations].include?(k)
remove_data_only_in_new_controllers(v, new_attributes[:associations][k])
end
}
end
def login(user)
login_as(user, {scope: user.username })
host! "#{user.username}.localhost.lan"
end
def base_url
'/api/v1/viz'
end
def response_body(params = nil)
get base_url, params.nil? ? nil : params.dup, @headers
last_response.status.should == 200
body = JSON.parse(last_response.body)
body['visualizations'] = body['visualizations'].map { |v| normalize_hash(v) }.map { |v| remove_data_only_in_new_controllers(v) }
body
end
def factory(user, attributes={})
visualization_template(user, attributes)
end
def table_factory(options={})
create_table_with_options(@user_1, @headers, options)
end
def api_visualization_creation(user, headers, additional_fields = {})
post api_v1_visualizations_create_url(user_domain: user.username, api_key: user.api_key), factory(user).merge(additional_fields).to_json, headers
id = JSON.parse(last_response.body).fetch('id')
id.should_not be_nil
CartoDB::Visualization::Member.new(id: id).fetch
end
def test_organization
organization = Organization.new
organization.name = unique_name('org')
organization.quota_in_bytes = 1234567890
organization.seats = 5
organization.builder_enabled = false
organization
end
def create_geometry_table(user, the_geom)
table = new_table(privacy: UserTable::PRIVACY_PUBLIC, user_id: user.id)
table.force_schema = "the_geom geometry"
table.the_geom_type = "point"
table.save.reload
table.insert_row!(the_geom: the_geom)
table.update_bounding_box
table
end
end