require 'spec_helper'

describe 'Leaderboard' do
  before(:each) do
    @redis_connection = Redis.new(:host => "127.0.0.1", :db => 15)
    @leaderboard = Leaderboard.new('name', Leaderboard::DEFAULT_OPTIONS, {:host => "127.0.0.1", :db => 15})
  end

  after(:each) do
    @redis_connection.flushdb
    @leaderboard.disconnect
    @redis_connection.client.disconnect
  end

  it 'should be initialized with defaults' do
    expect(@leaderboard.leaderboard_name).to eql('name')
    expect(@leaderboard.page_size).to eql(Leaderboard::DEFAULT_PAGE_SIZE)
  end

  it 'should be able to disconnect its connection to Redis' do
    expect(@leaderboard.disconnect).to be_nil
  end

  it 'should automatically reconnect to Redis after a disconnect' do
    expect(@leaderboard.total_members).to be(0)
    rank_members_in_leaderboard(5)
    expect(@leaderboard.total_members).to be(5)
    expect(@leaderboard.disconnect).to be_nil
    expect(@leaderboard.total_members).to be(5)
  end

  it 'should set the page size to the default page size if passed an invalid value' do
    some_leaderboard = Leaderboard.new('name', {:page_size => 0}, {:host => "127.0.0.1", :db => 15})

    expect(some_leaderboard.page_size).to be(Leaderboard::DEFAULT_PAGE_SIZE)
    some_leaderboard.disconnect
  end

  it 'should allow you to delete a leaderboard' do
    rank_members_in_leaderboard

    expect(@redis_connection.exists('name')).to be_truthy
    expect(@redis_connection.exists('name:member_data')).to be_truthy
    @leaderboard.delete_leaderboard
    expect(@redis_connection.exists('name')).to be_falsey
    expect(@redis_connection.exists('name:member_data')).to be_falsey
  end

  it 'should allow you to pass in an existing redis connection in the initializer' do
    @leaderboard = Leaderboard.new('name', Leaderboard::DEFAULT_OPTIONS, {:redis_connection => @redis_connection})
    rank_members_in_leaderboard

    expect(@redis_connection.info["connected_clients"].to_i).to be >= 1
  end

  it 'should allow you to rank a member and see that reflected in total members' do
    @leaderboard.rank_member('member', 1)

    expect(@leaderboard.total_members).to be(1)
  end

  it 'should return the correct number of members in a given score range' do
    rank_members_in_leaderboard(5)

    expect(@leaderboard.total_members_in_score_range(2, 4)).to be(3)
  end

  it 'should return the correct rank when calling rank_for' do
    rank_members_in_leaderboard(5)

    expect(@leaderboard.rank_for('member_4')).to be(2)
  end

  it 'should return the correct score when calling score_for' do
    rank_members_in_leaderboard(5)

    expect(@leaderboard.score_for('member_4')).to eql(4.0)
    expect(@leaderboard.score_for('jones')).to be_nil
  end

  it 'should return the correct total pages' do
    rank_members_in_leaderboard(10)

    expect(@leaderboard.total_pages).to be(1)
    expect(@leaderboard.total_pages(5)).to be(2)

    @redis_connection.flushdb

    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE + 1)

    expect(@leaderboard.total_pages).to be(2)
  end

  it 'should return the correct list when calling leaders' do
    rank_members_in_leaderboard(25)

    expect(@leaderboard.total_members).to be(25)

    leaders = @leaderboard.leaders(1)

    expect(leaders.size).to be(25)
    expect(leaders[0][:member]).to eql 'member_25'
    expect(leaders[-2][:member]).to eql 'member_2'
    expect(leaders[-1][:member]).to eql 'member_1'
    expect(leaders[-1][:score].to_i).to be(1)
  end

  it 'should return the correct list when calling members' do
    rank_members_in_leaderboard(25)

    expect(@leaderboard.total_members).to be(25)

    members = @leaderboard.members(1)

    expect(members.size).to be(25)
    expect(members[0][:member]).to eql('member_25')
    expect(members[-2][:member]).to eql('member_2')
    expect(members[-1][:member]).to eql('member_1')
    expect(members[-1][:score].to_i).to be(1)
  end

  it 'should return the correct number of members when calling leaders with multiple pages' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1)

    expect(@leaderboard.total_members).to be(Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1)

    leaders = @leaderboard.leaders(1)
    expect(leaders.size).to be(@leaderboard.page_size)

    leaders = @leaderboard.leaders(2)
    expect(leaders.size).to be(@leaderboard.page_size)

    leaders = @leaderboard.leaders(3)
    expect(leaders.size).to be(@leaderboard.page_size)

    leaders = @leaderboard.leaders(4)
    expect(leaders.size).to be(1)

    leaders = @leaderboard.leaders(-5)
    expect(leaders.size).to be(@leaderboard.page_size)

    leaders = @leaderboard.leaders(10)
    expect(leaders.size).to be(1)
  end

  %w(members leaders).each do |method|
    it "should return the entire leaderboard when you call 'all_#{method}'" do
      rank_members_in_leaderboard(27)

      expect(@leaderboard.total_members).to be(27)

      members = @leaderboard.send("all_#{method}")

      expect(members.size).to be(27)
      expect(members[0][:member]).to eql('member_27')
      expect(members[-2][:member]).to eql('member_2')
      expect(members[-1][:member]).to eql('member_1')
      expect(members[-1][:score].to_i).to be(1)
    end
  end

  it 'should allow you to retrieve members in a given score range' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)

    members = @leaderboard.members_from_score_range(10, 15)

    member_15 = {:member => 'member_15', :score => 15.0, :rank => 11}
    expect(members[0]).to eql(member_15)

    member_10 = {:member => 'member_10', :score => 10.0, :rank => 16}
    expect(members[5]).to eql(member_10)

    members = @leaderboard.members_from_score_range(10, 15, {:with_member_data => true})

    member_15 = {:member => 'member_15', :rank => 11, :score => 15.0, :member_data => {:member_name => 'Leaderboard member 15'}.to_s}
    expect(members[0]).to eq(member_15)

    member_10 = {:member => 'member_10', :rank => 16, :score => 10.0, :member_data => {:member_name => 'Leaderboard member 10'}.to_s}
    expect(members[5]).to eq(member_10)
  end

  it 'should allow you to retrieve leaders with extra data' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)

    expect(@leaderboard.total_members).to be(Leaderboard::DEFAULT_PAGE_SIZE)
    leaders = @leaderboard.leaders(1, {:with_member_data => true})

    member_25 = {:member => 'member_25', :score => 25.0, :rank => 1, :member_data => { :member_name => "Leaderboard member 25" }.to_s }
    expect(leaders[0]).to eq(member_25)

    member_1 = {:member => 'member_1', :score => 1.0, :rank => 25, :member_data => { :member_name => "Leaderboard member 1" }.to_s }
    expect(leaders[24]).to eq(member_1)
  end

  it 'should allow you to retrieve optional member data' do
    @leaderboard.rank_member('member_id', 1, {'username' => 'member_name', 'other_data_key' => 'other_data_value'})

    expect(@leaderboard.member_data_for('unknown_member')).to be_nil
    expect(@leaderboard.member_data_for('member_id')).to eq({'username' => 'member_name', 'other_data_key' => 'other_data_value'}.to_s)
  end

  it 'should allow you to retrieve optional member data for multiple members' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)

    members_data = @leaderboard.members_data_for(['member_1', 'member_3'])
    expect(members_data[0]).to eq({:member_name => 'Leaderboard member 1'}.to_s)
    expect(members_data[1]).to eq({:member_name => 'Leaderboard member 3'}.to_s)
  end

  it 'should allow you to update optional member data' do
    @leaderboard.rank_member('member_id', 1, {'username' => 'member_name'})

    expect(@leaderboard.member_data_for('member_id')).to eq({'username' => 'member_name'}.to_s)
    @leaderboard.update_member_data('member_id', {'username' => 'member_name', 'other_data_key' => 'other_data_value'})
    expect(@leaderboard.member_data_for('member_id')).to eq({'username' => 'member_name', 'other_data_key' => 'other_data_value'}.to_s)
  end

  it 'should allow you to remove optional member data' do
    @leaderboard.rank_member('member_id', 1, {'username' => 'member_name'})

    expect(@leaderboard.member_data_for('member_id')).to eq({'username' => 'member_name'}.to_s)
    @leaderboard.remove_member_data('member_id')
    expect(@leaderboard.member_data_for('member_id')).to be_nil
  end

  it 'should allow you to call leaders with various options that respect the defaults for the options not passed in' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE + 1)

    leaders = @leaderboard.leaders(1, :page_size => 1)
    expect(leaders.size).to be(1)
    member_26 = {:member => 'member_26', :score => 26.0, :rank => 1}
    expect(leaders[0]).to eql(member_26)
  end

  it 'should return a single member when calling member_at' do
    rank_members_in_leaderboard(50)
    expect(@leaderboard.member_at(1)[:rank]).to eql(1)
    expect(@leaderboard.member_at(1)[:score]).to eql(50.0)
    expect(@leaderboard.member_at(26)[:rank]).to eql(26)
    expect(@leaderboard.member_at(50)[:rank]).to eql(50)
    expect(@leaderboard.member_at(51)).to be_nil
    expect(@leaderboard.member_at(1, :with_member_data => true)[:member_data]).to eq({:member_name => 'Leaderboard member 50'}.to_s)
  end

  it 'should not return a single member when calling member_at with a position < 0' do
    rank_members_in_leaderboard(5)
    expect(@leaderboard.member_at(1)[:rank]).to eql(1)
    expect(@leaderboard.member_at(-5)).to be nil
  end

  it 'should return the correct information when calling around_me' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1)

    expect(@leaderboard.total_members).to be(Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1)

    leaders_around_me = @leaderboard.around_me('member_30')
    expect(leaders_around_me.size / 2).to be(@leaderboard.page_size / 2)

    leaders_around_me = @leaderboard.around_me('member_1')
    expect(leaders_around_me.size).to be(@leaderboard.page_size / 2 + 1)

    leaders_around_me = @leaderboard.around_me('member_76')
    expect(leaders_around_me.size / 2).to be(@leaderboard.page_size / 2)
  end

  it 'should return the correct information when calling ranked_in_list' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)

    expect(@leaderboard.total_members).to be(Leaderboard::DEFAULT_PAGE_SIZE)

    members = ['member_1', 'member_5', 'member_10']
    ranked_members = @leaderboard.ranked_in_list(members, Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS)

    expect(ranked_members.size).to be(3)

    expect(ranked_members[0][:rank]).to be(25)
    expect(ranked_members[0][:score]).to eql(1.0)

    expect(ranked_members[1][:rank]).to be(21)
    expect(ranked_members[1][:score]).to eql(5.0)

    expect(ranked_members[2][:rank]).to be(16)
    expect(ranked_members[2][:score]).to eql(10.0)
  end

  it 'should return the correct information when calling ranked_in_list without scores' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)

    expect(@leaderboard.total_members).to be(Leaderboard::DEFAULT_PAGE_SIZE)

    members = ['member_1', 'member_5', 'member_10']
    ranked_members = @leaderboard.ranked_in_list(members, {:with_rank => true})
    expect(ranked_members.size).to be(3)
    expect(ranked_members[0][:rank]).to be(25)
    expect(ranked_members[1][:rank]).to be(21)
    expect(ranked_members[2][:rank]).to be(16)
  end

  it 'should allow you to remove members' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)

    expect(@leaderboard.total_members).to be(Leaderboard::DEFAULT_PAGE_SIZE)

    @leaderboard.remove_member('member_1')

    expect(@leaderboard.total_members).to be(Leaderboard::DEFAULT_PAGE_SIZE - 1)
    expect(@leaderboard.rank_for('member_1')).to be_nil
  end

  it 'should allow you to change the score for a member' do
    @leaderboard.rank_member('member_1', 5)
    expect(@leaderboard.score_for('member_1')).to eql(5.0)

    @leaderboard.change_score_for('member_1', 5)
    expect(@leaderboard.score_for('member_1')).to eql(10.0)

    @leaderboard.change_score_for('member_1', -5)
    expect(@leaderboard.score_for('member_1')).to eql(5.0)
  end

  it 'should allow you to change the score and member data for a member' do
    @leaderboard.change_score_for('member_1', 5, 'optional-data')
    expect(@leaderboard.score_for('member_1')).to eql(5.0)
    expect(@leaderboard.member_data_for('member_1')).to eql('optional-data')
  end

  it 'should allow you to check if a member exists' do
    @leaderboard.rank_member('member_1', 10)

    expect(@leaderboard.check_member?('member_1')).to be_truthy
    expect(@leaderboard.check_member?('member_2')).to be_falsey
  end

  it 'should allow you to change the page size and have that reflected in the size of the result set' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)

    @leaderboard.page_size = 5

    expect(@leaderboard.total_pages).to be(5)
    expect(@leaderboard.leaders(1).size).to be(5)
  end

  it 'should not allow you to set the page size to an invalid page size' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)

    @leaderboard.page_size = 0
    expect(@leaderboard.total_pages).to be(1)
    expect(@leaderboard.leaders(1).size).to be(Leaderboard::DEFAULT_PAGE_SIZE)
  end

  it 'should return the correct information when calling score_and_rank_for' do
    rank_members_in_leaderboard

    data = @leaderboard.score_and_rank_for('member_1')
    expect(data[:member]).to eql('member_1')
    expect(data[:score]).to eql(1.0)
    expect(data[:rank]).to eql(5)
  end

  it 'should allow you to remove members in a given score range' do
    rank_members_in_leaderboard

    expect(@leaderboard.total_members).to be(5)

    @leaderboard.rank_member('cheater_1', 100)
    @leaderboard.rank_member('cheater_2', 101)
    @leaderboard.rank_member('cheater_3', 102)

    expect(@leaderboard.total_members).to be(8)

    @leaderboard.remove_members_in_score_range(100, 102)

    expect(@leaderboard.total_members).to be(5)

    leaders = @leaderboard.leaders(1)
    leaders.each do |leader|
      expect(leader[:score]).to be < 100
    end
  end

  it 'should allow you to remove members outside a given rank' do
    rank_members_in_leaderboard

    expect(@leaderboard.total_members).to be(5)
    expect(@leaderboard.remove_members_outside_rank(3)).to be(2)

    leaders = @leaderboard.leaders(1)
    expect(leaders.size).to be(3)
    expect(leaders[0][:member]).to eq('member_5')
    expect(leaders[2][:member]).to eq('member_3')
  end

  it 'should allow you to merge leaderboards' do
    foo = Leaderboard.new('foo', Leaderboard::DEFAULT_OPTIONS, {:host => "127.0.0.1", :db => 15})
    bar = Leaderboard.new('bar', Leaderboard::DEFAULT_OPTIONS, {:host => "127.0.0.1", :db => 15})

    foo.rank_member('foo_1', 1)
    foo.rank_member('foo_2', 2)
    bar.rank_member('bar_1', 3)
    bar.rank_member('bar_2', 4)
    bar.rank_member('bar_3', 5)

    foobar_keys = foo.merge_leaderboards('foobar', ['bar'])
    expect(foobar_keys).to be(5)

    foobar = Leaderboard.new('foobar', Leaderboard::DEFAULT_OPTIONS, {:host => "127.0.0.1", :db => 15})
    expect(foobar.total_members).to be(5)

    first_leader_in_foobar = foobar.leaders(1).first
    expect(first_leader_in_foobar[:rank]).to eql(1)
    expect(first_leader_in_foobar[:member]).to eql('bar_3')
    expect(first_leader_in_foobar[:score]).to eql(5.0)

    foo.disconnect
    bar.disconnect
    foobar.disconnect
  end

  it 'should allow you to intersect leaderboards' do
    foo = Leaderboard.new('foo', Leaderboard::DEFAULT_OPTIONS, {:host => "127.0.0.1", :db => 15})
    bar = Leaderboard.new('bar', Leaderboard::DEFAULT_OPTIONS, {:host => "127.0.0.1", :db => 15})

    foo.rank_member('foo_1', 1)
    foo.rank_member('foo_2', 2)
    foo.rank_member('bar_3', 6)
    bar.rank_member('bar_1', 3)
    bar.rank_member('foo_1', 4)
    bar.rank_member('bar_3', 5)

    foobar_keys = foo.intersect_leaderboards('foobar', ['bar'], {:aggregate => :max})
    expect(foobar_keys).to be(2)

    foobar = Leaderboard.new('foobar', Leaderboard::DEFAULT_OPTIONS, {:host => "127.0.0.1", :db => 15})
    expect(foobar.total_members).to be(2)

    first_leader_in_foobar = foobar.leaders(1).first
    expect(first_leader_in_foobar[:rank]).to eql(1)
    expect(first_leader_in_foobar[:member]).to eql('bar_3')
    expect(first_leader_in_foobar[:score]).to eql(6.0)

    foo.disconnect
    bar.disconnect
    foobar.disconnect
  end

  it 'should respect options in the massage_leader_data method' do
    rank_members_in_leaderboard(25)

    expect(@leaderboard.total_members).to be(25)

    leaders = @leaderboard.leaders(1)
    expect(leaders[0][:member]).not_to be_nil
    expect(leaders[0][:score]).not_to be_nil
    expect(leaders[0][:rank]).not_to be_nil

    @leaderboard.page_size = Leaderboard::DEFAULT_PAGE_SIZE
    leaders = @leaderboard.leaders(1, Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS)
    expect(leaders[0][:member]).not_to be_nil
    expect(leaders[0][:score]).not_to be_nil
    expect(leaders[0][:rank]).not_to be_nil

    @leaderboard.page_size = 25
    leaders = @leaderboard.leaders(1, Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS)
    expect(leaders.size).to be(25)
  end

  it 'should return the correct number of pages when calling total_pages_in page size option' do
    rank_members_in_leaderboard(25)

    expect(@leaderboard.total_pages_in(@leaderboard.leaderboard_name)).to be(1)
    expect(@leaderboard.total_pages_in(@leaderboard.leaderboard_name, 5)).to be(5)
  end

  it 'should return the correct number of members when calling leaders with a page_size option' do
    rank_members_in_leaderboard(25)

    expect(@leaderboard.leaders(1, Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS.merge({:page_size => 5})).size).to be(5)
    expect(@leaderboard.leaders(1, Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS.merge({:page_size => 10})).size).to be(10)
    expect(@leaderboard.leaders(2, Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS.merge({:page_size => 10})).size).to be(10)
    expect(@leaderboard.leaders(3, Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS.merge({:page_size => 10})).size).to be(5)
  end

  it 'should return the correct number of members when calling around_me with a page_size options' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1)

    leaders_around_me = @leaderboard.around_me('member_30', Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS.merge({:page_size => 3}))
    expect(leaders_around_me.size).to be(3)
    expect(leaders_around_me[0][:member]).to eql('member_31')
    expect(leaders_around_me[2][:member]).to eql('member_29')
  end

  it 'should return the correct information when calling percentile_for' do
    rank_members_in_leaderboard(12)

    expect(@leaderboard.percentile_for('member_1')).to eql(0)
    expect(@leaderboard.percentile_for('member_2')).to eql(9)
    expect(@leaderboard.percentile_for('member_3')).to eql(17)
    expect(@leaderboard.percentile_for('member_4')).to eql(25)
    expect(@leaderboard.percentile_for('member_12')).to eql(92)
  end

  it 'should return the correct information when calling score_for_percentile' do
    rank_members_in_leaderboard(5)

    expect(@leaderboard.score_for_percentile(0)).to eql(1.0)
    expect(@leaderboard.score_for_percentile(75)).to eql(4.0)
    expect(@leaderboard.score_for_percentile(87.5)).to eql(4.5)
    expect(@leaderboard.score_for_percentile(93.75)).to eql(4.75)
    expect(@leaderboard.score_for_percentile(100)).to eql(5.0)
  end

  it 'should not throw an exception when calling around_me with a member not in the leaderboard' do
    rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1)

    leaders_around_me = @leaderboard.around_me('jones', Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS.merge({:page_size => 3}))
    expect(leaders_around_me.size).to be(0)
  end

  it 'should not throw an exception when calling score_and_rank_for with a member not in the leaderboard' do
    score_and_rank_for_member = @leaderboard.score_and_rank_for('jones')

    expect(score_and_rank_for_member[:member]).to eql('jones')
    expect(score_and_rank_for_member[:score]).to be_nil
    expect(score_and_rank_for_member[:rank]).to be_nil
  end

  it 'should not throw an exception when calling ranked_in_list with a member not in the leaderboard' do
    rank_members_in_leaderboard

    members = ['member_1', 'member_5', 'jones']
    ranked_members = @leaderboard.ranked_in_list(members)

    expect(ranked_members.size).to be(3)
    expect(ranked_members[2][:rank]).to be_nil
  end

  it 'should not throw an exception when calling percentile_for with a member not in the leaderboard' do
    percentile = @leaderboard.percentile_for('jones')

    expect(percentile).to be_nil
  end

  it 'should allow you to change the score for a member not in the leaderboard' do
    expect(@leaderboard.score_for('jones')).to be_nil
    @leaderboard.change_score_for('jones', 5)
    expect(@leaderboard.score_for('jones')).to eql(5.0)
  end

  it 'should return the correct page when calling page_for a given member in the leaderboard' do
    expect(@leaderboard.page_for('jones')).to be(0)

    rank_members_in_leaderboard(20)

    expect(@leaderboard.page_for('member_17')).to be(1)
    expect(@leaderboard.page_for('member_11')).to be(1)
    expect(@leaderboard.page_for('member_10')).to be(1)
    expect(@leaderboard.page_for('member_1')).to be(1)

    expect(@leaderboard.page_for('member_17', 10)).to be(1)
    expect(@leaderboard.page_for('member_11', 10)).to be(1)
    expect(@leaderboard.page_for('member_10', 10)).to be(2)
    expect(@leaderboard.page_for('member_1', 10)).to be(2)
  end

  it 'should set an expire on the leaderboard' do
    rank_members_in_leaderboard

    @leaderboard.expire_leaderboard(3)
    @redis_connection.ttl(@leaderboard.leaderboard_name).tap do |ttl|
      expect(ttl).to be > 1
      expect(ttl).to be <= 3
    end
    @redis_connection.ttl(@leaderboard.send(:member_data_key, @leaderboard.leaderboard_name)).tap do |ttl|
      expect(ttl).to be > 1
      expect(ttl).to be <= 3
    end
  end

  it 'should set an expire on the leaderboard using a timestamp' do
    rank_members_in_leaderboard

    @leaderboard.expire_leaderboard_at((Time.now + 10).to_i)
    @redis_connection.ttl(@leaderboard.leaderboard_name).tap do |ttl|
      expect(ttl).to be > 1
      expect(ttl).to be <= 10
    end
    @redis_connection.ttl(@leaderboard.send(:member_data_key, @leaderboard.leaderboard_name)).tap do |ttl|
      expect(ttl).to be > 1
      expect(ttl).to be <= 10
    end
  end

  it 'should allow you to rank multiple members with a variable number of arguments' do
    expect(@leaderboard.total_members).to be(0)
    @leaderboard.rank_members('member_1', 1, 'member_10', 10)
    expect(@leaderboard.total_members).to be(2)
    expect(@leaderboard.leaders(1).first[:member]).to eql('member_10')
  end

  it 'should allow you to rank multiple members with an array' do
    expect(@leaderboard.total_members).to be(0)
    @leaderboard.rank_members(['member_1', 1, 'member_10', 10])
    expect(@leaderboard.total_members).to be(2)
    expect(@leaderboard.leaders(1).first[:member]).to eql('member_10')
  end

  it 'should allow you to set reverse after creating a leaderboard to see results in highest-to-lowest or lowest-to-highest order' do
    rank_members_in_leaderboard(25)

    leaders = @leaderboard.leaders(1)

    expect(leaders.size).to be(25)
    expect(leaders[0][:member]).to eql('member_25')
    expect(leaders[-2][:member]).to eql('member_2')
    expect(leaders[-1][:member]).to eql('member_1')
    expect(leaders[-1][:score].to_i).to be(1)

    @leaderboard.reverse = true

    leaders = @leaderboard.leaders(1)

    expect(leaders.size).to be(25)
    expect(leaders[0][:member]).to eql('member_1')
    expect(leaders[-2][:member]).to eql('member_24')
    expect(leaders[-1][:member]).to eql('member_25')
    expect(leaders[-1][:score].to_i).to be(25)
  end

  it 'should allow you to retrieve a given set of members from the leaderboard in a rank range' do
    rank_members_in_leaderboard(25)

    members = @leaderboard.members_from_rank_range(5, 9)
    expect(members.size).to be(5)
    expect(members[0][:member]).to eql('member_21')
    expect(members[0][:score].to_i).to be(21)
    expect(members[4][:member]).to eql('member_17')

    members = @leaderboard.members_from_rank_range(1, 1)
    expect(members.size).to be(1)
    expect(members[0][:member]).to eql('member_25')

    members = @leaderboard.members_from_rank_range(-1, 26)
    expect(members.size).to be(25)
    expect(members[0][:member]).to eql('member_25')
    expect(members[0][:score].to_i).to be(25)
    expect(members[24][:member]).to eql('member_1')
  end

  it 'should allow you to retrieve a given set of members from the leaderboard in a range from 1 to the number given' do
    rank_members_in_leaderboard(25)

    members = @leaderboard.top(5)
    expect(members.size).to be(5)
    expect(members[0][:member]).to eql('member_25')
    expect(members[0][:score].to_i).to be(25)
    expect(members[4][:member]).to eql('member_21')

    members = @leaderboard.top(1)
    expect(members.size).to be(1)
    expect(members[0][:member]).to eql('member_25')

    members = @leaderboard.top(26)
    expect(members.size).to be(25)
    expect(members[0][:member]).to eql('member_25')
    expect(members[0][:score].to_i).to be(25)
    expect(members[24][:member]).to eql('member_1')
  end

  it 'should allow you to retrieve a given set of members from the named leaderboard in a range from 1 to the number given' do
    rank_members_in_leaderboard(25)

    members = @leaderboard.top_in("name", 5)
    expect(members.size).to be(5)
    expect(members[0][:member]).to eql('member_25')
    expect(members[0][:score].to_i).to be(25)
    expect(members[4][:member]).to eql('member_21')

    members = @leaderboard.top(1)
    expect(members.size).to be(1)
    expect(members[0][:member]).to eql('member_25')

    members = @leaderboard.top(26)
    expect(members.size).to be(25)
    expect(members[0][:member]).to eql('member_25')
    expect(members[0][:score].to_i).to be(25)
    expect(members[24][:member]).to eql('member_1')
  end

  it 'should sort by rank if the :sort_by option is set to :rank' do
    rank_members_in_leaderboard(25)

    members = ['member_5', 'member_1', 'member_10']
    ranked_members = @leaderboard.ranked_in_list(members, :sort_by => :rank)

    expect(ranked_members.size).to be(3)

    expect(ranked_members[0][:rank]).to be(16)
    expect(ranked_members[0][:score]).to eql(10.0)

    expect(ranked_members[1][:rank]).to be(21)
    expect(ranked_members[1][:score]).to eql(5.0)

    expect(ranked_members[2][:rank]).to be(25)
    expect(ranked_members[2][:score]).to eql(1.0)
  end

  it 'should sort by score if the :sort_by option is set to :score' do
    rank_members_in_leaderboard(25)

    members = ['member_5', 'member_1', 'member_10']
    ranked_members = @leaderboard.ranked_in_list(members, :sort_by => :score)

    expect(ranked_members.size).to be(3)

    expect(ranked_members[0][:rank]).to be(25)
    expect(ranked_members[0][:score]).to eql(1.0)

    expect(ranked_members[1][:rank]).to be(21)
    expect(ranked_members[1][:score]).to eql(5.0)

    expect(ranked_members[2][:rank]).to be(16)
    expect(ranked_members[2][:score]).to eql(10.0)
  end

  it 'should return nil for the score and rank for ranked_in_list if a member is not in the leaderboard' do
    rank_members_in_leaderboard

    ranked_members = @leaderboard.ranked_in_list(['jones'])

    expect(ranked_members.size).to be(1)
    expect(ranked_members[0][:member]).to eql('jones')
    expect(ranked_members[0][:score]).to be_nil
    expect(ranked_members[0][:rank]).to be_nil
  end

  it 'should rank a member in the leaderboard with conditional execution' do
    highscore_check = lambda do |member, current_score, score, member_data, leaderboard_options|
      return true if current_score.nil?
      return true if score > current_score
      false
    end

    expect(@leaderboard.total_members).to be(0)
    @leaderboard.rank_member_if(highscore_check, 'david', 1337)
    expect(@leaderboard.total_members).to be(1)
    expect(@leaderboard.score_for('david')).to eql(1337.0)
    @leaderboard.rank_member_if(highscore_check, 'david', 1336)
    expect(@leaderboard.score_for('david')).to eql(1337.0)
    @leaderboard.rank_member_if(highscore_check, 'david', 1338)
    expect(@leaderboard.score_for('david')).to eql(1338.0)
  end

  it 'should not delete all the member data when calling remove_member' do
    rank_members_in_leaderboard

    expect(@redis_connection.exists("name:member_data")).to be_truthy
    expect(@redis_connection.hgetall("name:member_data").size).to be(5)
    expect(@leaderboard.total_members).to be(5)
    @leaderboard.remove_member('member_1')
    expect(@redis_connection.exists("name:member_data")).to be_truthy
    expect(@redis_connection.hgetall("name:member_data").size).to be(4)
    expect(@leaderboard.total_members).to be(4)
  end

  it 'should return the members only if the :members_only option is passed' do
    rank_members_in_leaderboard(25)

    leaders = @leaderboard.leaders(1, :page_size => 10, :members_only => true)
    expect(leaders.size).to eq(10)
    leaders.collect { |leader| expect(leader.keys).to eq([:member]) }

    leaders = @leaderboard.all_leaders(:members_only => true)
    expect(leaders.size).to eq(25)
    leaders.collect { |leader| expect(leader.keys).to eq([:member]) }

    leaders = @leaderboard.members_from_score_range(10, 14, :members_only => true)
    expect(leaders.size).to eq(5)
    leaders.collect { |leader| expect(leader.keys).to eq([:member]) }

    leaders = @leaderboard.members_from_rank_range(1, 5, :page_size => 10, :members_only => true)
    expect(leaders.size).to eq(5)
    leaders.collect { |leader| expect(leader.keys).to eq([:member]) }

    leaders = @leaderboard.around_me('member_10', :page_size => 3, :members_only => true)
    expect(leaders.size).to eq(3)
    leaders.collect { |leader| expect(leader.keys).to eq([:member]) }

    leaders = @leaderboard.ranked_in_list(['member_1', 'member_25'], :members_only => true)
    expect(leaders.size).to eq(2)
    leaders.collect { |leader| expect(leader.keys).to eq([:member]) }
  end

  it 'should allow you to rank a member across multiple leaderboards' do
    @leaderboard.rank_member_across(['highscores', 'more_highscores'], 'david', 50000, { :member_name => "david" })
    expect(@leaderboard.leaders_in('highscores', 1).size).to eql(1)
    expect(@leaderboard.leaders_in('more_highscores', 1).size).to eql(1)
  end

  it 'should allow you to set custom keys for member, score, rank and member_data' do
    @leaderboard = Leaderboard.new('name',
      {
        :member_key => :custom_member_key,
        :rank_key => :custom_rank_key,
        :score_key => :custom_score_key,
        :member_data_key => :custom_member_data_key
      },
      {:host => "127.0.0.1", :db => 15})

    rank_members_in_leaderboard(5)
    leaders = @leaderboard.leaders(1, :with_member_data => true)
    leaders.each do |leader|
      expect(leader[:custom_member_key]).not_to be_nil
      expect(leader[:member]).to be_nil
      expect(leader[:custom_score_key]).not_to be_nil
      expect(leader[:score]).to be_nil
      expect(leader[:custom_rank_key]).not_to be_nil
      expect(leader[:rank]).to be_nil
      expect(leader[:custom_member_data_key]).not_to be_nil
      expect(leader[:member_data]).to be_nil
    end
  end

  it 'should allow you to change the :member_data_namespace option' do
    @leaderboard = Leaderboard.new('name', {:member_data_namespace => 'md'}, {:host => "127.0.0.1", :db => 15})
    rank_members_in_leaderboard

    expect(@redis_connection.exists("name:member_data")).to be_falsey
    expect(@redis_connection.exists("name:md")).to be_truthy
  end

  it 'should allow you to have a global member data namespace' do
    @leaderboard = Leaderboard.new('name', {:global_member_data => true}, {:host => "127.0.0.1", :db => 15})
    rank_members_in_leaderboard

    expect(@redis_connection.exists("member_data")).to be_truthy
    expect(@redis_connection.exists("name:member_data")).to be_falsey
  end

  it 'should allow you to include or exclude missing members using the :include_missing option' do
    rank_members_in_leaderboard(25)

    leaders = @leaderboard.ranked_in_list(['member_1', 'member_15', 'member_25', 'member_200'])
    expect(leaders.size).to be(4)
    expect(leaders[0][:member]).to eql('member_1')
    expect(leaders[1][:member]).to eql('member_15')
    expect(leaders[2][:member]).to eql('member_25')
    expect(leaders[3][:member]).to eql('member_200')

    leaders = @leaderboard.ranked_in_list(['member_1', 'member_15', 'member_25', 'member_200'], :include_missing => false)
    expect(leaders.size).to be(3)
    expect(leaders[0][:member]).to eql('member_1')
    expect(leaders[1][:member]).to eql('member_15')
    expect(leaders[2][:member]).to eql('member_25')

    leaders = @leaderboard.ranked_in_list(['member_200'], :include_missing => false, :with_member_data => true)
    expect(leaders.size).to be(0)
  end

  it 'should return the sum of all scores in the leaderboard' do
    rank_members_in_leaderboard(25)

    expect(@leaderboard.total_scores()).to eq(325)
  end
end
