@@ -52,4 +52,61 @@ def fragment_doubles_factory(count:, ctx: {})
5252 end
5353 end
5454 end
55+
56+ describe "#read" do
57+ let ( :cache_key ) { "fragment-cache-key" }
58+ let ( :cache_store ) { double ( "cache_store" ) }
59+
60+ let ( :fragment ) do
61+ context = double ( "context" )
62+ allow ( context ) . to receive ( :namespace ) . with ( :interpreter ) . and_return ( current_path : [ "post" ] )
63+ allow ( context ) . to receive ( :[] ) . with ( :renew_cache ) . and_return ( nil )
64+
65+ described_class . new ( context ) . tap do |fragment |
66+ allow ( fragment ) . to receive ( :cache_key ) . and_return ( cache_key )
67+ end
68+ end
69+
70+ before do
71+ allow ( GraphQL ::FragmentCache ) . to receive ( :cache_store ) . and_return ( cache_store )
72+ end
73+
74+ # Regression test for the race condition fixed in #99: existence is checked
75+ # with #exist? *before* #read, so a key that is absent at lookup time is
76+ # reported as a cache miss without being read — instead of being read first
77+ # and mistaken for a cached nil if another request populates it in between.
78+ context "when the cache key does not exist" do
79+ before do
80+ allow ( cache_store ) . to receive ( :exist? ) . with ( cache_key ) . and_return ( false )
81+ allow ( cache_store ) . to receive ( :read ) . with ( cache_key )
82+ end
83+
84+ it "returns a cache miss without reading the absent key" do
85+ expect ( fragment . read ) . to be_nil
86+ expect ( cache_store ) . not_to have_received ( :read )
87+ end
88+ end
89+
90+ context "when a nil value is stored in the cache" do
91+ before do
92+ allow ( cache_store ) . to receive ( :exist? ) . with ( cache_key ) . and_return ( true )
93+ allow ( cache_store ) . to receive ( :read ) . with ( cache_key ) . and_return ( nil )
94+ end
95+
96+ it "returns the sentinel for a cached nil" do
97+ expect ( fragment . read ) . to be ( described_class ::NIL_IN_CACHE )
98+ end
99+ end
100+
101+ context "when a value is stored in the cache" do
102+ before do
103+ allow ( cache_store ) . to receive ( :exist? ) . with ( cache_key ) . and_return ( true )
104+ allow ( cache_store ) . to receive ( :read ) . with ( cache_key ) . and_return ( "cached value" )
105+ end
106+
107+ it "returns the cached value" do
108+ expect ( fragment . read ) . to eq ( "cached value" )
109+ end
110+ end
111+ end
55112end
0 commit comments