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