mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-24 01:04:49 -06:00
d310c4335c
Fixes: #5972 Signed-off-by: Johan Fylling <johan.dev@fylling.se>
495 lines
12 KiB
Go
495 lines
12 KiB
Go
// Copyright 2017 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 types
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/open-policy-agent/opa/util"
|
|
)
|
|
|
|
var dynamicPropertyAnyAny = NewDynamicProperty(A, A)
|
|
|
|
func TestAnySorted(t *testing.T) {
|
|
a := NewAny(S, N)
|
|
if Compare(a[0], N) != 0 {
|
|
t.Fatal("expected any type to be sorted")
|
|
}
|
|
}
|
|
|
|
func TestAnyMerge(t *testing.T) {
|
|
x := NewAny(S, B)
|
|
|
|
if Compare(x.Merge(N)[1], N) != 0 {
|
|
t.Fatal("expected number to be inserted into middle")
|
|
}
|
|
|
|
if Compare(x.Merge(NewNull())[0], NewNull()) != 0 {
|
|
t.Fatal("expected null to be inserted at front")
|
|
}
|
|
|
|
if Compare(x.Merge(NewArray(nil, A))[2], NewArray(nil, A)) != 0 {
|
|
t.Fatal("expected array to be inserted at back")
|
|
}
|
|
}
|
|
|
|
func TestAnyUnion(t *testing.T) {
|
|
x := NewAny(NewNull(), N)
|
|
y := NewAny(S, B)
|
|
z := x.Union(y)
|
|
exp := []Type{NewNull(), B, N, S}
|
|
if len(z) != len(exp) {
|
|
t.Fatalf("expected %v elements in result of union", len(exp))
|
|
}
|
|
for i := range z {
|
|
if Compare(z[i], exp[i]) != 0 {
|
|
t.Fatal("expected", exp[i], "but got", z[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStrings(t *testing.T) {
|
|
|
|
tpe := NewObject([]*StaticProperty{
|
|
{"foo", NewNull()},
|
|
{"bar", NewBoolean()},
|
|
{"baz", NewNumber()},
|
|
{"qux", NewString()},
|
|
{"corge", NewArray(
|
|
[]Type{
|
|
NewAny(),
|
|
NewAny(NewNull(), NewString()),
|
|
NewSet(NewString()),
|
|
}, NewString(),
|
|
)},
|
|
{"nil", nil},
|
|
}, NewDynamicProperty(S, N))
|
|
|
|
expected := `object<bar: boolean, baz: number, corge: array<any, any<null, string>, set[string]>[string], foo: null, nil: ???, qux: string>[string: number]`
|
|
|
|
if tpe.String() != expected {
|
|
t.Fatalf("Expected %v but got: %v", expected, tpe)
|
|
}
|
|
|
|
ftpe := NewFunction([]Type{S, S}, N)
|
|
expected = "(string, string) => number"
|
|
|
|
if ftpe.String() != expected {
|
|
t.Fatalf("Expected %v but got: %v", expected, ftpe)
|
|
}
|
|
|
|
ftpe = NewVariadicFunction([]Type{N}, S, nil)
|
|
expected = "(number, string...) => ???"
|
|
|
|
if ftpe.String() != expected {
|
|
t.Fatal("expected", expected, "but got:", ftpe)
|
|
}
|
|
}
|
|
|
|
func TestCompare(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
a Type
|
|
b Type
|
|
cmp int
|
|
}{
|
|
{NewNull(), NewNull(), 0},
|
|
{NewNull(), NewBoolean(), -1},
|
|
{NewBoolean(), NewNull(), 1},
|
|
{NewBoolean(), NewBoolean(), 0},
|
|
{NewBoolean(), NewNumber(), -1},
|
|
{NewNumber(), NewNumber(), 0},
|
|
{NewNumber(), NewString(), -1},
|
|
{NewString(), NewString(), 0},
|
|
{NewString(), NewArray(NewAny(), nil), -1},
|
|
{NewArray(NewAny(), nil), NewArray(NewAny(), NewAny()), -1},
|
|
{NewArray(NewAny(), NewAny()), NewArray(NewAny(), NewAny()), 0},
|
|
{NewArray(NewAny(), NewAny()), NewArray(NewAny(), NewString()), 1},
|
|
{NewArray(NewAny(), NewAny()), NewArray(NewAny(), nil), 1},
|
|
{NewArray([]Type{NewString()}, nil), NewArray([]Type{NewNumber()}, nil), 1},
|
|
{NewObject(nil, nil), NewObject(nil, dynamicPropertyAnyAny), -1},
|
|
{NewObject(nil, dynamicPropertyAnyAny), NewObject(nil, nil), 1},
|
|
{NewObject(nil, dynamicPropertyAnyAny), NewObject(nil, dynamicPropertyAnyAny), 0},
|
|
{NewObject(nil, NewDynamicProperty(S, NewAny(NewString(), NewNull()))), NewObject(nil, dynamicPropertyAnyAny), -1},
|
|
{NewSet(NewNull()), NewSet(NewAny()), -1},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}, {"bar", NewNumber()}},
|
|
nil),
|
|
1,
|
|
},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}, {"bar", NewNumber()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
-1,
|
|
},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewNull()}},
|
|
nil),
|
|
1,
|
|
},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}, {"foo-2", NewNumber()}},
|
|
nil),
|
|
-1,
|
|
},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}, {"foo-2", NewNumber()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
1,
|
|
},
|
|
{NewFunction(nil, nil), NewAny(), 1},
|
|
{NewFunction([]Type{B}, N), NewFunction([]Type{S}, N), -1},
|
|
{NewFunction(nil, S), NewFunction(nil, N), 1},
|
|
{NewFunction(nil, S), NewFunction([]Type{N}, S), -1},
|
|
{NewFunction([]Type{S}, N), NewFunction(nil, S), 1},
|
|
{NewFunction([]Type{S}, N), NewFunction([]Type{S}, N), 0},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
result := Compare(tc.a, tc.b)
|
|
if result != tc.cmp {
|
|
t.Fatalf("For Compare(%v, %v) expected %v but got: %v", tc.a, tc.b, tc.cmp, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContains(t *testing.T) {
|
|
tests := []struct {
|
|
a Type
|
|
b Type
|
|
expected bool
|
|
}{
|
|
{S, S, true},
|
|
{A, S, true},
|
|
{NewAny(N, B), S, false},
|
|
{N, S, false},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
if Contains(tc.a, tc.b) != tc.expected {
|
|
t.Fatalf("Expected Contains(%v, %v) == %v", tc.a, tc.b, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOr(t *testing.T) {
|
|
tests := []struct {
|
|
a Type
|
|
b Type
|
|
expected Type
|
|
}{
|
|
{nil, NewString(), NewString()},
|
|
{NewString(), nil, NewString()},
|
|
{NewNull(), NewNull(), NewNull()},
|
|
{NewString(), NewNumber(), NewAny(NewNumber(), NewString())},
|
|
{NewAny(), NewNull(), NewAny()},
|
|
{NewNull(), NewAny(), NewAny()},
|
|
{NewNull(), NewAny(NewString(), NewNumber()), NewAny(NewString(), NewNumber(), NewNull())},
|
|
{NewAny(), NewAny(), NewAny()},
|
|
{NewAny(NewNull(), NewNumber()), NewAny(), NewAny()},
|
|
{NewAny(NewNumber(), NewString()), NewAny(NewNull(), NewBoolean()), NewAny(NewNull(), NewBoolean(), NewString(), NewNumber())},
|
|
{NewAny(NewNull(), NewNumber()), NewNull(), NewAny(NewNull(), NewNumber())},
|
|
{NewFunction([]Type{S}, B), NewFunction([]Type{N}, B), NewFunction([]Type{NewAny(S, N)}, B)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
c := Or(tc.a, tc.b)
|
|
if Compare(c, tc.expected) != 0 {
|
|
t.Fatalf("Expected Or(%v, %v) to be %v but got: %v", tc.a, tc.b, tc.expected, c)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestSelect(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
note string
|
|
a Type
|
|
k interface{}
|
|
expected Type
|
|
}{
|
|
{"static", NewArray([]Type{S}, nil), json.Number("0"), S},
|
|
{"dynamic", NewArray(nil, S), json.Number("100"), S},
|
|
{"out of range", NewArray([]Type{S, N, B}, nil), json.Number("4"), nil},
|
|
{"out of range negative", NewArray([]Type{S, N, B}, nil), json.Number("-4"), nil},
|
|
{"negative", NewArray([]Type{S, N, B}, nil), json.Number("-2"), nil},
|
|
{"non int", NewArray([]Type{S, N, B}, nil), json.Number("1.5"), nil},
|
|
{"non int-2", NewArray([]Type{S, N, B}, nil), 1, nil},
|
|
{"static", NewObject([]*StaticProperty{NewStaticProperty("hello", S)}, nil), "hello", S},
|
|
{"dynamic", NewObject([]*StaticProperty{NewStaticProperty("hello", S)}, NewDynamicProperty(S, N)), "goodbye", N},
|
|
{"dynamic, different key types", NewObject([]*StaticProperty{NewStaticProperty("hello", S)}, NewDynamicProperty(N, N)), json.Number("2"), N},
|
|
{"dynamic, different key types", NewObject([]*StaticProperty{NewStaticProperty("hello", S)}, NewDynamicProperty(N, N)), "hello", S},
|
|
{"non exist", NewObject([]*StaticProperty{NewStaticProperty("hello", S)}, nil), "deadbeef", nil},
|
|
{"non string", NewObject([]*StaticProperty{NewStaticProperty(json.Number("1"), S), NewStaticProperty(json.Number("2"), N)}, nil), json.Number("2"), N},
|
|
{"member of", NewSet(N), json.Number("2"), N},
|
|
{"non exist", NewSet(N), "foo", nil},
|
|
{"superset", A, A, A},
|
|
{"union", NewAny(NewArray(nil, N), NewArray(nil, S)), json.Number("10"), NewAny(N, S)},
|
|
{"union set", NewSet(NewAny(S, N)), json.Number("1"), N},
|
|
{"scalar", N, "1", nil},
|
|
{"scalar-2", S, "1", nil},
|
|
{"scalar-3", B, "1", nil},
|
|
{"scalar-4", NewNull(), "1", nil},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
result := Select(tc.a, tc.k)
|
|
if Compare(result, tc.expected) != 0 {
|
|
t.Fatalf("Expected Select(%v, %v) to be %v but got: %v", tc.a, tc.k, tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKeys(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
tpe Type
|
|
expected Type
|
|
}{
|
|
{"array", NewArray(nil, nil), N},
|
|
{"object", NewObject(nil, NewDynamicProperty(S, S)), S},
|
|
{"set", NewSet(N), N},
|
|
{"any", NewAny(NewArray(nil, nil), NewSet(S)), NewAny(S, N)},
|
|
{"any", NewAny(NewArray(nil, nil), S), N},
|
|
{"superset", A, A},
|
|
{"scalar-1", N, nil},
|
|
{"scalar-2", S, nil},
|
|
{"scalar-3", B, nil},
|
|
{"scalar-4", NewNull(), nil},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
result := Keys(tc.tpe)
|
|
if Compare(result, tc.expected) != 0 {
|
|
t.Fatalf("Expected Keys(%v) to be %v but got: %v", tc.tpe, tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValues(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
tpe Type
|
|
expected Type
|
|
}{
|
|
{"array", NewArray([]Type{N}, nil), N},
|
|
{"array dynamic", NewArray([]Type{N, S}, B), NewAny(S, N, B)},
|
|
{"object", NewObject([]*StaticProperty{NewStaticProperty("a", S), NewStaticProperty("b", N)}, nil), NewAny(S, N)},
|
|
{"object dynamic", NewObject([]*StaticProperty{NewStaticProperty("a", S), NewStaticProperty("b", N)}, NewDynamicProperty(A, B)), NewAny(S, N, B)},
|
|
{"set", NewSet(N), N},
|
|
{"superset", A, A},
|
|
{"any", NewAny(NewArray(nil, N), S), N},
|
|
{"scalar-1", N, nil},
|
|
{"scalar-2", S, nil},
|
|
{"scalar-3", B, nil},
|
|
{"scalar-4", NewNull(), nil},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
result := Values(tc.tpe)
|
|
if Compare(result, tc.expected) != 0 {
|
|
t.Fatalf("Expected Keys(%v) to be %v but got: %v", tc.tpe, tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTypeOf(t *testing.T) {
|
|
tpe := TypeOf(map[interface{}]interface{}{
|
|
"foo": []interface{}{
|
|
json.Number("1"), true, nil, "hello",
|
|
},
|
|
})
|
|
|
|
exp := NewObject([]*StaticProperty{
|
|
NewStaticProperty("foo", NewArray(
|
|
[]Type{
|
|
N, B, NewNull(), S,
|
|
}, nil,
|
|
)),
|
|
}, nil)
|
|
|
|
if Compare(exp, tpe) != 0 {
|
|
t.Fatalf("Expected %v but got: %v", exp, tpe)
|
|
}
|
|
}
|
|
|
|
func TestTypeOfMapOfString(t *testing.T) {
|
|
tpe := TypeOf(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": "qux",
|
|
})
|
|
|
|
exp := NewObject([]*StaticProperty{
|
|
NewStaticProperty("foo", S),
|
|
NewStaticProperty("baz", S),
|
|
}, nil)
|
|
|
|
if Compare(exp, tpe) != 0 {
|
|
t.Fatalf("Expected %v but got: %v", exp, tpe)
|
|
}
|
|
}
|
|
|
|
func TestNil(t *testing.T) {
|
|
|
|
tpe := NewObject([]*StaticProperty{
|
|
NewStaticProperty("foo", NewArray(
|
|
[]Type{
|
|
N, B, NewNull(), S, NewSet(nil),
|
|
}, nil,
|
|
)),
|
|
}, nil)
|
|
|
|
if !Nil(tpe) {
|
|
t.Fatalf("Expected %v type to be unknown", tpe)
|
|
}
|
|
|
|
}
|
|
|
|
func TestMarshalJSON(t *testing.T) {
|
|
|
|
tpe := NewAny(
|
|
NewObject(
|
|
[]*StaticProperty{
|
|
{"foo", N},
|
|
{"func", NewFunction([]Type{S}, N)},
|
|
},
|
|
NewDynamicProperty(S, NewArray([]Type{NewSet(B)}, N)),
|
|
),
|
|
)
|
|
|
|
bs, err := json.Marshal(tpe)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
expected := util.MustUnmarshalJSON([]byte(`
|
|
{
|
|
"type": "any",
|
|
"of": [
|
|
{
|
|
"type": "object",
|
|
"static": [
|
|
{
|
|
"key": "foo",
|
|
"value": {"type": "number"}
|
|
},
|
|
{
|
|
"key": "func",
|
|
"value": {
|
|
"type": "function",
|
|
"args": [
|
|
{
|
|
"type": "string"
|
|
}
|
|
],
|
|
"result": {
|
|
"type": "number"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"dynamic": {
|
|
"key": {"type": "string"},
|
|
"value": {
|
|
"type": "array",
|
|
"static": [
|
|
{
|
|
"type": "set",
|
|
"of": {"type": "boolean"}
|
|
}
|
|
],
|
|
"dynamic": {"type": "number"}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`))
|
|
|
|
result := util.MustUnmarshalJSON(bs)
|
|
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Fatalf("Expected:\n\n%s\n\nGot:\n\n%s", util.MustMarshalJSON(expected), util.MustMarshalJSON(result))
|
|
}
|
|
|
|
}
|
|
|
|
func TestRoundtripJSON(t *testing.T) {
|
|
tpe := NewFunction([]Type{
|
|
NewArray([]Type{S, NewNull()}, N),
|
|
NewObject(
|
|
[]*StaticProperty{
|
|
NewStaticProperty("foo", B),
|
|
},
|
|
NewDynamicProperty(S, NewSet(N))),
|
|
NewObject(
|
|
[]*StaticProperty{
|
|
NewStaticProperty("bar", N),
|
|
},
|
|
nil,
|
|
),
|
|
}, NewAny(S, N))
|
|
|
|
bs, err := json.Marshal(tpe)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
result, err := Unmarshal(bs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if Compare(result, tpe) != 0 {
|
|
t.Fatalf("Got: %v\n\nExpected: %v", result, tpe)
|
|
}
|
|
}
|
|
|
|
func TestRoundtripJSONVariadicFunction(t *testing.T) {
|
|
tpe := NewVariadicFunction([]Type{S}, N, nil)
|
|
bs, err := json.Marshal(tpe)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
result, err := Unmarshal(bs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if Compare(result, tpe) != 0 {
|
|
t.Fatalf("Got: %v\n\nExpected: %v", result, tpe)
|
|
}
|
|
}
|