/* Copyright 2014 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package queryparams_test import ( "net/url" "reflect" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion/queryparams" "k8s.io/apimachinery/pkg/runtime/schema" ) type namedString string type namedBool bool type bar struct { Float1 float32 `json:"float1"` Float2 float64 `json:"float2"` Int1 int64 `json:"int1,omitempty"` Int2 int32 `json:"int2,omitempty"` Int3 int16 `json:"int3,omitempty"` Str1 string `json:"str1,omitempty"` Ignored int Ignored2 string } func (obj *bar) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } type foo struct { Str string `json:"str"` Integer int `json:"integer,omitempty"` Slice []string `json:"slice,omitempty"` Boolean bool `json:"boolean,omitempty"` NamedStr namedString `json:"namedStr,omitempty"` NamedBool namedBool `json:"namedBool,omitempty"` Foobar bar `json:"foobar,omitempty"` Testmap map[string]string `json:"testmap,omitempty"` } func (obj *foo) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } type baz struct { Ptr *int `json:"ptr"` Bptr *bool `json:"bptr,omitempty"` } func (obj *baz) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } // childStructs tests some of the types we serialize to query params for log API calls // notably, the nested time struct type childStructs struct { Container string `json:"container,omitempty"` Follow bool `json:"follow,omitempty"` Previous bool `json:"previous,omitempty"` SinceSeconds *int64 `json:"sinceSeconds,omitempty"` TailLines *int64 `json:"tailLines,omitempty"` SinceTime *metav1.Time `json:"sinceTime,omitempty"` EmptyTime *metav1.Time `json:"emptyTime"` NonPointerTime metav1.Time `json:"nonPointerTime"` } func (obj *childStructs) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func validateResult(t *testing.T, input interface{}, actual, expected url.Values) { local := url.Values{} for k, v := range expected { local[k] = v } for k, v := range actual { if ev, ok := local[k]; !ok || !reflect.DeepEqual(ev, v) { if !ok { t.Errorf("%#v: actual value key %s not found in expected map", input, k) } else { t.Errorf("%#v: values don't match: actual: %#v, expected: %#v", input, v, ev) } } delete(local, k) } if len(local) > 0 { t.Errorf("%#v: expected map has keys that were not found in actual map: %#v", input, local) } } func TestConvert(t *testing.T) { sinceSeconds := int64(123) tailLines := int64(0) sinceTime := metav1.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC) tests := []struct { input interface{} expected url.Values }{ { input: &foo{ Str: "hello", }, expected: url.Values{"str": {"hello"}}, }, { input: &foo{ Str: "test string", Slice: []string{"one", "two", "three"}, Integer: 234, Boolean: true, }, expected: url.Values{"str": {"test string"}, "slice": {"one", "two", "three"}, "integer": {"234"}, "boolean": {"true"}}, }, { input: &foo{ Str: "named types", NamedStr: "value1", NamedBool: true, }, expected: url.Values{"str": {"named types"}, "namedStr": {"value1"}, "namedBool": {"true"}}, }, { input: &foo{ Str: "don't ignore embedded struct", Foobar: bar{ Float1: 5.0, }, }, expected: url.Values{"str": {"don't ignore embedded struct"}, "float1": {"5"}, "float2": {"0"}}, }, { // Ignore untagged fields input: &bar{ Float1: 23.5, Float2: 100.7, Int1: 1, Int2: 2, Int3: 3, Ignored: 1, Ignored2: "ignored", }, expected: url.Values{"float1": {"23.5"}, "float2": {"100.7"}, "int1": {"1"}, "int2": {"2"}, "int3": {"3"}}, }, { // include fields that are not tagged omitempty input: &foo{ NamedStr: "named str", }, expected: url.Values{"str": {""}, "namedStr": {"named str"}}, }, { input: &baz{ Ptr: intp(5), Bptr: boolp(true), }, expected: url.Values{"ptr": {"5"}, "bptr": {"true"}}, }, { input: &baz{ Bptr: boolp(true), }, expected: url.Values{"ptr": {""}, "bptr": {"true"}}, }, { input: &baz{ Ptr: intp(5), }, expected: url.Values{"ptr": {"5"}}, }, { input: &childStructs{ Container: "mycontainer", Follow: true, Previous: true, SinceSeconds: &sinceSeconds, TailLines: nil, SinceTime: &sinceTime, // test a custom marshaller EmptyTime: nil, // test a nil custom marshaller without omitempty NonPointerTime: sinceTime, }, expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "sinceTime": {"2000-01-01T12:34:56Z"}, "emptyTime": {""}, "nonPointerTime": {"2000-01-01T12:34:56Z"}}, }, { input: &childStructs{ Container: "mycontainer", Follow: true, Previous: true, SinceSeconds: &sinceSeconds, TailLines: &tailLines, SinceTime: nil, // test a nil custom marshaller with omitempty NonPointerTime: sinceTime, }, expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "tailLines": {"0"}, "emptyTime": {""}, "nonPointerTime": {"2000-01-01T12:34:56Z"}}, }, } for _, test := range tests { result, err := queryparams.Convert(test.input) if err != nil { t.Errorf("Unexpected error while converting %#v: %v", test.input, err) } validateResult(t, test.input, result, test.expected) } } func intp(n int) *int { return &n } func boolp(b bool) *bool { return &b }