require_relative '../../spec_helper' require_relative '../../../services/data-repository/backend/sequel' require_relative '../../../services/data-repository/repository' require_relative '../../../app/models/visualization/collection' require_relative '../../../app/models/visualization/member' require_relative '../../doubles/support_tables.rb' require 'helpers/unique_names_helper' include UniqueNamesHelper include CartoDB describe Visualization::Collection do include Carto::Factories::Visualizations before(:all) do @user_1 = FactoryGirl.create(:valid_user, quota_in_bytes: 524288000, table_quota: 500, private_tables_enabled: true) @user_2 = FactoryGirl.create(:valid_user, private_tables_enabled: true) end before(:each) do bypass_named_maps delete_user_data @user_1 delete_user_data @user_2 end after(:all) do @user_1.destroy @user_2.destroy end describe '#fetch' do it 'filters by tag if the backend supports array columns' do attributes1 = random_attributes_for_vis_member(tags: ['tag 1', 'tag 11'], user_id: @user_1.id) attributes2 = random_attributes_for_vis_member(tags: ['tag 2', 'tag 22'], user_id: @user_1.id) Visualization::Member.new(attributes1).store Visualization::Member.new(attributes2).store collection = Visualization::Collection.new collection.fetch(user_id: @user_1.id, tags: 'tag 1').count.should == 1 end it 'filters by partial name / description match' do attributes1 = random_attributes_for_vis_member(name: 'viz_1', description: 'description_11', user_id: @user_1.id) attributes2 = random_attributes_for_vis_member(name: 'viz_2', description: 'description_22', user_id: @user_1.id) Visualization::Member.new(attributes1).store Visualization::Member.new(attributes2).store collection = Visualization::Collection.new collection.fetch(user_id: @user_1.id, q: 'viz').count.should == 2 collection.fetch(user_id: @user_1.id, q: 'viz_1').count.should == 1 collection = Visualization::Collection.new collection.fetch(user_id: @user_1.id, q: 'description').count.should == 2 collection.fetch(user_id: @user_1.id, q: 'ion_11').count.should == 1 collection.fetch(user_id: @user_1.id, q: 'ion_22').count.should == 1 end it 'orders the collection by the passed criteria' do Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_1.id, name: 'viz_1')).store Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_1.id, name: 'viz_2')).store collection = Visualization::Collection.new records = collection.fetch(user_id: @user_1.id, order: :name) records.first.name.should == 'viz_2' end it 'checks fetching with, without and only shared entities' do vis_1_name = unique_name('viz') vis_2_name = unique_name('viz') vis_3_name = unique_name('viz') Visualization::Member.new(random_attributes_for_vis_member(name: vis_1_name, user_id: @user_1.id)).store vis2 = Visualization::Member.new(random_attributes_for_vis_member(name: vis_2_name, user_id: @user_2.id)).store vis3 = Visualization::Member.new(random_attributes_for_vis_member(name: vis_3_name, user_id: @user_2.id)).store shared_entity = CartoDB::SharedEntity.new( recipient_id: @user_1.id, recipient_type: CartoDB::SharedEntity::RECIPIENT_TYPE_USER, entity_id: vis2.id, entity_type: CartoDB::SharedEntity::ENTITY_TYPE_VISUALIZATION ) shared_entity.save shared_entity.reload collection = Visualization::Collection.new collection.stubs(:user_shared_vis).with(@user_1.id).returns([vis2.id]) # Filter by user_id and non-owned id (excluding shared) records = collection.fetch(user_id: @user_1.id, id: vis3.id, exclude_shared: true) records.count.should eq 0 # Filter by user_id and non-owned id (not excluding shared) records = collection.fetch(user_id: @user_1.id, id: vis3.id) records.count.should eq 0 # Filter by name (not present user_id) records = collection.fetch(name: vis_1_name) records.count.should eq 1 records.first.name.should eq vis_1_name # Filter by user_id (includes shared) records = collection.fetch(user_id: @user_1.id).map { |item| item } records.count.should eq 2 ((records[0].name == vis_1_name || records[0].name == vis_2_name) && (records[1].name == vis_1_name || records[1].name == vis_2_name)).should eq true # Filter by user_id excluding shared records = collection.fetch(user_id: @user_1.id, exclude_shared: true) records.count.should eq 1 records.first.name.should eq vis_1_name # Filter by user_id and only shared records = collection.fetch(user_id: @user_1.id, only_shared: true) records.count.should eq 1 records.first.name.should eq vis_2_name end it 'checks that filtering collection by locked works' do collection = Visualization::Collection.new vis1 = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_1.id, name: 'viz_1', locked: true)).store vis2 = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_1.id, name: 'viz_2', locked: false)).store records = collection.fetch(user_id: @user_1.id, locked: false) records.count.should eq 1 records.first.name.should eq vis2.name records = collection.fetch(user_id: @user_1.id, locked: true) records.count.should eq 1 records.first.name.should eq vis1.name end it "checks that shared entities appear no matter if they're locked or not" do vis_1_name = 'viz_1' vis_2_name = 'viz_2' vis_3_name = 'viz_3' vis_4_name = 'viz_4' Visualization::Member.new(random_attributes_for_vis_member(name: vis_1_name, user_id: @user_1.id, locked: true)).store vis2 = Visualization::Member.new(random_attributes_for_vis_member(name: vis_2_name, user_id: @user_2.id, locked: true)).store vis3 = Visualization::Member.new(random_attributes_for_vis_member(name: vis_3_name, user_id: @user_2.id, locked: false)).store Visualization::Member.new(random_attributes_for_vis_member(name: vis_4_name, user_id: @user_1.id, locked: false)).store shared_entity = CartoDB::SharedEntity.new( recipient_id: @user_1.id, recipient_type: CartoDB::SharedEntity::RECIPIENT_TYPE_USER, entity_id: vis2.id, entity_type: CartoDB::SharedEntity::ENTITY_TYPE_VISUALIZATION ) shared_entity.save shared_entity = CartoDB::SharedEntity.new( recipient_id: @user_1.id, recipient_type: CartoDB::SharedEntity::RECIPIENT_TYPE_USER, entity_id: vis3.id, entity_type: CartoDB::SharedEntity::ENTITY_TYPE_VISUALIZATION ) shared_entity.save collection = Visualization::Collection.new collection.stubs(:user_shared_vis).with(@user_1.id).returns([vis2.id, vis3.id]) # Non-locked vis, all shared vis records = collection.fetch(user_id: @user_1.id) records.count.should eq 4 # Same behaviour, non-locked, all shared records = collection.fetch(user_id: @user_1.id, locked: false) records.count.should eq 3 # Only user vis, no shared vis at all records = collection.fetch(user_id: @user_1.id, locked: true) records.count.should eq 1 records.map(&:name).first.should eq vis_1_name end it 'Checks all supported sorting methods work' do # Supported: updated_at, likes, mapviews, row_count, size # TODO: Add mapviews test. As it uses redis requires more work vis1 = full_visualization_table(@user_1, nil).visualization vis2 = full_visualization_table(@user_1, nil).visualization vis3 = full_visualization_table(@user_1, nil).visualization # Biggest row count vis3.table.add_column!(name: "test_col", type: "text") vis3.table.insert_row!(test_col: "333") vis3.table.insert_row!(test_col: "333") vis3.table.insert_row!(test_col: "333") vis3.table.insert_row!(test_col: "333") vis3.table.insert_row!(test_col: "333") vis3.table.insert_row!(test_col: "333") # pg_class.reltuples only get updated after VACUUMs, etc. @user_1.in_database.run("VACUUM #{vis3.table.name}") vis3.updated_at = Time.now - 3.minute vis3.save # Biggest in size and likes long_string = "" 2000.times do long_string << rand(999).to_s end vis2.table.add_column!(name: "test_col", type: "text") vis2.table.add_column!(name: "test_col2", type: "text") vis2.table.add_column!(name: "test_col3", type: "text") vis2.table.insert_row!(test_col: long_string, test_col2: long_string, test_col3: long_string) vis2.table.insert_row!(test_col: long_string, test_col2: long_string, test_col3: long_string) vis2.table.insert_row!(test_col: long_string, test_col2: long_string, test_col3: long_string) vis2.table.insert_row!(test_col: long_string, test_col2: long_string, test_col3: long_string) @user_1.in_database.run("VACUUM #{vis2.table.name}") vis2.updated_at = Time.now - 2.minute vis2.save # Latest edited vis1.table.add_column!(name: "test_col", type: "text") @user_1.in_database.run("VACUUM #{vis1.table.name}") vis1.updated_at = Time.now - 1.minute vis1.save # Actual tests start here collection = Visualization::Collection.new.fetch(user_id: @user_1.id, order: 'updated_at', exclude_shared: true) collection.count.should eq 3 ids = collection.map(&:id) expected_updated_ats = [vis1.id, vis2.id, vis3.id] ids.should eq expected_updated_ats collection = Visualization::Collection.new.fetch(user_id: @user_1.id, order: :row_count, exclude_shared: true) collection.count.should eq 3 ids = collection.map(&:id) expected_row_count = [vis3.id, vis2.id, vis1.id] ids.should eq expected_row_count collection = Visualization::Collection.new.fetch(user_id: @user_1.id, order: :size, exclude_shared: true) collection.count.should eq 3 ids = collection.map(&:id) expected_size = [vis2.id, vis3.id, vis1.id] ids.should eq expected_size # Cleanup vis1.delete vis2.delete vis3.delete end def liked(user) Visualization::Collection.new.fetch(user_id: user.id, only_liked: true) end it "checks filtering by 'liked' " do user3 = create_user(quota_in_bytes: 524288000, table_quota: 500, private_tables_enabled: true) full_visualization_table(@user_1, nil).visualization vis2 = full_visualization_table(@user_1, nil).visualization vis2.privacy = Visualization::Member::PRIVACY_PUBLIC vis2.save vis3 = full_visualization_table(@user_1, nil).visualization vis3.privacy = Visualization::Member::PRIVACY_PUBLIC vis3.save vis4 = full_visualization_table(@user_1, nil).visualization vis4.privacy.should eq Visualization::Member::PRIVACY_PRIVATE vis_link = full_visualization_table(@user_1, nil).visualization vis_link.privacy = Visualization::Member::PRIVACY_LINK vis_link.save vis_private = full_visualization_table(@user_1, nil).visualization vis_private.privacy = Visualization::Member::PRIVACY_PRIVATE vis_private.save # vis1 0 likes vis2.add_like_from(@user_1) expect { vis2.add_like_from(@user_2) }.to raise_exception Carto::Visualization::UnauthorizedLikeError vis3.add_like_from(@user_1) # since vis4 is not public it won't count for users 2 and 3 vis4.add_like_from(@user_1) vis_link.add_like_from(@user_1) vis_private.add_like_from(@user_1) collection = Visualization::Collection.new.fetch(user_id: @user_1.id) collection.count.should eq 6 collection = Visualization::Collection.new.fetch(user_id: @user_1.id, only_liked: true) collection.count.should eq 5 ids = collection.map(&:id) collection = Visualization::Collection.new.fetch(user_id: @user_1.id, type: Visualization::Member::TYPE_CANONICAL, only_liked: true) collection.count.should eq 5 ids = collection.map(&:id) collection = Visualization::Collection.new.fetch(user_id: @user_1.id, only_liked: true, unauthenticated: true) collection.count.should eq 2 ids = collection.map(&:id) collection = Visualization::Collection.new.fetch(user_id: @user_1.id, only_liked: true, privacy: Visualization::Member::PRIVACY_PRIVATE) collection.count.should eq 2 ids = collection.map(&:id) collection = Visualization::Collection.new.fetch(only_liked: true) collection.count.should eq 0 collection = Visualization::Collection.new.fetch(user_id: @user_2.id, only_liked: true) collection.count.should eq 0 collection = Visualization::Collection.new.fetch(user_id: user3.id, only_liked: true) collection.count.should eq 0 # Liked link privacy one user3.destroy end end describe 'single methods' do it 'checks count_total method' do vis_1_name = 'viz_1' vis_2_name = 'viz_2' vis_3_name = 'viz_3' vis_4_name = 'viz_4' Visualization::Member.new(random_attributes(name: vis_1_name, user_id: @user_1.id)).store Visualization::Member.new(random_attributes(name: vis_2_name, privacy: 'private', user_id: @user_1.id)).store vis_user2 = Visualization::Member.new(random_attributes(name: vis_3_name, user_id: @user_2.id)).store vis2_user2 = Visualization::Member.new(random_attributes(name: vis_4_name, user_id: @user_2.id)).store shared_entity = CartoDB::SharedEntity.new( recipient_id: @user_1.id, recipient_type: CartoDB::SharedEntity::RECIPIENT_TYPE_USER, entity_id: vis_user2.id, entity_type: CartoDB::SharedEntity::ENTITY_TYPE_VISUALIZATION ) shared_entity.save shared_entity.reload collection = Visualization::Collection.new collection.stubs(:user_shared_vis).with(@user_1.id).returns([vis_user2.id]) records = collection.fetch(user_id: @user_1.id) records.count.should eq 3 collection.count_total(user_id: @user_1.id).should eq 2 # Other filters should be skipped collection.count_total(user_id: @user_1.id, id: vis_user2.id).should eq 2 collection.count_total(user_id: @user_1.id, id: vis2_user2.id).should eq 2 collection.count_total(user_id: @user_1.id, name: 'test').should eq 2 collection.count_total(user_id: @user_1.id, description: 'test').should eq 2 collection.count_total(user_id: @user_1.id, privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE).should eq 2 collection.count_total(user_id: @user_1.id, locked: true).should eq 2 collection.count_total(user_id: @user_1.id, exclude_shared: false).should eq 2 collection.count_total(user_id: @user_1.id, only_shared: false).should eq 2 # If unauthenticated remove private ones collection.count_total(user_id: @user_1.id, unauthenticated: true).should eq 1 # Type filters are allowed collection.count_total(user_id: @user_1.id, type: CartoDB::Visualization::Member::TYPE_CANONICAL).should eq 2 collection.count_total(user_id: @user_1.id, type: CartoDB::Visualization::Member::TYPE_DERIVED).should eq 0 # And filtering by user_id collection.count_total(user_id: @user_2.id).should eq 2 end end # Slide visualization types specs # This should be a member_spec test, but as those specs have no collection support... it 'checks the .children method' do parent = Visualization::Member.new(random_attributes_for_vis_member( user_id: @user_1.id, type: Visualization::Member::TYPE_DERIVED)).store parent.children.count.should eq 0 child1 = Visualization::Member.new(random_attributes_for_vis_member( user_id: @user_1.id, type: Visualization::Member::TYPE_SLIDE, parent_id: parent.id)).store.fetch parent.children.count.should eq 1 child2 = Visualization::Member.new(random_attributes_for_vis_member( user_id: @user_1.id, type: Visualization::Member::TYPE_SLIDE, parent_id: parent.id)).store.fetch child2.set_prev_list_item!(child1) parent.fetch parent.children.count.should eq 2 canonical = Visualization::Member.new(random_attributes_for_vis_member( user_id: @user_1.id, type: Visualization::Member::TYPE_CANONICAL)).store parent.children.count.should eq 2 canonical.children.count.should eq 0 child_2_1 = Visualization::Member.new(random_attributes_for_vis_member( user_id: @user_1.id, type: Visualization::Member::TYPE_DERIVED)).store Visualization::Member.new(random_attributes_for_vis_member( user_id: @user_1.id, type: Visualization::Member::TYPE_SLIDE, parent_id: child_2_1.id)).store child_2_1.children.count.should eq 1 end it 'checks retrieving slide type items' do member = Visualization::Member.new(random_attributes_for_vis_member( type: Visualization::Member::TYPE_DERIVED, user_id: @user_1.id)).store c1 = Visualization::Member.new(random_attributes_for_vis_member( type: Visualization::Member::TYPE_SLIDE, parent_id: member.id, user_id: @user_1.id)).store c2 = Visualization::Member.new(random_attributes_for_vis_member( type: Visualization::Member::TYPE_SLIDE, parent_id: member.id, user_id: @user_1.id)).store collection = Visualization::Collection.new.fetch(user_id: @user_1.id, type: Visualization::Member::TYPE_SLIDE) collection.count.should eq 2 c1.delete c2.delete member.delete end def random_attributes(attributes = {}) random = unique_name('viz') { name: attributes.fetch(:name, random), description: attributes.fetch(:description, "description #{random}"), privacy: attributes.fetch(:privacy, 'public'), tags: attributes.fetch(:tags, ['tag 1']), type: attributes.fetch(:type, CartoDB::Visualization::Member::TYPE_CANONICAL), user_id: attributes.fetch(:user_id, Carto::UUIDHelper.random_uuid), locked: attributes.fetch(:locked, false), title: attributes.fetch(:title, ''), source: attributes.fetch(:source, ''), license: attributes.fetch(:license, ''), attributions: attributes.fetch(:attributions, ''), kind: attributes.fetch(:kind, CartoDB::Visualization::Member::KIND_GEOM), map_id: attributes.fetch(:map_id, nil) } end # random_attributes end # Visualization::Collection