# frozen_string_literal: true require "cache" RSpec.describe Cache do subject(:cache) { Cache.new } it "supports exist?" do cache.write("testing", 1.1) expect(cache.exist?("testing")).to eq(true) expect(cache.exist?(SecureRandom.hex)).to eq(false) end it "supports float" do cache.write("float", 1.1) expect(cache.read("float")).to eq(1.1) end it "supports fixnum" do cache.write("num", 1) expect(cache.read("num")).to eq(1) end it "supports hash" do hash = { a: 1, b: [1, 2, 3] } cache.write("hash", hash) expect(cache.read("hash")).to eq(hash) end it "can be cleared" do Discourse.redis.set("boo", "boo") cache.write("hello0", "world") cache.write("hello1", "world") cache.clear expect(Discourse.redis.get("boo")).to eq("boo") expect(cache.read("hello0")).to eq(nil) end it "can delete correctly" do cache.delete("key") cache.fetch("key", expires_in: 1.minute) { "test" } expect(cache.fetch("key")).to eq("test") cache.delete("key") expect(cache.fetch("key")).to eq(nil) end it "calls setex in redis" do cache.delete("key") cache.delete("bla") key = cache.normalize_key("key") cache.fetch("key", expires_in: 1.minute) { "bob" } expect(Discourse.redis.ttl(key)).to be_within(2.seconds).of(1.minute) # we always expire withing a day cache.fetch("bla") { "hi" } key = cache.normalize_key("bla") expect(Discourse.redis.ttl(key)).to be_within(2.seconds).of(1.day) end it "can store and fetch correctly" do cache.delete "key" r = cache.fetch "key" do "bob" end expect(r).to eq("bob") end it "can fetch existing correctly" do cache.write "key", "bill" r = cache.fetch "key" do "bob" end expect(r).to eq("bill") end it "can fetch keys with pattern" do cache.write "users:admins", "jeff" cache.write "users:moderators", "bob" expect(cache.keys("users:*").count).to eq(2) end it "can fetch namespace" do expect(cache.namespace).to eq("_CACHE") end it "uses the defined expires_in" do cache.write "foo:bar", "baz", expires_in: 3.minutes expect(cache.redis.ttl("#{cache.namespace}:foo:bar")).to eq(180) end describe ".fetch" do subject(:fetch_value) { cache.fetch("my_key") { "bob" } } context "when the cache is corrupt" do before do cache.delete("my_key") Discourse.redis.setex(cache.normalize_key("my_key"), described_class::MAX_CACHE_AGE, "") end it "runs and return the provided block" do expect(fetch_value).to eq("bob") end it "generates a new cache entry" do fetch_value expect(cache.read("my_key")).to eq("bob") end end context "when there is a race condition due to key expiring between GET calls" do before do allow(Discourse.redis).to receive(:get).and_wrap_original do |original_method, *args| original_method.call(*args).tap { Discourse.redis.del(*args) } end end it "isn't prone to that race condition" do expect(fetch_value).to eq("bob") end end end end