package cluster import ( "context" "testing" "github.com/go-logr/logr/testr" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache/informertest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/controller/controllertest" "edge-infra.dev/pkg/edge/info" "edge-infra.dev/pkg/sds/interlock/internal/config" "edge-infra.dev/pkg/sds/interlock/topic" "edge-infra.dev/pkg/sds/interlock/websocket" "edge-infra.dev/pkg/sds/lib/k8s/retryclient" ) func SetupTestCtx(t *testing.T) context.Context { logOptions := testr.Options{ LogTimestamp: true, Verbosity: -1, } ctx := ctrl.LoggerInto(context.Background(), testr.NewWithOptions(t, logOptions)) return ctx } func getTestConfigMap(t *testing.T, name, namespace string, data map[string]string) *v1.ConfigMap { t.Helper() return &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, Data: data, } } func TestIsEdgeInfoCM(t *testing.T) { tests := map[string]struct { input interface{} want bool }{ "Invalid": { input: struct{}{}, want: false, }, "Incorrect_CMName": { input: getTestConfigMap(t, "", info.EdgeConfigMapNS, nil), want: false, }, "Incorrect_CMNamespace": { input: getTestConfigMap(t, info.EdgeConfigMapName, "", nil), want: false, }, "Correct_CM": { input: getTestConfigMap(t, info.EdgeConfigMapName, info.EdgeConfigMapNS, nil), want: true, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { got := IsEdgeInfoCM(tc.input) assert.Equal(t, tc.want, got) }) } } type testStruct struct { state *State } func (t *testStruct) updateState(fn topic.UpdateFunc) error { return fn(t.state) } func TestUpdateName(t *testing.T) { tests := map[string]struct { previousClusterName string want string }{ "NotPreviouslySet": { previousClusterName: "", want: "test-name", }, "PreviouslySet": { previousClusterName: "previous-name", want: "test-name", }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { ts := testStruct{&State{Name: tc.previousClusterName}} cm := getTestConfigMap(t, "name", "ns", map[string]string{ "cluster.name": tc.want, }) updateName(ts.updateState, cm) assert.Equal(t, tc.want, ts.state.Name) }) } } type mockCache struct { *informertest.FakeInformers } // integration test for SetupAPIInformers method // and NameEventHandler OnAdd method func TestSetupAPIInformersAdd(t *testing.T) { testCluster, fakeInformer := newTestClusterWithFakeInformer(t, &State{}) cm := getTestConfigMap(t, info.EdgeConfigMapName, info.EdgeConfigMapNS, map[string]string{ "cluster.name": "test-name", }) fakeInformer.Add(cm) clusterState := testCluster.topic.State() state, ok := clusterState.(*State) require.True(t, ok) assert.Equal(t, "test-name", state.Name) } // integration test for SetupAPIInformers method // and NameEventHandler OnUpdate method func TestSetupAPIInformersUpdate(t *testing.T) { testCluster, fakeInformer := newTestClusterWithFakeInformer(t, &State{}) oldCM := getTestConfigMap(t, info.EdgeConfigMapName, info.EdgeConfigMapNS, map[string]string{ "cluster.name": "test-name", }) newCM := getTestConfigMap(t, info.EdgeConfigMapName, info.EdgeConfigMapNS, map[string]string{ "cluster.name": "new-name", }) fakeInformer.Update(oldCM, newCM) clusterState := testCluster.topic.State() state, ok := clusterState.(*State) require.True(t, ok) assert.Equal(t, "new-name", state.Name) } // integration test for SetupAPIInformers method // and NameEventHandler OnDelete method func TestSetupAPIInformersDelete(t *testing.T) { testCluster, fakeInformer := newTestClusterWithFakeInformer(t, &State{"test-name"}) cm := getTestConfigMap(t, info.EdgeConfigMapName, info.EdgeConfigMapNS, map[string]string{ "cluster.name": "test-name", }) fakeInformer.Delete(cm) clusterState := testCluster.topic.State() state, ok := clusterState.(*State) require.True(t, ok) assert.Equal(t, "test-name", state.Name) } func newTestClusterWithFakeInformer(t *testing.T, initState *State) (*Cluster, *controllertest.FakeInformer) { testCfg := config.Config{ Cache: mockCache{&informertest.FakeInformers{}}, } testCluster := Cluster{ topic: topic.NewTopic( TopicName, initState, nil, websocket.NewManager(), ), } err := testCluster.SetupAPIInformers(context.Background(), &testCfg) require.NoError(t, err) informer, err := testCfg.Cache.GetInformer(context.Background(), &v1.ConfigMap{}) require.NoError(t, err) fakeInformer, ok := informer.(*controllertest.FakeInformer) require.True(t, ok) return &testCluster, fakeInformer } func TestNewState(t *testing.T) { ei := &info.EdgeInfo{ BannerName: "example", ProjectID: "example", Store: "test-cluster", Fleet: "example", ClusterType: "example", Location: "example", ForemanProjectID: "example", BannerEdgeID: "example", ClusterEdgeID: "example", EdgeAPIEndpoint: "example", } cm := ei.ToConfigMap() cli := getFakeKubeClient(cm) cfg := &config.Config{ Fs: afero.NewMemMapFs(), KubeRetryClient: retryclient.New(cli, cli, retryclient.Config{}), Cache: &informertest.FakeInformers{}, } state, err := newState(SetupTestCtx(t), cfg) require.NoError(t, err) assert.Equal(t, "test-cluster", state.Name) } func getFakeKubeClient(initObjs ...client.Object) client.Client { return fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(initObjs...).Build() } func createScheme() *kruntime.Scheme { scheme := kruntime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) return scheme }