<commit_before>require 'spec_helper'
require 'securerandom'

RSpec.describe Redlock::Client do
  # It is recommended to have at least 3 servers in production
  let(:lock_manager_opts) { { retry_count: 3 } }
  let(:lock_manager) { Redlock::Client.new(Redlock::Client::DEFAULT_REDIS_URLS, lock_manager_opts) }
  let(:resource_key) { SecureRandom.hex(3)  }
  let(:ttl) { 1000 }
  let(:redis1_host) { ENV["REDIS1_HOST"] || "localhost" }
  let(:redis1_port) { ENV["REDIS1_PORT"] || "6379" }
  let(:redis2_host) { ENV["REDIS2_HOST"] || "127.0.0.1" }
  let(:redis2_port) { ENV["REDIS2_PORT"] || "6379" }

  describe 'initialize' do
    it 'accepts both redis URLs and Redis objects' do
      print redis1_host
      servers = [ "redis://#{redis1_host}:#{redis1_port}", Redis.new(url: "redis://#{redis2_host}:#{redis2_port}") ]
      redlock = Redlock::Client.new(servers)

      redlock_servers = redlock.instance_variable_get(:@servers).map do |s|
        s.instance_variable_get(:@redis).connection[:host]
      end

      expect(redlock_servers).to match_array([redis1_host, redis2_host])
    end
  end

  describe 'lock' do
    context 'when lock is available' do
      after(:each) { lock_manager.unlock(@lock_info) if @lock_info }

      it 'locks' do
        @lock_info = lock_manager.lock(resource_key, ttl)

        expect(resource_key).to_not be_lockable(lock_manager, ttl)
      end

      it 'returns lock information' do
        @lock_info = lock_manager.lock(resource_key, ttl)

        expect(@lock_info).to be_lock_info_for(resource_key)
      end

      it 'can extend its own lock' do
        my_lock_info = lock_manager.lock(resource_key, ttl)
        @lock_info = lock_manager.lock(resource_key, ttl, extend: my_lock_info)
        expect(@lock_info).to be_lock_info_for(resource_key)
        expect(@lock_info[:value]).to eq(my_lock_info[:value])
      end

      context 'when extend param is nil' do
        it 'defaults to creating a new lock' do
          @lock_info = lock_manager.lock(resource_key, ttl, extend: nil)
          expect(@lock_info).to be_lock_info_for(resource_key)
          expect(@lock_info[:value]).to be
        end
      end

      context 'when extend_only_if_life flag is given' do
        it 'does not extend a non-existent lock' do
          @lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_only_if_life: true)
          expect(@lock_info).to eq(false)
        end
      end

      context 'when extend_only_if_life flag is not given' do
        it "sets the given value when trying to extend a non-existent lock" do
          @lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_only_if_life: false)
          expect(@lock_info).to be_lock_info_for(resource_key)
          expect(@lock_info[:value]).to eq('hello world') # really we should test what's in redis
        end
      end

      it "doesn't extend somebody else's lock" do
        @lock_info = lock_manager.lock(resource_key, ttl)
        second_attempt = lock_manager.lock(resource_key, ttl)
        expect(second_attempt).to eq(false)
      end
    end

    context 'when lock is not available' do
      before { @another_lock_info = lock_manager.lock(resource_key, ttl) }<commit_msg>Add specs around TTL behaviour<commit_after>require 'spec_helper'
require 'securerandom'
require 'redis'

RSpec.describe Redlock::Client do
  # It is recommended to have at least 3 servers in production
  let(:lock_manager_opts) { { retry_count: 3 } }
  let(:lock_manager) { Redlock::Client.new(Redlock::Client::DEFAULT_REDIS_URLS, lock_manager_opts) }
  let(:redis_client) { Redis.new }
  let(:resource_key) { SecureRandom.hex(3)  }
  let(:ttl) { 1000 }
  let(:redis1_host) { ENV["REDIS1_HOST"] || "localhost" }
  let(:redis1_port) { ENV["REDIS1_PORT"] || "6379" }
  let(:redis2_host) { ENV["REDIS2_HOST"] || "127.0.0.1" }
  let(:redis2_port) { ENV["REDIS2_PORT"] || "6379" }

  describe 'initialize' do
    it 'accepts both redis URLs and Redis objects' do
      print redis1_host
      servers = [ "redis://#{redis1_host}:#{redis1_port}", Redis.new(url: "redis://#{redis2_host}:#{redis2_port}") ]
      redlock = Redlock::Client.new(servers)

      redlock_servers = redlock.instance_variable_get(:@servers).map do |s|
        s.instance_variable_get(:@redis).connection[:host]
      end

      expect(redlock_servers).to match_array([redis1_host, redis2_host])
    end
  end

  describe 'lock' do
    context 'when lock is available' do
      after(:each) { lock_manager.unlock(@lock_info) if @lock_info }

      it 'locks' do
        @lock_info = lock_manager.lock(resource_key, ttl)

        expect(resource_key).to_not be_lockable(lock_manager, ttl)
      end

      it 'returns lock information' do
        @lock_info = lock_manager.lock(resource_key, ttl)

        expect(@lock_info).to be_lock_info_for(resource_key)
      end
      
      it 'interprets lock time as milliseconds' do
        # use a hardcoded value to avoid global change since this test is 
        # fairly dependent on that value
        ttl = 2000
        @lock_info = lock_manager.lock(resource_key, ttl)
        expect(redis_client.pttl(resource_key)).to be_within(50).of(ttl)
      end

      it 'can extend its own lock' do
        my_lock_info = lock_manager.lock(resource_key, ttl)
        @lock_info = lock_manager.lock(resource_key, ttl, extend: my_lock_info)
        expect(@lock_info).to be_lock_info_for(resource_key)
        expect(@lock_info[:value]).to eq(my_lock_info[:value])
      end

      context 'when extend param is nil' do
        it 'defaults to creating a new lock' do
          @lock_info = lock_manager.lock(resource_key, ttl, extend: nil)
          expect(@lock_info).to be_lock_info_for(resource_key)
          expect(@lock_info[:value]).to be
        end
      end

      context 'when extend_only_if_life flag is given' do
        it 'does not extend a non-existent lock' do
          @lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_only_if_life: true)
          expect(@lock_info).to eq(false)
        end
        
        context 'on an existent lock' do
          let!(:existent_lock) do
            lock_info = lock_manager.lock(resource_key, ttl)
            expect(resource_key).to_not be_lockable(lock_manager, ttl)
            lock_info
          end
          
          it 'interprets lock time in milliseconds' do
            # use a hardcoded value to avoid global change since this test is 
            # fairly dependent on that value
            ttl = 20000
            @lock_info = lock_manager.lock(resource_key, ttl, extend: existent_lock, extend_life: true)
            expect(redis_client.pttl(resource_key)).to be_within(50).of(ttl)
          end
          
          it 'reset the TTL, rather than adding extra time to it' do
            ttl = 2000
            lock_info = existent_lock
            # if we extend more than once, the TTL is reset at each call
            50.times do
              lock_info = lock_manager.lock(resource_key, ttl, extend: lock_info, extend_life: true)
              expect(lock_info).not_to be_nil
              expect(redis_client.pttl(resource_key)).to be_within(50).of(ttl)
            end
          end
        end
      end

      context 'when extend_only_if_life flag is not given' do
        it "sets the given value when trying to extend a non-existent lock" do
          @lock_info = lock_manager.lock(resource_key, ttl, extend: {value: 'hello world'}, extend_only_if_life: false)
          expect(@lock_info).to be_lock_info_for(resource_key)
          expect(@lock_info[:value]).to eq('hello world') # really we should test what's in redis
        end
      end

      it "doesn't extend somebody else's lock" do
        @lock_info = lock_manager.lock(resource_key, ttl)
        second_attempt = lock_manager.lock(resource_key, ttl)
        expect(second_attempt).to eq(false)
      end
    end

    context 'when lock is not available' do
      before { @another_lock_info = lock_manager.lock(resource_key, ttl) }
{'fraction_non_alphanumeric': 0.10292484265087005, 'fraction_numerical': 0.011477230655312847, 'mean_word_length': 2.980353634577603, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1313985', 'n_tokens_mistral': 2695, 'n_tokens_neox': 2605, 'n_words': 639}