From e76890b5b9eea650d1acdac9609bc3ab6704662b Mon Sep 17 00:00:00 2001 From: Navid EMAD Date: Wed, 27 May 2026 14:39:35 +0200 Subject: [PATCH] Wrap corrupt cache reads in CacheCorruptError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FileCache#read leaks JSON::ParserError and KeyError to callers when the cache file is malformed (truncated, hand-edited, partial write). The original message gives no hint that the file is FixtureKit's cache or that the fix is to delete and regenerate. Wrap both error classes in a new FixtureKit::CacheCorruptError that includes the cache path and the original error class/message, plus a short note pointing at the remediation. This is a strict superset of the previous behavior — a rescue on the new error class still catches malformed cache files, and StandardError catches everything as before. No retry, no auto-regeneration. Disk-vs-memory consistency bugs should be loud, not silently papered over. --- lib/fixture_kit.rb | 1 + lib/fixture_kit/file_cache.rb | 4 ++++ spec/unit/file_cache_spec.rb | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/lib/fixture_kit.rb b/lib/fixture_kit.rb index 7caf094..33557a1 100644 --- a/lib/fixture_kit.rb +++ b/lib/fixture_kit.rb @@ -6,6 +6,7 @@ class DuplicateNameError < Error; end class InvalidFixtureDeclaration < Error; end class MultipleFixtures < Error; end class CacheMissingError < Error; end + class CacheCorruptError < Error; end class FixtureDefinitionNotFound < Error; end class RunnerAlreadyStartedError < Error; end class CircularFixtureInheritance < Error; end diff --git a/lib/fixture_kit/file_cache.rb b/lib/fixture_kit/file_cache.rb index 51da248..b58bd91 100644 --- a/lib/fixture_kit/file_cache.rb +++ b/lib/fixture_kit/file_cache.rb @@ -33,6 +33,10 @@ def read end MemoryCache.new(data: data, exposed: exposed) + rescue JSON::ParserError, KeyError => e + raise FixtureKit::CacheCorruptError, + "FixtureKit cache file at #{path} is corrupt or malformed (#{e.class}: #{e.message}). " \ + "Delete it and re-run to regenerate." end def write(data) diff --git a/spec/unit/file_cache_spec.rb b/spec/unit/file_cache_spec.rb index e3415e8..0342548 100644 --- a/spec/unit/file_cache_spec.rb +++ b/spec/unit/file_cache_spec.rb @@ -83,6 +83,24 @@ expect(File.exist?(nested_path)).to be(true) end + + it "raises CacheCorruptError when the file contains invalid JSON" do + FileUtils.mkdir_p(cache_path) + File.write(file_path, "this is not json") + + expect { file_cache.read }.to raise_error(FixtureKit::CacheCorruptError) do |error| + expect(error.message).to include(file_path) + expect(error.message).to include("JSON::ParserError") + end + end + + it "raises CacheCorruptError when the JSON is missing required keys" do + FileUtils.mkdir_p(cache_path) + File.write(file_path, JSON.dump({ "data" => {} })) # no "exposed" key + + expect { file_cache.read } + .to raise_error(FixtureKit::CacheCorruptError, /is corrupt or malformed.*KeyError/) + end end describe "#serialize_exposed" do