spec/lib/chamber_spec.rb in chamber-0.0.4 vs spec/lib/chamber_spec.rb in chamber-1.0.0
- old
+ new
@@ -1,250 +1,286 @@
-require 'spec_helper'
+require 'rspectacular'
+require 'chamber'
+require 'fileutils'
-require 'tempfile'
+FileUtils.mkdir_p '/tmp/chamber/settings' unless File.exist? '/tmp/chamber/settings'
-class Settings
- extend Chamber
+File.open('/tmp/chamber/settings.yml', 'w+') do |file|
+ file.puts <<-HEREDOC
+test:
+ my_setting: my_value
+ my_boolean: "false"
+ my_dynamic_setting: <%= 1 + 1 %>
+ my_ftp_url: ftp://<%= Chamber[:test][:my_username] %>:<%= Chamber[:test][:my_password] %>@127.0.0.1
+ another_level:
+ setting_one: 1
+ setting_two: 2
+ level_three:
+ an_array:
+ - item 1
+ - item 2
+ - item 3
+ a_scalar: 'hello'
+ HEREDOC
end
-describe Chamber do
- before do
- Settings.clear!
+File.open('/tmp/chamber/credentials.yml', 'w+') do |file|
+ file.puts <<-HEREDOC
+test:
+ my_username: username
+ my_password: password
+ HEREDOC
+end
+
+File.open('/tmp/chamber/settings-blue.yml', 'w+') do |file|
+ file.puts <<-HEREDOC
+test:
+ my_other_setting: my_other_value
+ another_level:
+ setting_one: 3
+other:
+ everything: works
+ HEREDOC
+end
+
+File.open('/tmp/chamber/settings/some_settings_file.yml', 'w+') do |file|
+ file.puts <<-HEREDOC
+blue:
+ my_settings_for_inline_namespace: my_value_for_inline_namespace
+my_non_inline_namespaced_setting: my_value_for_non_inline_namespace
+ HEREDOC
+end
+
+File.open('/tmp/chamber/settings/sub_settings.yml', 'w+') do |file|
+ file.puts <<-HEREDOC
+sub_settings:
+ my_sub_setting: my_sub_setting_value
+ HEREDOC
+end
+
+File.open('/tmp/chamber/settings/sub_settings-blue.yml', 'w+') do |file|
+ file.puts <<-HEREDOC
+sub_settings:
+ my_namespaced_sub_setting: my_namespaced_sub_setting_value
+ HEREDOC
+end
+
+File.open('/tmp/chamber/settings/only_namespaced_settings-blue.yml', 'w+') do |file|
+ file.puts <<-HEREDOC
+only_namespaced_sub_settings:
+ another_sub_setting: namespaced
+ HEREDOC
+end
+
+describe Chamber, :singletons => [Chamber] do
+ before(:each) { Chamber.load(:basepath => '/tmp/chamber') }
+
+ it 'knows how to load itself with a path string' do
+ Chamber.load(:basepath => '/tmp/chamber')
+
+ expect(Chamber.basepath.to_s).to eql '/tmp/chamber'
end
- describe '.source' do
- context 'when an invalid option is specified' do
- let(:options) do
- { foo: 'bar' }
- end
+ it 'knows how to load itself with a path object' do
+ Chamber.load(:basepath => Pathname.new('/tmp/chamber'))
- it 'raises ChamberInvalidOptionError' do
- expect { Settings.source('filename', options) }.to raise_error(Chamber::ChamberInvalidOptionError)
- end
- end
+ expect(Chamber.basepath.to_s).to eql '/tmp/chamber'
+ end
- context 'when no options are specified' do
- it 'does not raise an error' do
- expect { Settings.source('filename') }.not_to raise_error
- end
- end
+ it 'processes settings files through ERB before YAML' do
+ expect(Chamber[:test][:my_dynamic_setting]).to eql 2
+ end
- context 'when valid options are specified' do
- context 'and options only contains :namespace' do
- let(:options) do
- { namespace: 'bar' }
- end
+ it 'can access settings through a hash-like syntax' do
+ expect(Chamber[:test][:my_setting]).to eql 'my_value'
+ end
- it 'does not raise an error' do
- expect { Settings.source('filename', options) }.not_to raise_error
- end
- end
+ it 'can access the settings through method-based access' do
+ expect(Chamber.instance.test.my_setting).to eql 'my_value'
+ end
- context 'and options only contains :override_from_environment' do
- let(:options) do
- { override_from_environment: 'bar' }
- end
+ it 'can access the instance via "env"' do
+ expect(Chamber.instance.test.my_setting).to eql 'my_value'
+ end
- it 'does not raise an error' do
- expect { Settings.source('filename', options) }.not_to raise_error
- end
- end
+ it 'prefers values stored in environment variables over those in the YAML files' do
+ ENV['TEST_MY_SETTING'] = 'some_other_value'
+ ENV['TEST_ANOTHER_LEVEL_LEVEL_THREE_AN_ARRAY'] = 'something'
- context 'and options contains both :namespace and :override_from_environment' do
- let(:options) do
- {
- namespace: 'bar',
- override_from_environment: 'bar'
- }
- end
+ Chamber.load(:basepath => '/tmp/chamber')
+ expect(Chamber.instance.test.my_setting).to eql 'some_other_value'
+ expect(Chamber.instance.test.another_level.level_three.an_array).to eql 'something'
+ expect(Chamber.instance.test.my_dynamic_setting).to eql 2
- it 'does not raise an error' do
- expect { Settings.source('filename', options) }.not_to raise_error
- end
- end
- end
+ ENV.delete 'TEST_MY_SETTING'
+ ENV.delete 'TEST_ANOTHER_LEVEL_LEVEL_THREE_AN_ARRAY'
end
- describe '.load!' do
- context 'when a non-existent file is specified' do
- let(:file) { Tempfile.new('test') }
- let!(:filename) { file.path }
+ it 'can load files based on the namespace passed in' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :my_namespace => -> { 'blue' } } )
- before do
- file.close
- file.unlink
- expect(File.exists?(filename)).to be_false
- Settings.source filename
- end
+ expect(Chamber.instance.other.everything).to eql 'works'
+ expect(Chamber.instance.test.my_dynamic_setting).to eql 2
+ end
- it 'does not raise an error' do
- expect { Settings.load! }.not_to raise_error
- end
+ it 'loads multiple namespaces if it is called twice' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :first_namespace_call => -> { :first },
+ :second_namespace_call => -> { :second }, } )
- it 'leaves the instance empty' do
- Settings.load!
- expect(Settings.instance).to be_empty
- end
- end
+ expect(Chamber.instance.namespaces.to_a).to eql [:first, :second]
+ end
- context 'when an existing file is specified' do
- let(:file) { Tempfile.new('test') }
- let(:filename) { file.path }
- let(:content) do
- <<-CONTENT
-secret:
- environment: CHAMBER_TEST
-development:
- foo: bar dev
-test:
- foo: bar test
-CONTENT
- end
+ it 'does not load the same namespace twice' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :first_namespace_call => -> { :first },
+ :first_namespace_call => -> { :first }, } )
- before do
- file.write(content)
- file.close
- end
+ expect(Chamber.instance.namespaces.to_a).to eql [:first]
+ end
- after do
- file.unlink
- end
+ it 'will load settings files which are only namespaced' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :my_namespace => -> { 'blue' } } )
- context 'and no options are specified' do
- before { Settings.source(filename) }
+ expect(Chamber[:only_namespaced_sub_settings][:another_sub_setting]).to eql 'namespaced'
+ end
- let(:expected) do
- {
- 'secret' => {
- 'environment' => 'CHAMBER_TEST'
- },
- 'development' => {
- 'foo' => 'bar dev'
- },
- 'test' => {
- 'foo' => 'bar test'
- }
- }
- end
+ it 'clears all settings each time the settings are loaded' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :my_namespace => -> { 'blue' } } )
- it 'loads all settings' do
- Settings.load!
- expect(Settings.instance.to_hash).to eq expected
- end
+ expect(Chamber[:only_namespaced_sub_settings][:another_sub_setting]).to eql 'namespaced'
- it 'provides access to all settings without the instance root' do
- Settings.load!
- expect(Settings.to_hash).to eq expected
- end
- end
+ Chamber.load(:basepath => '/tmp/chamber')
- context 'and the :namespace option is specified' do
- before { Settings.source(filename, namespace: namespace) }
+ expect(Chamber[:only_namespaced_sub_settings]).to be_nil
+ end
- context 'and it is valid' do
- let(:namespace) { 'development' }
- let(:expected) do
- {
- 'foo' => 'bar dev'
- }
- end
+ it 'still raises an error if you try to send a message which the settings hash does not understand' do
+ expect{ Chamber.instance.i_do_not_know }.to raise_error NoMethodError
+ end
- it 'loads settings for the specified namespace' do
- Settings.load!
- expect(Settings.instance.to_hash).to eq expected
- end
+ it 'does not raise an exception if a namespaced file does not exist' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :non_existant_namespace => -> { false } } )
- it 'provides access to all settings without the instance root' do
- Settings.load!
- expect(Settings.to_hash).to eq expected
- end
- end
+ expect { Chamber.load(:basepath => '/tmp/chamber') }.not_to raise_error
+ end
- context 'and it is not valid' do
- let(:namespace) { 'staging' }
+ it 'merges (not overrides) subsequent settings' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :my_namespace => -> { 'blue' } } )
- it 'raises a KeyError' do
- expect { Settings.load! }.to raise_error(KeyError)
- end
- end
- end
+ expect(Chamber.instance.test.my_setting).to eql 'my_value'
+ expect(Chamber.instance.test.my_other_setting).to eql 'my_other_value'
+ expect(Chamber.instance.test.another_level.setting_one).to eql 3
+ end
- context 'and the :override_from_environment option is specified' do
- before { Settings.source(filename, override_from_environment: true) }
+ it 'loads YAML files from the "settings" directory under the base directory if any exist' do
+ expect(Chamber.instance.sub_settings.my_sub_setting).to eql 'my_sub_setting_value'
+ end
- context 'and the environment variable is present' do
- before { ENV['CHAMBER_TEST'] = 'value' }
+ it 'does not load YAML files from the "settings" directory if it is namespaced' do
+ expect(Chamber['sub_settings-namespaced']).to be_nil
+ end
- it 'overrides the settings from the environment' do
- Settings.load!
- expect(Settings.instance.secret).to eq 'value'
- end
+ it 'loads namespaced YAML files in the "settings" directory if they correspond to a value namespace' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :my_namespace => -> { 'blue' } } )
- it 'provides access to all settings without the instance root' do
- Settings.load!
- expect(Settings.secret).to eq 'value'
- end
- end
+ expect(Chamber['sub_settings']['my_namespaced_sub_setting']).to eql 'my_namespaced_sub_setting_value'
+ end
- context 'and the environment variable is not present' do
- before { ENV.delete('CHAMBER_TEST') }
+ it 'loads namespaced settings if they are inline in a non-namespaced filename' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :my_namespace => -> { 'blue' } } )
- it 'sets the value to nil' do
- Settings.load!
- expect(Settings.instance.secret).to be_nil
- end
+ expect(Chamber['my_settings_for_inline_namespace']).to eql 'my_value_for_inline_namespace'
+ end
- it 'provides acccess to all settings without the instance root' do
- Settings.load!
- expect(Settings.secret).to be_nil
- end
- end
- end
- end
+ it 'does not load non-namespaced data from a file if inline namespaces are found' do
+ Chamber.load( :basepath => '/tmp/chamber',
+ :namespaces => {
+ :my_namespace => -> { 'blue' } } )
+
+ expect(Chamber['my_non_inline_namespaced_setting']).not_to eql 'my_value_for_non_inline_namespace'
end
- describe '.reload!' do
- context 'when a filename is changed after it is sourced and loaded' do
- let(:file) { Tempfile.new('test') }
- let!(:filename) { file.path }
- let(:content) do
- <<-CONTENT
-initial: value
-CONTENT
- end
- let(:modified) do
- <<-MODIFIED
-modified: changed
-MODIFIED
- end
+ it 'loads the entire inline namespaced file if no namespaces are passed in since it does not know they are namespaced' do
+ Chamber.load(:basepath => '/tmp/chamber')
- before do
- file.write(content)
- file.close
- Settings.source(filename)
- Settings.load!
- end
+ expect(Chamber['blue']['my_settings_for_inline_namespace']).to eql 'my_value_for_inline_namespace'
+ expect(Chamber['my_non_inline_namespaced_setting']).to eql 'my_value_for_non_inline_namespace'
+ end
- after do
- file.unlink
- end
+ it 'can convert the settings to their environment variable versions' do
+ Chamber.load(:basepath => '/tmp/chamber')
- it 'reloads the settings' do
- File.open(filename, 'w') { |writer| writer.write(modified) }
+ expect(Chamber.to_environment).to eql(
+ 'SUB_SETTINGS_MY_SUB_SETTING' => 'my_sub_setting_value',
+ 'TEST_ANOTHER_LEVEL_LEVEL_THREE_AN_ARRAY' => '["item 1", "item 2", "item 3"]',
+ 'TEST_ANOTHER_LEVEL_LEVEL_THREE_A_SCALAR' => 'hello',
+ 'TEST_ANOTHER_LEVEL_SETTING_ONE' => '1',
+ 'TEST_ANOTHER_LEVEL_SETTING_TWO' => '2',
+ 'TEST_MY_DYNAMIC_SETTING' => '2',
+ 'TEST_MY_SETTING' => 'my_value',
+ 'TEST_MY_FTP_URL' => 'ftp://username:password@127.0.0.1',
+ 'TEST_MY_PASSWORD' => 'password',
+ 'TEST_MY_SETTING' => 'my_value',
+ 'TEST_MY_USERNAME' => 'username',
+ 'TEST_MY_BOOLEAN' => 'false',
+ 'BLUE_MY_SETTINGS_FOR_INLINE_NAMESPACE' => 'my_value_for_inline_namespace',
+ 'MY_NON_INLINE_NAMESPACED_SETTING' => 'my_value_for_non_inline_namespace',
+ )
+ end
- expect { Settings.reload! }.to change { Settings.instance.to_hash }.from({ 'initial' => 'value' }).to({ 'modified' => 'changed' })
- end
- end
+ it 'can convert boolean-like strings to actual booleans' do
+ expect(Chamber[:test][:my_boolean]).to be_a FalseClass
end
- describe '.instance' do
- it 'is a Hashie::Mash' do
- expect(Settings.instance).to be_a(Hashie::Mash)
- end
+ it 'can use data from credentials in subsequently loaded files' do
+ expect(Chamber[:test][:my_ftp_url]).to eql 'ftp://username:password@127.0.0.1'
end
- describe '.env' do
- it 'is aliased to :instance' do
- expect(Settings.method(:env)).to eq Settings.method(:instance)
- end
+ it 'can notify properly whether it responds to messages if the underlying settings does' do
+ expect(Chamber.env.respond_to?(:sub_settings)).to be_a TrueClass
+ end
+
+ it 'can explicitly specify files without specifying a basepath' do
+ Chamber.load files: ['/tmp/chamber/credentials.yml']
+
+ expect(Chamber.filenames).to eql ['/tmp/chamber/credentials.yml']
+ expect(Chamber.settings.to_hash).to eql('test' => {
+ 'my_username' => 'username',
+ 'my_password' => 'password', } )
+ end
+
+ it 'ignores the basepath if file patterns are explicitly passed in' do
+ Chamber.load basepath: '/tmp/chamber',
+ files: 'credentials.yml'
+
+ expect(Chamber.filenames).to be_empty
+ end
+
+ it 'can render itself as a string even if it has not been loaded' do
+ Singleton.__init__(Chamber)
+
+ expect(Chamber.to_s).to eql ''
+ end
+
+ it 'can determine settings even if it has not been loaded' do
+ Singleton.__init__(Chamber)
+
+ expect(Chamber.settings.to_hash).to eql({})
end
end