Rego 策略的测试用例
Test Cases for Rego Polices
抱歉我的新手问题。我已经编写了一个 rego 规则来检查 ASV 名称,现在我正在寻找相同的测试用例。我查看了示例测试用例,但没有成功编写我的策略(粘贴在下面)。想知道我怎样才能得到下面规则的肯定和失败案例。
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
}
}
如果您尝试测试 check_asv
,您的正面和负面测试用例将看起来相似:
- 定义输入值
- 定义期望值
- 断言
check_asv
在使用测试输入评估时等于预期值
例如:
test_check_asv_positive {
expected := {"r1": "..."}
fake_input := [{"Name": "foo", ...}]
check_asv == expected with input as fake_input
}
尽管如此,在您开始为您的规则编写测试之前,我认为您应该澄清您试图表达的逻辑,因为有一些危险信号会跳出来。我已尝试细分我在以下政策中看到的问题。最后,我写了一些示例测试用例。
格式化文件以使其更易于阅读
首先,只需对 Rego 进行格式化,使其更易于阅读。我将您的示例粘贴到一个文件中,添加了一个 package
(使其成为有效的 .rego 文件),然后添加了 运行 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
}
}
这更容易阅读。
不命名规则data
我建议不要命名该规则 data
。 Rego 中的 data
是一个特殊的全局变量,它指的是策略引擎内部缓存的状态。来自 OPA 外部或由 OPA 内部规则生成的任何数据都可以在 data
下访问。允许定义名为 data
的规则,但它会造成混淆。将 data
重命名为域中有意义的名称。例如,如果这些是虚拟机资源,您可以将它们命名为 vm_resources
。在这种情况下,我没有太多要继续的,所以我将它重命名为 input_docs
因为 doc
被用作变量名:
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
辅助规则(原来叫data
)
乍一看,这条规则做了很多工作,但实际上并非如此。第一行 (doc = input[i]
) 只是遍历 input
中的每个元素。规则的其余部分基于 input
中的每个元素。第二个表达式 key_ids := [...
从元素计算对象键数组。第三个表达式 resources := {doc[k]: ...
通过映射元素构造一个新对象。
第一个表达式不能简化多少,但是,最好使用 :=
而不是 =
,因为 i
没有在其他任何地方引用,我们可以只使用 _
。第一个表达式变为:
doc := input[_]
第二个表达式计算键数组,但过滤有点多余:k
不能等于 tag.#
并且以 .key
结尾,所以 !=
可以删除表达式:
key_ids := [k |
some k
doc[k]
startswith(k, "tag.")
endswith(k, ".key")
]
此时我们可以停止,但是值得注意的是规则中的第二个和第三个表达式都只是迭代 doc
对象。没有理由在这里使用两个单独的表达式。为了简化这条规则,我们可以将它们组合起来:
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
规则
现在我们有了生成一组映射资源的 input_docs
规则的简化版本,我们可以专注于 check_asv
规则。 check_asv
规则可以简化很多:
- 规则主体中有多个迭代器(
i
和 j
),而本应只有一个。
- 无效资源列表的构建过于复杂。
规则可以简化为:
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
两次(事实上这是不正确的。)
- 不需要嵌套理解。
- 不需要从
asv_list
构造一个集合,它已经是一个集合了。
综合起来
此时策略如下所示:
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")]
}
}
要测试此策略,我们可以轻松编写一些测试用例:
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
}
这里是 playground 中完整政策的 link:https://play.openpolicyagent.org/p/6w7aC9xWYH
抱歉我的新手问题。我已经编写了一个 rego 规则来检查 ASV 名称,现在我正在寻找相同的测试用例。我查看了示例测试用例,但没有成功编写我的策略(粘贴在下面)。想知道我怎样才能得到下面规则的肯定和失败案例。
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
}
}
如果您尝试测试 check_asv
,您的正面和负面测试用例将看起来相似:
- 定义输入值
- 定义期望值
- 断言
check_asv
在使用测试输入评估时等于预期值
例如:
test_check_asv_positive {
expected := {"r1": "..."}
fake_input := [{"Name": "foo", ...}]
check_asv == expected with input as fake_input
}
尽管如此,在您开始为您的规则编写测试之前,我认为您应该澄清您试图表达的逻辑,因为有一些危险信号会跳出来。我已尝试细分我在以下政策中看到的问题。最后,我写了一些示例测试用例。
格式化文件以使其更易于阅读
首先,只需对 Rego 进行格式化,使其更易于阅读。我将您的示例粘贴到一个文件中,添加了一个 package
(使其成为有效的 .rego 文件),然后添加了 运行 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
}
}
这更容易阅读。
不命名规则data
我建议不要命名该规则 data
。 Rego 中的 data
是一个特殊的全局变量,它指的是策略引擎内部缓存的状态。来自 OPA 外部或由 OPA 内部规则生成的任何数据都可以在 data
下访问。允许定义名为 data
的规则,但它会造成混淆。将 data
重命名为域中有意义的名称。例如,如果这些是虚拟机资源,您可以将它们命名为 vm_resources
。在这种情况下,我没有太多要继续的,所以我将它重命名为 input_docs
因为 doc
被用作变量名:
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
辅助规则(原来叫data
)
乍一看,这条规则做了很多工作,但实际上并非如此。第一行 (doc = input[i]
) 只是遍历 input
中的每个元素。规则的其余部分基于 input
中的每个元素。第二个表达式 key_ids := [...
从元素计算对象键数组。第三个表达式 resources := {doc[k]: ...
通过映射元素构造一个新对象。
第一个表达式不能简化多少,但是,最好使用 :=
而不是 =
,因为 i
没有在其他任何地方引用,我们可以只使用 _
。第一个表达式变为:
doc := input[_]
第二个表达式计算键数组,但过滤有点多余:k
不能等于 tag.#
并且以 .key
结尾,所以 !=
可以删除表达式:
key_ids := [k |
some k
doc[k]
startswith(k, "tag.")
endswith(k, ".key")
]
此时我们可以停止,但是值得注意的是规则中的第二个和第三个表达式都只是迭代 doc
对象。没有理由在这里使用两个单独的表达式。为了简化这条规则,我们可以将它们组合起来:
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
规则
现在我们有了生成一组映射资源的 input_docs
规则的简化版本,我们可以专注于 check_asv
规则。 check_asv
规则可以简化很多:
- 规则主体中有多个迭代器(
i
和j
),而本应只有一个。 - 无效资源列表的构建过于复杂。
规则可以简化为:
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
两次(事实上这是不正确的。) - 不需要嵌套理解。
- 不需要从
asv_list
构造一个集合,它已经是一个集合了。
综合起来
此时策略如下所示:
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")]
}
}
要测试此策略,我们可以轻松编写一些测试用例:
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
}
这里是 playground 中完整政策的 link:https://play.openpolicyagent.org/p/6w7aC9xWYH