require 'spec_helper'

describe PuppetForge::LruCache do
  it 'creates a cache key from a list of strings' do
    expect { subject.class.new_key('foo', 'bar', 'baz') }.not_to raise_error 
  end

  it 'creates a new instance' do
    expect { PuppetForge::LruCache.new(1) }.not_to raise_error
  end

  it 'raises an error if max_size is not a positive integer' do
    expect { PuppetForge::LruCache.new(-1) }.to raise_error(ArgumentError)
    expect { PuppetForge::LruCache.new(0) }.to raise_error(ArgumentError)
    expect { PuppetForge::LruCache.new(1.5) }.to raise_error(ArgumentError)
  end

  it 'defaults to a max_size of 30' do
    expect(PuppetForge::LruCache.new.max_size).to eq(30)
  end

  it 'allows max_size to be set via the max_size parameter' do
    expect(PuppetForge::LruCache.new(42).max_size).to eq(42)
  end

  it 'provides a #get method' do
    expect(PuppetForge::LruCache.new).to respond_to(:get)
  end

  it 'provides a #put method' do
    expect(PuppetForge::LruCache.new).to respond_to(:put)
  end

  it 'provides a #clear method' do
    expect(PuppetForge::LruCache.new).to respond_to(:clear)
  end

  context 'with environment variables' do
    around(:each) do |example|
      @old_max_size = ENV['PUPPET_FORGE_MAX_CACHE_SIZE']
      ENV['PUPPET_FORGE_MAX_CACHE_SIZE'] = '42'
      example.run
      ENV['PUPPET_FORGE_MAX_CACHE_SIZE'] = @old_max_size
    end

    it 'uses the value of the PUPPET_FORGE_MAX_CACHE_SIZE environment variable if present' do
      expect(PuppetForge::LruCache.new.max_size).to eq(42)
    end
  end

  context '#get' do
    it 'returns nil if the key is not present in the cache' do
      expect(PuppetForge::LruCache.new.get('foo')).to be_nil
    end

    it 'returns the cached value for the given key' do
      cache = PuppetForge::LruCache.new
      cache.put('foo', 'bar')
      expect(cache.get('foo')).to eq('bar')
    end

    it 'moves the key to the front of the LRU list' do
      cache = PuppetForge::LruCache.new
      cache.put('foo', 'bar')
      cache.put('baz', 'qux')
      cache.get('foo')
      expect(cache.send(:lru)).to eq(['foo', 'baz'])
    end

    it 'is thread-safe for get calls' do
      cache = PuppetForge::LruCache.new
    
      # Populate the cache with initial values
      cache.put('foo', 'bar')
      cache.put('baz', 'qux')
    
      # Create two threads for concurrent cache get operations
      thread_one = Thread.new do
        100.times { expect(cache.get('foo')).to eq('bar') }
      end
    
      thread_two = Thread.new do
        100.times { expect(cache.get('baz')).to eq('qux') }
      end
    
      # Wait for both threads to complete
      thread_one.join
      thread_two.join
    end    
  end

  context '#put' do
    it 'adds the key to the front of the LRU list' do
      cache = PuppetForge::LruCache.new
      cache.put('foo', 'bar')
      expect(cache.send(:lru)).to eq(['foo'])
    end

    it 'adds the value to the cache' do
      cache = PuppetForge::LruCache.new
      cache.put('foo', 'bar')
      expect(cache.send(:cache)).to eq({ 'foo' => 'bar' })
    end

    it 'removes the least recently used item if the cache is full' do
      cache = PuppetForge::LruCache.new(2)
      cache.put('foo', 'bar')
      cache.put('baz', 'qux')
      cache.put('quux', 'corge')
      expect(cache.send(:lru)).to eq(['quux', 'baz'])
    end

    it 'is thread-safe' do
      cache = PuppetForge::LruCache.new
    
      # Create two threads for concurrent cache operations
      thread_one = Thread.new do
        100.times { cache.put('foo', 'bar') }
      end
    
      thread_two = Thread.new do
        100.times { cache.put('baz', 'qux') }
      end
    
      # Wait for both threads to complete
      thread_one.join
      thread_two.join
    
      # At this point, we don't need to compare the LRU list,
      # because the order may change due to concurrent puts.
      
      # Instead, we simply expect the code to run without errors.
      expect { thread_one.value }.not_to raise_error
      expect { thread_two.value }.not_to raise_error
    end
    
  end

  context '#clear' do
    it 'clears the cache' do
      cache = PuppetForge::LruCache.new
      cache.put('foo', 'bar')
      cache.put('baz', 'qux')
      cache.clear
      expect(cache.send(:lru).empty?).to be_truthy
      expect(cache.send(:cache).empty?).to be_truthy
    end
  end
end