inspec/test/unit/dsl/objects_test.rb

539 lines
15 KiB
Ruby
Raw Normal View History

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"], %w{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"], %w{hello world}]
obj.matcher = "be_in"
obj.expectation = %w{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"], %w{hello world}]
obj.matcher = "be_in"
obj.negate!
obj.expectation = %w{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 = %w{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 = [%w{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
it "constructs a simple resource + only_if" do
obj.qualifier = [["resource"], ["version"]]
obj.matcher = "cmp >="
obj.expectation = "2.4.2"
obj.only_if = "package('ntp').installed?"
_(obj.to_ruby).must_equal '
only_if { package(\'ntp\').installed? }
describe resource do
its("version") { should cmp >= "2.4.2" }
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 control with only_if" do
control = Inspec::Control.new
control.add_test(obj1)
control.only_if = "package('ntp').installed?"
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"})
only_if { package(\'ntp\').installed? }
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 = %r{^/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: %w{a b} }
tag2 = Inspec::Tag.new(res2[:name], res2[:value])
_(tag2.to_hash).must_equal res2
control.add_tag(tag2)
control.id = "tag.control.id"
_(control.to_ruby).must_equal '
control "tag.control.id" do
tag key: "value"
tag key2: ["a", "b"]
end
'.strip
control_hash = {
id: "tag.control.id",
title: nil,
descriptions: {},
impact: nil,
tests: [],
tags: [{
name: "key",
value: "value",
}, {
name: "key2",
value: %w{a b},
}],
}
_(control.to_hash).must_equal control_hash
end
end
describe "Inspec::Input" do
describe "to_ruby method" do
it "generates the code for the input" do
input = Inspec::Input.new("application_port", description: "The port my application uses", value: 80)
ruby_code = input.to_ruby
_(ruby_code).must_include "attr_application_port = " # Should assign to a var
# Should have the DSL call. This should be attribute(), not input(), for the
# foreseeable future, to maintain backwards compatibility.
_(ruby_code).must_include "attribute('application_port'"
_(ruby_code).must_include "value: 80"
_(ruby_code).must_include "default: 80"
_(ruby_code).must_include "description: 'The port my application uses'"
# Try to eval the code to verify that the generated code was valid ruby.
# Note that the input() method is part of the DSL, so we need to
# alter the call into something that can respond - the constructor will do
ruby_code_for_eval = ruby_code.sub(/attribute\(/, "Inspec::Input.new(")
# This will throw exceptions if there is a problem
new_attr = eval(ruby_code_for_eval) # rubocop:disable Security/Eval # Could use ripper!
_(new_attr.value).must_equal 80
end
end
# TODO - deprecate this, not sure it is used
describe "to_hash method" do
it "generates a similar hash" do
ipt = Inspec::Input.new(
"some_attr",
description: "The port my application uses",
value: 80,
identifier: "app_port",
required: false,
type: "numeric"
)
expected = {
name: "some_attr",
options: {
description: "The port my application uses",
value: 80,
identifier: "app_port",
required: false,
type: "Numeric", # This gets normalized
},
}
_(ipt.to_hash).must_equal expected
end
end
end
end