Sorry for my novice question. I have written a rego rule to check for ASV Names, and now I am looking to write a test case for the same. I have looked at sample test cases but has no success in writing for my policy(pasted below). Was wondering how could I get a positive and a failure case for the rule below.
asv_list = {"ASVONE","ASVXYZ"}
check_asv := { resources[i]: Reason |
resources:=[resource | data[j] ;
list := {x| x:=asv_list[_]}
not(list[data[j].ASV])
resource:=data[j].Name]
Reason := sprintf("THE ASV - %v being used is not a valid ASV", [data[j].ASV])
}
data = {resource |
doc = input[i];
key_ids := [k | doc[k]; startswith(k, "tag."); k != "tag.#"; endswith(k, ".key")]
resource := {
doc[k] : doc[replace(k, ".key", ".value")] | key_ids[_] == k
}
}
If you're trying to test check_asv
your positive and negative test cases will look similar:
check_asv
is equal to expected value when evaluated with test inputFor example:
test_check_asv_positive {
expected := {"r1": "..."}
fake_input := [{"Name": "foo", ...}]
check_asv == expected with input as fake_input
}
Before you start writing tests for your rule though, I think you should clarify the logic you're trying to express because there are a few red flags that jump out. I've tried to breakdown the issues I see in the policy below. At the end, I've written out some example test cases.
First of all, just format the Rego so that it's easier to read. I pasted your example into a file, added a package
(to make it a valid .rego file) and then ran opa fmt file.rego
:
asv_list = {"ASVONE", "ASVXYZ"}
check_asv := {resources[i]: Reason |
resources := [resource |
data[j]
list := {x | x := asv_list[_]}
not list[data[j].ASV]
resource := data[j].Name
]
Reason := sprintf("THE ASV - %v being used is not a valid ASV", [data[j].ASV])
}
data = {resource |
doc = input[i]
key_ids := [k |
doc[k]
startswith(k, "tag.")
k != "tag.#"
endswith(k, ".key")
]
resource := {doc[k]: doc[replace(k, ".key", ".value")] |
key_ids[_] == k
}
}
This a little bit easier to read.
data
I recommend NOT naming that rule data
. data
in Rego is a special global variable that refers to state cached inside the policy engine. Any data from outside OPA or generated by rules inside of OPA is accessible under data
. Defining a rule named data
is allowed but it's confusing. Rename data
to something meaningful in the domain. E.g., if these were virtual machine resources, you could name them vm_resources
. In this case, I don't have much to go on, so I'm going to rename it to input_docs
since doc
was used as a variable name:
check_asv := {resources[i]: Reason |
resources := [resource |
input_docs[j]
list := {x | x := asv_list[_]}
not list[input_docs[j].ASV]
resource := input_docs[j].Name
]
Reason := sprintf("THE ASV - %v being used is not a valid ASV", [input_docs[j].ASV])
}
input_docs = {resource |
doc = input[i]
key_ids := [k |
doc[k]
startswith(k, "tag.")
k != "tag.#"
endswith(k, ".key")
]
resource := {doc[k]: doc[replace(k, ".key", ".value")] |
key_ids[_] == k
}
}
input_docs
helper rule (which was called data
in the original)At first glance, this rule is doing quite a bit of work, but in reality, it's not. The first line (doc = input[i]
) just iterates over each element in input
. The rest of the rule is based on each element in input
. The second expression, key_ids := [...
, computes an array of object keys from the element. The third expression, resources := {doc[k]: ...
, constructs a new object by mapping the element.
The first expression can't be simplified much, however, it's better to use :=
instead of =
and since i
is not referenced anywhere else we can just use _
. The first expression becomes:
doc := input[_]
The second expression computes an array of keys but the filtering is a bit redundant: k
cannot be equal to tag.#
AND end with .key
so the !=
expression can be removed:
key_ids := [k |
some k
doc[k]
startswith(k, "tag.")
endswith(k, ".key")
]
At this point we could stop however it's worth noticing that the second and third expression in the rule are both just iterating over the doc
object. There's no reason for two separate expressions here. To simplify this rule we can combine them:
input_docs = {resource |
doc := input[_]
resource := {v: x |
some k
v := doc[k]
startswith(k, "tag.")
endswith(k, ".key")
x := doc[replace(k, ".key", ".value")]
}
}
check_asv
ruleNow that we have a simplified version of the input_docs
rule that generates a set of mapped resources we can focus on the check_asv
rule. The check_asv
rule can be simplified quite a bit:
i
and j
) when there should be only one.The rule can be simplified down to the following:
check_asv := {name: reason |
r := input_docs[_]
not asv_list[r.ASV]
name := r.Name
reason := sprintf("THE ASV - %v being used is not a valid ASV", [r.ASV])
}
input_docs
twice (in fact this would be incorrect.)asv_list
, it's already a set.At this point the policy looks like this:
asv_list = {"ASVONE", "ASVXYZ"}
check_asv := {name: reason |
r := input_docs[_]
not asv_list[r.ASV]
name := r.Name
reason := sprintf("THE ASV - %v being used is not a valid ASV", [r.ASV])
}
input_docs = {resource |
doc := input[_]
resource := {v: x |
some k
v := doc[k]
startswith(k, "tag.")
endswith(k, ".key")
x := doc[replace(k, ".key", ".value")]
}
}
To test this policy we can easily write a few test cases:
test_check_asv_positive {
exp := {
"dog": "THE ASV - ASVBAD being used is not a valid ASV"
}
inp := [
{
"tag.foo.key": "Name",
"tag.foo.value": "dog",
"tag.bar.key": "ASV",
"tag.bar.value": "ASVBAD"
}
]
check_asv == exp with input as inp
}
test_check_asv_positive_multiple_resources {
exp := {
"dog": "THE ASV - ASVBAD being used is not a valid ASV",
"horse": "THE ASV - ASVBAD2 being used is not a valid ASV"
}
inp := [
{
"tag.foo.key": "Name",
"tag.foo.value": "dog",
"tag.bar.key": "ASV",
"tag.bar.value": "ASVBAD"
},
{
"tag.foo.key": "Name",
"tag.foo.value": "horse",
"tag.bar.key": "ASV",
"tag.bar.value": "ASVBAD2"
}
]
check_asv == exp with input as inp
}
test_check_asv_positive_multiple_resources_mixed {
exp := {
"horse": "THE ASV - ASVBAD2 being used is not a valid ASV"
}
inp := [
{
"tag.foo.key": "Name",
"tag.foo.value": "cat",
"tag.bar.key": "ASV",
"tag.bar.value": "ASVONE"
},
{
"tag.foo.key": "Name",
"tag.foo.value": "horse",
"tag.bar.key": "ASV",
"tag.bar.value": "ASVBAD2"
}
]
check_asv == exp with input as inp
}
test_check_asv_negative {
exp := {}
inp := [
{
"tag.foo.key": "Name",
"tag.foo.value": "cat",
"tag.bar.key": "ASV",
"tag.bar.value": "ASVONE"
}
]
check_asv == exp with input as inp
}
Here's a link to the full policy in the playground: https://play.openpolicyagent.org/p/6w7aC9xWYH