Files
kustomize/api/k8sdeps/kunstruct/unstructadapter_test.go
2019-10-20 16:49:53 -07:00

832 lines
21 KiB
Go

// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kunstruct
import (
"reflect"
"testing"
)
var kunstructured = NewKunstructuredFactoryImpl().FromMap(map[string]interface{}{
"Kind": "Service",
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "application-name",
},
"name": "service-name",
},
"spec": map[string]interface{}{
"ports": map[string]interface{}{
"port": int64(80),
},
},
"this": map[string]interface{}{
"is": map[string]interface{}{
"aNumber": int64(1000),
"aFloat": float64(1.001),
"aNilValue": nil,
"aBool": true,
"anEmptyMap": map[string]interface{}{},
"anEmptySlice": []interface{}{},
"unrecognizable": testing.InternalExample{
Name: "fooBar",
},
},
},
"that": []interface{}{
"idx0",
"idx1",
"idx2",
"idx3",
},
"those": []interface{}{
map[string]interface{}{
"field1": "idx0foo",
"field2": "idx0bar",
},
map[string]interface{}{
"field1": "idx1foo",
"field2": "idx1bar",
},
map[string]interface{}{
"field1": "idx2foo",
"field2": "idx2bar",
},
},
"these": []interface{}{
map[string]interface{}{
"field1": []interface{}{"idx010", "idx011"},
"field2": []interface{}{"idx020", "idx021"},
},
map[string]interface{}{
"field1": []interface{}{"idx110", "idx111"},
"field2": []interface{}{"idx120", "idx121"},
},
map[string]interface{}{
"field1": []interface{}{"idx210", "idx211"},
"field2": []interface{}{"idx220", "idx221"},
},
},
"complextree": []interface{}{
map[string]interface{}{
"field1": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1010",
"intsubfield": int64(1010),
"floatsubfield": float64(1.010),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1011",
"intsubfield": int64(1011),
"floatsubfield": float64(1.011),
"boolfield": false,
},
},
"field2": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1020",
"intsubfield": int64(1020),
"floatsubfield": float64(1.020),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1021",
"intsubfield": int64(1021),
"floatsubfield": float64(1.021),
"boolfield": false,
},
},
},
map[string]interface{}{
"field1": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1110",
"intsubfield": int64(1110),
"floatsubfield": float64(1.110),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1111",
"intsubfield": int64(1111),
"floatsubfield": float64(1.111),
"boolfield": false,
},
},
"field2": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1120",
"intsubfield": int64(1120),
"floatsubfield": float64(1.1120),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1121",
"intsubfield": int64(1121),
"floatsubfield": float64(1.1121),
"boolfield": false,
},
},
},
},
})
func TestGetFieldValue(t *testing.T) {
tests := []struct {
name string
pathToField string
expectedValue interface{}
errorExpected bool
errorMsg string
}{
{
name: "oneField",
pathToField: "Kind",
expectedValue: "Service",
errorExpected: false,
},
{
name: "twoFields",
pathToField: "metadata.name",
expectedValue: "service-name",
errorExpected: false,
},
{
name: "threeFields",
pathToField: "spec.ports.port",
expectedValue: int64(80),
errorExpected: false,
},
{
name: "empty",
pathToField: "",
errorExpected: true,
errorMsg: "no field named ''",
},
{
name: "emptyDotEmpty",
pathToField: ".",
errorExpected: true,
errorMsg: "no field named '.'",
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "deeperMissingField",
pathToField: "this.is.aDeep.field.that.does.not.exist",
errorExpected: true,
errorMsg: "no field named 'this.is.aDeep.field.that.does.not.exist'",
},
{
name: "emptyMap",
pathToField: "this.is.anEmptyMap",
errorExpected: false,
expectedValue: map[string]interface{}{},
},
{
name: "emptySlice",
pathToField: "this.is.anEmptySlice",
errorExpected: false,
expectedValue: []interface{}{},
},
{
name: "numberAsValue",
pathToField: "this.is.aNumber",
errorExpected: false,
expectedValue: int64(1000),
},
{
name: "floatAsValue",
pathToField: "this.is.aFloat",
errorExpected: false,
expectedValue: float64(1.001),
},
{
name: "boolAsValue",
pathToField: "this.is.aBool",
errorExpected: false,
expectedValue: true,
},
{
name: "nilAsValue",
pathToField: "this.is.aNilValue",
errorExpected: false,
expectedValue: nil,
},
{
name: "unrecognizable",
pathToField: "this.is.unrecognizable.Name",
errorExpected: true,
errorMsg: ".this.is.unrecognizable.Name accessor error: {fooBar <nil> false} is of the type testing.InternalExample, expected map[string]interface{}",
},
{
name: "validStringIndex",
pathToField: "that[2]",
expectedValue: "idx2",
errorExpected: false,
},
{
name: "outOfBoundIndex",
pathToField: "that[99]",
errorMsg: "no field named 'that[99]'",
errorExpected: true,
},
{
name: "accessorError",
pathToField: "that[downwardapi]",
errorMsg: ".that.downwardapi accessor error: [idx0 idx1 idx2 idx3] is of the type []interface {}, expected map[string]interface{}",
errorExpected: true,
},
{
name: "unknownSlice",
pathToField: "unknown[0]",
errorMsg: "no field named 'unknown[0]'",
errorExpected: true,
},
{
name: "sliceInSlice",
pathToField: "that[2][0]",
errorExpected: true,
errorMsg: "no field named 'that[2][0]'",
},
{
name: "validStructIndex",
pathToField: "those[1]",
errorExpected: false,
expectedValue: map[string]interface{}{"field1": "idx1foo", "field2": "idx1bar"},
},
{
name: "validStructSubField",
pathToField: "those[1].field2",
errorExpected: false,
expectedValue: "idx1bar",
},
{
name: "validStructSubFieldIndex",
pathToField: "these[1].field2[1]",
errorExpected: false,
expectedValue: "idx121",
},
{
name: "validStructSubFieldOutOfBoundIndex",
pathToField: "these[1].field2[99]",
errorExpected: true,
errorMsg: "no field named 'these[1].field2[99]'",
},
{
name: "validStructSubFieldIndexSubfield",
pathToField: "complextree[1].field2[1].stringsubfield",
errorExpected: false,
expectedValue: "idx1121",
},
{
name: "validStructSubFieldIndexInvalidName",
pathToField: "complextree[1].field2[1].invalidsubfield",
errorExpected: true,
errorMsg: "no field named 'complextree[1].field2[1].invalidsubfield'",
},
{
name: "validDownwardAPILabels",
pathToField: `metadata.labels["app"]`,
errorExpected: false,
expectedValue: "application-name",
},
{
name: "validDownwardAPISpecs",
pathToField: `spec.ports['port']`,
errorExpected: false,
expectedValue: int64(80),
},
{
name: "validDownwardAPIThis",
pathToField: `this.is[aFloat]`,
errorExpected: false,
expectedValue: float64(1.001),
},
{
name: "downwardAPIInvalidLabel",
pathToField: `metadata.labels["theisnotanint"]`,
errorExpected: true,
errorMsg: `no field named 'metadata.labels["theisnotanint"]'`,
},
{
name: "downwardAPIInvalidLabel2",
pathToField: `invalidfield.labels["app"]`,
errorExpected: true,
errorMsg: `no field named 'invalidfield.labels["app"]'`,
},
{
name: "invalidIndexInIndex",
pathToField: "complextree[1[0]]",
errorExpected: true,
errorMsg: "no field named 'complextree[1[0]]'",
},
{
name: "invalidClosingBrackets",
pathToField: "complextree[1]]",
errorExpected: true,
errorMsg: "no field named 'complextree[1]]'",
},
{
name: "validFieldsWithQuotes",
pathToField: "'complextree'[1].field2[1].'stringsubfield'",
errorExpected: false,
expectedValue: "idx1121",
},
}
for _, test := range tests {
s, err := kunstructured.GetFieldValue(test.pathToField)
if test.errorExpected {
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
continue
}
if err != nil {
unExpectedError(t, test.name, test.pathToField, err)
}
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
}
}
func TestGetString(t *testing.T) {
tests := []struct {
name string
pathToField string
expectedValue string
errorExpected bool
errorMsg string
}{
{
name: "oneField",
pathToField: "Kind",
expectedValue: "Service",
errorExpected: false,
},
{
name: "twoFields",
pathToField: "metadata.name",
expectedValue: "service-name",
errorExpected: false,
},
{
name: "emptyMap",
pathToField: "this.is.anEmptyMap",
errorExpected: true,
errorMsg: ".this.is.anEmptyMap accessor error: map[] is of the type map[string]interface {}, expected string",
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "emptySlice",
pathToField: "this.is.anEmptySlice",
errorExpected: true,
errorMsg: ".this.is.anEmptySlice accessor error: [] is of the type []interface {}, expected string",
},
{
name: "numberAsValue",
pathToField: "this.is.aNumber",
errorExpected: true,
errorMsg: ".this.is.aNumber accessor error: 1000 is of the type int64, expected string",
},
{
name: "nilAsValue",
pathToField: "this.is.aNilValue",
errorExpected: true,
errorMsg: ".this.is.aNilValue accessor error: <nil> is of the type <nil>, expected string",
},
{
name: "validStringIndex",
pathToField: "that[2]",
expectedValue: "idx2",
errorExpected: false,
},
{
name: "validStructIndex",
pathToField: "those[1]",
errorExpected: true,
errorMsg: ".[1] accessor error: map[field1:idx1foo field2:idx1bar] is of the type map[string]interface {}, expected string",
},
{
name: "validStructSubField",
pathToField: "those[1].field2",
errorExpected: false,
expectedValue: "idx1bar",
},
{
name: "validStructSubFieldIndex",
pathToField: "these[1].field2[1]",
errorExpected: false,
expectedValue: "idx121",
},
{
name: "validStructSubFieldIndexSubfield",
pathToField: "complextree[1].field2[1].stringsubfield",
errorExpected: false,
expectedValue: "idx1121",
},
{
name: "invalidIndexInMap",
pathToField: "this.is[1]",
errorExpected: true,
errorMsg: "no field named 'this.is[1]'",
},
{
name: "anotherInvalidIndexInMap",
pathToField: "this.is[1].aString",
errorExpected: true,
errorMsg: "no field named 'this.is[1].aString'",
},
{
name: "validDownwardAPIField",
pathToField: `metadata.labels["app"]`,
errorExpected: false,
expectedValue: "application-name",
},
}
for _, test := range tests {
s, err := kunstructured.GetString(test.pathToField)
if test.errorExpected {
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
continue
}
if err != nil {
unExpectedError(t, test.name, test.pathToField, err)
}
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
}
}
func TestGetInt64(t *testing.T) {
tests := []struct {
name string
pathToField string
expectedValue int64
errorExpected bool
errorMsg string
}{
{
name: "numberAsValue",
pathToField: "this.is.aNumber",
errorExpected: false,
expectedValue: int64(1000),
},
{
name: "validStructSubFieldIndexSubfield",
pathToField: "complextree[1].field2[1].intsubfield",
errorExpected: false,
expectedValue: int64(1121),
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "validStructSubFieldOutOfBoundIndex",
pathToField: "these[1].field2[99]",
errorExpected: true,
errorMsg: "no field named 'these[1].field2[99]'",
},
{
name: "validDownwardAPISpecs",
pathToField: `spec.ports['port']`,
errorExpected: false,
expectedValue: int64(80),
},
}
for _, test := range tests {
s, err := kunstructured.GetInt64(test.pathToField)
if test.errorExpected {
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
continue
}
if err != nil {
unExpectedError(t, test.name, test.pathToField, err)
}
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
}
}
func TestGetFloat64(t *testing.T) {
tests := []struct {
name string
pathToField string
expectedValue float64
errorExpected bool
errorMsg string
}{
{
name: "floatAsValue",
pathToField: "this.is.aFloat",
errorExpected: false,
expectedValue: float64(1.001),
},
{
name: "validStructSubFieldIndexSubfield",
pathToField: "complextree[1].field2[1].floatsubfield",
errorExpected: false,
expectedValue: float64(1.1121),
},
{
name: "validDownwardAPIThis",
pathToField: `this.is[aFloat]`,
errorExpected: false,
expectedValue: float64(1.001),
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "validStructSubFieldOutOfBoundIndex",
pathToField: "these[1].field2[99]",
errorExpected: true,
errorMsg: "index 99 is out of bounds",
},
}
for _, test := range tests {
s, err := kunstructured.GetFloat64(test.pathToField)
if test.errorExpected {
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
continue
}
if err != nil {
unExpectedError(t, test.name, test.pathToField, err)
}
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
}
}
func TestGetBool(t *testing.T) {
tests := []struct {
name string
pathToField string
expectedValue bool
errorExpected bool
errorMsg string
}{
{
name: "boolAsValue",
pathToField: "this.is.aBool",
errorExpected: false,
expectedValue: true,
},
{
name: "validStructSubFieldIndexSubfield",
pathToField: "complextree[1].field2[1].boolfield",
errorExpected: false,
expectedValue: false,
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "validStructSubFieldOutOfBoundIndex",
pathToField: "these[1].field2[99]",
errorExpected: true,
errorMsg: "no field named 'these[1].field2[99]'",
},
}
for _, test := range tests {
s, err := kunstructured.GetBool(test.pathToField)
if test.errorExpected {
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
continue
}
if err != nil {
unExpectedError(t, test.name, test.pathToField, err)
}
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
}
}
func TestGetStringMap(t *testing.T) {
tests := []struct {
name string
pathToField string
errorExpected bool
errorMsg string
}{
{
name: "validStringMap",
pathToField: "those[2]",
errorExpected: false,
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "validStructSubFieldOutOfBoundIndex",
pathToField: "these[1].field2[99]",
errorExpected: true,
errorMsg: "no field named 'these[1].field2[99]'",
},
}
for _, test := range tests {
_, err := kunstructured.GetStringMap(test.pathToField)
if test.errorExpected {
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
continue
}
if err != nil {
unExpectedError(t, test.name, test.pathToField, err)
}
}
}
func TestGetMap(t *testing.T) {
tests := []struct {
name string
pathToField string
errorExpected bool
errorMsg string
}{
{
name: "validMap",
pathToField: "those[2]",
errorExpected: false,
},
{
name: "validStructSubFieldIndexSubfield",
pathToField: "complextree[1].field2[1]",
errorExpected: false,
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "validStructSubFieldOutOfBoundIndex",
pathToField: "these[1].field2[99]",
errorExpected: true,
errorMsg: "no field named 'these[1].field2[99]'",
},
}
for _, test := range tests {
_, err := kunstructured.GetMap(test.pathToField)
if test.errorExpected {
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
continue
}
if err != nil {
unExpectedError(t, test.name, test.pathToField, err)
}
}
}
func TestGetStringSlice(t *testing.T) {
tests := []struct {
name string
pathToField string
errorExpected bool
errorMsg string
}{
{
name: "validStringSlice",
pathToField: "that",
errorExpected: false,
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "validStructSubFieldOutOfBoundIndex",
pathToField: "these[1].field2[99]",
errorExpected: true,
errorMsg: "no field named 'these[1].field2[99]'",
},
}
for _, test := range tests {
_, err := kunstructured.GetStringSlice(test.pathToField)
if test.errorExpected {
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
continue
}
if err != nil {
unExpectedError(t, test.name, test.pathToField, err)
}
}
}
func TestGetSlice(t *testing.T) {
tests := []struct {
name string
pathToField string
errorExpected bool
errorMsg string
}{
{
name: "validSlice1",
pathToField: "that",
errorExpected: false,
},
{
name: "validSlice2",
pathToField: "those",
errorExpected: false,
},
{
name: "validSlice3",
pathToField: "these",
errorExpected: false,
},
{
name: "validSlice4",
pathToField: "complextree",
errorExpected: false,
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "validStructSubFieldOutOfBoundIndex",
pathToField: "these[1].field2[99]",
errorExpected: true,
errorMsg: "no field named 'these[1].field2[99]'",
},
}
for _, test := range tests {
_, err := kunstructured.GetSlice(test.pathToField)
if test.errorExpected {
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
continue
}
if err != nil {
unExpectedError(t, test.name, test.pathToField, err)
}
}
}
// unExpectedError function handles unexpected error
func unExpectedError(t *testing.T, name string, pathToField string, err error) {
t.Fatalf("%q; path %q - unexpected error %v", name, pathToField, err)
}
// compareExpectedError compares the expectedError and the actualError return by GetFieldValue
func compareExpectedError(t *testing.T, name string, pathToField string, err error, errorMsg string) {
if err == nil {
t.Fatalf("%q; path %q - should return error, but no error returned",
name, pathToField)
}
if errorMsg != err.Error() {
t.Fatalf("%q; path %q - expected error: \"%s\", got error: \"%v\"",
name, pathToField, errorMsg, err.Error())
}
}
// compareValues compares the expectedValue and actualValue returned by GetFieldValue
func compareValues(t *testing.T, name string, pathToField string, expectedValue interface{}, actualValue interface{}) {
t.Helper()
switch typedV := expectedValue.(type) {
case nil, string, bool, float64, int, int64:
if expectedValue != actualValue {
t.Fatalf("%q; Got: %v Expected: %v", name, actualValue, expectedValue)
}
case map[string]interface{}:
if !reflect.DeepEqual(expectedValue, actualValue) {
t.Fatalf("%q; Got: %v Expected: %v", name, actualValue, expectedValue)
}
case []interface{}:
if !reflect.DeepEqual(expectedValue, actualValue) {
t.Fatalf("%q; Got: %v Expected: %v", name, actualValue, expectedValue)
}
default:
t.Logf("%T value at `%s`", typedV, pathToField)
}
}