knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/kvstore/kvstore_cm_test.go (about)

     1  /*
     2  Copyright 2020 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 kvstore
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"reflect"
    23  	"testing"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/client-go/kubernetes/fake"
    29  	v1 "k8s.io/client-go/kubernetes/typed/core/v1"
    30  	clientgotesting "k8s.io/client-go/testing"
    31  )
    32  
    33  const (
    34  	namespace = "mynamespace"
    35  	name      = "mycm"
    36  )
    37  
    38  type testStruct struct {
    39  	LastThingProcessed string
    40  	Stuff              []string
    41  }
    42  
    43  type testClient struct {
    44  	created   *corev1.ConfigMap
    45  	updated   *corev1.ConfigMap
    46  	clientset v1.CoreV1Interface
    47  }
    48  
    49  func newTestClient(objects ...runtime.Object) *testClient {
    50  	tc := testClient{}
    51  	cs := fake.NewSimpleClientset(objects...)
    52  	cs.PrependReactor("create", "*", func(action clientgotesting.Action) (handled bool, ret runtime.Object, err error) {
    53  		createAction := action.(clientgotesting.CreateAction)
    54  		tc.created = createAction.GetObject().(*corev1.ConfigMap)
    55  		return true, tc.created, nil
    56  	})
    57  	cs.PrependReactor("update", "*", func(action clientgotesting.Action) (handled bool, ret runtime.Object, err error) {
    58  		updateAction := action.(clientgotesting.UpdateAction)
    59  		tc.updated = updateAction.GetObject().(*corev1.ConfigMap)
    60  		return true, tc.updated, nil
    61  	})
    62  	tc.clientset = cs.CoreV1()
    63  	return &tc
    64  }
    65  
    66  func TestInitCreates(t *testing.T) {
    67  	tc := newTestClient()
    68  	cs := NewConfigMapKVStore(context.Background(), name, namespace, tc.clientset)
    69  	err := cs.Init(context.Background())
    70  	if err != nil {
    71  		t.Error("Failed to Init ConfigStore:", err)
    72  	}
    73  	if tc.created == nil {
    74  		t.Errorf("ConfigMap not created")
    75  	}
    76  	if len(tc.created.Data) != 0 {
    77  		t.Errorf("ConfigMap data is not empty")
    78  	}
    79  	if tc.updated != nil {
    80  		t.Errorf("ConfigMap updated")
    81  	}
    82  }
    83  
    84  func TestLoadNonexisting(t *testing.T) {
    85  	tc := newTestClient()
    86  	if NewConfigMapKVStore(context.Background(), name, namespace, tc.clientset).Load(context.Background()) == nil {
    87  		t.Error("non-existent store load didn't fail")
    88  	}
    89  }
    90  
    91  func TestInitLoads(t *testing.T) {
    92  	tc := newTestClient([]runtime.Object{configMap(map[string]string{"foo": marshal(t, "bar")})}...)
    93  	cs := NewConfigMapKVStore(context.Background(), name, namespace, tc.clientset)
    94  	err := cs.Init(context.Background())
    95  	if err != nil {
    96  		t.Error("Failed to Init ConfigStore:", err)
    97  	}
    98  	if tc.created != nil {
    99  		t.Errorf("ConfigMap created")
   100  	}
   101  	if tc.updated != nil {
   102  		t.Errorf("ConfigMap updated")
   103  	}
   104  	var ret string
   105  	err = cs.Get(context.Background(), "foo", &ret)
   106  	if err != nil {
   107  		t.Error("failed to return string:", err)
   108  	}
   109  	if ret != "bar" {
   110  		t.Errorf("got back unexpected value, wanted %q got %q", "bar", ret)
   111  	}
   112  	if cs.Get(context.Background(), "not there", &ret) == nil {
   113  		t.Error("non-existent key didn't error")
   114  	}
   115  }
   116  
   117  func TestLoadSaveUpdate(t *testing.T) {
   118  	tc := newTestClient([]runtime.Object{configMap(map[string]string{"foo": marshal(t, "bar")})}...)
   119  	cs := NewConfigMapKVStore(context.Background(), name, namespace, tc.clientset)
   120  	err := cs.Init(context.Background())
   121  	if err != nil {
   122  		t.Error("Failed to Init ConfigStore:", err)
   123  	}
   124  	cs.Set(context.Background(), "jimmy", "otherbar")
   125  	cs.Save(context.Background())
   126  	if tc.updated == nil {
   127  		t.Errorf("ConfigMap Not updated")
   128  	}
   129  	var ret string
   130  	err = cs.Get(context.Background(), "jimmy", &ret)
   131  	if err != nil {
   132  		t.Error("failed to return string:", err)
   133  	}
   134  	if ret != "otherbar" {
   135  		t.Errorf("got back unexpected value, wanted %q got %q", "bar", ret)
   136  	}
   137  }
   138  
   139  func TestLoadSaveUpdateEmptyMap(t *testing.T) {
   140  	// empty map, i.e. no data field
   141  	cm := corev1.ConfigMap{
   142  		TypeMeta: metav1.TypeMeta{
   143  			APIVersion: "v1",
   144  			Kind:       "ConfigMap",
   145  		},
   146  		ObjectMeta: metav1.ObjectMeta{
   147  			Name:      name,
   148  			Namespace: namespace,
   149  		},
   150  	}
   151  
   152  	tc := newTestClient([]runtime.Object{&cm}...)
   153  	cs := NewConfigMapKVStore(context.Background(), name, namespace, tc.clientset)
   154  	if err := cs.Init(context.Background()); err != nil {
   155  		t.Error("Failed to Init ConfigStore:", err)
   156  	}
   157  
   158  	if err := cs.Set(context.Background(), "jimmy", "otherbar"); err != nil {
   159  		t.Error("Failed to set key:", err)
   160  	}
   161  
   162  	if err := cs.Save(context.Background()); err != nil {
   163  		t.Error("Failed to save configmap:", err)
   164  	}
   165  
   166  	if tc.updated == nil {
   167  		t.Errorf("ConfigMap Not updated")
   168  	}
   169  	var ret string
   170  	err := cs.Get(context.Background(), "jimmy", &ret)
   171  	if err != nil {
   172  		t.Error("failed to return string:", err)
   173  	}
   174  	if ret != "otherbar" {
   175  		t.Errorf("got back unexpected value, wanted %q got %q", "bar", ret)
   176  	}
   177  }
   178  
   179  func TestLoadSaveUpdateComplex(t *testing.T) {
   180  	ts := testStruct{
   181  		LastThingProcessed: "somethingie",
   182  		Stuff:              []string{"first", "second", "third"},
   183  	}
   184  
   185  	tc := newTestClient([]runtime.Object{configMap(map[string]string{"foo": marshal(t, &ts)})}...)
   186  	cs := NewConfigMapKVStore(context.Background(), name, namespace, tc.clientset)
   187  	err := cs.Init(context.Background())
   188  	if err != nil {
   189  		t.Error("Failed to Init ConfigStore:", err)
   190  	}
   191  	ts2 := testStruct{
   192  		LastThingProcessed: "otherthingie",
   193  		Stuff:              []string{"fourth", "fifth", "sixth"},
   194  	}
   195  	cs.Set(context.Background(), "jimmy", &ts2)
   196  	cs.Save(context.Background())
   197  	if tc.updated == nil {
   198  		t.Errorf("ConfigMap Not updated")
   199  	}
   200  	var ret testStruct
   201  	err = cs.Get(context.Background(), "jimmy", &ret)
   202  	if err != nil {
   203  		t.Error("failed to return string:", err)
   204  	}
   205  	if !reflect.DeepEqual(ret, ts2) {
   206  		t.Errorf("got back unexpected value, wanted %+v got %+v", ts2, ret)
   207  	}
   208  }
   209  
   210  func marshal(t *testing.T, value interface{}) string {
   211  	bytes, err := json.Marshal(value)
   212  	if err != nil {
   213  		t.Fatalf("Failed to Marshal %q: %v", value, err)
   214  	}
   215  	return string(bytes)
   216  }
   217  
   218  func configMap(data map[string]string) *corev1.ConfigMap {
   219  	return &corev1.ConfigMap{
   220  		TypeMeta: metav1.TypeMeta{
   221  			APIVersion: "v1",
   222  			Kind:       "ConfigMap",
   223  		},
   224  		ObjectMeta: metav1.ObjectMeta{
   225  			Name:      name,
   226  			Namespace: namespace,
   227  		},
   228  		Data: data,
   229  	}
   230  }