mirror of
https://github.com/inspec/inspec
synced 2024-11-23 13:13:22 +00:00
Fix ObjectTraverser when accessing array values
When attempting to access array values via the `json` resource: ``` describe json('/tmp/test.json') do its(['array',0]) { should eq "zero" } end ``` ... the resulting data would be an array of the size of the original array with all the values replaced with nils: ``` expected: "zero" got: [nil, nil, nil] ``` This was due to a bug in the ObjectTraverser mixin that mapped array values back through `extract_value` rather than properly handling the passed-in key(s). This worked fine for the specific data format created by the `csv` resource but did not work `json` or any other resource that subclassed the `JsonConfig` resource. This change fixes the logic when dealing with an array when it's encountered, and fixes up the `csv` resource with its own `value` method. This change also adds tests for ObjectTraverser. Signed-off-by: Adam Leff <adam@leff.co>
This commit is contained in:
parent
3d551f977f
commit
4f2b66302d
5 changed files with 110 additions and 7 deletions
|
@ -15,19 +15,36 @@ module Inspec::Resources
|
|||
end
|
||||
"
|
||||
|
||||
# override file load and parse hash from csv
|
||||
# override the parse method from JsonConfig
|
||||
# Assuming a header row of name,col1,col2, it will output an array of hashes like so:
|
||||
# [
|
||||
# { 'name' => 'row1', 'col1' => 'value1', 'col2' => 'value2' },
|
||||
# { 'name' => 'row2', 'col1' => 'value3', 'col2' => 'value4' }
|
||||
# ]
|
||||
def parse(content)
|
||||
require 'csv'
|
||||
|
||||
# convert empty field to nil
|
||||
CSV::Converters[:blank_to_nil] = lambda do |field|
|
||||
field && field.empty? ? nil : field
|
||||
end
|
||||
|
||||
# implicit conversion of values
|
||||
csv = CSV.new(content, headers: true, converters: [:all, :blank_to_nil])
|
||||
|
||||
# convert to hash
|
||||
csv.to_a.map(&:to_hash)
|
||||
end
|
||||
|
||||
# override the value method from JsonConfig
|
||||
# The format of the CSV hash as created by #parse is very different
|
||||
# than what the YAML, JSON, and INI resources create, so using the
|
||||
# #value method from JsonConfig (which uses ObjectTraverser.extract_value)
|
||||
# doesn't make sense here.
|
||||
def value(key)
|
||||
@params.map { |x| x[key.first.to_s] }.compact
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Csv #{@path}"
|
||||
end
|
||||
|
|
|
@ -6,11 +6,12 @@ module ObjectTraverser
|
|||
key = keys.shift
|
||||
return nil if key.nil? || value.nil?
|
||||
|
||||
# if value is an array, iterate over each child
|
||||
if value.is_a?(Array)
|
||||
value = value.map { |i|
|
||||
extract_value([key], i)
|
||||
}
|
||||
value = if key.is_a?(Fixnum)
|
||||
value[key]
|
||||
elsif value.respond_to?(key.to_sym)
|
||||
value.send(key.to_sym)
|
||||
end
|
||||
else
|
||||
value = value[key.to_s].nil? ? nil : value[key.to_s]
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name,version,license,title,description
|
||||
addressable,2.3.6,Apache 2.0,URI Implementation,"Addressable is a replacement for the URI implementation that is part of
|
||||
Ruby's standard library. It more closely conforms to the relevant RFCs and
|
||||
adds support for IRIs and URI templates."
|
||||
|
|
|
|
@ -17,7 +17,7 @@ describe 'Inspec::Resources::CSV' do
|
|||
end
|
||||
|
||||
it 'gets all value lines' do
|
||||
_(resource.params.length).must_equal 3
|
||||
_(resource.params.length).must_equal 4
|
||||
end
|
||||
|
||||
it 'captures a hashmap of entries of a line' do
|
||||
|
@ -25,11 +25,15 @@ describe 'Inspec::Resources::CSV' do
|
|||
end
|
||||
|
||||
it 'gets params by header fields' do
|
||||
_(resource.params[0]['addressable']).must_equal 'ast'
|
||||
_(resource.params[0]['name']).must_equal 'addressable'
|
||||
end
|
||||
|
||||
it 'retrieves nil if a param is missing' do
|
||||
_(resource.params[0]['missing']).must_be_nil
|
||||
end
|
||||
|
||||
it 'returns an array of values by column name' do
|
||||
_(resource.value(['name'])).must_equal([ 'addressable', 'ast', 'astrolabe', 'berkshelf' ])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
80
test/unit/utils/object_traversal_test.rb
Normal file
80
test/unit/utils/object_traversal_test.rb
Normal file
|
@ -0,0 +1,80 @@
|
|||
# encoding: utf-8
|
||||
# author: Adam Leff
|
||||
|
||||
require 'helper'
|
||||
|
||||
class Tester
|
||||
include ObjectTraverser
|
||||
end
|
||||
|
||||
describe ObjectTraverser do
|
||||
let(:subject) { Tester.new }
|
||||
let(:sample_data) do
|
||||
{
|
||||
'string1' => 'value1',
|
||||
'string2' => 'value2',
|
||||
'number1' => 2468,
|
||||
'hash1' => { 'key1' => 'value1' },
|
||||
'hash2' => {
|
||||
'hash1string1' => 'value3',
|
||||
'hash1number1' => 123,
|
||||
'hash1subhash' => { 'key1' => 1, 'key2' => 2 },
|
||||
},
|
||||
'array1' => %w(word1 word2 word3),
|
||||
'array2' => [
|
||||
123,
|
||||
456,
|
||||
{ 'array1hashkey1' => 1, 'array1hashkey2' => 2 },
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns values from the top-level' do
|
||||
subject.extract_value(['string1'], sample_data).must_equal('value1')
|
||||
subject.extract_value(['string2'], sample_data).must_equal('value2')
|
||||
subject.extract_value(['number1'], sample_data).must_equal(2468)
|
||||
end
|
||||
|
||||
it 'returns a full hash from the top-level' do
|
||||
subject.extract_value(['hash1'], sample_data).must_equal({ 'key1' => 'value1' })
|
||||
end
|
||||
|
||||
it 'returns values from a hash' do
|
||||
subject.extract_value(['hash2', 'hash1string1'], sample_data).must_equal('value3')
|
||||
subject.extract_value(['hash2', 'hash1number1'], sample_data).must_equal(123)
|
||||
end
|
||||
|
||||
it 'returns values from a nested hash' do
|
||||
subject.extract_value(['hash2', 'hash1subhash', 'key1'], sample_data).must_equal(1)
|
||||
subject.extract_value(['hash2', 'hash1subhash', 'key2'], sample_data).must_equal(2)
|
||||
end
|
||||
|
||||
it 'returns a full array from the top level' do
|
||||
subject.extract_value(['array1'], sample_data).must_equal(%w(word1 word2 word3))
|
||||
end
|
||||
|
||||
it 'returns values from the array using index numbers' do
|
||||
subject.extract_value(['array1', 0], sample_data).must_equal('word1')
|
||||
subject.extract_value(['array1', 1], sample_data).must_equal('word2')
|
||||
subject.extract_value(['array1', 2], sample_data).must_equal('word3')
|
||||
end
|
||||
|
||||
it 'returns values from the array using methods' do
|
||||
subject.extract_value(['array1', 'first'], sample_data).must_equal('word1')
|
||||
subject.extract_value(['array1', 'last'], sample_data).must_equal('word3')
|
||||
end
|
||||
|
||||
it 'returns nil when fetching from an array when it does not match a method' do
|
||||
subject.extract_value(['array1', 'not_a_valid_method'], sample_data).must_be_nil
|
||||
end
|
||||
|
||||
it 'returns values from a nested hash within an array, accessing the array using numbers' do
|
||||
subject.extract_value(['array2', 2, 'array1hashkey1'], sample_data).must_equal(1)
|
||||
subject.extract_value(['array2', 2, 'array1hashkey2'], sample_data).must_equal(2)
|
||||
end
|
||||
|
||||
it 'returns values from a nested hash within an array, accessing the array using methods' do
|
||||
subject.extract_value(['array2', 'last', 'array1hashkey1'], sample_data).must_equal(1)
|
||||
subject.extract_value(['array2', 'last', 'array1hashkey2'], sample_data).must_equal(2)
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue