Files
kustomize/kyaml/yaml/rnode_test.go
Karl Isenberg 43868688d5 Use require for Error and NoError
Assert keeps going after failure, but require immediately fails
the tests, making it easier to find the output related to the test
failure, rather than having to comb through a bunch of subsequent
assertion failures. For equality tests, we may or may not want to
continue, but for error checks we almost always want to immediately
fail the test. Exceptions can be changed as-needed.
2024-03-20 13:19:18 -07:00

2359 lines
49 KiB
Go

// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
yaml "sigs.k8s.io/yaml/goyaml.v3"
)
func TestRNodeHasNilEntryInList(t *testing.T) {
testConfigMap := map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
}
type resultExpected struct {
hasNil bool
path string
}
testCases := map[string]struct {
theMap map[string]interface{}
rsExp resultExpected
}{
"actuallyNil": {
theMap: nil,
rsExp: resultExpected{},
},
"empty": {
theMap: map[string]interface{}{},
rsExp: resultExpected{},
},
"list": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "List",
"items": []interface{}{
testConfigMap,
testConfigMap,
},
},
rsExp: resultExpected{},
},
"listWithNil": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "List",
"items": []interface{}{
testConfigMap,
nil,
},
},
rsExp: resultExpected{
hasNil: false, // TODO: This should be true.
path: "this/should/be/non-empty",
},
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
rn, err := FromMap(tc.theMap)
if !assert.NoError(t, err) {
t.FailNow()
}
hasNil, path := rn.HasNilEntryInList()
if tc.rsExp.hasNil {
if !assert.True(t, hasNil) {
t.FailNow()
}
if !assert.Equal(t, tc.rsExp.path, path) {
t.FailNow()
}
} else {
if !assert.False(t, hasNil) {
t.FailNow()
}
if !assert.Empty(t, path) {
t.FailNow()
}
}
})
}
}
func TestRNodeNewStringRNodeText(t *testing.T) {
rn := NewStringRNode("cat")
if !assert.Equal(t, `cat
`,
rn.MustString()) {
t.FailNow()
}
}
func TestRNodeNewStringRNodeBinary(t *testing.T) {
rn := NewStringRNode(string([]byte{
0xff, // non-utf8
0x68, // h
0x65, // e
0x6c, // l
0x6c, // l
0x6f, // o
}))
if !assert.Equal(t, `!!binary /2hlbGxv
`,
rn.MustString()) {
t.FailNow()
}
}
func TestRNodeGetDataMap(t *testing.T) {
emptyMap := map[string]string{}
testCases := map[string]struct {
theMap map[string]interface{}
expected map[string]string
}{
"actuallyNil": {
theMap: nil,
expected: emptyMap,
},
"empty": {
theMap: map[string]interface{}{},
expected: emptyMap,
},
"mostlyEmpty": {
theMap: map[string]interface{}{
"hey": "there",
},
expected: emptyMap,
},
"noNameConfigMap": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
},
expected: emptyMap,
},
"configmap": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
"data": map[string]string{
"wine": "cabernet",
"truck": "ford",
"rocket": "falcon9",
"planet": "mars",
"city": "brownsville",
},
},
// order irrelevant, because assert.Equals is smart about maps.
expected: map[string]string{
"city": "brownsville",
"wine": "cabernet",
"planet": "mars",
"rocket": "falcon9",
"truck": "ford",
},
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
rn, err := FromMap(tc.theMap)
if !assert.NoError(t, err) {
t.FailNow()
}
m := rn.GetDataMap()
if !assert.Equal(t, tc.expected, m) {
t.FailNow()
}
})
}
}
func TestRNodeGetValidatedDataMap(t *testing.T) {
emptyMap := map[string]string{}
testCases := map[string]struct {
theMap map[string]interface{}
theAllowedKeys []string
expected map[string]string
expectedError error
}{
"nilResultEmptyKeys": {
theMap: nil,
theAllowedKeys: []string{},
expected: emptyMap,
expectedError: nil,
},
"empty": {
theMap: map[string]interface{}{},
theAllowedKeys: []string{},
expected: emptyMap,
expectedError: nil,
},
"expectedKeysMatch": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
"data": map[string]string{
"wine": "cabernet",
"truck": "ford",
"rocket": "falcon9",
"planet": "mars",
"city": "brownsville",
},
},
theAllowedKeys: []string{
"wine",
"truck",
"rocket",
"planet",
"city",
"plane",
"country",
},
// order irrelevant, because assert.Equals is smart about maps.
expected: map[string]string{
"city": "brownsville",
"wine": "cabernet",
"planet": "mars",
"rocket": "falcon9",
"truck": "ford",
},
expectedError: nil,
},
"unexpectedKeyInConfigMap": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
"data": map[string]string{
"wine": "cabernet",
"truck": "ford",
"rocket": "falcon9",
},
},
theAllowedKeys: []string{
"wine",
"truck",
},
// order irrelevant, because assert.Equals is smart about maps.
expected: map[string]string{
"wine": "cabernet",
"rocket": "falcon9",
"truck": "ford",
},
expectedError: fmt.Errorf("an unexpected key (rocket) was found"),
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
rn, err := FromMap(tc.theMap)
if !assert.NoError(t, err) {
t.FailNow()
}
m, err := rn.GetValidatedDataMap(tc.theAllowedKeys)
if !assert.Equal(t, tc.expected, m) {
t.FailNow()
}
if !assert.Equal(t, tc.expectedError, err) {
t.FailNow()
}
})
}
}
func TestRNodeSetDataMap(t *testing.T) {
testCases := map[string]struct {
theMap map[string]interface{}
input map[string]string
expected map[string]string
}{
"empty": {
theMap: map[string]interface{}{},
input: map[string]string{
"wine": "cabernet",
"truck": "ford",
},
expected: map[string]string{
"wine": "cabernet",
"truck": "ford",
},
},
"replace": {
theMap: map[string]interface{}{
"foo": 3,
"data": map[string]string{
"rocket": "falcon9",
"planet": "mars",
},
},
input: map[string]string{
"wine": "cabernet",
"truck": "ford",
},
expected: map[string]string{
"wine": "cabernet",
"truck": "ford",
},
},
"clear1": {
theMap: map[string]interface{}{
"foo": 3,
"data": map[string]string{
"rocket": "falcon9",
"planet": "mars",
},
},
input: map[string]string{},
expected: map[string]string{},
},
"clear2": {
theMap: map[string]interface{}{
"foo": 3,
"data": map[string]string{
"rocket": "falcon9",
"planet": "mars",
},
},
input: nil,
expected: map[string]string{},
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
rn, err := FromMap(tc.theMap)
if !assert.NoError(t, err) {
t.FailNow()
}
rn.SetDataMap(tc.input)
m := rn.GetDataMap()
if !assert.Equal(t, tc.expected, m) {
t.FailNow()
}
})
}
}
func TestRNodeGetValidatedMetadata(t *testing.T) {
testConfigMap := map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
}
type resultExpected struct {
out ResourceMeta
errMsg string
}
testCases := map[string]struct {
theMap map[string]interface{}
rsExp resultExpected
}{
"actuallyNil": {
theMap: nil,
rsExp: resultExpected{
errMsg: "missing Resource metadata",
},
},
"empty": {
theMap: map[string]interface{}{},
rsExp: resultExpected{
errMsg: "missing Resource metadata",
},
},
"mostlyEmpty": {
theMap: map[string]interface{}{
"hey": "there",
},
rsExp: resultExpected{
errMsg: "missing Resource metadata",
},
},
"noNameConfigMap": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
},
rsExp: resultExpected{
errMsg: "missing metadata.name",
},
},
"configmap": {
theMap: testConfigMap,
rsExp: resultExpected{
out: ResourceMeta{
TypeMeta: TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: ObjectMeta{
NameMeta: NameMeta{
Name: "winnie",
},
},
},
},
},
"list": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "List",
"items": []interface{}{
testConfigMap,
testConfigMap,
},
},
rsExp: resultExpected{
out: ResourceMeta{
TypeMeta: TypeMeta{
APIVersion: "v1",
Kind: "List",
},
},
},
},
"configmaplist": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMapList",
"items": []interface{}{
testConfigMap,
testConfigMap,
},
},
rsExp: resultExpected{
out: ResourceMeta{
TypeMeta: TypeMeta{
APIVersion: "v1",
Kind: "ConfigMapList",
},
},
},
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
rn, err := FromMap(tc.theMap)
if !assert.NoError(t, err) {
t.FailNow()
}
m, err := rn.GetValidatedMetadata()
if tc.rsExp.errMsg == "" {
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, tc.rsExp.out, m) {
t.FailNow()
}
} else {
if !assert.Error(t, err) {
t.FailNow()
}
if !assert.Contains(t, err.Error(), tc.rsExp.errMsg) {
t.FailNow()
}
}
})
}
}
func TestRNodeMapEmpty(t *testing.T) {
newRNodeMap, err := NewRNode(nil).Map()
require.NoError(t, err)
assert.Equal(t, 0, len(newRNodeMap))
}
func TestRNodeMap(t *testing.T) {
wn := NewRNode(nil)
if err := wn.UnmarshalJSON([]byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "homer",
"namespace": "simpsons"
}
}`)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "homer",
"namespace": "simpsons",
},
}
actual, err := wn.Map()
require.NoError(t, err)
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
}
func TestRNodeFromMap(t *testing.T) {
testConfigMap := map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
}
type resultExpected struct {
out string
err error
}
testCases := map[string]struct {
theMap map[string]interface{}
rsExp resultExpected
}{
"actuallyNil": {
theMap: nil,
rsExp: resultExpected{
out: `{}`,
err: nil,
},
},
"empty": {
theMap: map[string]interface{}{},
rsExp: resultExpected{
out: `{}`,
err: nil,
},
},
"mostlyEmpty": {
theMap: map[string]interface{}{
"hey": "there",
},
rsExp: resultExpected{
out: `hey: there`,
err: nil,
},
},
"configmap": {
theMap: testConfigMap,
rsExp: resultExpected{
out: `
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`,
err: nil,
},
},
"list": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "List",
"items": []interface{}{
testConfigMap,
testConfigMap,
},
},
rsExp: resultExpected{
out: `
apiVersion: v1
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
kind: List
`,
err: nil,
},
},
"configmaplist": {
theMap: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMapList",
"items": []interface{}{
testConfigMap,
testConfigMap,
},
},
rsExp: resultExpected{
out: `
apiVersion: v1
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
kind: ConfigMapList
`,
err: nil,
},
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
rn, err := FromMap(tc.theMap)
if tc.rsExp.err == nil {
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(tc.rsExp.out),
strings.TrimSpace(rn.MustString())) {
t.FailNow()
}
} else {
if !assert.Error(t, err) {
t.FailNow()
}
if !assert.Equal(t, tc.rsExp.err, err) {
t.FailNow()
}
}
})
}
}
// Test that non-UTF8 characters in comments don't cause failures
func TestRNode_GetMeta_UTF16(t *testing.T) {
sr, err := Parse(`apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRole
metadata:
name: wildcard
namespace: default
# If set to [“*”], it refers to all services in the namespace
annotations:
foo: bar
spec:
rules:
# There is one service in default namespace, should not result in a validation error
# If set to [“*”], it refers to all services in the namespace
- services: ["*"]
methods: ["GET", "HEAD"]
`)
if !assert.NoError(t, err) {
t.FailNow()
}
actual, err := sr.GetMeta()
if !assert.NoError(t, err) {
t.FailNow()
}
expected := ResourceMeta{
TypeMeta: TypeMeta{
APIVersion: "rbac.istio.io/v1alpha1",
Kind: "ServiceRole",
},
ObjectMeta: ObjectMeta{
NameMeta: NameMeta{
Name: "wildcard",
Namespace: "default",
},
Annotations: map[string]string{"foo": "bar"},
},
}
if !assert.Equal(t, expected, actual) {
t.FailNow()
}
}
func TestDeAnchor(t *testing.T) {
rn, err := Parse(`
apiVersion: v1
kind: ConfigMap
metadata:
name: wildcard
data:
color: &color-used blue
feeling: *color-used
`)
require.NoError(t, err)
require.NoError(t, rn.DeAnchor())
actual, err := rn.String()
require.NoError(t, err)
assert.Equal(t, strings.TrimSpace(`
apiVersion: v1
kind: ConfigMap
metadata:
name: wildcard
data:
color: blue
feeling: blue
`), strings.TrimSpace(actual))
}
func TestDeAnchorMerge(t *testing.T) {
testCases := []struct {
description string
input string
expected string
expectedErr error
}{
// *********
// Test Case
// *********
{
description: "simplest merge tag",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color-used
foo: bar
primaryColor:
<<: *color-used
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color:
foo: bar
primaryColor:
foo: bar
`,
},
// *********
// Test Case
// *********
{
description: "keep duplicated keys",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: "#FF0000"
color: "#FF00FF"
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: "#FF0000"
color: "#FF00FF"
`,
},
// *********
// Test Case
// *********
{
description: "keep json",
input: `{"apiVersion": "v1", "kind": "MergeTagTest", "spec": {"color": {"rgb": "#FF0000"}}}`,
expected: `{"apiVersion": "v1", "kind": "MergeTagTest", "spec": {"color": {"rgb": "#FF0000"}}}`,
},
// *********
// Test Case
// *********
{
description: "keep comments",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color-used
foo: bar
primaryColor:
# use same color because is pretty
rgb: "#FF0000"
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color:
foo: bar
primaryColor:
# use same color because is pretty
rgb: "#FF0000"
`,
},
// *********
// Test Case
// *********
{
description: "works with explicit merge tag",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color-used
foo: bar
primaryColor:
!!merge <<: *color-used
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color:
foo: bar
primaryColor:
foo: bar
`,
},
// *********
// Test Case
// *********
{
description: "works with explicit long merge tag",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color-used
foo: bar
primaryColor:
!<tag:yaml.org,2002:merge> "<<" : *color-used
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color:
foo: bar
primaryColor:
foo: bar
`,
},
// *********
// Test Case
// *********
{
description: "merging properties",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color-used
rgb: "#FF0000"
primaryColor:
<<: *color-used
pretty: true
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color:
rgb: "#FF0000"
primaryColor:
pretty: true
rgb: "#FF0000"
`,
},
// *********
// Test Case
// *********
{
description: "overriding value",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color-used
rgb: "#FF0000"
pretty: false
primaryColor:
<<: *color-used
pretty: true
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color:
rgb: "#FF0000"
pretty: false
primaryColor:
pretty: true
rgb: "#FF0000"
`,
},
// *********
// Test Case
// *********
{
description: "returns error when defining multiple merge keys",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color
rgb: "#FF0000"
pretty: false
primaryColor: &primary
rgb: "#0000FF"
alpha: 50
secondaryColor:
<<: *color
<<: *primary
secondary: true
`,
expectedErr: fmt.Errorf("duplicate merge key"),
},
// *********
// Test Case
// *********
{
description: "merging multiple anchors with sequence node",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color
rgb: "#FF0000"
pretty: false
primaryColor: &primary
rgb: "#0000FF"
alpha: 50
secondaryColor:
<<: [ *color, *primary ]
secondary: true
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color:
rgb: "#FF0000"
pretty: false
primaryColor:
rgb: "#0000FF"
alpha: 50
secondaryColor:
secondary: true
rgb: "#FF0000"
alpha: 50
pretty: false
`,
},
// *********
// Test Case
// *********
{
description: "merging inline map",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color
rgb: "#FF0000"
pretty: false
primaryColor:
<<: {"pretty": true}
rgb: "#0000FF"
alpha: 50
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color:
rgb: "#FF0000"
pretty: false
primaryColor:
rgb: "#0000FF"
alpha: 50
"pretty": true
`,
},
// *********
// Test Case
// *********
{
description: "merging inline sequence map",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color
rgb: "#FF0000"
pretty: false
primaryColor:
<<: [ *color, {"name": "ugly blue"}]
rgb: "#0000FF"
alpha: 50
`,
expected: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color:
rgb: "#FF0000"
pretty: false
primaryColor:
rgb: "#0000FF"
alpha: 50
"name": "ugly blue"
pretty: false
`,
},
// *********
// Test Case
// *********
{
description: "error on nested lists on merges",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color
rgb: "#FF0000"
pretty: false
primaryColor:
<<: [ *color, [{"name": "ugly blue"}]]
rgb: "#0000FF"
alpha: 50
`,
expectedErr: fmt.Errorf("invalid map merge: received a nested sequence"),
},
// *********
// Test Case
// *********
{
description: "error on non-map references on merges",
input: `
apiVersion: v1
kind: MergeTagTest
metadata:
name: test
data:
color: &color
- rgb: "#FF0000"
pretty: false
primaryColor:
<<: [ *color, [{"name": "ugly blue"}]]
rgb: "#0000FF"
alpha: 50
`,
expectedErr: fmt.Errorf("invalid map merge: received alias for a non-map value"),
},
// *********
// Test Case
// *********
{
description: "merging on a list",
input: `
apiVersion: v1
kind: MergeTagTestList
items:
- apiVersion: v1
kind: MergeTagTest
metadata:
name: test
spec: &merge-spec
something: true
- apiVersion: v1
kind: MergeTagTest
metadata:
name: test
spec:
<<: *merge-spec
`,
expected: `
apiVersion: v1
kind: MergeTagTestList
items:
- apiVersion: v1
kind: MergeTagTest
metadata:
name: test
spec:
something: true
- apiVersion: v1
kind: MergeTagTest
metadata:
name: test
spec:
something: true
`,
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
rn, err := Parse(tc.input)
require.NoError(t, err)
err = rn.DeAnchor()
if tc.expectedErr == nil {
require.NoError(t, err)
actual, err := rn.String()
require.NoError(t, err)
assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(actual))
} else {
assert.NotNil(t, err)
assert.Equal(t, tc.expectedErr.Error(), err.Error())
}
})
}
}
func TestRNode_UnmarshalJSON(t *testing.T) {
testCases := []struct {
testName string
input string
output string
}{
{
testName: "simple document",
input: `{"hello":"world"}`,
output: `hello: world`,
},
{
testName: "nested structure",
input: `
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "my-deployment",
"namespace": "default"
}
}
`,
output: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
namespace: default
`,
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.testName, func(t *testing.T) {
instance := &RNode{}
err := instance.UnmarshalJSON([]byte(tc.input))
if !assert.NoError(t, err) {
t.FailNow()
}
actual, err := instance.String()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(tc.output), strings.TrimSpace(actual)) {
t.FailNow()
}
})
}
}
func TestRNode_MarshalJSON(t *testing.T) {
tests := []struct {
name string
ydoc string
want string
}{
{
name: "object",
ydoc: `
hello: world
`,
want: `{"hello":"world"}`,
},
{
name: "array",
ydoc: `
- name: s1
- name: s2
`,
want: `[{"name":"s1"},{"name":"s2"}]`,
},
}
for idx := range tests {
tt := tests[idx]
t.Run(tt.name, func(t *testing.T) {
instance, err := Parse(tt.ydoc)
if !assert.NoError(t, err) {
t.FailNow()
}
actual, err := instance.MarshalJSON()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(tt.want), strings.TrimSpace(string(actual))) {
t.FailNow()
}
})
}
}
func TestConvertJSONToYamlNode(t *testing.T) {
inputJSON := `{"type": "string", "maxLength": 15, "enum": ["allowedValue1", "allowedValue2"]}`
expected := `enum:
- allowedValue1
- allowedValue2
maxLength: 15
type: string
`
node, err := ConvertJSONToYamlNode(inputJSON)
if !assert.NoError(t, err) {
t.FailNow()
}
actual, err := node.String()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Equal(t, expected, actual)
}
func TestIsMissingOrNull(t *testing.T) {
if !IsMissingOrNull(nil) {
t.Fatalf("input: nil")
}
// missing value or null value
if !IsMissingOrNull(NewRNode(nil)) {
t.Fatalf("input: nil value")
}
if IsMissingOrNull(NewScalarRNode("foo")) {
t.Fatalf("input: valid node")
}
// node with NullNodeTag
if !IsMissingOrNull(MakeNullNode()) {
t.Fatalf("input: with NullNodeTag")
}
// empty array. empty array is not expected as empty
if IsMissingOrNull(NewListRNode()) {
t.Fatalf("input: empty array")
}
// array with 1 item
node := NewListRNode("foo")
if IsMissingOrNull(node) {
t.Fatalf("input: array with 1 item")
}
// delete the item in array
node.value.Content = nil
if IsMissingOrNull(node) {
t.Fatalf("input: empty array")
}
}
func TestCopy(t *testing.T) {
rn := RNode{
fieldPath: []string{"fp1", "fp2"},
value: &Node{
Kind: 200,
},
Match: []string{"m1", "m2"},
}
rnC := rn.Copy()
if !reflect.DeepEqual(&rn, rnC) {
t.Fatalf("copy %v is not deep equal to %v", rnC, rn)
}
tmp := rn.value.Kind
rn.value.Kind = 666
if reflect.DeepEqual(rn, rnC) {
t.Fatalf("changing component should break equality")
}
rn.value.Kind = tmp
if !reflect.DeepEqual(&rn, rnC) {
t.Fatalf("should be back to normal")
}
rn.fieldPath[0] = "Different"
if reflect.DeepEqual(rn, rnC) {
t.Fatalf("changing fieldpath should break equality")
}
}
func TestFieldRNodes(t *testing.T) {
testCases := []struct {
testName string
input string
output []string
err string
}{
{
testName: "simple document",
input: `apiVersion: example.com/v1beta1
kind: Example1
spec:
list:
- "a"
- "b"
- "c"`,
output: []string{"apiVersion", "kind", "spec", "list"},
},
{
testName: "non mapping node error",
input: `apiVersion`,
err: `wrong node kind: expected MappingNode but got ScalarNode: node contents:
apiVersion
`,
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.testName, func(t *testing.T) {
rNode, err := Parse(tc.input)
if !assert.NoError(t, err) {
t.FailNow()
}
fieldRNodes, err := rNode.FieldRNodes()
if tc.err == "" {
if !assert.NoError(t, err) {
t.FailNow()
}
} else {
if !assert.Equal(t, tc.err, err.Error()) {
t.FailNow()
}
}
for i := range fieldRNodes {
actual, err := fieldRNodes[i].String()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, tc.output[i], strings.TrimSpace(actual)) {
t.FailNow()
}
}
})
}
}
func TestIsEmptyMap(t *testing.T) {
node := NewMapRNode(nil)
// empty map
if !IsEmptyMap(node) {
t.Fatalf("input: empty map")
}
// map with 1 item
node = NewMapRNode(&map[string]string{
"foo": "bar",
})
if IsEmptyMap(node) {
t.Fatalf("input: map with 1 item")
}
// delete the item in map
node.value.Content = nil
if !IsEmptyMap(node) {
t.Fatalf("input: empty map")
}
}
func TestIsNil(t *testing.T) {
var rn *RNode
if !rn.IsNil() {
t.Fatalf("uninitialized RNode should be nil")
}
if !NewRNode(nil).IsNil() {
t.Fatalf("missing value YNode should be nil")
}
if MakeNullNode().IsNil() {
t.Fatalf("value tagged null is not nil")
}
if NewMapRNode(nil).IsNil() {
t.Fatalf("empty map not nil")
}
if NewListRNode().IsNil() {
t.Fatalf("empty list not nil")
}
}
func TestIsTaggedNull(t *testing.T) {
var rn *RNode
if rn.IsTaggedNull() {
t.Fatalf("nil RNode cannot be tagged")
}
if NewRNode(nil).IsTaggedNull() {
t.Fatalf("bare RNode should not be tagged")
}
if !MakeNullNode().IsTaggedNull() {
t.Fatalf("a null node is tagged null by definition")
}
if NewMapRNode(nil).IsTaggedNull() {
t.Fatalf("empty map should not be tagged null")
}
if NewListRNode().IsTaggedNull() {
t.Fatalf("empty list should not be tagged null")
}
}
func TestRNodeIsNilOrEmpty(t *testing.T) {
var rn *RNode
if !rn.IsNilOrEmpty() {
t.Fatalf("uninitialized RNode should be empty")
}
if !NewRNode(nil).IsNilOrEmpty() {
t.Fatalf("missing value YNode should be empty")
}
if !MakeNullNode().IsNilOrEmpty() {
t.Fatalf("value tagged null should be empty")
}
if !NewMapRNode(nil).IsNilOrEmpty() {
t.Fatalf("empty map should be empty")
}
if NewMapRNode(&map[string]string{"foo": "bar"}).IsNilOrEmpty() {
t.Fatalf("non-empty map should not be empty")
}
if !NewListRNode().IsNilOrEmpty() {
t.Fatalf("empty list should be empty")
}
if NewListRNode("foo").IsNilOrEmpty() {
t.Fatalf("non-empty list should not be empty")
}
if !NewRNode(&Node{}).IsNilOrEmpty() {
t.Fatalf("zero YNode should be empty")
}
}
const deploymentJSON = `
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "homer",
"namespace": "simpsons",
"labels": {
"fruit": "apple",
"veggie": "carrot"
},
"annotations": {
"area": "51",
"greeting": "Take me to your leader."
}
}
}
`
func TestRNodeSetNamespace(t *testing.T) {
n := NewRNode(nil)
assert.Equal(t, "", n.GetNamespace())
require.NoError(t, n.SetNamespace(""))
assert.Equal(t, "", n.GetNamespace())
if err := n.UnmarshalJSON([]byte(deploymentJSON)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
require.NoError(t, n.SetNamespace("flanders"))
assert.Equal(t, "flanders", n.GetNamespace())
}
func TestRNodeSetLabels(t *testing.T) {
n := NewRNode(nil)
assert.Equal(t, 0, len(n.GetLabels()))
require.NoError(t, n.SetLabels(map[string]string{}))
assert.Equal(t, 0, len(n.GetLabels()))
n = NewRNode(nil)
if err := n.UnmarshalJSON([]byte(deploymentJSON)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
labels := n.GetLabels()
assert.Equal(t, 2, len(labels))
assert.Equal(t, "apple", labels["fruit"])
assert.Equal(t, "carrot", labels["veggie"])
require.NoError(t, n.SetLabels(map[string]string{
"label1": "foo",
"label2": "bar",
}))
labels = n.GetLabels()
assert.Equal(t, 2, len(labels))
assert.Equal(t, "foo", labels["label1"])
assert.Equal(t, "bar", labels["label2"])
require.NoError(t, n.SetLabels(map[string]string{}))
assert.Equal(t, 0, len(n.GetLabels()))
}
func TestRNodeGetAnnotations(t *testing.T) {
n := NewRNode(nil)
assert.Equal(t, 0, len(n.GetAnnotations()))
require.NoError(t, n.SetAnnotations(map[string]string{}))
assert.Equal(t, 0, len(n.GetAnnotations()))
n = NewRNode(nil)
if err := n.UnmarshalJSON([]byte(deploymentJSON)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
annotations := n.GetAnnotations()
assert.Equal(t, 2, len(annotations))
assert.Equal(t, "51", annotations["area"])
assert.Equal(t, "Take me to your leader.", annotations["greeting"])
require.NoError(t, n.SetAnnotations(map[string]string{
"annotation1": "foo",
"annotation2": "bar",
}))
annotations = n.GetAnnotations()
assert.Equal(t, 2, len(annotations))
assert.Equal(t, "foo", annotations["annotation1"])
assert.Equal(t, "bar", annotations["annotation2"])
require.NoError(t, n.SetAnnotations(map[string]string{}))
assert.Equal(t, 0, len(n.GetAnnotations()))
}
func TestRNodeMatchesAnnotationSelector(t *testing.T) {
rn := NewRNode(nil)
require.NoError(t, rn.UnmarshalJSON([]byte(deploymentJSON)))
testcases := map[string]struct {
selector string
matches bool
errMsg string
}{
"select_01": {
selector: ".*",
matches: false,
errMsg: "name part must consist of alphanumeric character",
},
"select_02": {
selector: "area=51",
matches: true,
},
"select_03": {
selector: "area=florida",
matches: false,
},
"select_04": {
selector: "area in (disneyland, 51, iowa)",
matches: true,
},
"select_05": {
selector: "area in (disneyland, iowa)",
matches: false,
},
"select_06": {
selector: "area notin (disneyland, 51, iowa)",
matches: false,
},
}
for n, tc := range testcases {
gotMatch, err := rn.MatchesAnnotationSelector(tc.selector)
if tc.errMsg == "" {
require.NoError(t, err)
assert.Equalf(
t, tc.matches, gotMatch, "test=%s selector=%v", n, tc.selector)
} else {
assert.Truef(
t, strings.Contains(err.Error(), tc.errMsg),
"test=%s selector=%v", n, tc.selector)
}
}
}
func TestRNodeMatchesLabelSelector(t *testing.T) {
rn := NewRNode(nil)
require.NoError(t, rn.UnmarshalJSON([]byte(deploymentJSON)))
testcases := map[string]struct {
selector string
matches bool
errMsg string
}{
"select_01": {
selector: ".*",
matches: false,
errMsg: "name part must consist of alphanumeric character",
},
"select_02": {
selector: "fruit=apple",
matches: true,
},
"select_03": {
selector: "fruit=banana",
matches: false,
},
"select_04": {
selector: "fruit in (orange, banana, apple)",
matches: true,
},
"select_05": {
selector: "fruit in (orange, banana)",
matches: false,
},
"select_06": {
selector: "fruit notin (orange, banana, apple)",
matches: false,
},
}
for n, tc := range testcases {
gotMatch, err := rn.MatchesLabelSelector(tc.selector)
if tc.errMsg == "" {
require.NoError(t, err)
assert.Equalf(
t, tc.matches, gotMatch, "test=%s selector=%v", n, tc.selector)
} else {
assert.Truef(
t, strings.Contains(err.Error(), tc.errMsg),
"test=%s selector=%v", n, tc.selector)
}
}
}
const (
deploymentLittleJson = `{"apiVersion":"apps/v1","kind":"Deployment",` +
`"metadata":{"name":"homer","namespace":"simpsons"}}`
deploymentBiggerJson = `
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "homer",
"namespace": "simpsons",
"labels": {
"fruit": "apple",
"veggie": "carrot"
},
"annotations": {
"area": "51",
"greeting": "Take me to your leader."
}
},
"spec": {
"template": {
"spec": {
"containers": [
{
"env": [
{
"name": "CM_FOO",
"valueFrom": {
"configMapKeyRef": {
"key": "somekey",
"name": "myCm"
}
}
},
{
"name": "SECRET_FOO",
"valueFrom": {
"secretKeyRef": {
"key": "someKey",
"name": "mySecret"
}
}
}
],
"image": "nginx:1.7.9",
"name": "nginx"
}
]
}
}
}
}
`
bigMapYaml = `Kind: Service
complextree:
- field1:
- boolfield: true
floatsubfield: 1.01
intsubfield: 1010
stringsubfield: idx1010
- boolfield: false
floatsubfield: 1.011
intsubfield: 1011
stringsubfield: idx1011
field2:
- boolfield: true
floatsubfield: 1.02
intsubfield: 1020
stringsubfield: idx1020
- boolfield: false
floatsubfield: 1.021
intsubfield: 1021
stringsubfield: idx1021
- field1:
- boolfield: true
floatsubfield: 1.11
intsubfield: 1110
stringsubfield: idx1110
- boolfield: false
floatsubfield: 1.111
intsubfield: 1111
stringsubfield: idx1111
field2:
- boolfield: true
floatsubfield: 1.112
intsubfield: 1120
stringsubfield: idx1120
- boolfield: false
floatsubfield: 1.1121
intsubfield: 1121
stringsubfield: idx1121
metadata:
labels:
app: application-name
name: service-name
spec:
ports:
port: 80
that:
- idx0
- idx1
- idx2
- idx3
these:
- field1:
- idx010
- idx011
field2:
- idx020
- idx021
- field1:
- idx110
- idx111
field2:
- idx120
- idx121
- field1:
- idx210
- idx211
field2:
- idx220
- idx221
this:
is:
aBool: true
aFloat: 1.001
aNilValue: null
aNumber: 1000
anEmptyMap: {}
anEmptySlice: []
those:
- field1: idx0foo
field2: idx0bar
- field1: idx1foo
field2: idx1bar
- field1: idx2foo
field2: idx2bar
`
)
func TestGetFieldValueReturnsMap(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := map[string]interface{}{
"fruit": "apple",
"veggie": "carrot",
}
actual, err := rn.GetFieldValue("metadata.labels")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
}
func TestGetFieldValueReturnsStuff(t *testing.T) {
wn := NewRNode(nil)
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := []interface{}{
map[string]interface{}{
"env": []interface{}{
map[string]interface{}{
"name": "CM_FOO",
"valueFrom": map[string]interface{}{
"configMapKeyRef": map[string]interface{}{
"key": "somekey",
"name": "myCm",
},
},
},
map[string]interface{}{
"name": "SECRET_FOO",
"valueFrom": map[string]interface{}{
"secretKeyRef": map[string]interface{}{
"key": "someKey",
"name": "mySecret",
},
},
},
},
"image": string("nginx:1.7.9"),
"name": string("nginx"),
},
}
actual, err := wn.GetFieldValue("spec.template.spec.containers")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
// Cannot go deeper yet.
_, err = wn.GetFieldValue("spec.template.spec.containers.env")
if err == nil {
t.Fatalf("expected err %v", err)
}
}
func makeBigMap() map[string]interface{} {
return 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{}{},
/*
TODO: test for unrecognizable (e.g. a function)
"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 TestBasicYamlOperationFromMap(t *testing.T) {
bytes, err := Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
if string(bytes) != bigMapYaml {
t.Fatalf("unexpected string equality")
}
rNode, err := Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rNodeString := rNode.MustString()
// The result from MustString has more indentation
// than bigMapYaml.
rNodeStrings := strings.Split(rNodeString, "\n")
bigMapStrings := strings.Split(bigMapYaml, "\n")
if len(rNodeStrings) != len(bigMapStrings) {
t.Fatalf("line count mismatch")
}
for i := range rNodeStrings {
s1 := strings.TrimSpace(rNodeStrings[i])
s2 := strings.TrimSpace(bigMapStrings[i])
if s1 != s2 {
t.Fatalf("expected '%s'=='%s'", s1, s2)
}
}
}
func TestGetFieldValueReturnsSlice(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rNode, err := Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
actual, err := rNode.GetFieldValue("that")
if err != nil {
t.Fatalf("error getting slice: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
}
}
func TestGetFieldValueReturnsSliceOfMappings(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rn, err := Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
expected := []interface{}{
map[string]interface{}{
"field1": "idx0foo",
"field2": "idx0bar",
},
map[string]interface{}{
"field1": "idx1foo",
"field2": "idx1bar",
},
map[string]interface{}{
"field1": "idx2foo",
"field2": "idx2bar",
},
}
actual, err := rn.GetFieldValue("those")
if err != nil {
t.Fatalf("error getting slice: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
}
}
func TestGetFieldValueReturnsString(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
actual, err := rn.GetFieldValue("metadata.labels.fruit")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
v, ok := actual.(string)
if !ok || v != "apple" {
t.Fatalf("unexpected value '%v'", actual)
}
}
func TestGetFieldValueResolvesAlias(t *testing.T) {
yamlWithAlias := `
foo: &a theValue
bar: *a
`
rn, err := Parse(yamlWithAlias)
if err != nil {
t.Fatalf("unexpected yaml parse error: %v", err)
}
actual, err := rn.GetFieldValue("bar")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
v, ok := actual.(string)
if !ok || v != "theValue" {
t.Fatalf("unexpected value '%v'", actual)
}
}
func TestGetString(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := "carrot"
actual, err := rn.GetString("metadata.labels.veggie")
if err != nil {
t.Fatalf("error getting string: %v", err)
}
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestGetSlice(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rn, err := Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
actual, err := rn.GetSlice("that")
if err != nil {
t.Fatalf("error getting slice: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
}
}
func TestRoundTripJSON(t *testing.T) {
rn := NewRNode(nil)
err := rn.UnmarshalJSON([]byte(deploymentLittleJson))
if err != nil {
t.Fatalf("unexpected UnmarshalJSON err: %v", err)
}
data, err := rn.MarshalJSON()
if err != nil {
t.Fatalf("unexpected MarshalJSON err: %v", err)
}
if actual := string(data); actual != deploymentLittleJson {
t.Fatalf("expected %s, got %s", deploymentLittleJson, actual)
}
}
func TestGettingFields(t *testing.T) {
rn := NewRNode(nil)
err := rn.UnmarshalJSON([]byte(deploymentBiggerJson))
if err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := "Deployment"
actual := rn.GetKind()
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
expected = "homer"
actual = rn.GetName()
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
actualMap := rn.GetLabels()
v, ok := actualMap["fruit"]
if !ok || v != "apple" {
t.Fatalf("unexpected labels '%v'", actualMap)
}
actualMap = rn.GetAnnotations()
v, ok = actualMap["greeting"]
if !ok || v != "Take me to your leader." {
t.Fatalf("unexpected annotations '%v'", actualMap)
}
}
func TestMapEmpty(t *testing.T) {
newNodeMap, err := NewRNode(nil).Map()
require.NoError(t, err)
assert.Equal(t, 0, len(newNodeMap))
}
func TestMap(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentLittleJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "homer",
"namespace": "simpsons",
},
}
actual, err := rn.Map()
require.NoError(t, err)
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
}
func TestSetName(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
err := rn.SetName("marge")
require.NoError(t, err)
if expected, actual := "marge", rn.GetName(); expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetNamespace(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
err := rn.SetNamespace("flanders")
require.NoError(t, err)
meta, err := rn.GetMeta()
require.NoError(t, err)
if expected, actual := "flanders", meta.Namespace; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetLabels(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
require.NoError(t, rn.SetLabels(map[string]string{
"label1": "foo",
"label2": "bar",
}))
labels := rn.GetLabels()
if expected, actual := 2, len(labels); expected != actual {
t.Fatalf("expected '%d', got '%d'", expected, actual)
}
if expected, actual := "foo", labels["label1"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "bar", labels["label2"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestGetAnnotations(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
require.NoError(t, rn.SetAnnotations(map[string]string{
"annotation1": "foo",
"annotation2": "bar",
}))
annotations := rn.GetAnnotations()
if expected, actual := 2, len(annotations); expected != actual {
t.Fatalf("expected '%d', got '%d'", expected, actual)
}
if expected, actual := "foo", annotations["annotation1"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "bar", annotations["annotation2"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func BenchmarkGetAnnotations(b *testing.B) {
counts := []int{0, 2, 5, 8}
for _, count := range counts {
appliedAnnotations := make(map[string]string, count)
for i := 1; i <= count; i++ {
key := fmt.Sprintf("annotation-key-%d", i)
value := fmt.Sprintf("annotation-value-%d", i)
appliedAnnotations[key] = value
}
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
b.Fatalf("unexpected unmarshaljson err: %v", err)
}
require.NoError(b, rn.SetAnnotations(appliedAnnotations))
b.Run(fmt.Sprintf("%02d", count), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = rn.GetAnnotations()
}
})
}
}
func TestGetFieldValueWithDot(t *testing.T) {
const input = `
kind: Pod
metadata:
name: hello-world
labels:
app: hello-world-app
foo.appname: hello-world-foo
`
data, err := Parse(input)
require.NoError(t, err)
labelRNode, err := data.Pipe(Lookup("metadata", "labels"))
require.NoError(t, err)
app, err := labelRNode.GetFieldValue("app")
require.NoError(t, err)
require.Equal(t, "hello-world-app", app)
fooAppName, err := labelRNode.GetFieldValue(`foo\.appname`)
require.NoError(t, err)
require.Equal(t, "hello-world-foo", fooAppName) // no field named 'foo.appname'
}