mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-28 03:05:04 -06:00
topdown: Remove duplicate tests
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit is contained in:
@@ -1,93 +0,0 @@
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTopDownAggregates(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"count", []string{`p[x] { count(a, x) }`}, "[4]"},
|
||||
{"count virtual", []string{`p[x] { count([y | q[y]], x) }`, `q[x] { x = a[_] }`}, "[4]"},
|
||||
{"count keys", []string{`p[x] { count(b, x) }`}, "[2]"},
|
||||
{"count keys virtual", []string{`p[x] { count([k | q[k] = _], x) }`, `q[k] = v { b[k] = v }`}, "[2]"},
|
||||
{"count set", []string{`p = x { count(q, x) }`, `q[x] { x = a[_] }`}, "4"},
|
||||
{"sum", []string{`p[x] { sum([1, 2, 3, 4], x) }`}, "[10]"},
|
||||
{"sum set", []string{`p = x { sum({1, 2, 3, 4}, x) }`}, "10"},
|
||||
{"sum virtual", []string{`p[x] { sum([y | q[y]], x) }`, `q[x] { a[_] = x }`}, "[10]"},
|
||||
{"sum virtual set", []string{`p = x { sum(q, x) }`, `q[x] { a[_] = x }`}, "10"},
|
||||
{"bug 2469 - precision", []string{"p = true { sum([49649733057, 1]) == 49649733058 }"}, "true"},
|
||||
{"product", []string{"p { product([1,2,3,4], 24) }"}, "true"},
|
||||
{"product set", []string{`p = x { product({1, 2, 3, 4}, x) }`}, "24"},
|
||||
{"max", []string{`p[x] { max([1, 2, 3, 4], x) }`}, "[4]"},
|
||||
{"max set", []string{`p = x { max({1, 2, 3, 4}, x) }`}, "4"},
|
||||
{"max virtual", []string{`p[x] { max([y | q[y]], x) }`, `q[x] { a[_] = x }`}, "[4]"},
|
||||
{"max virtual set", []string{`p = x { max(q, x) }`, `q[x] { a[_] = x }`}, "4"},
|
||||
{"min", []string{`p[x] { min([1, 2, 3, 4], x) }`}, "[1]"},
|
||||
{"min dups", []string{`p[x] { min([1, 2, 1, 3, 4], x) }`}, "[1]"},
|
||||
{"min out-of-order", []string{`p[x] { min([3, 2, 1, 4, 6, -7, 10], x) }`}, "[-7]"},
|
||||
{"min set", []string{`p = x { min({1, 2, 3, 4}, x) }`}, "1"},
|
||||
{"min virtual", []string{`p[x] { min([y | q[y]], x) }`, `q[x] { a[_] = x }`}, "[1]"},
|
||||
{"min virtual set", []string{`p = x { min(q, x) }`, `q[x] { a[_] = x }`}, "1"},
|
||||
{"reduce ref dest", []string{`p = true { max([1, 2, 3, 4], a[3]) }`}, "true"},
|
||||
{"reduce ref dest (2)", []string{`p = true { not max([1, 2, 3, 4, 5], a[3]) }`}, "true"},
|
||||
{"sort", []string{`p = x { sort([4, 3, 2, 1], x) }`}, "[1 ,2, 3, 4]"},
|
||||
{"sort set", []string{`p = x { sort({4,3,2,1}, x) }`}, "[1,2,3,4]"},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"empty set", []string{`p = x { x := all(set()) }`}, "true"},
|
||||
{"empty array", []string{`p = x { x := all([]) }`}, "true"},
|
||||
{"set success", []string{`p = x { x := all({true, true, true}) }`}, "true"},
|
||||
{"array success", []string{`p = x { x := all( [true, true, true] ) }`}, "true"},
|
||||
{"set fail", []string{`p = x { x := all( {true, false, true} ) }`}, "false"},
|
||||
{"array fail", []string{`p = x { x := all( [false, true, true] ) }`}, "false"},
|
||||
{"other types", []string{`p = x { x := all( [{}, "", true, true, 123] ) }`}, "false"},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAny(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"empty set", []string{`p = x { x := any(set()) }`}, "false"},
|
||||
{"empty array", []string{`p = x { x := any([]) }`}, "false"},
|
||||
{"set success", []string{`p = x { x := any({false, false, true}) }`}, "true"},
|
||||
{"array success", []string{`p = x { x := any( [true, true, true, false, false] ) }`}, "true"},
|
||||
{"set fail", []string{`p = x { x := any( {false, false, false} ) }`}, "false"},
|
||||
{"array fail", []string{`p = x { x := any( [false] ) }`}, "false"},
|
||||
{"other types", []string{`p = x { x := any( [true, {}, "false"] ) }`}, "true"},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright 2018 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTopDownArray(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"concat", []string{`p = x { x = array.concat([1,2], [3,4]) }`}, "[1,2,3,4]"},
|
||||
{"concat: err", []string{`p = x { x = array.concat(data.b, [3,4]) }`}, &Error{Code: TypeErr, Message: "array.concat: operand 1 must be array but got object"}},
|
||||
{"concat: err rhs", []string{`p = x { x = array.concat([1,2], data.b) }`}, &Error{Code: TypeErr, Message: "array.concat: operand 2 must be array but got object"}},
|
||||
|
||||
{"slice", []string{`p = x { x = array.slice([1,2,3,4,5], 1, 3) }`}, "[2,3]"},
|
||||
{"slice: empty slice", []string{`p = x { x = array.slice([1,2,3], 0, 0) }`}, "[]"},
|
||||
{"slice: negative indices", []string{`p = x { x = array.slice([1,2,3,4,5], -4, -1) }`}, "[]"},
|
||||
{"slice: stopIndex < startIndex", []string{`p = x { x = array.slice([1,2,3,4,5], 4, 1) }`}, "[]"},
|
||||
{"slice: clamp startIndex", []string{`p = x { x = array.slice([1,2,3,4,5], -1, 2) }`}, `[1,2]`},
|
||||
{"slice: clamp stopIndex", []string{`p = x {x = array.slice([1,2,3,4,5], 3, 6) }`}, `[4,5]`},
|
||||
{"slice: clamp both out of range", []string{"p = x { x = array.slice([], 1000, 2000) }"}, `[]`},
|
||||
{"slice: clamp both out of range non-empty", []string{"p = x { x = array.slice([1,2,3], 1000, 2000) }"}, `[]`},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
)
|
||||
|
||||
func TestBuiltinBitsOr(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"basic bitwise-or", []string{`p[x] { x := bits.or(7, 9) }`}, `[15]`},
|
||||
{"or with zero is value", []string{`p[x] { x := bits.or(50, 0) }`}, `[50]`},
|
||||
{"lhs (float) error", []string{`p = x { x := bits.or(7.2, 42) }`}, &Error{Code: TypeErr, Message: "bits.or: operand 1 must be integer number but got floating-point number"}},
|
||||
{
|
||||
"rhs (wrong type-type) error",
|
||||
[]string{`p = x { x := bits.or(7, "hi") }`},
|
||||
ast.Errors{ast.NewError(ast.TypeErr, nil, "bits.or: invalid argument(s)")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinBitsAnd(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"basic bitwise-and", []string{`p[x] { x := bits.and(7, 9) }`}, `[1]`},
|
||||
{"and with zero is and", []string{`p[x] { x := bits.and(50, 0) }`}, `[0]`},
|
||||
{"lhs (float) error", []string{`p = x { x := bits.and(7.2, 42) }`}, &Error{Code: TypeErr, Message: "bits.and: operand 1 must be integer number but got floating-point number"}},
|
||||
{
|
||||
"rhs (wrong type-type) error",
|
||||
[]string{`p = x { x := bits.and(7, "hi") }`},
|
||||
ast.Errors{ast.NewError(ast.TypeErr, nil, "bits.and: invalid argument(s)")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinBitsNegate(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"basic bitwise-negate", []string{`p[x] { x := bits.negate(42) }`}, `[-43]`},
|
||||
{"float error", []string{`p = x { x := bits.negate(7.2) }`}, &Error{Code: TypeErr, Message: "bits.negate: operand 1 must be integer number but got floating-point number"}},
|
||||
{
|
||||
"type error",
|
||||
[]string{`p = x { x := bits.negate("hi") }`},
|
||||
ast.Errors{ast.NewError(ast.TypeErr, nil, "bits.negate: invalid argument(s)")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinBitsXOr(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"basic bitwise-xor", []string{`p[x] { x := bits.xor(42, 3) }`}, `[41]`},
|
||||
{"xor same is 0", []string{`p[x] { x := bits.xor(42, 42) }`}, `[0]`},
|
||||
{"lhs (float) error", []string{`p = x { x := bits.xor(7.2, 42) }`}, &Error{Code: TypeErr, Message: "bits.xor: operand 1 must be integer number but got floating-point number"}},
|
||||
{
|
||||
"rhs (wrong type-type) error",
|
||||
[]string{`p = x { x := bits.xor(7, "hi") }`},
|
||||
ast.Errors{ast.NewError(ast.TypeErr, nil, "bits.xor: invalid argument(s)")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinBitsShiftLeft(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"basic shift-left", []string{`p[x] { x := bits.lsh(1, 3) }`}, `[8]`},
|
||||
{"lhs (float) error", []string{`p = x { x := bits.lsh(7.2, 42) }`}, &Error{Code: TypeErr, Message: "bits.lsh: operand 1 must be integer number but got floating-point number"}},
|
||||
{
|
||||
"rhs (wrong type-type) error",
|
||||
[]string{`p = x { x := bits.lsh(7, "hi") }`},
|
||||
ast.Errors{ast.NewError(ast.TypeErr, nil, "bits.lsh: invalid argument(s)")},
|
||||
},
|
||||
{"rhs must be unsigned", []string{`p = x { x := bits.lsh(7, -1) }`}, &Error{Code: TypeErr, Message: "bits.lsh: operand 2 must be an unsigned integer number but got a negative integer"}},
|
||||
{
|
||||
"shift of max int32 doesn't overflow",
|
||||
[]string{fmt.Sprintf(`p = x { x := bits.lsh(%d, 1) }`, math.MaxInt32)},
|
||||
`4294967294`,
|
||||
},
|
||||
{
|
||||
"shift of max int64 doesn't overflow and is not lossy",
|
||||
[]string{fmt.Sprintf(`p = x { x := bits.lsh(%d, 1) }`, math.MaxInt64)},
|
||||
`18446744073709551614`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinBitsShiftRight(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"basic shift-right", []string{`p[x] { x := bits.rsh(8, 3) }`}, `[1]`},
|
||||
{"lhs (float) error", []string{`p = x { x := bits.rsh(7.2, 42) }`}, &Error{Code: TypeErr, Message: "bits.rsh: operand 1 must be integer number but got floating-point number"}},
|
||||
{
|
||||
"rhs (wrong type-type) error",
|
||||
[]string{`p = x { x := bits.rsh(7, "hi") }`},
|
||||
ast.Errors{ast.NewError(ast.TypeErr, nil, "bits.rsh: invalid argument(s)")},
|
||||
},
|
||||
{"rhs must be unsigned", []string{`p = x { x := bits.rsh(7, -1) }`}, &Error{Code: TypeErr, Message: "bits.rsh: operand 2 must be an unsigned integer number but got a negative integer"}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2018 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
)
|
||||
|
||||
func TestToArray(t *testing.T) {
|
||||
|
||||
// expected result
|
||||
expectedResult := []interface{}{1, 2, 3}
|
||||
resultObj, err := ast.InterfaceToValue(expectedResult)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
typeErr := &Error{Code: TypeErr, Message: "operand 1 must be one of {array, set}"}
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"array input", []string{`p = x { cast_array([1,2,3], x) }`}, resultObj.String()},
|
||||
{"set input", []string{`p = x { cast_array({1,2,3}, x) }`}, resultObj.String()},
|
||||
{"bad type", []string{`p = x { cast_array("hello", x) }`}, typeErr},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSet(t *testing.T) {
|
||||
|
||||
typeErr := &Error{Code: TypeErr, Message: "operand 1 must be one of {array, set}"}
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"array input", []string{`p = x { cast_set([1,1,1], x) }`}, "[1]"},
|
||||
{"set input", []string{`p = x { cast_set({1,1,2,3}, x) }`}, "[1,2,3]"},
|
||||
{"bad type", []string{`p = x { cast_set("hello", x) }`}, typeErr},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCasts(t *testing.T) {
|
||||
typeErr := &Error{Code: TypeErr}
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"null valid", []string{`p = x { cast_null(null, x) }`}, "null"},
|
||||
{"null invalid", []string{`p = x { cast_null({}, x) }`}, typeErr},
|
||||
//{"string valid", []string{`p = x { cast_string("potato", x) }`}, "potato"},
|
||||
{"string invalid", []string{`p = x { cast_string({1,1,2,3}, x) }`}, typeErr},
|
||||
{"boolean valid", []string{`p = x { cast_boolean(false, x) }`}, "false"},
|
||||
{"boolean valid", []string{`p = x { cast_boolean(1, x) }`}, typeErr},
|
||||
{"obj valid", []string{`p = x { cast_object({}, x) }`}, "{}"},
|
||||
{"obj invalid", []string{`p = x { cast_object([1,2,3], x) }`}, typeErr},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -10,186 +10,6 @@ import (
|
||||
"github.com/open-policy-agent/opa/storage/inmem"
|
||||
)
|
||||
|
||||
func TestNetCIDROverlap(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"cidr match", []string{`p[x] { net.cidr_overlap("192.168.1.0/24", "192.168.1.67", x) }`}, "[true]"},
|
||||
{"cidr mismatch", []string{`p[x] { net.cidr_overlap("192.168.1.0/28", "192.168.1.67", x) }`}, "[false]"},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetCIDRIntersects(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"cidr subnet overlaps", []string{`p[x] { net.cidr_intersects("192.168.1.0/25", "192.168.1.64/25", x) }`}, "[true]"},
|
||||
{"cidr subnet does not overlap", []string{`p[x] { net.cidr_intersects("192.168.1.0/24", "192.168.2.0/24", x) }`}, "[false]"},
|
||||
{"cidr ipv6 subnet overlaps", []string{`p[x] { net.cidr_intersects("fd1e:5bfe:8af3:9ddc::/64", "fd1e:5bfe:8af3:9ddc:1111::/72", x) }`}, "[true]"},
|
||||
{"cidr ipv6 subnet does not overlap", []string{`p[x] { net.cidr_intersects("fd1e:5bfe:8af3:9ddc::/64", "2001:4860:4860::8888/32", x) }`}, "[false]"},
|
||||
{"cidr subnet overlap malformed cidr a", []string{`p[x] { net.cidr_intersects("not-a-cidr", "192.168.1.0/24", x) }`}, &Error{Code: BuiltinErr}},
|
||||
{"cidr subnet overlap malformed cidr b", []string{`p[x] { net.cidr_intersects("192.168.1.0/28", "not-a-cidr", x) }`}, &Error{Code: BuiltinErr}},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetCIDRContains(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"cidr contains subnet", []string{`p[x] { net.cidr_contains("10.0.0.0/8", "10.1.0.0/24", x) }`}, "[true]"},
|
||||
{"cidr does not contain subnet partial", []string{`p[x] { net.cidr_contains("172.17.0.0/24", "172.17.0.0/16", x) }`}, "[false]"},
|
||||
{"cidr does not contain subnet", []string{`p[x] { net.cidr_contains("10.0.0.0/8", "192.168.1.0/24", x) }`}, "[false]"},
|
||||
{"cidr contains single ip subnet", []string{`p[x] { net.cidr_contains("10.0.0.0/8", "10.1.1.1/32", x) }`}, "[true]"},
|
||||
{"cidr contains subnet ipv6", []string{`p[x] { net.cidr_contains("2001:4860:4860::8888/32", "2001:4860:4860:1234::8888/40", x) }`}, "[true]"},
|
||||
{"cidr contains single ip subnet ipv6", []string{`p[x] { net.cidr_contains("2001:4860:4860::8888/32", "2001:4860:4860:1234:5678:1234:5678:8888/128", x) }`}, "[true]"},
|
||||
{"cidr does not contain subnet partial ipv6", []string{`p[x] { net.cidr_contains("2001:4860::/96", "2001:4860::/32", x) }`}, "[false]"},
|
||||
{"cidr does not contain subnet ipv6", []string{`p[x] { net.cidr_contains("2001:4860::/32", "fd1e:5bfe:8af3:9ddc::/64", x) }`}, "[false]"},
|
||||
{"cidr subnet overlap malformed cidr a", []string{`p[x] { net.cidr_contains("not-a-cidr", "192.168.1.67", x) }`}, &Error{Code: BuiltinErr}},
|
||||
{"cidr subnet overlap malformed cider b", []string{`p[x] { net.cidr_contains("192.168.1.0/28", "not-a-cidr", x) }`}, &Error{Code: BuiltinErr}},
|
||||
{"cidr contains ip", []string{`p[x] { net.cidr_contains("10.0.0.0/8", "10.1.2.3", x) }`}, "[true]"},
|
||||
{"cidr does not contain ip", []string{`p[x] { net.cidr_contains("10.0.0.0/8", "192.168.1.1", x) }`}, "[false]"},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetCIDRContainsMatches(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "strings",
|
||||
rules: []string{`p = x { x := net.cidr_contains_matches("1.1.1.0/24", "1.1.1.1") }`},
|
||||
expected: `[["1.1.1.0/24", "1.1.1.1"]]`,
|
||||
},
|
||||
{
|
||||
note: "arrays",
|
||||
rules: []string{`p = x { x := net.cidr_contains_matches(["1.1.2.0/24", "1.1.1.0/24"], ["1.1.1.1", "1.1.2.1"]) }`},
|
||||
expected: `[[0,1], [1,0]]`,
|
||||
},
|
||||
{
|
||||
note: "arrays of tuples",
|
||||
rules: []string{`p = x { x := net.cidr_contains_matches([["1.1.2.0/24", 1], "1.1.1.0/24"], ["1.1.1.1", "1.1.2.1"]) }`},
|
||||
expected: `[[0,1], [1,0]]`,
|
||||
},
|
||||
{
|
||||
note: "bad array",
|
||||
rules: []string{`p = x { x := net.cidr_contains_matches(["1.1.2.0/24", "1.1.1.0/24"], ["1.1.1.1", data.a[0]]) }`},
|
||||
expected: &Error{Code: BuiltinErr, Message: "net.cidr_contains_matches: operand 2: element must be string or non-empty array"},
|
||||
},
|
||||
{
|
||||
note: "sets of strings",
|
||||
rules: []string{`p = x { x := net.cidr_contains_matches({"1.1.2.0/24", "1.1.1.0/24"}, {"1.1.1.1", "1.1.2.1"}) }`},
|
||||
expected: `[["1.1.1.0/24", "1.1.1.1"], ["1.1.2.0/24", "1.1.2.1"]]`,
|
||||
},
|
||||
{
|
||||
note: "sets of tuples",
|
||||
rules: []string{`p = x { x := net.cidr_contains_matches({["1.1.2.0/24", "foo"], ["1.1.1.0/24", "bar"]}, {["1.1.1.1", "baz"], ["1.1.2.1", "qux"]}) }`},
|
||||
expected: `[[["1.1.1.0/24", "bar"], ["1.1.1.1", "baz"]], [["1.1.2.0/24", "foo"], ["1.1.2.1", "qux"]]]`,
|
||||
},
|
||||
{
|
||||
note: "bad set",
|
||||
rules: []string{`p = x { x := net.cidr_contains_matches({["1.1.2.0/24", "foo"], ["1.1.1.0/24", "bar"]}, {data.a[0], ["1.1.2.1", "qux"]}) }`},
|
||||
expected: &Error{Code: BuiltinErr, Message: `net.cidr_contains_matches: operand 2: element must be string or non-empty array`},
|
||||
},
|
||||
{
|
||||
note: "bad set tuple element",
|
||||
rules: []string{`p = x { x := net.cidr_contains_matches({["1.1.2.0/24", "foo"], ["1.1.1.0/24", "bar"]}, {[], ["1.1.2.1", "qux"]}) }`},
|
||||
expected: &Error{Code: BuiltinErr, Message: `net.cidr_contains_matches: operand 2: element must be string or non-empty array`},
|
||||
},
|
||||
{
|
||||
note: "objects",
|
||||
rules: []string{`p = x { x := net.cidr_contains_matches({"k1": "1.1.1.1/24", "k2": ["1.1.1.2/24", 1]}, "1.1.1.128") }`},
|
||||
expected: `[["k1", "1.1.1.128"], ["k2", "1.1.1.128"]]`,
|
||||
},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetCIDRExpand(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "cidr includes host and broadcast",
|
||||
rules: []string{
|
||||
`p = x { net.cidr_expand("192.168.1.1/30", x) }`,
|
||||
},
|
||||
expected: `[
|
||||
"192.168.1.0",
|
||||
"192.168.1.1",
|
||||
"192.168.1.2",
|
||||
"192.168.1.3"
|
||||
]`,
|
||||
},
|
||||
{
|
||||
note: "cidr last octet all 1s",
|
||||
rules: []string{
|
||||
`p = x { net.cidr_expand("172.16.100.255/30", x) }`,
|
||||
},
|
||||
expected: `[
|
||||
"172.16.100.252",
|
||||
"172.16.100.253",
|
||||
"172.16.100.254",
|
||||
"172.16.100.255"
|
||||
]`,
|
||||
},
|
||||
{
|
||||
note: "cidr all bits",
|
||||
rules: []string{
|
||||
`p = x { net.cidr_expand("192.168.1.1/32", x) }`,
|
||||
},
|
||||
expected: `[
|
||||
"192.168.1.1"
|
||||
]`,
|
||||
},
|
||||
{
|
||||
note: "cidr invalid mask",
|
||||
rules: []string{
|
||||
`p = x { net.cidr_expand("192.168.1.1/33", x) }`,
|
||||
},
|
||||
expected: &Error{Code: BuiltinErr, Message: "net.cidr_expand: invalid CIDR address: 192.168.1.1/33"},
|
||||
},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetCIDRExpandCancellation(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,60 +0,0 @@
|
||||
package topdown
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGlobMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"glob match with . delimiter", []string{`p[x] { glob.match("*.github.com", ["."], "api.github.com", x) }`}, "[true]"},
|
||||
{"super glob match with . delimiter", []string{`p[x] { glob.match("api.**.com", ["."], "api.github.com", x) }`}, "[true]"},
|
||||
{"super glob match with . delimiter", []string{`p[x] { glob.match("api.**.com", ["."], "api.cdn.github.com", x) }`}, "[true]"},
|
||||
{"glob match with : delimiter", []string{`p[x] { glob.match("*:github:com", [":"], "api:github:com", x) }`}, "[true]"},
|
||||
{"glob no match with . delimiter", []string{`p[x] { glob.match("*.github.com", ["."], "api.not-github.com", x) }`}, "[false]"},
|
||||
{"glob match with character-list matchers", []string{`p[x] { glob.match("[abc]at", [], "cat", x) }`}, "[true]"},
|
||||
{"glob no match with character-list matchers", []string{`p[x] { glob.match("[abc]at", [], "fat", x) }`}, "[false]"},
|
||||
{"glob match with negated character-list matchers", []string{`p[x] { glob.match("[!abc]at", [], "fat", x) }`}, "[true]"},
|
||||
{"glob no match with negated character-list matchers", []string{`p[x] { glob.match("[!abc]at", [], "cat", x) }`}, "[false]"},
|
||||
{"glob match with character-range matchers", []string{`p[x] { glob.match("[a-c]at", [], "bat", x) }`}, "[true]"},
|
||||
{"glob no match with character-range matchers", []string{`p[x] { glob.match("[a-c]at", [], "fat", x) }`}, "[false]"},
|
||||
{"glob no match with character-range matchers", []string{`p[x] { glob.match("[!a-c]at", [], "bat", x) }`}, "[false]"},
|
||||
{"glob match with character-range matchers", []string{`p[x] { glob.match("[!a-c]at", [], "fat", x) }`}, "[true]"},
|
||||
{"glob match with single wild-card", []string{`p[x] { glob.match("?at", [], "fat", x) }`}, "[true]"},
|
||||
{"glob no match with single wild-card", []string{`p[x] { glob.match("?at", [], "at", x) }`}, "[false]"},
|
||||
{"glob match with single wild-card and delimiter", []string{`p[x] { glob.match("?at", ["f"], "bat", x) }`}, "[true]"},
|
||||
{"glob no match with single wild-card and delimiter", []string{`p[x] { glob.match("?at", ["f"], "fat", x) }`}, "[false]"},
|
||||
{"glob match with pattern-alternatives list (cat)", []string{`p[x] { glob.match("{cat,bat,[fr]at}", [], "cat", x) }`}, "[true]"},
|
||||
{"glob match with pattern-alternatives list (bat)", []string{`p[x] { glob.match("{cat,bat,[fr]at}", [], "bat", x) }`}, "[true]"},
|
||||
{"glob match with pattern-alternatives list (fat)", []string{`p[x] { glob.match("{cat,bat,[fr]at}", [], "fat", x) }`}, "[true]"},
|
||||
{"glob match with pattern-alternatives list (rat)", []string{`p[x] { glob.match("{cat,bat,[fr]at}", [], "rat", x) }`}, "[true]"},
|
||||
{"glob no match with pattern-alternatives list", []string{`p[x] { glob.match("{cat,bat,[fr]at}", [], "at", x) }`}, "[false]"},
|
||||
{"glob match single with . delimiter", []string{`p[x] { glob.match("*", ["."], "foo", x) }`}, "[true]"},
|
||||
{"glob match single with default delimiter", []string{`p[x] { glob.match("*", [], "foo", x) }`}, "[true]"},
|
||||
{"glob no match single with . delimiter", []string{`p[x] { glob.match("*", ["."], "foo.bar", x) }`}, "[false]"},
|
||||
{"glob no match single with default delimiter", []string{`p[x] { glob.match("*", [], "foo.bar", x) }`}, "[false]"},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobQuoteMeta(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"glob quote meta", []string{`p[x] { glob.quote_meta("*.github.com", x) }`}, `["\\*.github.com"]`},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -5,108 +5,11 @@
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
)
|
||||
|
||||
func TestBuiltinJSONFilter(t *testing.T) {
|
||||
cases := []struct {
|
||||
note string
|
||||
object string
|
||||
filters string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "base",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
filters: `{"a/b/c"}`,
|
||||
expected: `{"a": {"b": {"c": 7}}}`,
|
||||
},
|
||||
{
|
||||
note: "multiple roots",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
filters: `{"a/b/c", "e"}`,
|
||||
expected: `{"a": {"b": {"c": 7}}, "e": 9}`,
|
||||
},
|
||||
{
|
||||
note: "multiple roots array",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
filters: `["a/b/c", "e"]`,
|
||||
expected: `{"a": {"b": {"c": 7}}, "e": 9}`,
|
||||
},
|
||||
{
|
||||
note: "shared roots",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}, "e": 9}}`,
|
||||
filters: `{"a/b/c", "a/e"}`,
|
||||
expected: `{"a": {"b": {"c": 7}, "e": 9}}`,
|
||||
},
|
||||
{
|
||||
note: "conflict",
|
||||
object: `{"a": {"b": 7}}`,
|
||||
filters: `{"a", "a/b"}`,
|
||||
expected: `{"a": {"b": 7}}`,
|
||||
},
|
||||
{
|
||||
note: "empty list",
|
||||
object: `{"a": 7}`,
|
||||
filters: `set()`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "empty object",
|
||||
object: `{}`,
|
||||
filters: `{"a/b"}`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "arrays",
|
||||
object: `{"a": [{"b": 7, "c": 8}, {"d": 9}]}`,
|
||||
filters: `{"a/0/b", "a/1"}`,
|
||||
expected: `{"a": [{"b": 7}, {"d": 9}]}`,
|
||||
},
|
||||
{
|
||||
note: "object with number keys",
|
||||
object: `{"a": [{"1":["b", "c", "d"]}, {"x": "y"}]}`,
|
||||
filters: `{"a/0/1/2"}`,
|
||||
expected: `{"a": [{"1": ["d"]}]}`,
|
||||
},
|
||||
{
|
||||
note: "arrays of roots",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
filters: `{["a", "b", "c"], ["e"]}`,
|
||||
expected: `{"a": {"b": {"c": 7}}, "e": 9}`,
|
||||
},
|
||||
{
|
||||
note: "mixed root types",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8, "x": 0}}, "e": 9}`,
|
||||
filters: `{["a", "b", "c"], "a/b/d"}`,
|
||||
expected: `{"a": {"b": {"c": 7, "d": 8}}}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
rules := []string{
|
||||
fmt.Sprintf("p = x { x := json.filter(%s, %s) }", tc.object, tc.filters),
|
||||
}
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinJSONFilterIdempotent(t *testing.T) {
|
||||
rule := `
|
||||
p {
|
||||
# "base" should never be mutated
|
||||
base := {"a": {"b": 2, "c": 3}}
|
||||
json.filter(base, {"a/b"}) == {"a": {"b": 2}}
|
||||
json.filter(base, {"a/c"}) == {"a": {"c": 3}}
|
||||
base == {"a": {"b": 2, "c": 3}}
|
||||
}
|
||||
`
|
||||
runTopDownTestCase(t, map[string]interface{}{}, t.Name(), []string{rule}, "true")
|
||||
}
|
||||
|
||||
func TestFiltersToObject(t *testing.T) {
|
||||
cases := []struct {
|
||||
note string
|
||||
@@ -198,275 +101,3 @@ func TestFiltersToObject(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinJSONRemove(t *testing.T) {
|
||||
cases := []struct {
|
||||
note string
|
||||
object string
|
||||
paths string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "base",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
paths: `{"a/b/c"}`,
|
||||
expected: `{"a": {"b": {"d": 8}}, "e": 9}`,
|
||||
},
|
||||
{
|
||||
note: "multiple roots",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
paths: `{"a/b/c", "e"}`,
|
||||
expected: `{"a": {"b": {"d": 8}}}`,
|
||||
},
|
||||
{
|
||||
note: "multiple roots array",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
paths: `["a/b/c", "e"]`,
|
||||
expected: `{"a": {"b": {"d": 8}}}`,
|
||||
},
|
||||
{
|
||||
note: "shared roots",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}, "e": 9}}`,
|
||||
paths: `{"a/b/c", "a/e"}`,
|
||||
expected: `{"a": {"b": {"d": 8}}}`,
|
||||
},
|
||||
{
|
||||
note: "conflict",
|
||||
object: `{"a": {"b": 7}, "c": 1}`,
|
||||
paths: `{"a", "a/b"}`,
|
||||
expected: `{"c": 1}`,
|
||||
},
|
||||
{
|
||||
note: "empty list",
|
||||
object: `{"a": 7}`,
|
||||
paths: `set()`,
|
||||
expected: `{"a": 7}`,
|
||||
},
|
||||
{
|
||||
note: "empty object",
|
||||
object: `{}`,
|
||||
paths: `{"a/b"}`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "delete all",
|
||||
object: `{"a": {"b": 7}, "c": 1}`,
|
||||
paths: `{"a", "c"}`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "delete last in object",
|
||||
object: `{"a": {"b": 7}, "c": 1}`,
|
||||
paths: `{"a/b", "c"}`,
|
||||
expected: `{"a": {}}`,
|
||||
},
|
||||
{
|
||||
note: "arrays",
|
||||
object: `{"a": [{"b": 7, "c": 8}, {"d": 9}]}`,
|
||||
paths: `{"a/0/b", "a/1"}`,
|
||||
expected: `{"a": [{"c": 8}]}`,
|
||||
},
|
||||
{
|
||||
note: "object with number keys",
|
||||
object: `{"a": [{"1":["b", "c", "d"]}, {"x": "y"}]}`,
|
||||
paths: `{"a/0/1/2"}`,
|
||||
expected: `{"a": [{"1":["b", "c"]}, {"x": "y"}]}`,
|
||||
},
|
||||
{
|
||||
note: "arrays of roots",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
paths: `{["a", "b", "c"], ["e"]}`,
|
||||
expected: `{"a": {"b": {"d": 8}}}`,
|
||||
},
|
||||
{
|
||||
note: "mixed root types",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8, "x": 0}}, "e": 9}`,
|
||||
paths: `{["a", "b", "c"], "a/b/d"}`,
|
||||
expected: `{"a": {"b": {"x": 0}}, "e": 9}`,
|
||||
},
|
||||
{
|
||||
note: "error invalid target type string",
|
||||
object: `"foo"`,
|
||||
paths: `{"a/b/c"}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid target type number",
|
||||
object: `22`,
|
||||
paths: `{"a/b/c"}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid target type boolean",
|
||||
object: `false`,
|
||||
paths: `{"a/b/c"}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid target type set",
|
||||
object: `{"a"}`,
|
||||
paths: `{"a/b/c"}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid target type array",
|
||||
object: `["a"]`,
|
||||
paths: `{"a/b/c"}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid target type string input",
|
||||
object: `input.x`,
|
||||
paths: `{"a/b/c"}`,
|
||||
input: `{"x": "foo"}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 1 must be object but got string"},
|
||||
},
|
||||
{
|
||||
note: "error invalid target type number input",
|
||||
object: `input.x`,
|
||||
paths: `{"a/b/c"}`,
|
||||
input: `{"x": 22}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 1 must be object but got number"},
|
||||
},
|
||||
{
|
||||
note: "error invalid target type boolean input",
|
||||
object: `input.x`,
|
||||
paths: `{"a/b/c"}`,
|
||||
input: `{"x": true}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 1 must be object but got boolean"},
|
||||
},
|
||||
{
|
||||
note: "error invalid target type array input",
|
||||
object: `input.x`,
|
||||
paths: `{"a/b/c"}`,
|
||||
input: `{"x": ["a", "b", "c"]}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 1 must be object but got array"},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type string",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `"foo"`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type number",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `22`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type boolean",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `true`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type object",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `{"x": 1}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type set with numbers",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `{"a", 1, 2, 3}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type set with objects",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `{"a", {"x": 1}, {"y": 2}}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type array with numbers",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `["a", 1, 2, 3]`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type array with objects",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `["a", {"x": 1}, {"y": 2}]`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "json.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type string",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `input.x`,
|
||||
input: `{"x": "foo"}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 2 must be one of {set, array} but got string"},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type number",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `input.x`,
|
||||
input: `{"x": 22}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 2 must be one of {set, array} but got number"},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type boolean",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `input.x`,
|
||||
input: `{"x": true}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 2 must be one of {set, array} but got boolean"},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type object",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `input.x`,
|
||||
input: `{"x": {"y": 123}}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 2 must be one of {set, array} but got object"},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type set with numbers",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `input.x`,
|
||||
input: `{"x": {"a", 1, 2, 3}}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 2 must be one of {set, array} containing string paths or array of path segments but got number"},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type set with objects",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `input.x`,
|
||||
input: `{"x": {"a", {"x": 1}, {"y": 2}}}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 2 must be one of {set, array} containing string paths or array of path segments but got object"},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type array with numbers",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `input.x`,
|
||||
input: `{"x": ["a", 1, 2, 3]}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 2 must be one of {set, array} containing string paths or array of path segments but got number"},
|
||||
},
|
||||
{
|
||||
note: "error invalid paths type array with objects",
|
||||
object: `{"a": {"b": {"c": 123}}}`,
|
||||
paths: `input.x`,
|
||||
input: `{"x": ["a", {"x": 1}, {"y": 2}]}`,
|
||||
expected: &Error{Code: TypeErr, Message: "json.remove: operand 2 must be one of {set, array} containing string paths or array of path segments but got object"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
rules := []string{
|
||||
fmt.Sprintf("p = x { x := json.remove(%s, %s) }", tc.object, tc.paths),
|
||||
}
|
||||
runTopDownTestCaseWithModules(t, map[string]interface{}{}, tc.note, rules, nil, tc.input, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinJSONRemoveIdempotent(t *testing.T) {
|
||||
rule := `
|
||||
p {
|
||||
# "base" should never be mutated
|
||||
base := {"a": {"b": 2, "c": 3}}
|
||||
json.remove(base, {"a"}) == {}
|
||||
json.remove(base, {"a/b"}) == {"a": {"c": 3}}
|
||||
json.remove(base, {"a/c"}) == {"a": {"b": 2}}
|
||||
base == {"a": {"b": 2, "c": 3}}
|
||||
}
|
||||
`
|
||||
runTopDownTestCase(t, map[string]interface{}{}, t.Name(), []string{rule}, "true")
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuiltinNumbersRange(t *testing.T) {
|
||||
cases := []struct {
|
||||
note string
|
||||
stmt string
|
||||
exp interface{}
|
||||
}{
|
||||
{
|
||||
note: "one",
|
||||
stmt: "p = x { x := numbers.range(0, 0) }",
|
||||
exp: "[0]",
|
||||
},
|
||||
{
|
||||
note: "ascending",
|
||||
stmt: "p = x { x := numbers.range(-2, 3) }",
|
||||
exp: "[-2, -1, 0, 1, 2, 3]",
|
||||
},
|
||||
{
|
||||
note: "descending",
|
||||
stmt: "p = x { x := numbers.range(2, -3) }",
|
||||
exp: "[2, 1, 0, -1, -2, -3]",
|
||||
},
|
||||
{
|
||||
note: "precision",
|
||||
stmt: "p { numbers.range(49649733057, 49649733060, [49649733057, 49649733058, 49649733059, 49649733060]) }",
|
||||
exp: "true",
|
||||
},
|
||||
{
|
||||
note: "error: floating-point number pos 1",
|
||||
stmt: "p { numbers.range(3.14, 4) }",
|
||||
exp: &Error{Code: TypeErr, Message: "numbers.range: operand 1 must be integer number but got floating-point number"},
|
||||
},
|
||||
{
|
||||
note: "error: floating-point number pos 2",
|
||||
stmt: "p { numbers.range(3, 3.14) }",
|
||||
exp: &Error{Code: TypeErr, Message: "numbers.range: operand 2 must be integer number but got floating-point number"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, []string{tc.stmt}, tc.exp)
|
||||
}
|
||||
}
|
||||
@@ -1,543 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
)
|
||||
|
||||
func TestObjectGet(t *testing.T) {
|
||||
cases := []struct {
|
||||
note string
|
||||
object string
|
||||
key interface{}
|
||||
fallback interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "basic case . found",
|
||||
object: `{"a": "b"}`,
|
||||
key: `"a"`,
|
||||
fallback: `"c"`,
|
||||
expected: `"b"`,
|
||||
},
|
||||
{
|
||||
note: "basic case . not found",
|
||||
object: `{"a": "b"}`,
|
||||
key: `"c"`,
|
||||
fallback: `"c"`,
|
||||
expected: `"c"`,
|
||||
},
|
||||
{
|
||||
|
||||
note: "integer key . found",
|
||||
object: "{1: 2}",
|
||||
key: "1",
|
||||
fallback: "3",
|
||||
expected: "2",
|
||||
},
|
||||
{
|
||||
note: "integer key . not found",
|
||||
object: "{1: 2}",
|
||||
key: "2",
|
||||
fallback: "3",
|
||||
expected: "3",
|
||||
},
|
||||
{
|
||||
note: "complex value . found",
|
||||
object: `{"a": {"b": "c"}}`,
|
||||
key: `"a"`,
|
||||
fallback: "true",
|
||||
expected: `{"b": "c"}`,
|
||||
},
|
||||
{
|
||||
note: "complex value . not found",
|
||||
object: `{"a": {"b": "c"}}`,
|
||||
key: `"b"`,
|
||||
fallback: "true",
|
||||
expected: "true",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
rules := []string{
|
||||
fmt.Sprintf("p = x { x := object.get(%s, %s, %s) }", tc.object, tc.key, tc.fallback),
|
||||
}
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinObjectUnion(t *testing.T) {
|
||||
cases := []struct {
|
||||
note string
|
||||
objectA string
|
||||
objectB string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "both empty",
|
||||
objectA: `{}`,
|
||||
objectB: `{}`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "left empty",
|
||||
objectA: `{}`,
|
||||
objectB: `{"a": 1}`,
|
||||
expected: `{"a": 1}`,
|
||||
},
|
||||
{
|
||||
note: "right empty",
|
||||
objectA: `{"a": 1}`,
|
||||
objectB: `{}`,
|
||||
expected: `{"a": 1}`,
|
||||
},
|
||||
{
|
||||
note: "base",
|
||||
objectA: `{"a": 1}`,
|
||||
objectB: `{"b": 2}`,
|
||||
expected: `{"a": 1, "b": 2}`,
|
||||
},
|
||||
{
|
||||
note: "nested",
|
||||
objectA: `{"a": {"b": {"c": 1}}}`,
|
||||
objectB: `{"b": 2}`,
|
||||
expected: `{"a": {"b": {"c": 1}}, "b": 2}`,
|
||||
},
|
||||
{
|
||||
note: "nested reverse",
|
||||
objectA: `{"b": 2}`,
|
||||
objectB: `{"a": {"b": {"c": 1}}}`,
|
||||
expected: `{"a": {"b": {"c": 1}}, "b": 2}`,
|
||||
},
|
||||
{
|
||||
note: "conflict simple",
|
||||
objectA: `{"a": 1}`,
|
||||
objectB: `{"a": 2}`,
|
||||
expected: `{"a": 2}`,
|
||||
},
|
||||
{
|
||||
note: "conflict nested and extra field",
|
||||
objectA: `{"a": 1}`,
|
||||
objectB: `{"a": {"b": {"c": 1}}, "d": 7}`,
|
||||
expected: `{"a": {"b": {"c": 1}}, "d": 7}`,
|
||||
},
|
||||
{
|
||||
note: "conflict multiple",
|
||||
objectA: `{"a": {"b": {"c": 1}}, "e": 1}`,
|
||||
objectB: `{"a": {"b": "foo", "b1": "bar"}, "d": 7, "e": 17}`,
|
||||
expected: `{"a": {"b": "foo", "b1": "bar"}, "d": 7, "e": 17}`,
|
||||
},
|
||||
{
|
||||
note: "error wrong lhs type",
|
||||
objectA: `[1, 2, 3]`,
|
||||
objectB: `{"b": 2}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.union: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error wrong lhs type input",
|
||||
objectA: `input.a`,
|
||||
objectB: `{"b": 2}`,
|
||||
input: `{"a": [1, 2, 3]}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.union: operand 1 must be object but got array"},
|
||||
},
|
||||
{
|
||||
note: "error wrong rhs type",
|
||||
objectA: `{"a": 1}`,
|
||||
objectB: `[1, 2, 3]`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.union: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error wrong rhs type input",
|
||||
objectA: `{"a": 1}`,
|
||||
objectB: `input.b`,
|
||||
input: `{"b": [1, 2, 3]}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.union: operand 2 must be object but got array"},
|
||||
},
|
||||
{
|
||||
note: "error wrong both params",
|
||||
objectA: `"foo"`,
|
||||
objectB: `[1, 2, 3]`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.union: invalid argument(s)")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
rules := []string{
|
||||
fmt.Sprintf("p = x { x := object.union(%s, %s) }", tc.objectA, tc.objectB),
|
||||
}
|
||||
runTopDownTestCaseWithModules(t, map[string]interface{}{}, tc.note, rules, nil, tc.input, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinObjectRemove(t *testing.T) {
|
||||
cases := []struct {
|
||||
note string
|
||||
object string
|
||||
keys string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "base",
|
||||
object: `{"a": 1, "b": {"c": 3}}`,
|
||||
keys: `{"a"}`,
|
||||
expected: `{"b": {"c": 3}}`,
|
||||
},
|
||||
{
|
||||
note: "multiple keys set",
|
||||
object: `{"a": 1, "b": {"c": 3}, "d": 4}`,
|
||||
keys: `{"d", "b"}`,
|
||||
expected: `{"a": 1}`,
|
||||
},
|
||||
{
|
||||
note: "multiple keys array",
|
||||
object: `{"a": 1, "b": {"c": 3}, "d": 4}`,
|
||||
keys: `["d", "b"]`,
|
||||
expected: `{"a": 1}`,
|
||||
},
|
||||
{
|
||||
note: "multiple keys object",
|
||||
object: `{"a": 1, "b": {"c": 3}, "d": 4}`,
|
||||
keys: `{"d": "", "b": 1}`,
|
||||
expected: `{"a": 1}`,
|
||||
},
|
||||
{
|
||||
note: "multiple keys object nested",
|
||||
object: `{"a": {"b": {"c": 2}}, "x": 123}`,
|
||||
keys: `{"a": {"b": {"foo": "bar"}}}`,
|
||||
expected: `{"x": 123}`,
|
||||
},
|
||||
{
|
||||
note: "empty object",
|
||||
object: `{}`,
|
||||
keys: `{"a", "b"}`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "empty keys set",
|
||||
object: `{"a": 1, "b": {"c": 3}}`,
|
||||
keys: `set()`,
|
||||
expected: `{"a": 1, "b": {"c": 3}}`,
|
||||
},
|
||||
{
|
||||
note: "empty keys array",
|
||||
object: `{"a": 1, "b": {"c": 3}}`,
|
||||
keys: `[]`,
|
||||
expected: `{"a": 1, "b": {"c": 3}}`,
|
||||
},
|
||||
{
|
||||
note: "empty keys obj",
|
||||
object: `{"a": 1, "b": {"c": 3}}`,
|
||||
keys: `{}`,
|
||||
expected: `{"a": 1, "b": {"c": 3}}`,
|
||||
},
|
||||
{
|
||||
note: "key doesnt exist",
|
||||
object: `{"a": 1, "b": {"c": 3}}`,
|
||||
keys: `{"z"}`,
|
||||
expected: `{"a": 1, "b": {"c": 3}}`,
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type set",
|
||||
object: `{"a"}`,
|
||||
keys: `{"a"}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type bool",
|
||||
object: `false`,
|
||||
keys: `{"a"}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type array input",
|
||||
object: `input.x`,
|
||||
keys: `{"a"}`,
|
||||
input: `{"x": ["a"]}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.remove: operand 1 must be object but got array"},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type bool input",
|
||||
object: `input.x`,
|
||||
keys: `{"a"}`,
|
||||
input: `{"x": false}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.remove: operand 1 must be object but got boolean"},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type number input",
|
||||
object: `input.x`,
|
||||
keys: `{"a"}`,
|
||||
input: `{"x": 123}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.remove: operand 1 must be object but got number"},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type string input",
|
||||
object: `input.x`,
|
||||
keys: `{"a"}`,
|
||||
input: `{"x": "foo"}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.remove: operand 1 must be object but got string"},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type nil input",
|
||||
object: `input.x`,
|
||||
keys: `{"a"}`,
|
||||
input: `{"x": null}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.remove: operand 1 must be object but got null"},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type string",
|
||||
object: `{"a": 1}`,
|
||||
keys: `"a"`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type boolean",
|
||||
object: `{"a": 1}`,
|
||||
keys: `false`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.remove: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type string input",
|
||||
object: `{"a": 1}`,
|
||||
keys: `input.x`,
|
||||
input: `{"x": "foo"}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.remove: operand 2 must be one of {object, string, array} but got string"},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type boolean input",
|
||||
object: `{"a": 1}`,
|
||||
keys: `input.x`,
|
||||
input: `{"x": true}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.remove: operand 2 must be one of {object, string, array} but got boolean"},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type number input",
|
||||
object: `{"a": 1}`,
|
||||
keys: `input.x`,
|
||||
input: `{"x": 22}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.remove: operand 2 must be one of {object, string, array} but got number"},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type nil input",
|
||||
object: `{"a": 1}`,
|
||||
keys: `input.x`,
|
||||
input: `{"x": null}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.remove: operand 2 must be one of {object, string, array} but got null"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
rules := []string{
|
||||
fmt.Sprintf("p = x { x := object.remove(%s, %s) }", tc.object, tc.keys),
|
||||
}
|
||||
runTopDownTestCaseWithModules(t, map[string]interface{}{}, tc.note, rules, nil, tc.input, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinObjectRemoveIdempotent(t *testing.T) {
|
||||
rule := `
|
||||
p {
|
||||
# "base" should never be mutated
|
||||
base := {"a": 1, "b": 2, "c": 3}
|
||||
object.remove(base, {"a"}) == {"b": 2, "c": 3}
|
||||
object.remove(base, {"b"}) == {"a": 1, "c": 3}
|
||||
object.remove(base, {"c"}) == {"a": 1, "b": 2}
|
||||
base == {"a": 1, "b": 2, "c": 3}
|
||||
}
|
||||
`
|
||||
runTopDownTestCase(t, map[string]interface{}{}, t.Name(), []string{rule}, "true")
|
||||
}
|
||||
|
||||
func TestBuiltinObjectRemoveNonStringKey(t *testing.T) {
|
||||
rules := []string{
|
||||
`p { x := object.remove({"a": 1, [[7]]: 2}, {[[7]]}); x == {"a": 1} }`,
|
||||
}
|
||||
runTopDownTestCase(t, map[string]interface{}{}, "non string root", rules, "true")
|
||||
}
|
||||
|
||||
func TestBuiltinObjectFilter(t *testing.T) {
|
||||
cases := []struct {
|
||||
note string
|
||||
object string
|
||||
filters string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "base",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
filters: `{"a"}`,
|
||||
expected: `{"a": {"b": {"c": 7, "d": 8}}}`,
|
||||
},
|
||||
{
|
||||
note: "multiple roots set",
|
||||
object: `{"a": 1, "b": 2, "c": 3, "e": 9}`,
|
||||
filters: `{"a", "e"}`,
|
||||
expected: `{"a": 1, "e": 9}`,
|
||||
},
|
||||
{
|
||||
note: "multiple roots array",
|
||||
object: `{"a": 1, "b": 2, "c": 3, "e": 9}`,
|
||||
filters: `["a", "e"]`,
|
||||
expected: `{"a": 1, "e": 9}`,
|
||||
},
|
||||
{
|
||||
note: "multiple roots object",
|
||||
object: `{"a": 1, "b": 2, "c": 3, "e": 9}`,
|
||||
filters: `{"a": "foo", "e": ""}`,
|
||||
expected: `{"a": 1, "e": 9}`,
|
||||
},
|
||||
{
|
||||
note: "duplicate roots",
|
||||
object: `{"a": {"b": {"c": 7, "d": 8}}, "e": 9}`,
|
||||
filters: `{"a", "a"}`,
|
||||
expected: `{"a": {"b": {"c": 7, "d": 8}}}`,
|
||||
},
|
||||
{
|
||||
note: "empty roots set",
|
||||
object: `{"a": 7}`,
|
||||
filters: `set()`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "empty roots array",
|
||||
object: `{"a": 7}`,
|
||||
filters: `[]`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "empty roots object",
|
||||
object: `{"a": 7}`,
|
||||
filters: `{}`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "empty object",
|
||||
object: `{}`,
|
||||
filters: `{"a"}`,
|
||||
expected: `{}`,
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type set",
|
||||
object: `{"a"}`,
|
||||
filters: `{"a"}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.filter: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type bool",
|
||||
object: `false`,
|
||||
filters: `{"a"}`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.filter: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type array input",
|
||||
object: `input.x`,
|
||||
filters: `{"a"}`,
|
||||
input: `{"x": ["a"]}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.filter: operand 1 must be object but got array"},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type bool input",
|
||||
object: `input.x`,
|
||||
filters: `{"a"}`,
|
||||
input: `{"x": false}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.filter: operand 1 must be object but got boolean"},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type number input",
|
||||
object: `input.x`,
|
||||
filters: `{"a"}`,
|
||||
input: `{"x": 123}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.filter: operand 1 must be object but got number"},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type string input",
|
||||
object: `input.x`,
|
||||
filters: `{"a"}`,
|
||||
input: `{"x": "foo"}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.filter: operand 1 must be object but got string"},
|
||||
},
|
||||
{
|
||||
note: "error invalid object param type nil input",
|
||||
object: `input.x`,
|
||||
filters: `{"a"}`,
|
||||
input: `{"x": null}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.filter: operand 1 must be object but got null"},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type string",
|
||||
object: `{"a": 1}`,
|
||||
filters: `"a"`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.filter: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type boolean",
|
||||
object: `{"a": 1}`,
|
||||
filters: `false`,
|
||||
expected: ast.Errors{ast.NewError(ast.TypeErr, nil, "object.filter: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type string input",
|
||||
object: `{"a": 1}`,
|
||||
filters: `input.x`,
|
||||
input: `{"x": "foo"}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.filter: operand 2 must be one of {object, string, array} but got string"},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type boolean input",
|
||||
object: `{"a": 1}`,
|
||||
filters: `input.x`,
|
||||
input: `{"x": true}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.filter: operand 2 must be one of {object, string, array} but got boolean"},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type number input",
|
||||
object: `{"a": 1}`,
|
||||
filters: `input.x`,
|
||||
input: `{"x": 22}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.filter: operand 2 must be one of {object, string, array} but got number"},
|
||||
},
|
||||
{
|
||||
note: "error invalid key param type nil input",
|
||||
object: `{"a": 1}`,
|
||||
filters: `input.x`,
|
||||
input: `{"x": null}`,
|
||||
expected: &Error{Code: TypeErr, Message: "object.filter: operand 2 must be one of {object, string, array} but got null"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
rules := []string{
|
||||
fmt.Sprintf("p = x { x := object.filter(%s, %s) }", tc.object, tc.filters),
|
||||
}
|
||||
runTopDownTestCaseWithModules(t, map[string]interface{}{}, tc.note, rules, nil, tc.input, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinObjectFilterNonStringKey(t *testing.T) {
|
||||
rules := []string{
|
||||
`p { x := object.filter({"a": 1, [[7]]: 2}, {[[7]]}); x == {[[7]]: 2} }`,
|
||||
}
|
||||
runTopDownTestCase(t, map[string]interface{}{}, "non string root", rules, "true")
|
||||
}
|
||||
|
||||
func TestBuiltinObjectFilterIdempotent(t *testing.T) {
|
||||
rule := `
|
||||
p {
|
||||
# "base" should never be mutated
|
||||
base := {"a": 1, "b": 2, "c": 3}
|
||||
object.filter(base, {"a"}) == {"a": 1}
|
||||
object.filter(base, {"b"}) == {"b": 2}
|
||||
object.filter(base, {"c"}) == {"c": 3}
|
||||
base == {"a": 1, "b": 2, "c": 3}
|
||||
}
|
||||
`
|
||||
runTopDownTestCase(t, map[string]interface{}{}, t.Name(), []string{rule}, "true")
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2018 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegoParseModule(t *testing.T) {
|
||||
|
||||
data := map[string]interface{}{
|
||||
"ok": `package foo.bar
|
||||
|
||||
import data.a
|
||||
|
||||
p { a = true }`,
|
||||
"err": `package foo.`,
|
||||
}
|
||||
|
||||
runTopDownTestCase(t, data, "ok", []string{
|
||||
`p = x { rego.parse_module("x.rego", data.ok, module); x = module["package"].path[1].value }`}, `"foo"`)
|
||||
|
||||
runTopDownTestCase(t, data, "error", []string{
|
||||
`p = x { rego.parse_module("x.rego", data.err, x) }`}, &Error{Code: BuiltinErr, Message: "rego_parse_error: unexpected eof token: expected ident"})
|
||||
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReachable(t *testing.T) {
|
||||
data := map[string]interface{}{}
|
||||
modules := []string{}
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
input string
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
[]string{`p = x {x := graph.reachable({}, {"a"})}`},
|
||||
`[]`,
|
||||
`{}`,
|
||||
},
|
||||
{
|
||||
"cycle",
|
||||
[]string{
|
||||
`p = x {
|
||||
x := sort(graph.reachable(
|
||||
{
|
||||
"a": {"b"},
|
||||
"b": {"c"},
|
||||
"c": {"a"},
|
||||
},
|
||||
{"a"}
|
||||
))
|
||||
}`},
|
||||
`["a", "b", "c"]`,
|
||||
`{}`,
|
||||
},
|
||||
{
|
||||
"components",
|
||||
[]string{
|
||||
`p = x {
|
||||
x := sort(graph.reachable(
|
||||
{
|
||||
"a": {"b", "c"},
|
||||
"b": {"d"},
|
||||
"c": {"d"},
|
||||
"d": set(),
|
||||
"e": {"f"},
|
||||
"f": {"e"},
|
||||
"x": {"x"},
|
||||
},
|
||||
{"b", "e"}
|
||||
))
|
||||
}`},
|
||||
`["b", "d", "e", "f"]`,
|
||||
`{}`,
|
||||
},
|
||||
{
|
||||
"arrays",
|
||||
[]string{
|
||||
`p = x {
|
||||
x := sort(graph.reachable(
|
||||
{
|
||||
"a": ["b"],
|
||||
"b": ["c"],
|
||||
"c": ["a"],
|
||||
},
|
||||
["a"]
|
||||
))
|
||||
}`},
|
||||
`["a", "b", "c"]`,
|
||||
`{}`,
|
||||
},
|
||||
{
|
||||
"malformed 1",
|
||||
[]string{`p = x {x := graph.reachable(input.graph, input.initial)}`},
|
||||
`[]`,
|
||||
`{"graph": 1, "initial": [1]}`,
|
||||
},
|
||||
{
|
||||
"malformed 2",
|
||||
[]string{`p = x {x := graph.reachable(input.graph, input.initial)}`},
|
||||
`["a"]`,
|
||||
`{"graph": {"a": null}, "initial": ["a"]}`,
|
||||
},
|
||||
{
|
||||
"malformed 3",
|
||||
[]string{`p = x {x := graph.reachable(input.graph, input.initial)}`},
|
||||
`[]`,
|
||||
`{"graph": {"a": []}, "initial": "a"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCaseWithModules(t, data, tc.note, tc.rules, modules, tc.input, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegexIsValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
note: "bad operand type",
|
||||
rules: []string{"p = x { regex.is_valid(data.num, x) }"},
|
||||
expected: "false",
|
||||
},
|
||||
{
|
||||
note: "bad pattern",
|
||||
rules: []string{"p = x { regex.is_valid(`++`, x) }"},
|
||||
expected: "false",
|
||||
},
|
||||
{
|
||||
note: "good pattern",
|
||||
rules: []string{"p = x { regex.is_valid(`.+`, x) }"},
|
||||
expected: "true",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{"num": json.Number("10")}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexMatchTemplate(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"matches wildcard with {}", []string{`p[x] { regex.template_match("urn:foo:{.*}", "urn:foo:bar:baz", "{", "}", x) }`}, "[true]"},
|
||||
{"matches wildcard with <>", []string{`p[x] { regex.template_match("urn:foo:<.*>", "urn:foo:bar:baz", "<", ">", x) }`}, "[true]"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexFind(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"finds all match values", []string{`p[x] { x = regex.find_n("a.", "paranormal", -1) }`}, `[["ar", "an", "al"]]`},
|
||||
{"finds specified number of match values", []string{`p[x] { x = regex.find_n("a.", "paranormal", 2) }`}, `[["ar", "an"]]`},
|
||||
{"finds no matching values", []string{`p[x] { x = regex.find_n("bork", "paranormal", -1) }`}, `[[]]`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexFindAllStringSubmatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"finds no matches", []string{`p[x] { x = regex.find_all_string_submatch_n("a(x*)b", "-", -1) }`}, `[[]]`},
|
||||
{"single match without captures", []string{`p[x] { x = regex.find_all_string_submatch_n("a(x*)b", "-ab-", -1) }`}, `[[["ab", ""]]]`},
|
||||
{"single match with a capture", []string{`p[x] { x = regex.find_all_string_submatch_n("a(x*)b", "-axxb-", -1) }`}, `[[["axxb", "xx"]]]`},
|
||||
{"multiple matches with captures-1", []string{`p[x] { x = regex.find_all_string_submatch_n("a(x*)b", "-ab-axb-", -1) }`}, `[[["ab", ""], ["axb", "x"]]]`},
|
||||
{"multiple matches with captures-2", []string{`p[x] { x = regex.find_all_string_submatch_n("a(x*)b", "-axxb-ab-", -1) }`}, `[[["axxb", "xx"], ["ab", ""]]]`},
|
||||
{"multiple patterns, matches, and captures", []string{`p[x] { x = regex.find_all_string_submatch_n("[^aouiye]([aouiye])([^aouiye])?", "somestri", -1) }`}, `[[["som", "o", "m"], ["ri", "i", ""]]]`},
|
||||
{"multiple patterns, matches, and captures with specified number of matches", []string{`p[x] { x = regex.find_all_string_submatch_n("[^aouiye]([aouiye])([^aouiye])?", "somestri", 1) }`}, `[[["som", "o", "m"]]]`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
)
|
||||
|
||||
func TestSemVerCompare(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"a < b", []string{`p = x { x = semver.compare("1.0.0", "2.0.0") }`}, "-1"},
|
||||
{"a > b", []string{`p = x { x = semver.compare("2.0.0", "1.0.0") }`}, "1"},
|
||||
{"a == b", []string{`p = x { x = semver.compare("1.0.0", "1.0.0") }`}, "0"},
|
||||
{
|
||||
"invalid type a",
|
||||
[]string{`p = x { x = semver.compare(1, "1.0.0") }`},
|
||||
ast.Errors{ast.NewError(ast.TypeErr, nil, "semver.compare: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
"invalid type b",
|
||||
[]string{`p = x { x = semver.compare("1.0.0", false) }`},
|
||||
ast.Errors{ast.NewError(ast.TypeErr, nil, "semver.compare: invalid argument(s)")},
|
||||
},
|
||||
{
|
||||
"invalid version a",
|
||||
[]string{`p = x { x = semver.compare("1", "1.0.0") }`},
|
||||
&Error{Code: BuiltinErr, Message: `semver.compare("1", "1.0.0"): eval_builtin_error: semver.compare: operand 1: string "1" is not a valid SemVer`},
|
||||
},
|
||||
{
|
||||
"invalid version b",
|
||||
[]string{`p = x { x = semver.compare("1.0.0", "1") }`},
|
||||
&Error{Code: BuiltinErr, Message: `semver.compare("1.0.0", "1"): eval_builtin_error: semver.compare: operand 2: string "1" is not a valid SemVer`},
|
||||
},
|
||||
}
|
||||
|
||||
data := map[string]interface{}{}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemVerIsValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"valid", []string{`p = x { x = semver.is_valid("1.0.0") }`}, "true"},
|
||||
{"invalid version", []string{`p = x { x = semver.is_valid("1") }`}, "false"},
|
||||
{"invalid type", []string{`p = x { x = semver.is_valid(1) }`}, "false"},
|
||||
}
|
||||
|
||||
data := map[string]interface{}{}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2018 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestIntersection tests intersection of the given input sets
|
||||
func TestIntersection(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"intersection_0_sets", []string{`p = x { intersection(set(), x) }`}, "[]"},
|
||||
{"intersection_2_sets", []string{`p = x { intersection({set(), {1, 2}}, x) }`}, "[]"},
|
||||
{"intersection_2_sets", []string{`p = x { s1 = {1, 2, 3}; s2 = {2}; intersection({s1, s2}, x) }`}, "[2]"},
|
||||
{"intersection_3_sets", []string{`p = x { s1 = {1, 2, 3}; s2 = {2, 3, 4}; s3 = {4, 5, 6}; intersection({s1, s2, s3}, x) }`}, "[]"},
|
||||
{"intersection_4_sets", []string{`p = x { s1 = {"a", "b", "c", "d"}; s2 = {"b", "c", "d"}; s3 = {"c", "d"}; s4 = {"d"}; intersection({s1, s2, s3, s4}, x) }`}, "[\"d\"]"},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnion tests union of the given input sets
|
||||
func TestUnion(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"union_0_sets", []string{`p = x { union(set(), x) }`}, "[]"},
|
||||
{"union_2_sets", []string{`p = x { union({set(), {1, 2}}, x) }`}, "[1, 2]"},
|
||||
{"union_2_sets", []string{`p = x { s1 = {1, 2, 3}; s2 = {2}; union({s1, s2}, x) }`}, "[1, 2, 3]"},
|
||||
{"union_3_sets", []string{`p = x { s1 = {1, 2, 3}; s2 = {2, 3, 4}; s3 = {4, 5, 6}; union({s1, s2, s3}, x) }`}, "[1, 2, 3, 4, 5, 6]"},
|
||||
{"union_4_sets", []string{`p = x { s1 = {"a", "b", "c", "d"}; s2 = {"b", "c", "d"}; s3 = {"c", "d"}; s4 = {"d"}; union({s1, s2, s3, s4}, x) }`}, "[\"a\", \"b\", \"c\", \"d\"]"},
|
||||
}
|
||||
|
||||
data := loadSmallTestData()
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
// Copyright 2019 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package topdown
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBuiltinTrim(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"trims '!¡' from string", []string{`p[x] { x := trim("¡¡¡foo, bar!!!", "!¡") }`}, `["foo, bar"]`},
|
||||
{"trims nothing from string", []string{`p[x] { x := trim("¡¡¡foo, bar!!!", "i") }`}, `["¡¡¡foo, bar!!!"]`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinTrimLeft(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"trims leading '!¡' from string", []string{`p[x] { x := trim_left("¡¡¡foo, bar!!!", "!¡") }`}, `["foo, bar!!!"]`},
|
||||
{"trims nothing from string", []string{`p[x] { x := trim_left("!!!foo, bar¡¡¡", "¡") }`}, `["!!!foo, bar¡¡¡"]`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinTrimPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"trims prefix '!¡' from string", []string{`p[x] { x := trim_prefix("¡¡¡foo, bar!!!", "¡¡¡foo") }`}, `[", bar!!!"]`},
|
||||
{"trims nothing from string", []string{`p[x] { x := trim_prefix("¡¡¡foo, bar!!!", "¡¡¡bar") }`}, `["¡¡¡foo, bar!!!"]`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinTrimRight(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"trims trailing '!¡' from string", []string{`p[x] { x := trim_right("¡¡¡foo, bar!!!", "!¡") }`}, `["¡¡¡foo, bar"]`},
|
||||
{"trims nothing from string", []string{`p[x] { x := trim_right("!!!foo, bar¡¡¡", "!") }`}, `["!!!foo, bar¡¡¡"]`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinTrimSuffix(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"trims suffix '!¡' from string", []string{`p[x] { x := trim_suffix("¡¡¡foo, bar!!!", ", bar!!!") }`}, `["¡¡¡foo"]`},
|
||||
{"trims nothing from string", []string{`p[x] { x := trim_suffix("¡¡¡foo, bar!!!", ", foo!!!") }`}, `["¡¡¡foo, bar!!!"]`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinTrimSpace(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"trims all leading and trailing white space from string", []string{`p[x] { x := trim_space(" \t\n foo, bar \n\t\r\n") }`}, `["foo, bar"]`},
|
||||
{"trims nothing from string", []string{`p[x] { x := trim_space("foo, bar") }`}, `["foo, bar"]`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceN(t *testing.T) {
|
||||
tests := []struct {
|
||||
note string
|
||||
rules []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"replace multiple patterns", []string{`p[x] { x = strings.replace_n({"<": "<", ">": ">"}, "This is <b>HTML</b>!") }`}, `["This is <b>HTML</b>!"]`},
|
||||
{"find no patterns", []string{`p[x] { x = strings.replace_n({"old1": "new1", "old2": "new2"}, "Everything is new1, new2") }`}, `["Everything is new1, new2"]`},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
runTopDownTestCase(t, map[string]interface{}{}, tc.note, tc.rules, tc.expected)
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,15 @@ cases:
|
||||
p = [x, y, z] {
|
||||
io.jwt.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiAiMCIsImlzcyI6ICJub3Qgb3BhIiwgImlzcyI6ICJhbHNvIG5vdCBvcGEiLCAiaXNzIjogIm9wYSJ9.XmVoLoHI3pxMtMO_WRONMSJzGUDP9pDjy8Jp0_tdRXY", [x, y, z])
|
||||
}
|
||||
# The test below checks that payloads with duplicate keys
|
||||
# in their encoding produce a token object that binds the key
|
||||
# to the last occurring value, as per RFC 7519 Section 4.
|
||||
# It tests a payload encoding that has 3 duplicates of the
|
||||
# "iss" key, with the values "not opa", "also not opa" and
|
||||
# "opa", in that order.
|
||||
# Go's json.Unmarshal exhibits this behavior, but it is not
|
||||
# documented, so this test is meant to catch that behavior
|
||||
# if it changes.
|
||||
note: jwtbuiltins/duplicate-keys
|
||||
query: data.generated.p = x
|
||||
want_result:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user