diff --git a/lib/fixture_kit/file_cache.rb b/lib/fixture_kit/file_cache.rb index 8306e35..58a2a05 100644 --- a/lib/fixture_kit/file_cache.rb +++ b/lib/fixture_kit/file_cache.rb @@ -2,6 +2,7 @@ require "json" require "fileutils" +require "active_support/core_ext/file/atomic" require "active_support/inflector" module FixtureKit @@ -45,7 +46,7 @@ def write(data) } FileUtils.mkdir_p(File.dirname(path)) - File.write(path, JSON.pretty_generate(content)) + File.atomic_write(path) { |file| file.write(JSON.pretty_generate(content)) } end private diff --git a/spec/unit/file_cache_spec.rb b/spec/unit/file_cache_spec.rb index 46e0f8e..db8a8ef 100644 --- a/spec/unit/file_cache_spec.rb +++ b/spec/unit/file_cache_spec.rb @@ -84,6 +84,25 @@ expect(File.exist?(nested_path)).to be(true) end + it "leaves nothing but the cache file behind" do + file_cache.write(FixtureKit::MemoryCache.new(data: {}, exposed: {})) + + expect(Dir.children(cache_path)).to contain_exactly("test_fixture.json") + end + + it "publishes the file with File.atomic_write so a concurrent reader never sees a partial write" do + data = FixtureKit::MemoryCache.new(data: {}, exposed: {}) + FileUtils.mkdir_p(cache_path) + file = File.new(file_path, "w") + allow(File).to receive(:atomic_write).and_yield(file) + + file_cache.write(data) + file.close + + expect(File).to have_received(:atomic_write).with(file_path) + expect(JSON.parse(File.read(file_path))).to eq({ "data" => {}, "exposed" => {} }) + end + it "raises CacheCorruptError when the file contains invalid JSON" do FileUtils.mkdir_p(cache_path) File.write(file_path, "this is not json")