1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package hash
18
19import (
20	"reflect"
21	"strings"
22	"testing"
23
24	"k8s.io/api/core/v1"
25)
26
27func TestConfigMapHash(t *testing.T) {
28	cases := []struct {
29		desc string
30		cm   *v1.ConfigMap
31		hash string
32		err  string
33	}{
34		// empty map
35		{"empty data", &v1.ConfigMap{Data: map[string]string{}, BinaryData: map[string][]byte{}}, "42745tchd9", ""},
36		// one key
37		{"one key", &v1.ConfigMap{Data: map[string]string{"one": ""}}, "9g67k2htb6", ""},
38		// three keys (tests sorting order)
39		{"three keys", &v1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, "f5h7t85m9b", ""},
40		// empty binary data map
41		{"empty binary data", &v1.ConfigMap{BinaryData: map[string][]byte{}}, "dk855m5d49", ""},
42		// one key with binary data
43		{"one key with binary data", &v1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}}, "mk79584b8c", ""},
44		// three keys with binary data (tests sorting order)
45		{"three keys with binary data", &v1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "t458mc6db2", ""},
46		// two keys, one with string and another with binary data
47		{"two keys with one each", &v1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}}, "698h7c7t9m", ""},
48	}
49
50	for _, c := range cases {
51		h, err := ConfigMapHash(c.cm)
52		if SkipRest(t, c.desc, err, c.err) {
53			continue
54		}
55		if c.hash != h {
56			t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
57		}
58	}
59}
60
61func TestSecretHash(t *testing.T) {
62	cases := []struct {
63		desc   string
64		secret *v1.Secret
65		hash   string
66		err    string
67	}{
68		// empty map
69		{"empty data", &v1.Secret{Type: "my-type", Data: map[string][]byte{}}, "t75bgf6ctb", ""},
70		// one key
71		{"one key", &v1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, "74bd68bm66", ""},
72		// three keys (tests sorting order)
73		{"three keys", &v1.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "dgcb6h9tmk", ""},
74	}
75
76	for _, c := range cases {
77		h, err := SecretHash(c.secret)
78		if SkipRest(t, c.desc, err, c.err) {
79			continue
80		}
81		if c.hash != h {
82			t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
83		}
84	}
85}
86
87func TestEncodeConfigMap(t *testing.T) {
88	cases := []struct {
89		desc   string
90		cm     *v1.ConfigMap
91		expect string
92		err    string
93	}{
94		// empty map
95		{"empty data", &v1.ConfigMap{Data: map[string]string{}}, `{"data":{},"kind":"ConfigMap","name":""}`, ""},
96		// one key
97		{"one key", &v1.ConfigMap{Data: map[string]string{"one": ""}}, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
98		// three keys (tests sorting order)
99		{"three keys", &v1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, `{"data":{"one":"","three":"3","two":"2"},"kind":"ConfigMap","name":""}`, ""},
100		// empty binary map
101		{"empty data", &v1.ConfigMap{BinaryData: map[string][]byte{}}, `{"data":null,"kind":"ConfigMap","name":""}`, ""},
102		// one key with binary data
103		{"one key", &v1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}}, `{"binaryData":{"one":""},"data":null,"kind":"ConfigMap","name":""}`, ""},
104		// three keys with binary data (tests sorting order)
105		{"three keys", &v1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, `{"binaryData":{"one":"","three":"Mw==","two":"Mg=="},"data":null,"kind":"ConfigMap","name":""}`, ""},
106		// two keys, one string and one binary values
107		{"two keys with one each", &v1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}}, `{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
108	}
109	for _, c := range cases {
110		s, err := encodeConfigMap(c.cm)
111		if SkipRest(t, c.desc, err, c.err) {
112			continue
113		}
114		if s != c.expect {
115			t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cm)
116		}
117	}
118}
119
120func TestEncodeSecret(t *testing.T) {
121	cases := []struct {
122		desc   string
123		secret *v1.Secret
124		expect string
125		err    string
126	}{
127		// empty map
128		{"empty data", &v1.Secret{Type: "my-type", Data: map[string][]byte{}}, `{"data":{},"kind":"Secret","name":"","type":"my-type"}`, ""},
129		// one key
130		{"one key", &v1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
131		// three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte
132		{"three keys", &v1.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, `{"data":{"one":"","three":"Mw==","two":"Mg=="},"kind":"Secret","name":"","type":"my-type"}`, ""},
133	}
134	for _, c := range cases {
135		s, err := encodeSecret(c.secret)
136		if SkipRest(t, c.desc, err, c.err) {
137			continue
138		}
139		if s != c.expect {
140			t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secret)
141		}
142	}
143}
144
145func TestHash(t *testing.T) {
146	// hash the empty string to be sure that sha256 is being used
147	expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
148	sum := hash("")
149	if expect != sum {
150		t.Errorf("expected hash %q but got %q", expect, sum)
151	}
152}
153
154// warn devs who change types that they might have to update a hash function
155// not perfect, as it only checks the number of top-level fields
156func TestTypeStability(t *testing.T) {
157	errfmt := `case %q, expected %d fields but got %d
158Depending on the field(s) you added, you may need to modify the hash function for this type.
159To guide you: the hash function targets fields that comprise the contents of objects,
160not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta).
161`
162	cases := []struct {
163		typeName string
164		obj      interface{}
165		expect   int
166	}{
167		{"ConfigMap", v1.ConfigMap{}, 5},
168		{"Secret", v1.Secret{}, 6},
169	}
170	for _, c := range cases {
171		val := reflect.ValueOf(c.obj)
172		if num := val.NumField(); c.expect != num {
173			t.Errorf(errfmt, c.typeName, c.expect, num)
174		}
175	}
176}
177
178// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen,
179// and logs the appropriate error on the test object.
180// The return value indicates whether we should skip the rest of the test case due to the error result.
181func SkipRest(t *testing.T, desc string, err error, contains string) bool {
182	if err != nil {
183		if len(contains) == 0 {
184			t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
185		} else if !strings.Contains(err.Error(), contains) {
186			t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error())
187		}
188		return true
189	} else if len(contains) > 0 {
190		t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains)
191		return true
192	}
193	return false
194}
195