diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb index e2ce7a0d..056f5cff 100644 --- a/app/helpers/admin/configs_helper.rb +++ b/app/helpers/admin/configs_helper.rb @@ -41,6 +41,7 @@ module Admin::ConfigsHelper # @todo find out how to pass +checked_value+ and +unchecked_value+ to +input_field+ def config_input_field(form, key, options = {}) return unless @cfg.allowed_key? :key + options[:required] ||= false config_input_field_options form, key, options config_input_tooltip_options form, key, options if options[:as] == :boolean diff --git a/config/environments/test.rb b/config/environments/test.rb index f389ad59..f0349f7c 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -7,6 +7,9 @@ Foodsoft::Application.configure do # and recreated between test runs. Don't rely on the data there! config.cache_classes = true + # We clear the cache for each test, let's do that in memory. + config.cache_store = :memory_store + # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that # preloads Rails for running tests, you may have to set it to true. diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index 6d224279..4d4c67db 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -8,6 +8,26 @@ # In addition to the configuration file, values can be overridden in the database # using {RailsSettings::CachedSettings} as +foodcoop..**+. # +# Some values may not be set in the database (e.g. the database connection to +# sharedlists, or +default_scope+), these are defined as children of the +# +protected+ key. The default contains a sensible list, but you can modify +# that. Here's an almost minimal example: +# +# default: +# default_scope: f +# host: order.foodstuff.test # hostname for urls in emails +# +# name: Fairy Foodstuff # the name of our foodcoop +# contact: +# # ... +# email: fairy@foodstuff.test # general contact email address +# +# price_markup: 6 # foodcoop margin +# +# protected: +# shared_lists: false # allow database connection override +# use_messages: true # foodcoops can't disable the use of messages +# class FoodsoftConfig # @!attribute scope @@ -132,10 +152,14 @@ class FoodsoftConfig # @return [Boolean] Whether this key may be set in the database def allowed_key?(key) # fast check for keys without nesting - return !self.config[:protected].keys.include?(key.to_s) + return !self.config[:protected][key] # @todo allow to check nested keys as well end + # @return [Hash] Full configuration. + def to_hash + Hash[keys.map {|k| [k, self[k]]} ] + end protected @@ -185,16 +209,19 @@ class FoodsoftConfig ActiveRecord::Base.establish_connection(database_config) end - # When new options are introduced, put backward-compatible defaults here, so that - # configuration files that haven't been updated, still work as they did. This also - # makes sure that the configuration editor picks up the defaults. - # @see #default_foodsoft_config + # Completes foodcoop configuration with program defaults. + # @see #foodsoft_config def set_missing - config.replace(default_config.merge(config)) + config.replace(default_config.deep_merge(config)) end + # Returns program-default configuration. + # When new options are introduced, put backward-compatible defaults here, so that + # configuration files that haven't been updated, still work as they did. This also + # makes sure that the configuration editor picks up the defaults. # @return [Hash] Program-default foodcoop configuration. # @see #default_config + # @see #set_missing def get_default_config cfg = { use_nick: true, @@ -202,14 +229,14 @@ class FoodsoftConfig # English is the default language, and this makes it show up as default. default_locale: 'en', foodsoft_url: 'https://github.com/foodcoops/foodsoft', - # The following keys cannot be set by foodcoops themselves. + # The following keys cannot, by default, be set by foodcoops themselves. protected: { - multi_coop_install: nil, - default_scope: nil, - notification: nil, - shared_lists: nil, - protected: nil, - database: nil + multi_coop_install: true, + default_scope: true, + notification: true, + shared_lists: true, + protected: true, + database: true } } # allow engines to easily add to this diff --git a/spec/app_config.yml b/spec/app_config.yml index f9d0ed81..5da8aa3c 100644 --- a/spec/app_config.yml +++ b/spec/app_config.yml @@ -13,6 +13,9 @@ default: &defaults contact: email: fc@minimal.test + # required by configuration form (but otherwise not) + homepage: http://www.minimal.test/ + # true by default to keep compat with older installations, but test with false here use_nick: false diff --git a/spec/integration/config_spec.rb b/spec/integration/config_spec.rb new file mode 100644 index 00000000..d62f0894 --- /dev/null +++ b/spec/integration/config_spec.rb @@ -0,0 +1,45 @@ +require_relative '../spec_helper' + +describe 'admin/configs', type: :feature do + let(:name) { Faker::Lorem.words(rand(2..4)).join(' ') } + + describe type: :feature, js: true do + let(:admin) { create :admin } + before { login admin } + + it 'has initial value' do + FoodsoftConfig[:name] = name + visit admin_config_path + within('form.config') do + expect(find_field('config_name').value).to eq name + end + end + + it 'can modify a value' do + visit admin_config_path + fill_in 'config_name', with: name + within('form.config') do + find('input[type="submit"]').click + expect(find_field('config_name').value).to eq name + end + expect(FoodsoftConfig[:name]).to eq name + end + + it 'keeps config the same without changes' do + orig_values = get_full_config + visit admin_config_path + within('form.config') do + find('input[type="submit"]').click + expect(find_field('config_name').value).to eq FoodsoftConfig[:name] + end + expect(get_full_config).to eq orig_values + end + + def get_full_config + cfg = FoodsoftConfig.to_hash.deep_dup + cfg.each {|k,v| v.reject! {|k,v| v.blank?} if v.is_a? Hash} + cfg + end + + end +end diff --git a/spec/lib/foodsoft_config_spec.rb b/spec/lib/foodsoft_config_spec.rb new file mode 100644 index 00000000..6b35a3c2 --- /dev/null +++ b/spec/lib/foodsoft_config_spec.rb @@ -0,0 +1,85 @@ +require_relative '../spec_helper' + +describe FoodsoftConfig do + let(:name) { Faker::Lorem.words(rand(2..4)).join(' ') } + let(:other_name) { Faker::Lorem.words(rand(2..4)).join(' ') } + + it 'returns a default value' do + expect(FoodsoftConfig[:protected][:database]).to be_true + end + + it 'returns an empty default value' do + expect(FoodsoftConfig[:protected][:LIUhniuyGNKUQTWfbiOQIWYexngo78hqexul]).to be_false + end + + it 'returns a configuration value' do + FoodsoftConfig.config[:name] = name + expect(FoodsoftConfig[:name]).to eq name + end + + it 'can set a configuration value' do + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq name + end + + it 'can override a configuration value' do + FoodsoftConfig.config[:name] = name + FoodsoftConfig[:name] = other_name + expect(FoodsoftConfig[:name]).to eq other_name + end + + it 'cannot set a default protected value' do + old = FoodsoftConfig[:database] + FoodsoftConfig[:database] = name + expect(FoodsoftConfig.config[:database]).to eq old + end + + it 'can unprotect a default protected value' do + FoodsoftConfig.config[:protected][:database] = false + old = FoodsoftConfig[:database] + FoodsoftConfig[:database] = name + expect(FoodsoftConfig[:database]).to eq name + end + + describe 'can protect a value', type: :feature do + before do + FoodsoftConfig.config[:protected][:name] = true + end + + it 'can protect a value' do + old_name = FoodsoftConfig[:name] + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq old_name + end + + it 'and unprotect it again' do + old_name = FoodsoftConfig[:name] + FoodsoftConfig.config[:protected][:name] = false + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq name + end + end + + describe 'has indifferent access', type: :feature do + it 'with symbol' do + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq FoodsoftConfig['name'] + end + + it 'with string' do + FoodsoftConfig['name'] = name + expect(FoodsoftConfig['name']).to eq FoodsoftConfig[:name] + end + + it 'with nested symbol' do + FoodsoftConfig[:protected][:database] = true + expect(FoodsoftConfig[:protected]['database']).to eq FoodsoftConfig[:protected][:database] + end + + it 'with nested string' do + FoodsoftConfig[:protected]['database'] = true + expect(FoodsoftConfig[:protected]['database']).to eq FoodsoftConfig[:protected][:database] + end + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 739df522..3b7389c0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -35,6 +35,8 @@ RSpec.configure do |config| end config.after(:each) do DatabaseCleaner.clean + # Need to clear cache for RailsSettings::CachedSettings + Rails.cache.clear end # reload foodsoft configuration, so that tests can use FoodsoftConfig.config[:foo]=x