require 'minitest/autorun' require 'tmpdir' require_relative '../../../../lib/inspec/plugin/v2' # This file relies on setting environment variables for some # of its tests - it is NOT thread-safe. describe 'Inspec::Plugin::V2::ConfigFile' do orig_home = ENV['HOME'] let(:repo_path) { File.expand_path(File.join( __FILE__, '..', '..', '..', '..', '..')) } let(:config_fixtures_path) { File.join(repo_path, 'test', 'unit', 'mock', 'config_dirs') } let(:config_file_obj) { Inspec::Plugin::V2::ConfigFile.new(constructor_arg) } let(:constructor_arg) { File.join(config_fixtures_path, 'plugin_config_files', fixture_name + '.json') } after do ENV['HOME'] = orig_home ENV['INSPEC_CONFIG_DIR'] = nil end #----------------------------------------------------------# # Path Handling #----------------------------------------------------------# describe 'locating the file' do describe 'when no env var is set' do let(:constructor_arg) { nil } it 'defaults to the home directory' do ENV['HOME'] = File.join(config_fixtures_path, 'fakehome') expected_path = File.join(ENV['HOME'], '.inspec', 'plugins.json') config_file_obj.path.must_equal expected_path end end describe 'when an env var is set' do let(:constructor_arg) { nil } it 'looks to the dir specified by the env var' do ENV['INSPEC_CONFIG_DIR'] = File.join(config_fixtures_path, 'meaning-by-path') expected_path = File.join(ENV['INSPEC_CONFIG_DIR'], 'plugins.json') config_file_obj.path.must_equal expected_path end end describe 'when a path is provided to the constructor' do let(:fixture_name) { 'no_plugins' } it 'uses the provided path' do config_file_obj.path.must_equal constructor_arg end end end #----------------------------------------------------------# # Reading a File #----------------------------------------------------------# describe 'reading the file' do describe 'when the file is missing' do let(:fixture_name) { 'nonesuch' } it 'creates a empty datastructure' do Dir.mktmpdir do |tmp_dir| constructor_arg = File.join(tmp_dir, 'plugins.json') config_file_obj.count.must_equal 0 end end end describe 'when the file is corrupt' do let(:fixture_name) { 'corrupt' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('Failed to load') ex.message.must_include('JSON') ex.message.must_include('unexpected token') end end describe 'when the file is valid' do let(:fixture_name) { 'basic' } it 'can count plugins' do config_file_obj.count.must_equal 3 end it 'can look up plugins by name with a String' do config_file_obj.plugin_by_name('inspec-test-fixture-01').wont_be_nil config_file_obj.plugin_by_name('inspec-test-fixture-99').must_be_nil end it 'can look up plugins by name with a Symbol' do config_file_obj.plugin_by_name(:'inspec-test-fixture-01').wont_be_nil config_file_obj.plugin_by_name(:'inspec-test-fixture-99').must_be_nil end it 'symbolizes the keys of the entries' do config_file_obj.each do |entry| entry.keys.each do |key| key.must_be_kind_of(Symbol) end end end it 'implements Enumerable' do config_file_obj.select { |entry| entry[:name].to_s.start_with?('inspec-test-fixture') }.count.must_equal 3 end end #----------------------------------------------------------# # Validation #----------------------------------------------------------# describe 'when the file is invalid' do describe 'because the file version is wrong' do let(:fixture_name) { 'bad_plugin_conf_version' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('Unsupported') ex.message.must_include('version') ex.message.must_include('99.99.9') ex.message.must_include('1.0.0') end end describe 'because the file version is missing' do let(:fixture_name) { 'missing_plugin_conf_version' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('Missing') ex.message.must_include('version') ex.message.must_include('1.0.0') end end describe 'because the plugins field is missing' do let(:fixture_name) { 'missing_plugins_key' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('missing') ex.message.must_include("'plugins'") ex.message.must_include('array') end end describe 'because the plugins field is not an array' do let(:fixture_name) { 'hash_plugins_key' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('Malformed') ex.message.must_include("'plugins'") ex.message.must_include('array') end end describe 'because a plugin entry is not a hash' do let(:fixture_name) { 'entry_not_hash' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('Malformed') ex.message.must_include('Hash') ex.message.must_include('at index 2') end end describe 'because it contains duplicate plugin entries' do let(:fixture_name) { 'entry_duplicate' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('Malformed') ex.message.must_include('duplicate') ex.message.must_include('inspec-test-fixture-01') ex.message.must_include('at index 1 and 3') end end describe 'because a plugin entry does not have a name' do let(:fixture_name) { 'entry_no_name' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('Malformed') ex.message.must_include("missing 'name'") ex.message.must_include('at index 1') end end describe 'because a plugin entry has an unrecognized installation type' do let(:fixture_name) { 'entry_bad_installation_type' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('Malformed') ex.message.must_include('unrecognized installation_type') ex.message.must_include("one of 'gem' or 'path'") ex.message.must_include('at index 1') end end describe 'because a path plugin entry does not have a path' do let(:fixture_name) { 'entry_no_path_for_path_type' } it 'throws an exception' do ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj } ex.message.must_include('Malformed') ex.message.must_include('missing installation path') ex.message.must_include('at index 2') end end end end describe 'modifying the conf file' do #----------------------------------------------------------# # Adding Entries #----------------------------------------------------------# describe 'adding an entry' do let(:fixture_name) { 'no_plugins' } describe 'when the conf is empty' do it 'should add one valid entry' do config_file_obj.count.must_equal 0 config_file_obj.add_entry(name: 'inspec-test-fixture') config_file_obj.count.must_equal 1 config_file_obj.plugin_by_name(:'inspec-test-fixture').wont_be_nil end end describe 'when the conf has entries' do let(:fixture_name) { 'basic' } it 'should append one valid entry' do config_file_obj.count.must_equal 3 config_file_obj.add_entry(name: 'inspec-test-fixture-03') config_file_obj.count.must_equal 4 config_file_obj.plugin_by_name(:'inspec-test-fixture-03').wont_be_nil end end describe 'when adding a gem entry' do it 'should add a gem entry' do config_file_obj.add_entry( name: 'inspec-test-fixture-03', installation_type: :gem, ) entry = config_file_obj.plugin_by_name(:'inspec-test-fixture-03') entry.wont_be_nil entry[:installation_type].must_equal :gem end end describe 'when adding a path entry' do it 'should add a path entry' do config_file_obj.add_entry( name: 'inspec-test-fixture-03', installation_type: :path, installation_path: '/my/path.rb', ) entry = config_file_obj.plugin_by_name(:'inspec-test-fixture-03') entry.wont_be_nil entry[:installation_type].must_equal :path entry[:installation_path].must_equal '/my/path.rb' end end describe 'when adding a duplicate plugin name' do let(:fixture_name) { 'basic' } it 'should throw an exception' do assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj.add_entry(name: 'inspec-test-fixture-02') } end end describe 'when adding an invalid entry' do it 'should throw an exception' do [ { name: 'inspec-test-fixture', installation_type: :path }, { installation_type: :gem }, { name: 'inspec-test-fixture', installation_type: :invalid }, { 'name' => 'inspec-test-fixture' }, ].each do |proposed_entry| assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj.add_entry(proposed_entry) } end end end end #----------------------------------------------------------# # Removing Entries #----------------------------------------------------------# describe 'removing an entry' do let(:fixture_name) { 'basic' } describe 'when the entry exists' do it 'should remove the entry by symbol name' do config_file_obj.count.must_equal 3 config_file_obj.plugin_by_name(:'inspec-test-fixture-01').wont_be_nil config_file_obj.remove_entry(:'inspec-test-fixture-01') config_file_obj.count.must_equal 2 config_file_obj.plugin_by_name(:'inspec-test-fixture-01').must_be_nil end it 'should remove the entry by String name' do config_file_obj.count.must_equal 3 config_file_obj.plugin_by_name('inspec-test-fixture-01').wont_be_nil config_file_obj.remove_entry('inspec-test-fixture-01') config_file_obj.count.must_equal 2 config_file_obj.plugin_by_name('inspec-test-fixture-01').must_be_nil end end describe 'when the entry does not exist' do let(:fixture_name) { 'basic' } it 'should throw an exception' do config_file_obj.count.must_equal 3 config_file_obj.plugin_by_name(:'inspec-test-fixture-99').must_be_nil ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj.remove_entry(:'inspec-test-fixture-99') } ex.message.must_include 'No such entry' ex.message.must_include 'inspec-test-fixture-99' config_file_obj.count.must_equal 3 end end end describe 'writing the file' do let(:fixture_name) { 'unused' } describe 'when the file does not exist' do it 'is created' do Dir.mktmpdir do |tmp_dir| path = File.join(tmp_dir, 'plugins.json') File.exist?(path).must_equal false cfo_writer = Inspec::Plugin::V2::ConfigFile.new(path) cfo_writer.add_entry(name: :'inspec-resource-lister') cfo_writer.save File.exist?(path).must_equal true cfo_reader = Inspec::Plugin::V2::ConfigFile.new(path) cfo_reader.existing_entry?(:'inspec-resource-lister').must_equal true end end end describe 'when the directory does not exist' do it 'is created' do Dir.mktmpdir do |tmp_dir| path = File.join(tmp_dir, 'subdir', 'plugins.json') File.exist?(path).must_equal false cfo_writer = Inspec::Plugin::V2::ConfigFile.new(path) cfo_writer.add_entry(name: :'inspec-resource-lister') cfo_writer.save File.exist?(path).must_equal true cfo_reader = Inspec::Plugin::V2::ConfigFile.new(path) cfo_reader.existing_entry?(:'inspec-resource-lister').must_equal true end end end describe 'when the file does exist' do it 'is overwritten' do Dir.mktmpdir do |tmp_dir| path = File.join(tmp_dir, 'plugins.json') cfo_writer = Inspec::Plugin::V2::ConfigFile.new(path) cfo_writer.add_entry(name: :'inspec-resource-lister') cfo_writer.save File.exist?(path).must_equal true cfo_modifier = Inspec::Plugin::V2::ConfigFile.new(path) cfo_modifier.remove_entry(:'inspec-resource-lister') cfo_modifier.add_entry(name: :'inspec-test-fixture') cfo_modifier.save cfo_reader = Inspec::Plugin::V2::ConfigFile.new(path) cfo_reader.existing_entry?(:'inspec-resource-lister').must_equal false cfo_reader.existing_entry?(:'inspec-test-fixture').must_equal true end end end end end end