cartodb-4.42/spec/requests/carto/api/visualizations_controller_index_spec.rb
2024-04-06 05:25:13 +00:00

570 lines
26 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_relative './helpers/visualization_controller_helper'
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
include VisualizationControllerHelper
before(:all) do
create_account_type_fg('ORGANIZATION USER')
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
Delorean.time_travel_to "2018/01/01 00:00:00" do
_, table = create_full_visualization(@user)
@dependencies = Array.new(2) do
Delorean.jump(1.day)
_, _, _, visualization = create_full_visualization(@user, table: table)
visualization
end
end
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[1].id
collection[0]['dependent_visualizations'].first['name'].should_not be_nil
collection[0]['dependent_visualizations'].second['id'].should eql @dependencies[0].id
collection[0]['dependent_visualizations'].second['name'].should_not be_nil
end
context 'with faster dependencies' do
before(:all) do
@feature_flag = FactoryGirl.create(:feature_flag, name: 'faster-dependencies', restricted: true)
end
after(:all) do
@feature_flag.destroy
end
it 'does not return the dependent visualizations by default' do
with_feature_flag(@user, 'faster-dependencies', true) do
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'table',
order: 'dependent_visualizations'), {}, @headers
end
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
with_feature_flag(@user, 'faster-dependencies', true) do
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'table',
with_dependent_visualizations: 0), {}, @headers
end
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
with_feature_flag(@user, 'faster-dependencies', true) do
get api_v1_visualizations_index_url(api_key: @user.api_key, types: 'table',
with_dependent_visualizations: 2), {}, @headers
end
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[1].id
collection[0]['dependent_visualizations'].first['name'].should_not be_nil
collection[0]['dependent_visualizations'].second['id'].should eql @dependencies[0].id
collection[0]['dependent_visualizations'].second['name'].should_not be_nil
end
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 their 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
end