diff --git a/web/lib/potato_mesh/application/identity.rb b/web/lib/potato_mesh/application/identity.rb index 05d69d5..a4f9df9 100644 --- a/web/lib/potato_mesh/application/identity.rb +++ b/web/lib/potato_mesh/application/identity.rb @@ -195,24 +195,31 @@ module PotatoMesh [json_output, signature] end - # Regenerate the well-known document when the on-disk copy is stale. + # Regenerate the well-known document when it is stale or when the existing + # content no longer matches the current instance configuration. # # @return [void] def refresh_well_known_document_if_stale FileUtils.mkdir_p(well_known_directory) path = well_known_file_path now = Time.now + json_output, signature = build_well_known_document + expected_contents = json_output.end_with?("\n") ? json_output : "#{json_output}\n" + + needs_update = true if File.exist?(path) + current_contents = File.binread(path) mtime = File.mtime(path) - if (now - mtime) < PotatoMesh::Config.well_known_refresh_interval - return + if current_contents == expected_contents && + (now - mtime) < PotatoMesh::Config.well_known_refresh_interval + needs_update = false end end - json_output, signature = build_well_known_document + return unless needs_update + File.open(path, File::WRONLY | File::CREAT | File::TRUNC, 0o644) do |file| - file.write(json_output) - file.write("\n") unless json_output.end_with?("\n") + file.write(expected_contents) end debug_log( diff --git a/web/spec/identity_spec.rb b/web/spec/identity_spec.rb index 4264bee..a3bc570 100644 --- a/web/spec/identity_spec.rb +++ b/web/spec/identity_spec.rb @@ -64,4 +64,59 @@ RSpec.describe PotatoMesh::App::Identity do allow(PotatoMesh::Config).to receive(:legacy_keyfile_candidates).and_call_original end end + + describe ".refresh_well_known_document_if_stale" do + let(:storage_dir) { Dir.mktmpdir } + let(:well_known_path) do + File.join(storage_dir, File.basename(PotatoMesh::Config.well_known_relative_path)) + end + + before do + allow(PotatoMesh::Config).to receive(:well_known_storage_root).and_return(storage_dir) + allow(PotatoMesh::Config).to receive(:well_known_relative_path).and_return(".well-known/potato-mesh") + allow(PotatoMesh::Config).to receive(:well_known_refresh_interval).and_return(86_400) + allow(PotatoMesh::Sanitizer).to receive(:sanitized_site_name).and_return("Test Instance") + allow(PotatoMesh::Sanitizer).to receive(:sanitize_instance_domain).and_return("example.com") + end + + after do + FileUtils.remove_entry(storage_dir) + allow(PotatoMesh::Config).to receive(:well_known_storage_root).and_call_original + allow(PotatoMesh::Config).to receive(:well_known_relative_path).and_call_original + allow(PotatoMesh::Config).to receive(:well_known_refresh_interval).and_call_original + allow(PotatoMesh::Sanitizer).to receive(:sanitized_site_name).and_call_original + allow(PotatoMesh::Sanitizer).to receive(:sanitize_instance_domain).and_call_original + end + + it "writes a well-known document when none exists" do + PotatoMesh::Application.refresh_well_known_document_if_stale + + expect(File.exist?(well_known_path)).to be(true) + document = JSON.parse(File.read(well_known_path)) + expect(document.fetch("version")).to eq(PotatoMesh::Application::APP_VERSION) + expect(document.fetch("domain")).to eq("example.com") + end + + it "rewrites the document when configuration values change" do + PotatoMesh::Application.refresh_well_known_document_if_stale + original_contents = File.binread(well_known_path) + + stub_const("PotatoMesh::Application::APP_VERSION", "9.9.9-test") + PotatoMesh::Application.refresh_well_known_document_if_stale + + rewritten_contents = File.binread(well_known_path) + expect(rewritten_contents).not_to eq(original_contents) + document = JSON.parse(rewritten_contents) + expect(document.fetch("version")).to eq("9.9.9-test") + end + + it "does not rewrite when content is current and within the refresh interval" do + PotatoMesh::Application.refresh_well_known_document_if_stale + original_contents = File.binread(well_known_path) + + PotatoMesh::Application.refresh_well_known_document_if_stale + + expect(File.binread(well_known_path)).to eq(original_contents) + end + end end