dry-configurable/spec/integration/dry/configurable/config_spec.rb

278 lines
7.4 KiB
Ruby

# frozen_string_literal: true
require "pathname"
require "set"
RSpec.describe Dry::Configurable::Config do
subject(:config) do
klass.config
end
let(:klass) do
Class.new do
extend Dry::Configurable
end
end
describe "#configured?" do
it "returns false if the key has not been accessed" do
klass.setting :db
expect(klass.config.configured?(:db)).to be false
end
it "returns true if the key has been assigned" do
klass.setting :db
klass.config.db = "sqlite"
expect(klass.config.configured?(:db)).to be true
end
it "returns true when the key was assigned in a superclass" do
klass.setting :db
klass.config.db = "sqlite"
subclass = Class.new(klass)
expect(subclass.config.configured?(:db)).to be true
end
describe "assignable values" do
it "returns false if the key has been read" do
klass.setting :db
klass.config.db
expect(klass.config.configured?(:db)).to be false
end
it "returns false when the key was read in a superclass" do
klass.setting :db
klass.config.db
subclass = Class.new(klass)
expect(subclass.config.configured?(:db)).to be false
end
end
describe "cloneable values" do
it "returns false if the key has been read only" do
klass.setting :db, cloneable: true, default: []
klass.config.db
expect(klass.config.configured?(:db)).to be false
end
it "returns false if the key was read in a superclass" do
klass.setting :db, cloneable: true, default: []
klass.config.db
subclass = Class.new(klass)
expect(subclass.config.configured?(:db)).to be false
end
it "returns true if the value has been mutated" do
klass.setting :db, cloneable: true, default: []
klass.config.db << "sqlite"
expect(klass.config.configured?(:db)).to be true
end
it "returns true if the value was mutated in a superclass" do
klass.setting :db, cloneable: true, default: []
klass.config.db << "sqlite"
subclass = Class.new(klass)
expect(subclass.config.configured?(:db)).to be true
end
end
end
describe "#update" do
it "sets new config values in a flat config" do
klass.setting :db
config = klass.config.update(db: "sqlite")
expect(klass.config).to be(config)
expect(config.db).to eql("sqlite")
end
it "sets new config values in a nested config" do
klass.setting :db do
setting :user, default: "root"
setting :pass, default: "secret"
end
klass.config.update(db: {user: "jane", pass: "supersecret"})
expect(klass.config.db.user).to eql("jane")
expect(klass.config.db.pass).to eql("supersecret")
end
it "runs constructors" do
klass.setting :db do
setting :user, default: "root", constructor: ->(v) { v.upcase }
setting :sslcert, constructor: ->(v) { v&.values_at(:pem, :pass)&.join }
end
klass.config.update(db: {user: "jane", sslcert: {pem: "cert", pass: "qwerty"}})
expect(klass.config.db.user).to eql("JANE")
expect(klass.config.db.sslcert).to eql("certqwerty")
end
it "raises ArgumentError when setting value is not a Hash" do
klass.setting :db do
setting :user
end
expect { klass.config.update(db: "string") }
.to raise_error(ArgumentError, '"string" is not a valid setting value')
end
end
describe "#to_h" do
before do
klass.setting :db do
setting :user, default: "root"
setting :pass, default: "secret"
setting :ports, default: Set[123, 321]
end
end
it "returns the values as a hash" do
expect(klass.config.to_h).to eq(
db: {
user: "root",
pass: "secret",
ports: Set[123, 321]
}
)
end
end
describe "#dup" do
context "with an object" do
it "returns a deep-copy" do
klass = Class.new do
include Dry::Configurable
setting :db do
setting :user, default: "root"
setting :pass, default: "secret"
setting :ports, default: Set[123]
end
end
parent = Class.new(klass).new
parent.config.db.ports << 312
expect(parent.config.db.ports).to eql(Set[123, 312])
expect(parent.config.dup.db.ports).to eql(Set[123, 312])
child = Class.new(parent.class).new
child.config.db.ports << 476
expect(child.config.db.ports).to eql(Set[123, 476])
expect(child.config.dup.db.ports).to eql(Set[123, 476])
expect(klass.new.config.db.ports).to eql(Set[123])
end
it "returns an equal copy" do
klass = Class.new do
include Dry::Configurable
setting :db do
setting :user, default: "root"
setting :pass, default: "secret"
setting :ports, default: Set[123]
end
end
object = klass.new
expect(object.config.dup).to eql(object.config)
end
end
context "with a class" do
it "returns a deep-copy" do
klass.setting :db do
setting :user, default: "root"
setting :pass, default: "secret"
setting :ports, default: Set[123]
end
parent = Class.new(klass) do
config.db.ports << 312
end
child = Class.new(parent) do
config.db.ports << 476
end
expect(klass.config.db.ports).to eql(Set[123])
expect(parent.config.db.ports).to eql(Set[123, 312])
expect(child.config.db.ports).to eql(Set[123, 312, 476])
expect(parent.config.dup.db.ports).to eql(Set[123, 312])
expect(child.config.dup.db.ports).to eql(Set[123, 312, 476])
end
it "returns an equal copy" do
klass = Class.new do
extend Dry::Configurable
setting :db do
setting :user, default: "root"
setting :pass, default: "secret"
setting :ports, default: Set[123]
end
end
expect(klass.config.dup).to eql(klass.config)
end
end
end
describe "#[]" do
it "coerces name from string" do
klass.setting :db, default: :sqlite
expect(klass.config["db"]).to eql(:sqlite)
end
it "raises ArgumentError when name is not valid" do
expect { klass.config[:hello] }.to raise_error(ArgumentError, /hello/)
end
end
describe "#hash" do
it "returns the integer hash value for the convig based on its values" do
klass.setting :db
expect(klass.config.hash).to be_an_instance_of(Integer)
expect { klass.config.db = "sqlite" }.to change { klass.config.hash }
end
it "is memoized when the config is finalized", :performance do
klass.setting :a
klass.setting :b
klass.setting :c
klass.setting :d
klass.setting :e
finalized_config = klass.config.dup.finalize!
expect(finalized_config.hash).to eq klass.config.hash
expect { finalized_config.hash }.to perform_faster_than { klass.config.hash }.at_least(50).times
end
end
describe "#method_missing" do
it "provides access to reader methods" do
klass.setting :hello
expect { klass.config.method(:hello) }.to_not raise_error
end
it "provides access to writer methods" do
klass.setting :hello
expect { klass.config.method(:hello=) }.to_not raise_error
end
end
end