/* Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ package kubernetes import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" k8s "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) func TestMarshalK8SResources(t *testing.T) { testCases := map[string]struct { resources any wantErr bool wantYAML string }{ "ConfigMap as only field can be marshaled": { resources: &struct { ConfigMap k8s.ConfigMap }{ ConfigMap: k8s.ConfigMap{ TypeMeta: v1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, Data: map[string]string{ "key": "value", }, }, }, wantYAML: `apiVersion: v1 data: key: value kind: ConfigMap metadata: creationTimestamp: null `, }, "Multiple fields are correctly encoded": { resources: &struct { ConfigMap k8s.ConfigMap Secret k8s.Secret }{ ConfigMap: k8s.ConfigMap{ TypeMeta: v1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, Data: map[string]string{ "key": "value", }, }, Secret: k8s.Secret{ TypeMeta: v1.TypeMeta{ Kind: "Secret", APIVersion: "v1", }, Data: map[string][]byte{ "key": []byte("value"), }, }, }, wantYAML: `apiVersion: v1 data: key: value kind: ConfigMap metadata: creationTimestamp: null --- apiVersion: v1 data: key: dmFsdWU= kind: Secret metadata: creationTimestamp: null `, }, "Non-pointer is detected": { resources: "non-pointer", wantErr: true, }, "Nil resource pointer is detected": { resources: nil, wantErr: true, }, "Non-pointer field is ignored": { resources: &struct{ String string }{String: "somestring"}, }, "nil field is ignored": { resources: &struct { ConfigMap *k8s.ConfigMap }{ ConfigMap: nil, }, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { assert := assert.New(t) require := require.New(t) yaml, err := MarshalK8SResources(tc.resources) if tc.wantErr { assert.Error(err) return } require.NoError(err) assert.Equal(tc.wantYAML, string(yaml)) }) } } func TestUnmarshalK8SResources(t *testing.T) { testCases := map[string]struct { data string into any wantObj any wantErr bool }{ "ConfigMap as only field can be unmarshaled": { data: `apiVersion: v1 data: key: value kind: ConfigMap metadata: creationTimestamp: null `, into: &struct { ConfigMap k8s.ConfigMap }{}, wantObj: &struct { ConfigMap k8s.ConfigMap }{ ConfigMap: k8s.ConfigMap{ TypeMeta: v1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, Data: map[string]string{ "key": "value", }, }, }, }, "Multiple fields are correctly unmarshaled": { data: `apiVersion: v1 data: key: value kind: ConfigMap metadata: creationTimestamp: null --- apiVersion: v1 data: key: dmFsdWU= kind: Secret metadata: creationTimestamp: null `, into: &struct { ConfigMap k8s.ConfigMap Secret k8s.Secret }{}, wantObj: &struct { ConfigMap k8s.ConfigMap Secret k8s.Secret }{ ConfigMap: k8s.ConfigMap{ TypeMeta: v1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, Data: map[string]string{ "key": "value", }, }, Secret: k8s.Secret{ TypeMeta: v1.TypeMeta{ Kind: "Secret", APIVersion: "v1", }, Data: map[string][]byte{ "key": []byte("value"), }, }, }, }, "Mismatching amount of fields is detected": { data: `apiVersion: v1 data: key: value kind: ConfigMap metadata: creationTimestamp: null --- apiVersion: v1 data: key: dmFsdWU= kind: Secret metadata: creationTimestamp: null `, into: &struct { ConfigMap k8s.ConfigMap }{}, wantErr: true, }, "Non-struct pointer is detected": { into: proto.String("test"), wantErr: true, }, "Nil into is detected": { into: nil, wantErr: true, }, "Invalid yaml is detected": { data: `duplicateKey: value duplicateKey: value`, into: &struct { ConfigMap k8s.ConfigMap }{}, wantErr: true, }, "Struct field cannot interface with runtime.Object": { data: `apiVersion: v1 data: key: value kind: ConfigMap metadata: creationTimestamp: null `, into: &struct { String string }{}, wantErr: true, }, "Struct field mismatch": { data: `apiVersion: v1 data: key: value kind: ConfigMap metadata: creationTimestamp: null `, into: &struct { Secret k8s.Secret }{}, wantErr: true, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { assert := assert.New(t) require := require.New(t) err := UnmarshalK8SResources([]byte(tc.data), tc.into) if tc.wantErr { assert.Error(err) return } require.NoError(err) assert.Equal(tc.wantObj, tc.into) }) } } func TestMarshalK8SResourcesList(t *testing.T) { testCases := map[string]struct { resources []runtime.Object wantErr bool wantYAML string }{ "ConfigMap as only element be marshaled": { resources: []runtime.Object{ &k8s.ConfigMap{ TypeMeta: v1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, Data: map[string]string{ "key": "value", }, }, }, wantYAML: `apiVersion: v1 data: key: value kind: ConfigMap metadata: creationTimestamp: null `, }, "Multiple fields are correctly encoded": { resources: []runtime.Object{ &k8s.ConfigMap{ TypeMeta: v1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, Data: map[string]string{ "key": "value", }, }, &k8s.Secret{ TypeMeta: v1.TypeMeta{ Kind: "Secret", APIVersion: "v1", }, Data: map[string][]byte{ "key": []byte("value"), }, }, }, wantYAML: `apiVersion: v1 data: key: value kind: ConfigMap metadata: creationTimestamp: null --- apiVersion: v1 data: key: dmFsdWU= kind: Secret metadata: creationTimestamp: null `, }, "Nil resource pointer is encodes": { resources: []runtime.Object{nil}, wantYAML: "null\n", }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { assert := assert.New(t) require := require.New(t) yaml, err := MarshalK8SResourcesList(tc.resources) if tc.wantErr { assert.Error(err) return } require.NoError(err) assert.Equal(tc.wantYAML, string(yaml)) }) } }