<commit_before>require 'helper'
require 'rack/test'
require 'flipper/middleware/local_cache'

describe Flipper::Middleware::LocalCache do
  include Rack::Test::Methods

  class LoggedHash < Hash
    attr_reader :reads, :writes

    Read  = Struct.new(:key)
    Write = Struct.new(:key, :value)

    def initialize(*args)
      @reads, @writes = [], []
      super
    end

    def [](key)
      @reads << Read.new(key)
      super
    end

    def []=(key, value)
      @writes << Write.new(key, value)
      super
    end
  end

  class Enum < Struct.new(:iter)
    def each(&b)
      iter.call(&b)
    end
  end

  let(:source)  { LoggedHash.new }
  let(:adapter) { Flipper::Adapters::Memory.new(source) }
  let(:flipper) { Flipper.new(adapter) }

  let(:app) {
    # ensure scoped for builder block, annoying...
    instance = flipper
    middleware = described_class

    Rack::Builder.new do
      use middleware, instance

      map "/" do
        run lambda {|env| [200, {}, []] }
      end

      map "/fail" do
        run lambda {|env| raise "FAIL!" }
      end
    end.to_app
  }

  it "delegates" do
    called = false
    app = lambda { |env|
      called = true
      [200, {}, nil]
    }
    middleware = described_class.new app, flipper
    middleware.call({})
    called.should be_true
  end

  it "enables memoization during delegation" do
    app = lambda { |env|
      flipper.adapter.using_local_cache?.should be_true
      [200, {}, nil]
    }
    middleware = described_class.new app, flipper
    middleware.call({})
  end

  it "enables local cache for body each" do
    app = lambda { |env|
      [200, {}, Enum.new(lambda { |&b|
        flipper.adapter.using_local_cache?.should be_true
        b.call "hello"
      })]
    }
    middleware = described_class.new app, flipper
    body = middleware.call({}).last
    body.each { |x| x.should eql('hello') }
  end

  it "disables local cache after body close" do
    app = lambda { |env| [200, {}, []] }
    middleware = described_class.new app, flipper
    body = middleware.call({}).last

    flipper.adapter.using_local_cache?.should be_true
    body.close
    flipper.adapter.using_local_cache?.should be_false
  end

  it "clears local cache after body close" do
    app = lambda { |env| [200, {}, []] }
    middleware = described_class.new app, flipper
    body = middleware.call({}).last
    flipper.adapter.local_cache['hello'] = 'world'

    flipper.adapter.local_cache.should_not be_empty
    body.close
    flipper.adapter.local_cache.should be_empty
  end

  it "really does cache" do
    flipper[:stats].enable

    app = lambda { |env|
      flipper[:stats].enabled?
      flipper[:stats].enabled?
      flipper[:stats].enabled?
      flipper[:stats].enabled?
      flipper[:stats].enabled?
      flipper[:stats].enabled?

      [200, {}, []]
    }
    middleware = described_class.new app, flipper
    middleware.call({})

    source.reads.map(&:key).should eq(["stats/boolean"])
  end

  context "with a successful request" do
    it "clears the local cache" do
      flipper.adapter.local_cache.should_receive(:clear).twice
      get '/'
    end
  end

  context "when the request raises an error" do
    it "clears the local cache" do
      flipper.adapter.local_cache.should_receive(:clear).once
      get '/fail' rescue nil
    end
  end
end<commit_msg>Use operation logger adapter for local cache spec.<commit_after>require 'helper'
require 'rack/test'
require 'flipper/middleware/local_cache'
require 'flipper/adapters/operation_logger'
require 'flipper/adapters/memory'

describe Flipper::Middleware::LocalCache do
  include Rack::Test::Methods

  class Enum < Struct.new(:iter)
    def each(&block)
      iter.call(&block)
    end
  end

  let(:source)         { {} }
  let(:memory_adapter) { Flipper::Adapters::Memory.new(source) }
  let(:adapter)        { Flipper::Adapters::OperationLogger.new(memory_adapter) }
  let(:flipper)        { Flipper.new(adapter) }

  let(:app) {
    # ensure scoped for builder block, annoying...
    instance = flipper
    middleware = described_class

    Rack::Builder.new do
      use middleware, instance

      map "/" do
        run lambda {|env| [200, {}, []] }
      end

      map "/fail" do
        run lambda {|env| raise "FAIL!" }
      end
    end.to_app
  }

  before do
    adapter.reset
  end

  it "delegates" do
    called = false
    app = lambda { |env|
      called = true
      [200, {}, nil]
    }
    middleware = described_class.new app, flipper
    middleware.call({})
    called.should be_true
  end

  it "enables memoization during delegation" do
    app = lambda { |env|
      flipper.adapter.using_local_cache?.should be_true
      [200, {}, nil]
    }
    middleware = described_class.new app, flipper
    middleware.call({})
  end

  it "enables local cache for body each" do
    app = lambda { |env|
      [200, {}, Enum.new(lambda { |&block|
        flipper.adapter.using_local_cache?.should be_true
        block.call "hello"
      })]
    }
    middleware = described_class.new app, flipper
    body = middleware.call({}).last
    body.each { |x| x.should eql('hello') }
  end

  it "disables local cache after body close" do
    app = lambda { |env| [200, {}, []] }
    middleware = described_class.new app, flipper
    body = middleware.call({}).last

    flipper.adapter.using_local_cache?.should be_true
    body.close
    flipper.adapter.using_local_cache?.should be_false
  end

  it "clears local cache after body close" do
    app = lambda { |env| [200, {}, []] }
    middleware = described_class.new app, flipper
    body = middleware.call({}).last
    flipper.adapter.local_cache['hello'] = 'world'

    flipper.adapter.local_cache.should_not be_empty
    body.close
    flipper.adapter.local_cache.should be_empty
  end

  it "really does cache" do
    flipper[:stats].enable
    adapter.reset

    app = lambda { |env|
      flipper[:stats].enabled?
      flipper[:stats].enabled?
      flipper[:stats].enabled?
      flipper[:stats].enabled?
      flipper[:stats].enabled?
      flipper[:stats].enabled?

      [200, {}, []]
    }
    middleware = described_class.new app, flipper
    middleware.call({})

    adapter.operations.should eq([
      Flipper::Adapters::OperationLogger::Read.new("stats/boolean"),
    ])
  end

  context "with a successful request" do
    it "clears the local cache" do
      flipper.adapter.local_cache.should_receive(:clear).twice
      get '/'
    end
  end

  context "when the request raises an error" do
    it "clears the local cache" do
      flipper.adapter.local_cache.should_receive(:clear).once
      get '/fail' rescue nil
    end
  end
end
{'fraction_non_alphanumeric': 0.12334933973589436, 'fraction_numerical': 0.0063025210084033615, 'mean_word_length': 2.7193080357142856, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, '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': '2483559', 'n_tokens_mistral': 2203, 'n_tokens_neox': 2158, 'n_words': 525}