inspec/test/unit/dsl/objects_test.rb
Jerry Aldrich 20776b363d Add support for multiple descriptions for controls (#3424)
* Add support for multiple descriptions for controls

This adds the ability to specify multiple descriptions in controls.

Example:

```ruby
control 'my-control' do
  impact 1.0
  title 'My control'
  desc 'A default description'
  desc 'rational', 'I need an example'
  describe file('/tmp') do
    it { should be_directory }
  end
end
```

Many thanks to @jquick for helping me with the unit tests.
* Remove unused `descriptions` method
* Remove unused profile from test mocks
* Respond to feedback

Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
2018-09-26 13:28:57 -04:00

470 lines
12 KiB
Ruby

# encoding: utf-8
# author: Dominik Richter
# author: Christoph Hartmann
require 'helper'
require 'inspec/objects'
describe 'Objects' do
describe 'Inspec::Test' do
let(:obj) { Inspec::Test.new }
it 'constructs a simple resource + its("argument")' do
obj.qualifier = [['resource'], ['version']]
obj.matcher = 'cmp >='
obj.expectation = '2.4.2'
obj.to_ruby.must_equal '
describe resource do
its("version") { should cmp >= "2.4.2" }
end
'.strip
end
# same as the above test but with it
it 'constructs a simple resource.argument' do
# [''] forces an 'it' instead of 'its':
obj.qualifier = [['resource'], ['version'], ['']]
obj.matcher = 'cmp >='
obj.expectation = '2.4.2'
obj.to_ruby.must_equal '
describe resource.version do
it { should cmp >= "2.4.2" }
end
'.strip
end
it 'constructs a simple resource+argument with to_s' do
obj.qualifier = [['resource'], ['to_s']]
obj.matcher = 'cmp'
obj.expectation = Regexp.new('^Desc.+$')
obj.to_ruby.must_equal '
describe resource.to_s do
it { should cmp(/^Desc.+$/) }
end
'.strip
end
it 'constructs a simple resource+argument with to_i' do
obj.qualifier = [['resource'], ['to_i']]
obj.matcher = 'cmp >'
obj.expectation = 3
obj.to_ruby.must_equal '
describe resource.to_i do
it { should cmp > 3 }
end
'.strip
end
it 'constructs a simple resource+argument with array accessors' do
obj.qualifier = [['resource'], ['name[2]']]
obj.matcher = 'exist'
obj.matcher = 'eq'
obj.expectation = 'mytest'
obj.to_ruby.must_equal '
describe resource.name[2] do
it { should eq "mytest" }
end
'.strip
end
it 'constructs a simple resource+argument with method calls' do
obj.qualifier = [['resource'], ['hello', 'world']]
obj.matcher = 'eq'
obj.expectation = 'mytest'
obj.to_ruby.must_equal '
describe resource.hello("world") do
it { should eq "mytest" }
end
'.strip
end
it 'constructs a simple resource+argument with method calls' do
obj.qualifier = [['resource'], ['hello', 'world']]
obj.matcher = 'be_in'
obj.expectation = ['mytest','mytest2','mytest3']
obj.to_ruby.must_equal '
describe resource.hello("world") do
it { should be_in ["mytest", "mytest2", "mytest3"] }
end
'.strip
end
it 'constructs a simple resource+argument with method calls' do
obj.qualifier = [['resource'], ['hello', 'world']]
obj.matcher = 'be_in'
obj.negate!
obj.expectation = ['mytest2','mytest3','mytest4']
obj.to_ruby.must_equal '
describe resource.hello("world") do
it { should_not be_in ["mytest2", "mytest3", "mytest4"] }
end
'.strip
end
it 'constructs a simple resource+argument with method calls' do
obj.qualifier = [['["item1","item2","item3"]']]
obj.matcher = 'be_in'
obj.expectation = ["item1","item2","item3","item4","item5"]
obj.to_ruby.must_equal '
describe ["item1","item2","item3"] do
it { should be_in ["item1", "item2", "item3", "item4", "item5"] }
end
'.strip
end
it 'constructs a simple resource+argument with method calls' do
obj.qualifier = [['resource'], [:mode]]
obj.matcher = 'cmp'
obj.expectation = '0755'
obj.to_ruby.must_equal '
describe resource do
its("mode") { should cmp "0755" }
end
'.strip
end
it 'constructs a resource+argument block with method call, matcher and expectation' do
obj.qualifier = [['command','ls /etc'], ['exit_status']]
obj.matcher = 'eq'
obj.expectation = 0
obj.to_ruby.must_equal '
describe command("ls /etc") do
its("exit_status") { should eq 0 }
end
'.strip
end
it 'constructs a simple describe with static data, negated regex matcher and expectation' do
obj.qualifier = [['"aaa"']]
obj.matcher = 'match'
obj.negate!
obj.expectation = Regexp.new('^aa.*')
obj.to_ruby.must_equal '
describe "aaa" do
it { should_not match(/^aa.*/) }
end
'.strip
end
it 'constructs a resource+argument block without a property call' do
obj.qualifier = [['service', 'avahi-daemon']]
obj.qualifier.push(["info['properties']['UnitFileState']"])
obj.expectation = "enabled"
obj.matcher = 'eq'
obj.to_ruby.must_equal '
describe service("avahi-daemon").info[\'properties\'][\'UnitFileState\'] do
it { should eq "enabled" }
end
'.strip
end
end
describe 'Inspec::EachLoop, each_loop' do
it 'constructs an each loop to match listening addresses' do
loop_obj = Inspec::EachLoop.new
loop_obj.qualifier = [['port', 25]]
loop_obj.qualifier.push(['addresses'])
obj = Inspec::Test.new
obj.matcher = 'match'
obj.negate!
obj.expectation = '0.0.0.0'
loop_obj.add_test(obj)
loop_obj.to_ruby.must_equal '
port(25).addresses.each do |entry|
describe entry do
it { should_not match "0.0.0.0" }
end
end
'.strip
end
end
describe 'Inspec::List' do
it 'constructs a list filtering test' do
list_obj = Inspec::List.new([['passwd']])
list_obj.qualifier.push(["where { user =~ /^(?!root|sync|shutdown|halt).*$/ }"])
obj = Inspec::Test.new
obj.qualifier = list_obj.qualifier
obj.matcher = 'be_empty'
obj.qualifier.push(['entries'])
obj.negate!
obj.to_ruby.must_equal '
describe passwd.where { user =~ /^(?!root|sync|shutdown|halt).*$/ } do
its("entries") { should_not be_empty }
end
'.strip
end
end
describe 'Inspec::OrTest and Inspec::Control' do
let(:obj1) do
obj1 = Inspec::Test.new
obj1.qualifier = [['command','ls /etc'], ['exit_status']]
obj1.matcher = 'eq'
obj1.expectation = 0
obj1
end
let(:obj2) do
obj2 = obj1.dup
obj2.negate!
obj2.expectation = 100
obj2
end
it 'constructs a simple describe.one block wrapping two tests' do
or_obj = Inspec::OrTest.new([obj1,obj2])
or_obj.to_ruby.must_equal '
describe.one do
describe command("ls /etc") do
its("exit_status") { should eq 0 }
end
describe command("ls /etc") do
its("exit_status") { should_not eq 100 }
end
end
'.strip
end
it 'negates a describe.one block, wow!' do
or_obj = Inspec::OrTest.new([obj1,obj2])
or_obj.negate!
or_obj.to_ruby.must_equal '
describe command("ls /etc") do
its("exit_status") { should_not eq 0 }
end
describe command("ls /etc") do
its("exit_status") { should eq 100 }
end
'.strip
end
it 'loops a describe.one block, ooooooo!' do
res = Inspec::EachLoop.new
res.qualifier.push(['(1..5)'])
# already defined in the let block:
obj1.matcher = 'eq entity'
obj2.matcher = 'eq entity'
obj1.remove_expectation
obj2.remove_expectation
or_obj = Inspec::OrTest.new([obj1,obj2])
res.tests = [or_obj]
res.to_ruby.must_equal '
(1..5).each do |entry|
describe.one do
describe command("ls /etc") do
its("exit_status") { should eq entity }
end
describe command("ls /etc") do
its("exit_status") { should_not eq entity }
end
end
end
'.strip
end
it 'constructs a control' do
control = Inspec::Control.new
control.add_test(obj1)
control.id = 'sample.control.id'
control.title = 'Sample Control Important Title'
control.descriptions = {
default: 'The most critical control the world has ever seen',
rationale: 'It is needed to save the planet',
'more info': 'Insert clever joke here',
}
control.refs = ['simple ref', {ref: 'title', url: 'my url'}]
control.impact = 1.0
control.to_ruby.must_equal '
control "sample.control.id" do
title "Sample Control Important Title"
desc "The most critical control the world has ever seen"
desc "rationale", "It is needed to save the planet"
desc "more info", "Insert clever joke here"
impact 1.0
ref "simple ref"
ref ({:ref=>"title", :url=>"my url"})
describe command("ls /etc") do
its("exit_status") { should eq 0 }
end
end
'.strip
end
it 'constructs a multiline desc in a control with indentation' do
control = Inspec::Control.new
control.descriptions[:default] = "Multiline\n control"
control.to_ruby.must_equal '
control nil do
desc "
Multiline
control
"
end
'.strip
end
it 'ignores empty control descriptions' do
control = Inspec::Control.new
x = '
control nil do
end
'.strip
control.descriptions[:default] = ''
control.to_ruby.must_equal x
control.descriptions[:default] = nil
control.to_ruby.must_equal x
end
it 'handles non-string descriptions' do
control = Inspec::Control.new
control.descriptions[:default] = 123
control.to_ruby.must_equal '
control nil do
desc "123"
end
'.strip
end
end
describe 'Inspec::Variable, take #1' do
it 'constructs a control with variable to instantiate a resource only once' do
control = Inspec::Control.new
variable = Inspec::Value.new([['command','which grep']])
variable_id = variable.name_variable.to_s
obj1 = Inspec::Test.new
obj1.variables.push(variable)
obj1.qualifier.push([variable_id])
obj1.qualifier.push(['exit_status'])
obj1.matcher = 'eq'
obj1.expectation = 0
control.add_test(obj1)
obj2 = Inspec::Test.new
obj2.qualifier.push([variable_id.to_s])
obj2.qualifier.push(['stdout'])
obj2.matcher = 'contain'
obj2.expectation = 'grep'
control.add_test(obj2)
control.id = 'variable.control.id'
control.title = 'Variable Control Important Title'
control.descriptions[:default] = 'The most variable control the world has ever seen'
control.impact = 1.0
control.to_ruby.must_equal '
control "variable.control.id" do
title "Variable Control Important Title"
desc "The most variable control the world has ever seen"
impact 1.0
a = command("which grep")
describe a do
its("exit_status") { should eq 0 }
end
describe a do
its("stdout") { should contain "grep" }
end
end
'.strip
end
end
describe 'Inspec::Variable, take #2' do
it 'constructs a control with variable, loop and var reference' do
control = Inspec::Control.new
command_value = /^\/usr\/bin\/chrony/
pid_filter = '>'
pid_value = 0
loopy = Inspec::EachLoop.new
loopy.qualifier = [['processes', command_value]]
loopy.qualifier.push(["where { pid #{pid_filter} #{pid_value} }.entries"])
obj = loopy.add_test
variable = Inspec::Value.new([['passwd.where { user == "_chrony" }.uids.first']])
variable_id = variable.name_variable.to_s
obj.variables.push(variable)
obj.qualifier = [['user(entry.user)'], ['uid']]
obj.matcher = "cmp #{variable_id}"
control.add_test(obj)
control.id = 'variable.control.id'
control.impact = 0.1
control.to_ruby.must_equal '
control "variable.control.id" do
impact 0.1
a = passwd.where { user == "_chrony" }.uids.first
describe user(entry.user) do
its("uid") { should cmp a }
end
end
'.strip
end
end
describe 'Inspec::Tag' do
it 'constructs a tag with key and value' do
control = Inspec::Control.new
res1 = { name: "key", value: "value" }
tag1 = Inspec::Tag.new(res1[:name], res1[:value])
tag1.to_hash.must_equal res1
control.add_tag(tag1)
res2 = { name: "key2'", value: "value'" }
tag2 = Inspec::Tag.new(res2[:name], res2[:value])
tag2.to_hash.must_equal res2
control.add_tag(tag2)
res3 = { name: "key3\"", value: "value\"" }
tag3 = Inspec::Tag.new(res3[:name], res3[:value])
tag3.to_hash.must_equal res3
control.add_tag(tag3)
res4 = { name: 'key4', value: ['a', 'b'] }
tag4 = Inspec::Tag.new(res4[:name], res4[:value])
tag4.to_hash.must_equal res4
control.add_tag(tag4)
control.id = 'tag.control.id'
control.to_ruby.must_equal '
control "tag.control.id" do
tag "key": "value"
tag "key2\'": "value\'"
tag "key3\"": "value\""
tag "key4": ["a", "b"]
end
'.strip
control_hash = {
id:"tag.control.id",
title: nil,
descriptions: {},
impact: nil,
tests: [],
tags:[{
name:"key",
value:"value"
}, {
name:"key2'",
value:"value'"
}, {
name:"key3\"",
value:"value\""
}, {
name:"key4",
value:["a", "b"]
}]
}
control.to_hash.must_equal control_hash
end
end
end