require "spec_helper"

describe InfoparkComponentCache::ComponentCache do
  subject(:component_cache) { described_class.new(obj, name, params, [guard]) }

  let(:obj)    { double(name: "spec_obj", id: 2001) }
  let(:name)   { "spec_cached_component" }
  let(:params) { { some: "additional", params: "supplied" } }
  let(:guard)  { Class.new(Struct.new(:component)) }

  context "with caching disabled" do
    before { allow(Rails.application.config.action_controller).to receive(:perform_caching).and_return(false) }

    describe "#fetch" do
      let(:value) { "very_hard_computation_required_for_this_string" }
      let(:specific_guard) { component_cache.guards.first }

      it "returns the passed the block value" do
        expect(component_cache.fetch { value }).to eq(value)
      end

      it "never calls any methods on the guard" do
        expect(specific_guard).not_to receive(:consistent?)
        expect(specific_guard).not_to receive(:guard!)

        component_cache.fetch { value }
      end
    end
  end

  context "with caching enabled" do
    before { allow(Rails.application.config.action_controller).to receive(:perform_caching).and_return(true) }

    let(:guard) do
      Class.new(InfoparkComponentCache::ConsistencyGuard) do
        def consistent?
          cache.exist?(:guard_called)
        end

        def guard!
          cache.write(:guard_called, 1)
        end
      end
    end

    describe "#fetch" do
      let(:value) { "very_hard_computation_required_for_this_string" }
      let(:specific_guard) { component_cache.guards.first }
      let(:computer) { double }

      it "returns the passed the block value" do
        expect(component_cache.fetch { value }).to eq(value)
      end

      it "calls the required methods on the guard" do
        expect(specific_guard).to receive(:consistent?).exactly(3).times
        expect(specific_guard).to receive(:guard!).exactly(3).times

        3.times { component_cache.fetch { value } }
      end

      it "only evalues the block once" do
        expect(computer).to receive(:compute).and_return(value).once

        3.times { expect(component_cache.fetch { computer.compute }).to eq(value) }
      end
    end
  end

  describe "#compontent" do
    subject(:component_cache) { described_class.new(obj, name, params).component }

    it "stores the passed obj" do
      expect(component_cache.obj).to eq(obj)
    end

    it "stores the passed name" do
      expect(component_cache.name).to eq(name)
    end

    it "stores the passed params" do
      expect(component_cache.params).to eq(params)
    end
  end

  describe "#guards" do
    subject(:component_cache) { described_class.new(obj, name, params, [guard_class1, guard_params]).guards }

    let(:guard_class1) { Struct.new(:component) }
    let(:guard_class2) { Struct.new(:compontent, :extra) }
    let(:guard_params) { { guard: guard_class2, something: "more" } }

    it "contains an array of passed guards" do
      expect(component_cache).to match_array([
                                               an_instance_of(guard_class1),
                                               an_instance_of(guard_class2)
                                             ])
    end

    it "preserves the extra params" do
      expect(component_cache.last.extra).to eq(guard_params)
    end

    context "with no guards specified in the constructors" do
      subject(:component_cache) { described_class.new(obj, name, params).guards }

      it "contains standard guards" do
        expect(component_cache).to match_array([
                                                 an_instance_of(InfoparkComponentCache::Guards::ValuePresent),
                                                 an_instance_of(InfoparkComponentCache::Guards::LastChanged),
                                                 an_instance_of(InfoparkComponentCache::Guards::ObjCount),
                                                 an_instance_of(InfoparkComponentCache::Guards::ValidFrom),
                                                 an_instance_of(InfoparkComponentCache::Guards::ValidUntil)
                                               ])
      end
    end
  end
end