cartodb-4.42/spec/models/map_spec.rb
2024-04-06 05:25:13 +00:00

580 lines
19 KiB
Ruby

require_relative '../spec_helper'
require_relative '../../app/models/map'
require_relative '../../app/models/visualization/member'
require_dependency 'carto/bounding_box_utils'
describe Map do
before(:each) do
CartoDB::UserModule::DBService.any_instance.stubs(:enable_remote_db_user).returns(true)
bypass_named_maps
@user = FactoryGirl.create(:valid_user, private_tables_enabled: true)
@table = create_table(user_id: @user.id)
end
after(:each) do
@table.destroy
@user.destroy
end
describe 'viewer role support' do
describe '#save' do
it 'should fail for viewer users' do
@user.stubs(:viewer).returns(true)
new_map = Map.new(user: @user, table_id: @table.id)
new_map.save.should eq nil
new_map.errors[:user].should eq ["Viewer users can't save maps"]
@user.stubs(:viewer).returns(false)
end
end
describe '#update' do
it 'should fail for existing maps and viewer users' do
new_map = Map.create(user_id: @user.id, table_id: @table.id)
new_map.user.stubs(:viewer).returns(true)
new_map.save.should eq nil
new_map.errors[:user].should eq ["Viewer users can't save maps"]
new_map.user.stubs(:viewer).returns(false)
new_map.destroy
end
end
describe '#validations' do
before(:all) do
@map_user = FactoryGirl.create(:carto_user)
@map = Carto::Map.create(user_id: @map_user.id)
end
after(:all) do
@map.destroy
@map_user.destroy
end
describe '#options' do
it 'sets dashboard_menu true by default' do
@map.dashboard_menu.should eq true
end
it 'sets layer_selector false by default' do
@map.layer_selector.should eq false
end
it 'allows to change dashboard_menu' do
@map.dashboard_menu = false
@map.dashboard_menu.should be_false
@map.dashboard_menu = true
@map.dashboard_menu.should be_true
end
it 'allows to change layer_selector' do
@map.layer_selector = false
@map.layer_selector.should be_false
@map.layer_selector = true
@map.layer_selector.should be_true
end
it 'rejects a non-boolean dashboard_menu value' do
@map.dashboard_menu = 'patata'
@map.valid?.should be_false
@map.errors[:options][0].should include('String did not match the following type: boolean')
end
it 'rejects a non-boolean layer_selector value' do
@map.layer_selector = 'patata'
@map.valid?.should be_false
@map.errors[:options][0].should include('String did not match the following type: boolean')
end
it 'requires a dashboard_menu value' do
@map.dashboard_menu = nil
@map.valid?.should be_false
@map.errors[:options].should_not be_empty
@map.errors[:options][0].should include('NilClass did not match the following type: boolean')
end
it 'requires a layer_selector value' do
@map.layer_selector = nil
@map.valid?.should be_false
@map.errors[:options].should_not be_empty
@map.errors[:options][0].should include('NilClass did not match the following type: boolean')
end
it 'requires dashboard_menu to be present' do
old_options = @map.options.dup
@map.options = Hash.new
@map.valid?.should be_false
@map.errors[:options].should_not be_empty
@map.errors[:options][0].should include('did not contain a required property of \'dashboard_menu\'')
@map.options = old_options
end
it 'requires layer_selector to be present' do
old_options = @map.options.dup
@map.options = Hash.new
@map.valid?.should be_false
@map.errors[:options].should_not be_empty
@map.errors[:options][0].should include('did not contain a required property of \'layer_selector\'')
@map.options = old_options
end
it 'rejects spammy options' do
@map.options[:spam] = 'hell'
@map.valid?.should be_false
@map.errors[:options].should_not be_empty
@map.errors[:options][0].should include('spam')
end
it 'rejects incomplete options' do
@map.options.delete(:dashboard_menu)
@map.valid?.should be_false
@map.errors[:options].should_not be_empty
@map.errors[:options][0].should include('dashboard_menu')
end
end
end
describe '#destroy' do
it 'should fail for existing maps and viewer users' do
new_map = Map.create(user_id: @user.id, table_id: @table.id)
new_map.user.stubs(:viewer).returns(true)
expect { new_map.destroy }.to raise_error(CartoDB::InvalidMember, /Viewer users can't destroy maps/)
new_map.user.stubs(:viewer).returns(false)
new_map.destroy
end
end
end
describe '#bounds' do
it 'checks max-min bounds' do
new_map = Map.create(user_id: @user.id, table_id: @table.id)
max_value = :maxx # 179
min_value = :minx # -179
value1 = 5
value2 = 179
value3 = -179
value4 = 180
value5 = -180
value6 = 0
Carto::BoundingBoxUtils.bound_for(value1, min_value, max_value).should eq value1
Carto::BoundingBoxUtils.bound_for(value2, min_value, max_value).should eq value2
Carto::BoundingBoxUtils.bound_for(value2, min_value, max_value).should eq Carto::BoundingBoxUtils::DEFAULT_BOUNDS[max_value]
Carto::BoundingBoxUtils.bound_for(value3, min_value, max_value).should eq value3
Carto::BoundingBoxUtils.bound_for(value3, min_value, max_value).should eq Carto::BoundingBoxUtils::DEFAULT_BOUNDS[min_value]
Carto::BoundingBoxUtils.bound_for(value4, min_value, max_value).should eq Carto::BoundingBoxUtils::DEFAULT_BOUNDS[max_value]
Carto::BoundingBoxUtils.bound_for(value5, min_value, max_value).should eq Carto::BoundingBoxUtils::DEFAULT_BOUNDS[min_value]
Carto::BoundingBoxUtils.bound_for(value6, min_value, max_value).should eq value6
# As map has no geometries, bounds should still be default ones instead of zeros
new_map.send(:get_map_bounds).should be_nil
new_map.destroy
end
end
describe '#tables' do
it 'returns the associated tables' do
map = Map.create(user_id: @user.id, table_id: @table.id)
@table.reload
map.reload
map.tables.map(&:id).should include(@table.id)
map.destroy
end
it 'updates associated tables/vis upon change' do
map = Map.create(user_id: @user.id, table_id: @table.id)
@table.reload
CartoDB::Visualization::Member.new(
privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC,
name: 'wadus',
type: CartoDB::Visualization::Member::TYPE_CANONICAL,
user_id: @user.id,
map_id: map.id
).store
map2 = Map.create(user_id: @user.id, table_id: @table.id)
# Change map_id on the table, but visualization still points to old map.id
@table.map_id = map2.id
@table.save
# Upon save of the original map, will sanitize all visualizations pointing to old one, saving with new one
CartoDB::Visualization::Member.any_instance.expects(:store_from_map).at_least_once
map.save
map.destroy
map2.destroy
end
end
describe '#base_layers' do
it 'returns the associated base layer' do
map = Map.create(user_id: @user.id, table_id: @table.id)
base_layer = Layer.create(kind: 'carto')
map.add_layer(base_layer)
5.times { map.add_layer(Layer.create(kind: 'carto')) }
map.reload.base_layers.first.id.should == base_layer.id
map.destroy
end
end
describe 'data_layers' do
it 'returns the associated data layers' do
map = Map.create(user_id: @user.id, table_id: @table.id)
5.times { map.add_layer(Layer.create(kind: 'tiled')) }
data_layer = Layer.create(kind: 'carto')
map.add_layer(data_layer)
map.reload.data_layers.first.id.should == data_layer.id
map.destroy
end
end
describe '#user_layers' do
it 'returns all user-defined layers' do
map = Map.create(user_id: @user.id, table_id: @table.id)
5.times { map.add_layer(Layer.create(kind: 'tiled')) }
data_layer = Layer.create(kind: 'carto')
map.add_layer(data_layer)
map.reload.base_layers.length.should == 6
map.reload.user_layers.length.should == 5
map.destroy
end
end
describe '#after_save' do
it 'invalidates varnish cache' do
map = ::Map[@table.map.id]
# One per save, one per destroy
map.expects(:invalidate_vizjson_varnish_cache).twice
map.save
map.destroy
end
it "recalculates bounds" do
table = Table.new :privacy => UserTable::PRIVACY_PRIVATE, :name => 'Madrid Bars', :tags => 'movies, personal'
table.user_id = @user.id
table.force_schema = "name text, address text, latitude float, longitude float"
table.save
table.insert_row!({:name => "Hawai", :address => "Calle de Pérez Galdós 9, Madrid, Spain", :latitude => 40.423012, :longitude => -3.699732})
table.insert_row!({:name => "El Estocolmo", :address => "Calle de la Palma 72, Madrid, Spain", :latitude => 40.426949, :longitude => -3.708969})
table.insert_row!({:name => "El Rey del Tallarín", :address => "Plaza Conde de Toreno 2, Madrid, Spain", :latitude => 40.424654, :longitude => -3.709570})
table.insert_row!({:name => "El Lacón", :address => "Manuel Fernández y González 8, Madrid, Spain", :latitude => 40.415113, :longitude => -3.699871})
table.insert_row!({:name => "El Pico", :address => "Calle Divino Pastor 12, Madrid, Spain", :latitude => 40.428198, :longitude => -3.703991})
table.reload
table.georeference_from!(:latitude_column => :latitude, :longitude_column => :longitude)
table.optimize
table.map.recalculate_bounds!
table.map.view_bounds_ne.should == "[40.4283, -3.69968]"
table.map.view_bounds_sw.should == "[40.415, -3.70962]"
end
it "recenters map using bounds" do
table = Table.new :privacy => UserTable::PRIVACY_PRIVATE, :name => 'bounds tests', :tags => 'testing'
table.user_id = @user.id
table.force_schema = "name text, latitude float, longitude float"
table.save
table.insert_row!({:name => "A", :latitude => 40.0, :longitude => -20.0})
table.insert_row!({:name => "B", :latitude => 80.0, :longitude => 30.0})
table.reload
table.georeference_from!(:latitude_column => :latitude, :longitude_column => :longitude)
table.optimize
table.map.set_default_boundaries!
# casting to string :_( but currently only used by frontend
table.map.center_data.should == [ 60.0.to_s, 5.0.to_s ]
end
it "recalculates zoom using bounds" do
table = Table.new :privacy => UserTable::PRIVACY_PRIVATE, :name => 'zoom recalc test'
table.user_id = @user.id
table.force_schema = "name text, latitude float, longitude float"
table.save
# Out of usual bounds by being bigger than "full world bounding box"
table.map.stubs(:get_map_bounds)
.returns({ minx: -379, maxx: 379, miny: -285 , maxy: 285.0511})
table.map.set_default_boundaries!
table.map.zoom.should == 1
table.map.stubs(:get_map_bounds)
.returns({ minx: -179, maxx: 179, miny: -85 , maxy: 85.0511})
table.map.set_default_boundaries!
table.map.zoom.should == 1
table.map.stubs(:get_map_bounds)
.returns({ minx: 1, maxx: 2, miny: 1 , maxy: 2})
table.map.set_default_boundaries!
table.map.zoom.should == 8
table.map.stubs(:get_map_bounds)
.returns({ minx: 0.025, maxx: 0.05, miny: 0.025 , maxy: 0.05})
table.map.set_default_boundaries!
table.map.zoom.should == 14
# Smaller than our max zoom level
table.map.stubs(:get_map_bounds)
.returns({ minx: 0.000001, maxx: 0.000002, miny: 0.000001 , maxy: 0.000002})
table.map.set_default_boundaries!
table.map.zoom.should == 18
end
end
describe '#notify_map_change' do
before(:each) do
@map = Map.create(user_id: @user.id, table_id: @table.id)
layer = Layer.new(kind: 'tiled')
@map.add_layer(layer)
end
after(:each) do
@map.destroy
end
it 'invalidates vizjson cache' do
CartoDB::Varnish.any_instance.stubs(:purge).with(@map.visualization.varnish_vizjson_key).once
@map.notify_map_change
CartoDB::Varnish.any_instance.stubs(:purge) # Needed to avoid counting the call from destroy
end
it 'updates_named_maps' do
named_maps_api_mock = mock
Carto::NamedMaps::Api.stubs(:new).with { |vis| vis.id == @map.visualization.id }.returns(named_maps_api_mock)
named_maps_api_mock.stubs(:show).returns(true)
named_maps_api_mock.stubs(:update).once
@map.notify_map_change
Carto::NamedMaps::Api.unstub(:new)
end
end
describe '#updated_at' do
it 'is updated after saving the map' do
map = Map.create(user_id: @user.id, table_id: @table.id)
updated_at = map.updated_at
sleep 0.5
map.save
map.updated_at.should > updated_at
map.destroy
end
end
describe '#can_add_layer?' do
it 'should only take into account data layers' do
map = Map.create(user_id: @user.id, table_id: @table.id)
@user.max_layers = 1
@user.save.reload
# First base layer is always allowed
layer = Layer.new(kind: 'tiled')
map.can_add_layer?(@user, layer).should == true
map.add_layer(layer)
map.save.reload
layer_carto1 = Layer.new(kind: 'carto')
map.can_add_layer?(@user, layer_carto1).should == true
map.add_layer(layer_carto1)
map.save.reload
layer_carto2 = Layer.new(kind: 'carto')
map.can_add_layer?(@user, layer_carto2).should == false
# This is now a valid scenario, for example switcing from a basemap with labels on top to another that has too
third_layer = Layer.new(kind: 'tiled', order: 15)
map.can_add_layer?(@user, third_layer).should == true
map.add_layer(third_layer)
map.save.reload
map.destroy
end
end
describe '#admits?' do
it 'checks base layer admission rules' do
map = Map.create(user_id: @user.id, table_id: @table.id)
# First base layer is always allowed
layer = Layer.new(kind: 'tiled')
map.admits_layer?(layer).should == true
map.add_layer(layer)
map.save.reload
map.add_layer(Layer.new(kind: 'carto', order: 5))
map.save.reload
second_tiled_layer = Layer.new(kind: 'tiled')
# more tiled layers allowed only if at top
second_tiled_layer.order = 0
map.admits_layer?(second_tiled_layer).should == false
second_tiled_layer.order = 15
map.admits_layer?(second_tiled_layer).should == true
map.add_layer(second_tiled_layer)
map.save.reload
# This is now a valid scenario, for example switcing from a basemap with labels on top to another that has too
third_layer = Layer.new(kind: 'tiled', order: 15)
map.admits_layer?(third_layer).should == true
third_layer.order = 100
map.admits_layer?(third_layer).should == true
map.destroy
end
describe 'when linked to a table visualization' do
it 'returns false when passed a data layer and it is already linked to a base layer' do
map = ::Map[@table.map.id]
map.remove_layer(map.data_layers.first)
map.reload
map.admits_layer?(Layer.new(kind: 'carto')).should == true
map.add_layer(Layer.new(kind: 'carto'))
map.save.reload
map.admits_layer?(Layer.new(kind: 'carto')).should == false
end
end
end
it "should correcly set vizjson updated_at" do
map = Map.create(user_id: @user.id, table_id: @table.id)
# When the table data is newer
time = Time.now + 2.minutes
Table.any_instance.stubs(:data_last_modified).returns(time)
map.viz_updated_at.to_s.should == time.to_s
# When the data layer is newer
time = Time.now + 3.minutes
map.stubs(:data_layers).returns([Layer.new(updated_at: time)])
map.viz_updated_at.to_s.should == time.to_s
end
describe '#before_destroy' do
it 'invalidates varnish cache' do
map = ::Map[@table.map.id]
map.expects(:invalidate_vizjson_varnish_cache)
map.destroy
end
end
describe '#process_privacy_in' do
it 'sets related visualization private if layer uses private tables' do
@table1 = Table.new
@table1.user_id = @user.id
@table1.save
@table2 = Table.new
@table2.user_id = @user.id
@table2.save
visualization = @table1.table_visualization
visualization.data_layers.length.should eq 1
@table1.privacy = UserTable::PRIVACY_PUBLIC
@table1.save
visualization.privacy = CartoDB::Visualization::Member::PRIVACY_PUBLIC
visualization.store
visualization.private?.should be_false
layer = Carto::Layer.create(
kind: 'carto',
options: { table_name: @table2.name }
)
layer.add_map(visualization.map)
layer.save
layer.reload
@user.reload
layer.uses_private_tables?.should be_true
visualization.map.process_privacy_in(layer)
visualization.private?.should be_true
end
end
context 'viewer role support on layer management' do
after(:each) do
@user.viewer = false
@user.save
@user.reload
@map.reload && @map.destroy if @map
end
def become_viewer(user)
user.viewer = true
user.save
user.reload
end
describe 'add layers' do
it 'should fail for viewer users' do
@map = Map.create(user_id: @user.id, table_id: @table.id)
become_viewer(@user)
@map.reload
@layer = Layer.create(kind: 'carto', options: { query: "select * from #{@table.name}" })
expect { @map.add_layer(@layer) }.to raise_error(/Viewer users can't edit layers/)
end
end
describe 'remove layers' do
it 'should fail for viewer users' do
@map = Map.create(user_id: @user.id, table_id: @table.id)
layer = Layer.create(kind: 'carto', options: { table_name: @table.name })
layer.add_map(@map)
layer.save
layer.reload
@map.reload
@map.layers_dataset.where(layer_id: layer.id).should_not be_empty
become_viewer(@user)
expect {
@map.layers_dataset.where(layer_id: layer.id).destroy
}.to raise_error(/Viewer users can't destroy layers/)
@map.reload
@map.layers_dataset.where(layer_id: layer.id).should_not be_empty
end
end
describe 'can_add_layer?' do
it 'should return false for viewer users' do
@map = Map.create(user_id: @user.id, table_id: @table.id)
become_viewer(@user)
@map.reload
layer = Layer.create(kind: 'carto', options: { table_name: @table.name })
@map.can_add_layer?(@user, layer).should eq false
end
end
end
end