Files
potato-mesh/web/spec/filesystem_spec.rb
l5y ea9c633eff Fix legacy configuration migration to XDG directories (#317)
* Handle legacy config migration for XDG assets

* Ensure legacy key migration precedes identity load

* Apply rufo formatting to identity module
2025-10-13 14:02:17 +02:00

223 lines
7.5 KiB
Ruby

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# frozen_string_literal: true
require "spec_helper"
RSpec.describe PotatoMesh::App::Filesystem do
let(:harness_class) do
Class.new do
extend PotatoMesh::App::Filesystem
class << self
def debug_entries
@debug_entries ||= []
end
def warning_entries
@warning_entries ||= []
end
def debug_log(message, context:, **metadata)
debug_entries << { message: message, context: context, metadata: metadata }
end
def warn_log(message, context:, **metadata)
warning_entries << { message: message, context: context, metadata: metadata }
end
def reset_logs!
@debug_entries = []
@warning_entries = []
end
end
end
end
around do |example|
harness_class.reset_logs!
example.run
harness_class.reset_logs!
end
describe "#perform_initial_filesystem_setup!" do
it "migrates the legacy database and keyfile" do
Dir.mktmpdir do |dir|
legacy_db = File.join(dir, "legacy", "mesh.db")
legacy_key = File.join(dir, "legacy-config", "keyfile")
new_db = File.join(dir, "data", "potato-mesh", "mesh.db")
new_key = File.join(dir, "config", "potato-mesh", "keyfile")
FileUtils.mkdir_p(File.dirname(legacy_db))
File.write(legacy_db, "db")
FileUtils.mkdir_p(File.dirname(legacy_key))
File.write(legacy_key, "key")
allow(PotatoMesh::Config).to receive_messages(
legacy_db_path: legacy_db,
db_path: new_db,
default_db_path: new_db,
keyfile_path: new_key,
)
allow(PotatoMesh::Config).to receive(:legacy_keyfile_candidates).and_return([legacy_key])
harness_class.perform_initial_filesystem_setup!
expect(File).to exist(new_db)
expect(File).to exist(new_key)
expect(File.read(new_db)).to eq("db")
expect(File.read(new_key)).to eq("key")
expect(File.stat(new_key).mode & 0o777).to eq(0o600)
expect(File.stat(new_db).mode & 0o777).to eq(0o600)
expect(harness_class.debug_entries.size).to eq(2)
expect(harness_class.warning_entries).to be_empty
end
end
it "migrates repository configuration assets from web/config" do
Dir.mktmpdir do |dir|
web_root = File.join(dir, "web")
legacy_key = File.join(web_root, "config", "potato-mesh", "keyfile")
legacy_well_known = File.join(web_root, "config", "potato-mesh", "well-known", "potato-mesh")
destination_root = File.join(dir, "xdg-config", "potato-mesh")
new_key = File.join(destination_root, "keyfile")
new_well_known = File.join(destination_root, "well-known", "potato-mesh")
FileUtils.mkdir_p(File.dirname(legacy_key))
File.write(legacy_key, "legacy-key")
FileUtils.mkdir_p(File.dirname(legacy_well_known))
File.write(legacy_well_known, "{\"legacy\":true}")
allow(PotatoMesh::Config).to receive(:web_root).and_return(web_root)
allow(PotatoMesh::Config).to receive(:keyfile_path).and_return(new_key)
allow(PotatoMesh::Config).to receive(:well_known_storage_root).and_return(File.dirname(new_well_known))
allow(PotatoMesh::Config).to receive(:well_known_relative_path).and_return(".well-known/potato-mesh")
allow(PotatoMesh::Config).to receive(:legacy_db_path).and_return(File.join(dir, "legacy", "mesh.db"))
allow(PotatoMesh::Config).to receive(:db_path).and_return(File.join(dir, "data", "potato-mesh", "mesh.db"))
allow(PotatoMesh::Config).to receive(:default_db_path).and_return(File.join(dir, "data", "potato-mesh", "mesh.db"))
harness_class.perform_initial_filesystem_setup!
expect(File).to exist(new_key)
expect(File.read(new_key)).to eq("legacy-key")
expect(File.stat(new_key).mode & 0o777).to eq(0o600)
expect(File).to exist(new_well_known)
expect(File.read(new_well_known)).to eq("{\"legacy\":true}")
expect(File.stat(new_well_known).mode & 0o777).to eq(0o644)
expect(harness_class.debug_entries.map { |entry| entry[:context] }).to include("filesystem.keys", "filesystem.well_known")
end
end
it "skips database migration when using a custom destination" do
Dir.mktmpdir do |dir|
legacy_db = File.join(dir, "legacy", "mesh.db")
new_db = File.join(dir, "custom", "database.db")
FileUtils.mkdir_p(File.dirname(legacy_db))
File.write(legacy_db, "db")
allow(PotatoMesh::Config).to receive_messages(
legacy_db_path: legacy_db,
db_path: new_db,
default_db_path: File.join(dir, "default", "mesh.db"),
legacy_keyfile_path: File.join(dir, "old", "keyfile"),
keyfile_path: File.join(dir, "config", "keyfile"),
)
harness_class.perform_initial_filesystem_setup!
expect(File).not_to exist(new_db)
end
end
end
describe "private migration helpers" do
it "does not migrate when the source is missing" do
Dir.mktmpdir do |dir|
destination = File.join(dir, "target", "file")
harness_class.send(
:migrate_legacy_file,
File.join(dir, "missing"),
destination,
chmod: 0o600,
context: "spec.context",
)
expect(File).not_to exist(destination)
expect(harness_class.debug_entries).to be_empty
end
end
it "does not overwrite existing destinations" do
Dir.mktmpdir do |dir|
source = File.join(dir, "source")
destination = File.join(dir, "destination")
File.write(source, "alpha")
FileUtils.mkdir_p(File.dirname(destination))
File.write(destination, "beta")
harness_class.send(
:migrate_legacy_file,
source,
destination,
chmod: 0o600,
context: "spec.context",
)
expect(File.read(destination)).to eq("beta")
end
end
it "ignores migrations when the source and destination are identical" do
Dir.mktmpdir do |dir|
path = File.join(dir, "shared")
File.write(path, "same")
harness_class.send(
:migrate_legacy_file,
path,
path,
chmod: 0o600,
context: "spec.context",
)
expect(harness_class.debug_entries).to be_empty
end
end
it "logs warnings when the migration fails" do
Dir.mktmpdir do |dir|
source = File.join(dir, "source")
destination = File.join(dir, "destination")
File.write(source, "data")
allow(FileUtils).to receive(:mkdir_p).and_raise(Errno::EACCES)
harness_class.send(
:migrate_legacy_file,
source,
destination,
chmod: 0o600,
context: "spec.context",
)
expect(harness_class.warning_entries.size).to eq(1)
expect(harness_class.debug_entries).to be_empty
end
ensure
allow(FileUtils).to receive(:mkdir_p).and_call_original
end
end
end