github.com/percona/percona-xtradb-cluster-operator@v1.14.0/pkg/webhook/json/decode_test.go (about)

     1  /*
     2  Copyright 2021 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package json
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  )
    29  
    30  func TestDecode_KnownFields(t *testing.T) {
    31  	// We ignore type meta since inline only works with k8s yaml parsers
    32  	cases := []struct {
    33  		name  string
    34  		input string
    35  
    36  		want    fixture
    37  		wantErr bool
    38  	}{{
    39  		name:  "no metadata",
    40  		input: `{}`,
    41  		want:  fixture{},
    42  	}, {
    43  		name:  "known fields",
    44  		input: `{ "metadata":{"name":"some-name", "namespace":"some-namespace"} }`,
    45  		want: fixture{
    46  			ObjectMeta: metav1.ObjectMeta{
    47  				Name:      "some-name",
    48  				Namespace: "some-namespace",
    49  			},
    50  		},
    51  	}, {
    52  		name: "with spec",
    53  		input: `{
    54              "metadata":{"name":"some-name", "namespace":"some-namespace"},
    55              "spec":{"key":"value"}
    56          }`,
    57  		want: fixture{
    58  			ObjectMeta: metav1.ObjectMeta{
    59  				Name:      "some-name",
    60  				Namespace: "some-namespace",
    61  			},
    62  			Spec: map[string]string{"key": "value"},
    63  		},
    64  	}, {
    65  		name: "unknown metadata field",
    66  		input: `{
    67              "metadata":{"name":"some-name", "namespace":"some-namespace", "bomba":"boom"},
    68              "spec":{"key":"value"}
    69          }`,
    70  		want: fixture{
    71  			ObjectMeta: metav1.ObjectMeta{
    72  				Name:      "some-name",
    73  				Namespace: "some-namespace",
    74  			},
    75  			Spec: map[string]string{"key": "value"},
    76  		},
    77  	}, {
    78  		name: "unknown field top level",
    79  		input: `{
    80              "metadata":{"name":"some-name", "namespace":"some-namespace"},
    81              "bomba": "boom",
    82              "spec":{"key":"value"}
    83          }`,
    84  		wantErr: true,
    85  	}, {
    86  		name: "multiple metadata keys in the JSON",
    87  		input: `{
    88              "metadata":{"name":"some-name", "namespace":"some-namespace", "bomba":"boom"},
    89              "spec":{"metadata":"value"}
    90          }`,
    91  		want: fixture{
    92  			ObjectMeta: metav1.ObjectMeta{
    93  				Name:      "some-name",
    94  				Namespace: "some-namespace",
    95  			},
    96  			Spec: map[string]string{"metadata": "value"},
    97  		},
    98  	}, {
    99  		name: "nested object in metadata",
   100  		input: `{
   101              "metadata":{"name":"some-name", "namespace":"some-namespace", "labels":{"key":"value"}}
   102          }`,
   103  		want: fixture{
   104  			ObjectMeta: metav1.ObjectMeta{
   105  				Name:      "some-name",
   106  				Namespace: "some-namespace",
   107  				Labels: map[string]string{
   108  					"key": "value",
   109  				},
   110  			},
   111  		},
   112  	}, {
   113  		name: "nested array in metadata",
   114  		input: `{
   115              "metadata":{"name":"some-name", "namespace":"some-namespace", "finalizers":["first","second"]}
   116          }`,
   117  		want: fixture{
   118  			ObjectMeta: metav1.ObjectMeta{
   119  				Name:       "some-name",
   120  				Namespace:  "some-namespace",
   121  				Finalizers: []string{"first", "second"},
   122  			},
   123  		},
   124  	}, {
   125  		name: "bad input",
   126  		// note use two characters so our decoder fails on the second token lookup
   127  		input:   "{{",
   128  		wantErr: true,
   129  	}}
   130  
   131  	for _, tc := range cases {
   132  		t.Run(tc.name, func(t *testing.T) {
   133  			got := &fixture{}
   134  			err := Decode([]byte(tc.input), got, true)
   135  
   136  			if tc.wantErr && err == nil {
   137  				t.Fatal("DecodeTo() expected an error")
   138  			} else if !tc.wantErr && err != nil {
   139  				t.Fatal("unexpected error", err)
   140  			}
   141  
   142  			// Don't bother checking against the fixture if
   143  			// we expected an error
   144  			if tc.wantErr {
   145  				return
   146  			}
   147  
   148  			if diff := cmp.Diff(&tc.want, got); diff != "" {
   149  				t.Error("unexpected diff", diff)
   150  			}
   151  		})
   152  	}
   153  }
   154  
   155  func TestDecode_AllowUnknownFields(t *testing.T) {
   156  	input := `{
   157              "metadata":{"name":"some-name", "namespace":"some-namespace"},
   158              "bomba": "boom",
   159              "spec": {"some":"value"}
   160          }`
   161  
   162  	got := &fixture{}
   163  	want := &fixture{
   164  		ObjectMeta: metav1.ObjectMeta{
   165  			Name:      "some-name",
   166  			Namespace: "some-namespace",
   167  		},
   168  		Spec: map[string]string{"some": "value"},
   169  	}
   170  
   171  	err := Decode([]byte(input), got, false)
   172  	if err != nil {
   173  		t.Fatal("unexpected error", err)
   174  	}
   175  
   176  	if diff := cmp.Diff(want, got); diff != "" {
   177  		t.Error("unexpected diff", diff)
   178  	}
   179  }
   180  
   181  // Note: this test is paired with the failingFixture and knows
   182  // that the implementation of Decode parses the json in two passes
   183  // the first being with an empty metadata '{}' - the second being the real
   184  // input
   185  func TestDecode_UnmarshalMetadataFailed(t *testing.T) {
   186  	input := `{ "metadata":{"name":"some-name", "namespace":"some-namespace"} }`
   187  	err := Decode([]byte(input), &failingFixture{}, true)
   188  	if err == nil {
   189  		t.Fatal("expected error")
   190  	}
   191  }
   192  
   193  type fixture struct {
   194  	// Our decoder doesn't support `inline` that's a sig.k8s.io/yaml feature
   195  	// So we skip parsing this property
   196  	metav1.TypeMeta   `json:"-"`
   197  	metav1.ObjectMeta `json:"metadata"`
   198  	Spec              map[string]string `json:"spec"`
   199  }
   200  
   201  func (f *fixture) DeepCopyObject() runtime.Object {
   202  	panic("not implemented")
   203  }
   204  
   205  func (f *fixture) SetDefaults(context.Context) {
   206  	panic("not implemented")
   207  }
   208  
   209  type failingFixture struct {
   210  	fixture
   211  	Meta failingMeta `json:"metadata"`
   212  }
   213  
   214  type failingMeta struct {
   215  	metav1.ObjectMeta
   216  }
   217  
   218  func (f *failingMeta) UnmarshalJSON(bites []byte) error {
   219  	if bytes.Equal([]byte("{}"), bites) {
   220  		return nil
   221  	}
   222  	return errors.New("bomba")
   223  }