package controllers import ( "fmt" "reflect" "testing" "github.com/golang/mock/gomock" appv1 "github.com/kakao/bluegreen/api/v1" "github.com/kakao/bluegreen/controllers/bluegreen" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) var ( testScheme = runtime.NewScheme() ) func init() { utilruntime.Must(clientgoscheme.AddToScheme(testScheme)) utilruntime.Must(appv1.AddToScheme(testScheme)) } func TestBlueGreenResconciler(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() c := bluegreen.NewMockClient(ctrl) r := &BlueGreenReconciler{ Client: c, Scheme: testScheme, } req := reconcile.Request{NamespacedName: types.NamespacedName{ Namespace: "test", Name: "bluegreen", }} bg := appv1.BlueGreen{ ObjectMeta: metav1.ObjectMeta{ Namespace: req.Namespace, Name: req.Name, }, Spec: appv1.BlueGreenSpec{ RouteTo: appv1.Blue, BlueSpec: &v1.PodSpec{ Containers: []v1.Container{{Name: "test"}}, }, GreenSpec: nil, }, } blugreenMatcher := &GenericMatcher[*appv1.BlueGreen]{func(in *appv1.BlueGreen) bool { return reflect.DeepEqual(bg, *in) }} // EXPECTING c.EXPECT().Get(gomock.Any(), gomock.Eq(req.NamespacedName), gomock.Any()). SetArg(2, bg). Return(nil) c.EXPECT().CreateOrUpdateService(gomock.Any(), blugreenMatcher). Return(nil) c.EXPECT().CreateOrUpdateDeployment(gomock.Any(), blugreenMatcher, gomock.Eq(appv1.Blue), gomock.Eq(*bg.Spec.BlueSpec)). Return(nil) c.EXPECT().UpdateStatus(gomock.Any(), &GenericMatcher[*appv1.BlueGreen]{func(bg *appv1.BlueGreen) bool { return *bg.Status.RouteTo == appv1.Blue }}, ).Return(nil) // Reconciling result, err := r.Reconcile(ctx, req) // Validation assert.NoError(t, err) assert.Equal(t, reconcile.Result{Requeue: false}, result) } var _ gomock.Matcher = &GenericMatcher[interface{}]{} type GenericMatcher[T interface{}] struct { F func(T) bool } func (m *GenericMatcher[T]) Matches(x interface{}) bool { if v, ok := x.(T); ok { return m.F(v) } else { return false } } func (m *GenericMatcher[T]) String() string { return fmt.Sprintf("matching %v", reflect.TypeOf(new(T))) } var _ gomock.Matcher = &RuntimeObjectMatcher{} type RuntimeObjectMatcher struct { object runtime.Object } // Matches implements gomock.Matcher func (r *RuntimeObjectMatcher) Matches(x interface{}) bool { return reflect.DeepEqual(r.object, x) } // String implements gomock.Matcher func (r *RuntimeObjectMatcher) String() string { gok := r.object.GetObjectKind().GroupVersionKind() return fmt.Sprintf("should match %s/%s/%s", gok.Group, gok.Version, gok.Kind) }