require_relative '../../spec_helper'
require_relative '../../../services/data-repository/backend/sequel'
require_relative '../../../app/models/visualization/member'
require_relative '../../../app/models/visualization/collection'
require_relative '../../../services/data-repository/repository'
require_relative '../../doubles/support_tables.rb'
require_dependency 'cartodb/redis_vizjson_cache'

include CartoDB

describe Visualization::Member do
  include Carto::Factories::Visualizations
  let(:user) { create(:carto_user) }

  before(:all) do
    @db = SequelRails.connection
    Sequel.extension(:pagination)

    Visualization.repository  = DataRepository::Backend::Sequel.new(@db, :visualizations)

    @user = create(:valid_user)
  end

  after(:all) do
    @user.destroy
  end

  def stub_visualization_member_user
    bypass_named_maps

    # For relator->permission
    user_id = Carto::UUIDHelper.random_uuid
    user_name = 'whatever'
    user_apikey = '123'
    @user_mock = mock
    @user_mock.stubs(:id).returns(user_id)
    @user_mock.stubs(:username).returns(user_name)
    @user_mock.stubs(:api_key).returns(user_apikey)
    @user_mock.stubs(:viewer).returns(false)
    @user_mock.stubs(:has_feature_flag?).returns(false)
    @user_mock.stubs(:new_visualizations_version).returns(2)
    CartoDB::Visualization::Relator.any_instance.stubs(:user).returns(@user_mock)

    support_tables_mock = Doubles::Visualization::SupportTables.new
    Visualization::Relator.any_instance.stubs(:support_tables).returns(support_tables_mock)
  end

  describe '#password' do
    it 'checks that when using password protected type, encrypted password is generated and stored correctly' do
      password_value = '000123456'
      password_second_value = '456789'

      visualization = Visualization::Member.new(type: Visualization::Member::TYPE_DERIVED)
      visualization.privacy = Visualization::Member::PRIVACY_PROTECTED

      visualization.password = password_value
      visualization.has_password?.should be_true
      visualization.password_valid?(password_value).should be_true

      # Shouldn't remove the password, and be equal
      visualization.password = ''
      visualization.has_password?.should be_true
      visualization.password_valid?(password_value).should be_true
      visualization.password = nil
      visualization.has_password?.should be_true
      visualization.password_valid?(password_value).should be_true

      # Modify the password
      visualization.password = password_second_value
      visualization.has_password?.should be_true
      visualization.password_valid?(password_second_value).should be_true
      visualization.password_valid?(password_value).should be_false

      # Test removing the password, should work
      # :remove_password doesn't need to be public, so in the new model it's kept private. :send is needed here, then.
      visualization.send(:remove_password)
      visualization.has_password?.should be_false
      visualization.password_valid?(password_value).should be_false
    end
  end

  describe '#initialize' do
    it 'assigns an id by default' do
      member = Visualization::Member.new
      member.should be_an_instance_of Visualization::Member
      member.id.should_not be_nil
    end
  end

  describe '#store' do
    before { stub_visualization_member_user }

    it 'should fail if no user_id attribute present' do
      attributes  = random_attributes_for_vis_member(user_id: @user_mock.id)
      attributes.delete(:user_id)
      member      = Visualization::Member.new(attributes)
      expect {
        member.store
      }.to raise_exception CartoDB::InvalidMember
    end

    it 'should fail for new visualizations and viewer users' do
      @user_mock.stubs(:viewer).returns(true)
      attributes = random_attributes_for_vis_member(user_id: @user_mock.id)
      member = Visualization::Member.new(attributes)
      expect { member.store }.to raise_error(CartoDB::InvalidMember, /Viewer users can't store visualizations/)

      @user_mock.stubs(:viewer).returns(false)
    end

    it 'should fail for existing visualizations if user is now a viewer' do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))
      member.store

      member = Visualization::Member.new(id: member.id).fetch
      @user_mock.stubs(:viewer).returns(true)
      member.name = 'changed'
      expect { member.store }.to raise_error(CartoDB::InvalidMember, /Viewer users can't store visualizations/)

      @user_mock.stubs(:viewer).returns(false)
    end

    it 'persists attributes to the data repository' do
      attributes  = random_attributes_for_vis_member(user_id: @user_mock.id)
      member      = Visualization::Member.new(attributes)
      member.store

      member = Visualization::Member.new(id: member.id)
      member.name.should be_nil

      member.fetch
      member.name             .should == attributes.fetch(:name)
      member.active_layer_id  .should == attributes.fetch(:active_layer_id)
      member.privacy          .should == attributes.fetch(:privacy)

      member.permission.should_not be nil
      member.permission.owner_id.should eq @user_mock.id
      member.permission.owner_username.should eq @user_mock.username
    end

    it 'persists tags as an array if the backend supports it' do
      Carto::Permission.any_instance.stubs(:update_shared_entities).returns(nil)

      attributes  = random_attributes_for_vis_member(user_id: @user_mock.id, tags: ['tag 1', 'tag 2'])
      member      = Visualization::Member.new(attributes)
      member.store

      member = Visualization::Member.new(id: member.id)
      member.fetch
      member.tags.should include('tag 1')
      member.tags.should include('tag 2')
      member.delete
    end

    it 'persists tags as JSON if the backend does not support arrays' do
      attributes  = random_attributes_for_vis_member(user_id: @user_mock.id, tags: ['tag 1', 'tag 2'])
      member      = Visualization::Member.new(attributes)
      member.store

      member = Visualization::Member.new(id: member.id)
      member.fetch
      member.tags.should include('tag 1')
      member.tags.should include('tag 2')
    end

    it 'prevents empty tags from being created' do
      attributes = random_attributes_for_vis_member(user_id: @user_mock.id, tags: ['tag 1', '', '   '])
      member = Visualization::Member.new(attributes)
      member.store

      member = Visualization::Member.new(id: member.id)
      member.fetch
      member.tags.should eq ['tag 1']
    end

    it 'invalidates vizjson cache in varnish if name changed' do
      member      = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))
      member.store

      CartoDB::Visualization::NameChecker.any_instance.stubs(:available?).returns(true)

      member = Visualization::Member.new(id: member.id).fetch
      CartoDB::Varnish.any_instance.expects(:purge).with(member.varnish_vizjson_key)
      member.name = 'changed'
      member.store
    end

    it 'invalidates vizjson cache in varnish if privacy changed' do
      # Need to at least have this decorated in the user data or checks before becoming private will raise an error
      CartoDB::Visualization::Member.any_instance.stubs(:supports_private_maps?).returns(true)

      member      = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))
      member.store

      member = Visualization::Member.new(id: member.id).fetch
      CartoDB::Varnish.any_instance.expects(:purge).with(member.varnish_vizjson_key)
      member.privacy = Visualization::Member::PRIVACY_PRIVATE
      member.store
    end

    it 'invalidates vizjson cache in varnish if description changed' do
      member      = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))
      member.store

      member = Visualization::Member.new(id: member.id).fetch
      CartoDB::Varnish.any_instance.expects(:purge).with(member.varnish_vizjson_key)
      member.description = 'changed description'
      member.store
    end
  end

  describe '#fetch' do
    before { stub_visualization_member_user }

    it 'fetches attributes from the data repository' do
      attributes  = random_attributes_for_vis_member(user_id: @user_mock.id)
      member      = Visualization::Member.new(attributes).store
      member      = Visualization::Member.new(id: member.id)
      member.name = 'changed'
      member.fetch
      member.name.should == attributes.fetch(:name)
    end
  end

  describe '#delete' do
    let(:member) { Visualization::Member.new(random_attributes_for_vis_member(user_id: user.id)).store }

    it 'fails for viewer users' do
      member.fetch
      user.update!(viewer: true)

      expect { member.delete }.to raise_error(CartoDB::InvalidMember, /Viewer users can't delete visualizations/)
    end

    it 'deletes this member data from the data repository' do
      member.delete

      expect { member.fetch }.to raise_error KeyError
    end

    it 'invalidates vizjson cache' do
      Carto::Visualization.any_instance.stubs(:perform_invalidations).once

      member.delete
    end

    describe 'dataset' do
      include Carto::Factories::Visualizations

      before(:all) do
        @user = create(:carto_user)
        @other_table = create(:carto_user_table, user: @user)
      end

      before(:each) do
        @map, @table, @table_visualization, @visualization = create_full_visualization(@user)
        @visualization.data_layers.first.user_tables << @table
      end

      after(:each) do
        destroy_full_visualization(@map, @table, @table_visualization, @visualization)
      end

      after(:all) do
        @user.destroy
      end

      it 'destroys maps if they are dependent' do
        table_visualization = CartoDB::Visualization::Member.new(id: @table_visualization.id).fetch
        table_visualization.delete

        Carto::Visualization.exists?(@visualization.id).should be_false
      end

      it 'destroys maps with join analyses if they are dependent' do
        # First layer uses tables @table, Second layer uses tables @table and @other_table. Map is dependent on @table
        layer = build(:carto_layer, kind: 'carto', maps: [@map])
        layer.options[:query] = "SELECT * FROM #{@other_table.name}"
        layer.save
        layer.user_tables << @table << @other_table

        table_visualization = CartoDB::Visualization::Member.new(id: @table_visualization.id).fetch
        table_visualization.delete

        Carto::Visualization.exists?(@visualization.id).should be_false
      end

      it 'unlinks only dependent data layers' do
        layer_to_be_deleted = @visualization.data_layers.first
        layer = build(:carto_layer, kind: 'carto', maps: [@map])
        layer.options[:query] = "SELECT * FROM #{@other_table.name}"
        layer.save
        layer.user_tables << @other_table

        table_visualization = CartoDB::Visualization::Member.new(id: @table_visualization.id).fetch
        table_visualization.delete

        Carto::Visualization.exists?(@visualization.id).should be_true
        Carto::Layer.exists?(layer_to_be_deleted.id).should be_false
        Carto::Layer.exists?(layer.id).should be_true
      end

      it 'deletes related analysis' do
        layer_to_be_deleted = @visualization.data_layers.first
        layer_to_be_deleted.options[:source] = 'a0'
        layer_to_be_deleted.save
        layer_to_be_deleted.user_tables << @table

        layer = build(:carto_layer, kind: 'carto', maps: [@map])
        layer.options[:source] = 'b0'
        layer.save
        layer.user_tables << @other_table

        # We are doing dependencies manually because the physical table does not exist
        Carto::Map.any_instance.stubs(:update_dataset_dependencies)
        analysis_to_be_deleted = @visualization.analyses.create(user: @user, analysis_definition: { id: 'a0' })
        analysis_to_keep = @visualization.analyses.create(user: @user, analysis_definition: { id: 'b0' })

        table_visualization = CartoDB::Visualization::Member.new(id: @table_visualization.id).fetch
        table_visualization.delete

        Carto::Visualization.exists?(@visualization.id).should be_true
        Carto::Layer.exists?(layer_to_be_deleted.id).should be_false
        Carto::Layer.exists?(layer.id).should be_true
        Carto::Analysis.exists?(analysis_to_be_deleted.id).should be_false
        Carto::Analysis.exists?(analysis_to_keep.id).should be_true
      end

      it 'does not delete any metadata in case of deletion error' do
        canonical_map = @table_visualization.map
        canonical_layer = @table_visualization.data_layers.first
        Table.any_instance.stubs(:remove_table_from_user_database).raises(Sequel::DatabaseError.new('cannot drop'))

        table_visualization = CartoDB::Visualization::Member.new(id: @table_visualization.id).fetch
        expect { table_visualization.delete }.to raise_error

        Carto::Visualization.exists?(@visualization.id).should be_true
        Carto::Visualization.exists?(@table_visualization.id).should be_true
        Carto::Layer.exists?(canonical_layer.id).should be_true
        Carto::UserTable.exists?(@table.id).should be_true
        Carto::Map.exists?(canonical_map.id).should be_true

        Table.any_instance.unstub(:remove_table_from_user_database)
      end
    end
  end

  describe '#unlink_from' do
    before { stub_visualization_member_user }

    it 'invalidates varnish cache' do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id)).store
      member.expects(:invalidate_cache)
      member.expects(:remove_layers_from)
      member.unlink_from(Object.new)
    end
  end

  describe '#public?' do
    before { stub_visualization_member_user }

    it 'returns true if privacy set to public' do
      visualization = Visualization::Member.new(privacy: 'public')
      visualization.public?.should == true

      visualization.privacy = Visualization::Member::PRIVACY_PRIVATE
      visualization.public?.should == false

      visualization.privacy = Visualization::Member::PRIVACY_PUBLIC
      visualization.public?.should == true
    end
  end

  describe '#permissions' do
    before { stub_visualization_member_user }

    it 'checks is_owner? permissions' do
      user_id  = Carto::UUIDHelper.random_uuid
      member  = Visualization::Member.new(name: 'foo', user_id: user_id)

      user    = OpenStruct.new(id: user_id)
      member.is_owner?(user).should == true

      user    = OpenStruct.new(id: Carto::UUIDHelper.random_uuid)
      member.is_owner?(user).should == false
    end

    def mock_user(username, viewer: false)
      user_mock = mock
      user_mock.stubs(:id).returns(Carto::UUIDHelper.random_uuid)
      user_mock.stubs(:username).returns(username)
      user_mock.stubs(:organization).returns(nil)
      user_mock.stubs(:viewer).returns(viewer)
      user_mock
    end

    it 'checks has_permission? permissions' do
      Carto::Permission.any_instance.stubs(:grant_db_permission).returns(nil)
      Carto::Permission.any_instance.stubs(:notify_permissions_change).returns(nil)

      read_user = create(:carto_user)
      write_user = create(:carto_user)
      unauthorized_user = create(:carto_user)

      _map, _table, visualization, _map_visualization = create_full_visualization(@user)

      acl = [
        {
          type: Carto::Permission::TYPE_USER,
          entity: { id: read_user.id, username: read_user.username },
          access: Carto::Permission::ACCESS_READONLY
        },
        {
          type: Carto::Permission::TYPE_USER,
          entity: { id: write_user.id, username: write_user.username },
          access: Carto::Permission::ACCESS_READWRITE
        }
      ]
      acl_expected = [
        {
          type: Carto::Permission::TYPE_USER,
          id: read_user.id,
          access: Carto::Permission::ACCESS_READONLY
        },
        {
          type: Carto::Permission::TYPE_USER,
          id: write_user.id,
          access: Carto::Permission::ACCESS_READWRITE
        }
      ]

      visualization.permission.update!(acl: acl)

      expect(visualization.permission.acl).to eq(acl_expected)

      expect(visualization.has_permission?(read_user, Visualization::Member::PERMISSION_READONLY)).to be_true
      expect(visualization.has_permission?(read_user, Visualization::Member::PERMISSION_READWRITE)).to be_false

      expect(visualization.has_permission?(write_user, Visualization::Member::PERMISSION_READONLY)).to be_true
      expect(visualization.has_permission?(write_user, Visualization::Member::PERMISSION_READWRITE)).to be_true

      expect(visualization.has_permission?(unauthorized_user, Visualization::Member::PERMISSION_READONLY)).to be_false
      expect(visualization.has_permission?(unauthorized_user, Visualization::Member::PERMISSION_READWRITE)).to be_false

      delete_user_data(@user)
    end
  end

  describe 'validations' do
    before { stub_visualization_member_user }

    describe '#privacy' do
      it 'must be present' do
        visualization = Visualization::Member.new
        visualization.valid?.should == false
        visualization.errors.fetch(:privacy).should_not be_nil
      end

      it 'must be valid' do
        visualization = Visualization::Member.new(privacy: 'wadus')
        visualization.valid?.should == false
        visualization.errors.fetch(:privacy).should_not be_nil
      end
    end

    describe '#name' do
      it 'downcases names for table_visualizations' do
        visualization = Visualization::Member.new(type: 'table')
        visualization.name = 'visualization_1'
        visualization.name.should == 'visualization_1'
        visualization.name = 'Visualization_1'
        visualization.name.should == 'visualization_1'

        visualization = Visualization::Member.new(type: 'derived')
        visualization.name = 'Visualization 1'
        visualization.name.should == 'Visualization 1'
      end
    end

    describe '#full_errors' do
      it 'returns full error messages' do
        visualization = Visualization::Member.new
        visualization.valid?.should == false

        visualization.full_errors.join("\n").should =~ /privacy/
        visualization.full_errors.join("\n").should =~ /type/
        visualization.full_errors.join("\n").should =~ /name/
      end
    end
  end

  describe '#derived?' do
    before { stub_visualization_member_user }

    it 'returns true if type is derived' do
      visualization = Visualization::Member.new(type: Visualization::Member::TYPE_DERIVED)
      visualization.derived?.should be_true
      visualization.table?.should be_false
      visualization.type_slide?.should be_false

      visualization.type = 'bogus'
      visualization.derived?.should be_false
      visualization.table?.should be_false
      visualization.type_slide?.should be_false
    end
  end

  describe '#table?' do
    before { stub_visualization_member_user }

    it "returns true if type is 'table'" do
      visualization = Visualization::Member.new(type: Visualization::Member::TYPE_CANONICAL)
      visualization.derived?.should be_false
      visualization.table?.should be_true
      visualization.type_slide?.should be_false

      visualization.type = 'bogus'
      visualization.derived?.should be_false
      visualization.table?.should be_false
      visualization.type_slide?.should be_false
    end
  end

  describe '#type_slide?' do
    before { stub_visualization_member_user }

    it "returns true if type is 'slide'" do
      visualization = Visualization::Member.new(type: Visualization::Member::TYPE_SLIDE)
      visualization.derived?.should be_false
      visualization.table?.should be_false
      visualization.type_slide?.should be_true

      visualization.type = 'bogus'
      visualization.derived?.should be_false
      visualization.table?.should be_false
      visualization.type_slide?.should be_false
    end
  end

  describe '#privacy_and_exceptions' do
    before { stub_visualization_member_user }

    it 'checks different privacy options to make sure exceptions are raised when they should' do
      user_id = Carto::UUIDHelper.random_uuid

      visualization = Visualization::Member.new(type: Visualization::Member::TYPE_DERIVED)
      visualization.name = 'test'
      visualization.user_id = user_id

      @user_mock.stubs(:invalidate_varnish_cache)


      # Private maps allowed
      @user_mock.stubs(:private_maps_enabled?).returns(true)

      # Forces internal "dirty" flag
      visualization.privacy = Visualization::Member::PRIVACY_PUBLIC
      visualization.privacy = Visualization::Member::PRIVACY_PRIVATE

      visualization.store

      # -------------

      # No private maps allowed
      @user_mock.stubs(:private_maps_enabled?).returns(false)

      visualization.privacy = Visualization::Member::PRIVACY_PUBLIC
      visualization.privacy = Visualization::Member::PRIVACY_PRIVATE

      # Derived table without private maps flag = error
      expect {
        visualization.store
      }.to raise_exception CartoDB::InvalidMember

      # -------------

      visualization = Visualization::Member.new(type: Visualization::Member::TYPE_CANONICAL)
      visualization.name = 'test'
      visualization.user_id = user_id
      # No private maps allowed yet

      visualization.privacy = Visualization::Member::PRIVACY_PUBLIC
      visualization.privacy = Visualization::Member::PRIVACY_PRIVATE

      # Canonical visualizations can always be private
      visualization.store
    end
  end

  describe '#validation_for_link_privacy' do
    before { stub_visualization_member_user }

    it 'checks that only users with private tables enabled can set LINK privacy' do
      user_id = Carto::UUIDHelper.random_uuid
      Visualization::Member.any_instance.stubs(:named_maps)

      visualization = Visualization::Member.new(
          privacy: Visualization::Member::PRIVACY_PUBLIC,
          name:     'test',
          type:     Visualization::Member::TYPE_CANONICAL,
          user_id:  user_id
      )
      @user_mock.stubs(:private_maps_enabled?).returns(true)

      # Careful, do a user mock after touching user_data as it does some checks about user too
      user_mock = mock
      user_mock.stubs(:viewer).returns(false)
      user_mock.stubs(:private_tables_enabled).returns(true)
      user_mock.stubs(:id).returns(user_id)
      Visualization::Member.any_instance.stubs(:user).returns(user_mock)

      visualization.valid?.should eq true

      visualization.privacy = Visualization::Member::PRIVACY_PRIVATE
      visualization.valid?.should eq true


      visualization.privacy = Visualization::Member::PRIVACY_LINK
      visualization.valid?.should eq true

      visualization.privacy = Visualization::Member::PRIVACY_PUBLIC

      user_mock.stubs(:private_tables_enabled).returns(false)

      visualization.valid?.should eq true

      visualization.privacy = Visualization::Member::PRIVACY_LINK
      visualization.valid?.should eq false

      # "Reset"
      visualization = Visualization::Member.new(
          privacy: Visualization::Member::PRIVACY_LINK,
          name: 'test',
          type: Visualization::Member::TYPE_CANONICAL,
          user_id:  user_id
      )
      # Unchanged visualizations could be
      visualization.valid?.should eq true

      # Simulate editing the privacy
      visualization.stubs(:privacy_changed).returns(true)
      # Now it can't
      visualization.valid?.should eq false
    end
  end

  describe '#default_privacy_values' do
    before { stub_visualization_member_user }

    it 'Checks deault privacies for visualizations' do
      user_id = Carto::UUIDHelper.random_uuid
      user_mock = mock
      user_mock.stubs(:id).returns(user_id)

      # We don't care about values, just want an instance
      visualization = Visualization::Member.new(
          privacy: Visualization::Member::PRIVACY_PUBLIC,
          name: 'test',
          type: Visualization::Member::TYPE_CANONICAL,
          user_id: user_id
      )

      user_mock.stubs(:private_tables_enabled).returns(true)
      visualization.stubs(:user).returns(user_mock)
      visualization.default_privacy.should eq Visualization::Member::PRIVACY_LINK

      user_mock.stubs(:private_tables_enabled).returns(false)
      visualization.stubs(:user).returns(user_mock)
      visualization.default_privacy.should eq Visualization::Member::PRIVACY_PUBLIC
    end
  end

  it 'should not allow to change permission from the outside' do
    member = Visualization::Member.new(random_attributes_for_vis_member({user_id: @user.id}))
    member.store
    member = Visualization::Member.new(id: member.id).fetch
    member.permission.should_not be nil
    member.permission_id = Carto::UUIDHelper.random_uuid
    member.valid?.should eq false
    delete_user_data(@user)
  end

  it 'checks that slides can have a parent_id' do
    stub_visualization_member_user

    Visualization::Member.any_instance.stubs(:supports_private_maps?).returns(true)

    expected_errors = { parent_id: 'Type slide must have a parent' }

    member = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                          type: Visualization::Member::TYPE_DERIVED }))
    member.store

    member = Visualization::Member.new(id: member.id).fetch
    member.parent_id.should be nil
    member.parent.should be nil

    child_member = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                                type: Visualization::Member::TYPE_SLIDE }))
    # Can't save a children of type slide without parent_id
    expect {
      child_member.store
    }.to raise_exception InvalidMember
    child_member.valid?.should eq false
    child_member.errors.should eq expected_errors

    child_member = Visualization::Member.new(random_attributes_for_vis_member({
      user_id: @user_mock.id,
      type:       Visualization::Member::TYPE_SLIDE,
      parent_id:  member.id
    }))
    child_member.store

    child_member = Visualization::Member.new(id: child_member.id).fetch
    child_member.type.should eq Visualization::Member::TYPE_SLIDE
    child_member.parent_id.should eq member.id
    child_member.parent.id.should eq member.id

    expect {
      table_member = Visualization::Member.new(
        random_attributes_for_vis_member({
                                           user_id: @user_mock.id,
                                           type: Visualization::Member::TYPE_CANONICAL,
                                           parent_id: member.id
                                         }))
      table_member.store
    }.to raise_exception CartoDB::InvalidMember
    expect {
      table_member = Visualization::Member.new(
        random_attributes_for_vis_member({
                                           user_id: @user_mock.id,
                                           type:       Visualization::Member::TYPE_DERIVED,
                                           parent_id:  member.id
                                         }))
      table_member.store
    }.to raise_exception CartoDB::InvalidMember

    child_member = Visualization::Member.new(
      random_attributes_for_vis_member({
                                          user_id: @user_mock.id,
                                          type:       Visualization::Member::TYPE_SLIDE,
                                          parent_id:  Carto::UUIDHelper.random_uuid
                                        }))
    expect {
      child_member.store
    }.to raise_exception CartoDB::InvalidMember

    member_2 = Visualization::Member.new(
                                         random_attributes_for_vis_member({ user_id: @user_mock.id,
                                         type: Visualization::Member::TYPE_CANONICAL }))
    member_2.store.fetch
    child_member.parent_id = member_2.id
    expect {
      child_member.store
    }.to raise_exception InvalidMember

  end

  it 'tests transition_options field jsonification' do
    stub_visualization_member_user

    Visualization::Member.any_instance.stubs(:supports_private_maps?).returns(true)

    transition_options = { first: true, second: 6 }

    member = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                          transition_options: transition_options }))

    member.transition_options.should eq transition_options
    member.slide_transition_options.should eq ::JSON.dump(transition_options)

    transition_options[:third] = 'testing'

    member.transition_options = transition_options

    member.transition_options.should eq transition_options
    member.slide_transition_options.should eq ::JSON.dump(transition_options)

    member = member.store.fetch
    member.transition_options.should eq transition_options
    member.slide_transition_options.should eq ::JSON.dump(transition_options)
  end

  describe '#linked_list_tests' do
    before { stub_visualization_member_user }

    it 'checks set_next! and unlink_self_from_list! on visualizations when set' do
      Visualization::Member.any_instance.stubs(:supports_private_maps?).returns(true)

      member_a = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'A',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_a = member_a.store.fetch
      member_b = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'B',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_b = member_b.store.fetch
      member_c = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'C',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_c = member_c.store.fetch
      member_d = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'D',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_d = member_d.store.fetch

      # A
      member_a.prev_id.should eq nil
      member_a.prev_list_item.should eq nil
      member_a.next_id.should eq nil
      member_a.next_list_item.should eq nil

      # A -> B
      member_a.set_next_list_item! member_b

      member_a.next_list_item.should eq member_b
      member_a.prev_list_item.should eq nil
      member_b.prev_list_item.should eq member_a
      member_b.next_list_item.should eq nil

      # A -> B -> C
      member_b.set_next_list_item! member_c

      # set_next reloads "actor and subject", but other affected items need to be reloaded
      member_a.fetch

      member_a.prev_list_item.should eq nil
      member_a.next_list_item.should eq member_b
      member_b.prev_list_item.should eq member_a
      member_b.next_list_item.should eq member_c
      member_c.prev_list_item.should eq member_b
      member_c.next_list_item.should eq nil

      # A -> D -> B -> C
      member_a.set_next_list_item! member_d

      member_b.fetch
      member_c.fetch

      member_a.prev_list_item.should eq nil
      member_a.next_list_item.should eq member_d
      member_d.prev_list_item.should eq member_a
      member_d.next_list_item.should eq member_b
      member_b.prev_list_item.should eq member_d
      member_b.next_list_item.should eq member_c
      member_c.prev_list_item.should eq member_b
      member_c.next_list_item.should eq nil

      member_a.next_list_item.next_list_item.should eq member_b
      member_a.next_list_item.next_list_item.next_list_item.should eq member_c
      member_a.next_list_item.next_list_item.next_list_item.next_list_item.should eq nil

      # A -> D -> C
      member_b.delete
      # triggers unlink_self_from_list! inside, should reorder remaining items

      member_c.fetch
      member_d.fetch

      member_a.prev_list_item.should eq nil
      member_a.next_list_item.should eq member_d
      member_d.prev_list_item.should eq member_a
      member_d.next_list_item.should eq member_c
      member_c.prev_list_item.should eq member_d
      member_c.next_list_item.should eq nil

      # D -> C
      member_a.delete

      member_d.fetch

      member_d.prev_list_item.should eq nil
      member_d.next_list_item.should eq member_c
      member_c.prev_list_item.should eq member_d
      member_c.next_list_item.should eq nil

      # D
      member_c.delete

      member_d.fetch

      member_d.prev_list_item.should eq nil
      member_d.next_list_item.should eq nil

      member_d.delete
    end

    it 'checks reordering visualizations items' do
      Visualization::Member.any_instance.stubs(:supports_private_maps?).returns(true)

      member_a = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'A',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_a = member_a.store.fetch
      member_b = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'B',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_b = member_b.store.fetch
      member_c = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'C',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_c = member_c.store.fetch
      member_d = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'D',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_d = member_d.store.fetch
      member_e = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'E',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_e = member_e.store.fetch

      # A -> B
      member_a.set_next_list_item! member_b
      # A -> B -> C
      member_b.set_next_list_item! member_c
      member_a.fetch
      # A -> B -> C -> D
      member_c.set_next_list_item! member_d
      member_a.fetch
      member_b.fetch
      # A -> B -> C -> D -> E
      member_d.set_next_list_item! member_e
      member_a.fetch
      member_b.fetch
      member_c.fetch

      member_a.prev_list_item.should eq nil
      member_a.next_list_item.should eq member_b
      member_b.prev_list_item.should eq member_a
      member_b.next_list_item.should eq member_c
      member_c.prev_list_item.should eq member_b
      member_c.next_list_item.should eq member_d
      member_d.prev_list_item.should eq member_c
      member_d.next_list_item.should eq member_e
      member_e.prev_list_item.should eq member_d
      member_e.next_list_item.should eq nil

      # Actual reordering starts here
      # -----------------------------

      # B -> A -> C -> D -> E
      member_b.set_next_list_item! member_a

      member_c.fetch

      member_b.prev_list_item.should eq nil
      member_b.next_list_item.should eq member_a
      member_a.prev_list_item.should eq member_b
      member_a.next_list_item.should eq member_c
      member_c.prev_list_item.should eq member_a
      member_c.next_list_item.should eq member_d
      member_d.prev_list_item.should eq member_c
      member_d.next_list_item.should eq member_e
      member_e.prev_list_item.should eq member_d
      member_e.next_list_item.should eq nil

      # B -> A -> D -> E -> C
      member_e.set_next_list_item! member_c

      member_a.fetch
      member_d.fetch

      member_b.prev_list_item.should eq nil
      member_b.next_list_item.should eq member_a
      member_a.prev_list_item.should eq member_b
      member_a.next_list_item.should eq member_d
      member_d.prev_list_item.should eq member_a
      member_d.next_list_item.should eq member_e
      member_e.prev_list_item.should eq member_d
      member_e.next_list_item.should eq member_c
      member_c.prev_list_item.should eq member_e
      member_c.next_list_item.should eq nil

      # B -> E -> A -> D -> C

      member_b.set_next_list_item! member_e

      member_a.fetch
      member_c.fetch
      member_d.fetch

      member_b.prev_list_item.should eq nil
      member_b.next_list_item.should eq member_e
      member_e.prev_list_item.should eq member_b
      member_e.next_list_item.should eq member_a
      member_a.prev_list_item.should eq member_e
      member_a.next_list_item.should eq member_d
      member_d.prev_list_item.should eq member_a
      member_d.next_list_item.should eq member_c
      member_c.prev_list_item.should eq member_d
      member_c.next_list_item.should eq nil

      # Cleanup
      member_a.delete
      member_b.fetch
      member_b.delete
      member_c.fetch
      member_c.delete
      member_d.fetch
      member_d.delete
      member_e.fetch
      member_e.delete
    end

    it 'checks that upon destruction children are destroyed too' do
      Visualization::Member.any_instance.stubs(:supports_private_maps?).returns(true)
      support_tables_mock = Doubles::Visualization::SupportTables.new
      Visualization::Relator.any_instance.stubs(:support_tables).returns(support_tables_mock)

      starting_collection_count = Visualization::Collection.new.fetch.count

      parent = Visualization::Member.new(
        random_attributes_for_vis_member(
          user_id: @user_mock.id,
          name: 'PARENT',
          type: Visualization::Member::TYPE_DERIVED
        )
      ).store

      child1 = Visualization::Member.new(
        random_attributes_for_vis_member(
          user_id: @user_mock.id,
          name: 'CHILD 1',
          type: Visualization::Member::TYPE_SLIDE,
          parent_id: parent.id
        )
      ).store.fetch

      child2 = Visualization::Member.new(
        random_attributes_for_vis_member(
          user_id: @user_mock.id,
          name: 'CHILD 2',
          type: Visualization::Member::TYPE_SLIDE,
          parent_id: parent.id
        )
      ).store.fetch

      child2.set_prev_list_item!(child1)
      parent.fetch

      parent.delete

      Visualization::Collection.new.fetch.count.should eq starting_collection_count
    end

    it 'checks transactional wrappings for prev-next' do
      Visualization::Member.any_instance.stubs(:supports_private_maps?).returns(true)

      member_a = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'A',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_a = member_a.store.fetch
      member_b = Visualization::Member.new(random_attributes_for_vis_member({ user_id: @user_mock.id,
                                                                              name:'B',
                                                                              type: Visualization::Member::TYPE_DERIVED }))
      member_b = member_b.store.fetch

      # trick to not stub but also check that validator and other internal fields are reset upon fetch, etc.
      member_b.privacy = 'invalid value'

      expect {
        member_a.set_next_list_item! member_b
      }.to raise_error

      member_a.fetch
      member_b.fetch
      member_a.next_id.should eq nil
      member_b.prev_id.should eq nil
      member_b.privacy.should eq Visualization::Member::PRIVACY_PUBLIC


      member_b.privacy = 'invalid value'

      expect {
        member_b.set_prev_list_item! member_a
      }.to raise_error

      member_a.fetch
      member_b.fetch
      member_a.next_id.should eq nil
      member_b.prev_id.should eq nil
      member_b.privacy.should eq Visualization::Member::PRIVACY_PUBLIC


      member_a.set_next_list_item! member_b
      member_a.privacy = 'invalid value'

      CartoDB::Visualization::Relator.any_instance.stubs(:next_list_item) do
        raise "Forced error"
      end

      expect {
        member_a.unlink_self_from_list!
      }.to raise_error

      member_a = member_a.fetch
      member_b = member_b.fetch
      member_a.next_id.should eq member_b.id
      member_b.prev_id.should eq member_a.id
    end
  end

  describe '#to_vizjson' do
    before { stub_visualization_member_user }

    it "calculates and returns the vizjson if not cached" do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))
      member.store
      mocked_vizjson = {mocked: 'vizjson'}
      member.expects(:calculate_vizjson).returns(mocked_vizjson).once

      member.to_vizjson.should eq mocked_vizjson
    end

    it "Returns the vizjson if it was cached before" do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))
      member.store
      mocked_vizjson = {mocked: 'vizjson'}
      member.expects(:calculate_vizjson).returns(mocked_vizjson).once

      vizjson = member.to_vizjson # calculate and cache
      member.to_vizjson.should eq vizjson
    end
  end

  describe '#redis_cached' do
    before { stub_visualization_member_user }

    it "Uses the block given to calculate a hash if there's a cache miss" do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))

      redis_cache = mock
      redis_cache.expects(:get).returns(nil).once # cache miss
      redis_cache.expects(:setex).once
      Visualization::RedisVizjsonCache.any_instance.stubs(:redis).returns(redis_cache)

      any_hash = {
        any_key: 'any_value'
      }
      member.expects(:calculate_vizjson).once.returns(any_hash)

      member.to_vizjson.should eq any_hash
    end

    it "Caches an arbitrary hash serialized if there's a miss " do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))

      any_hash = {
        any_key: 'any_value'
      }

      key = 'any_key'
      Visualization::RedisVizjsonCache.any_instance.stubs(:key).returns(key)
      redis_cache = mock
      redis_cache.expects(:get).returns(nil).once # cache miss
      redis_cache.expects(:setex).once.with(key, 24.hours.to_i, any_hash.to_json)
      Visualization::RedisVizjsonCache.any_instance.stubs(:redis).returns(redis_cache)
      member.expects(:calculate_vizjson).once.returns(any_hash)

      member.to_vizjson.should eq any_hash
    end

    it "Deserializes an arbitrary hash previously stored if there's a cache hit" do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))

      any_hash = {
        any_key: 'any_value'
      }
      any_hash_serialized = any_hash.to_json

      key = 'any_key'
      redis_cache = mock
      redis_cache.expects(:get).returns(any_hash_serialized).once # cache hit
      redis_cache.expects(:setex).never
      Visualization::RedisVizjsonCache.any_instance.stubs(:redis).returns(redis_cache)
      member.expects(:calculate_vizjson).never

      member.to_vizjson.should eq any_hash
    end

    it "Does not execute the block if there's a cache hit" do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))

      any_hash_serialized = {}.to_json

      key = 'any_key'
      redis_cache = mock
      redis_cache.expects(:get).once.returns(any_hash_serialized) # cache hit
      redis_cache.expects(:setex).never
      Visualization::RedisVizjsonCache.any_instance.stubs(:redis).returns(redis_cache)
      member.expects(:calculate_vizjson).never

      member.to_vizjson
    end
  end

  describe '#invalidate_cache' do
    before { stub_visualization_member_user }

    it "Invalidates the varnish and redis caches" do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))
      member.expects(:invalidate_varnish_vizjson_cache).once
      member.expects(:invalidate_redis_cache).once

      member.invalidate_cache
    end
  end

  describe '#invalidate_redis_cache' do
    before { stub_visualization_member_user }

    it "Invalidates the vizjson in redis cache" do
      member = Visualization::Member.new(random_attributes_for_vis_member(user_id: @user_mock.id))
      member.store
      mocked_vizjson = {mocked: 'vizjson'}
      member.expects(:calculate_vizjson).returns(mocked_vizjson).once

      vizjson = member.to_vizjson
      redis_vizjson_cache = Visualization::RedisVizjsonCache.new($tables_metadata)
      redis_key = redis_vizjson_cache.key(member.id)
      redis_vizjson_cache.send(:redis).get(redis_key).should eq vizjson.to_json

      member.invalidate_cache
      redis_vizjson_cache.send(:redis).get(redis_key).should be_nil
    end
  end

  describe 'licenses' do

    before(:all) do
      @user = create_user
    end

    after(:all) do
      @user.delete
    end

    it 'should store correctly a visualization with its license' do
      table = create_table({:name => 'table1', :user_id => @user.id})
      vis = CartoDB::Visualization::Member.new(id: table.table_visualization.id).fetch
      vis.license = "apache"
      vis.store
      vis.fetch
      vis.license_info.id.should eq :apache
      vis.license_info.name.should eq "Apache license"
    end

    it 'should return nil if the license is nil, empty or unkown' do
      table = create_table({:name => 'table1', :user_id => @user.id})
      vis = CartoDB::Visualization::Member.new(id: table.table_visualization.id).fetch
      vis.license = nil
      vis.store
      vis.fetch
      vis.license_info.nil?.should eq true
      vis.license = ""
      vis.store
      vis.fetch
      vis.license_info.nil?.should eq true
      # I cant save with a wrong value
      vis.stubs(:license).returns("lololo")
      vis.license_info.nil?.should eq true
    end

    it 'should raise exception when try to store a unknown license, empty or nil' do
      table = create_table({:name => 'table1', :user_id => @user.id})
      vis = CartoDB::Visualization::Member.new(id: table.table_visualization.id).fetch
      vis.license = "wadus"
      expect {
        vis.store
      }.to raise_error CartoDB::InvalidMember
      vis.license = ""
      expect {
        vis.store
      }.to raise_error CartoDB::InvalidMember
      vis.license = nil
      expect {
        vis.store
      }.to raise_error CartoDB::InvalidMember
    end
  end
  describe 'remote member' do
    before(:all) do
      @user = create_user
      @name = "example_name"
      @display_name = "Example display name"
      @user_id = @user.id
      @privacy = "public"
      @description = "Example description"
      @tags = ["tag1", "tag2"]
      @license = "apache"
      @source = "[source](http://www.example.com)"
      @attributions = "Attributions example"
    end

    it 'should create a new remote member' do
      remote_member = CartoDB::Visualization::Member.remote_member(
        @name, @user_id, @privacy, @description, @tags, @license, @source, @attributions, @display_name
      )
      remote_member.name.should eq @name
      remote_member.user_id.should eq @user_id
      remote_member.privacy.should eq @privacy
      remote_member.description.should eq @description
      remote_member.tags.should eq @tags
      remote_member.license.should eq @license
      remote_member.source.should eq @source
      remote_member.attributions.should eq @attributions
      remote_member.display_name.should eq @display_name
    end
  end
end
