// Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package controllers_test import ( "context" "errors" "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/stretchr/testify/mock" apiv1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" "time" hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" "github.com/ory/hydra-maester/controllers" mocks "github.com/ory/hydra-maester/controllers/mocks/hydra" "github.com/ory/hydra-maester/hydra" ) const ( timeout = time.Second * 5 tstNamespace = "default" tstSecret = "testSecret" ) var _ = Describe("OAuth2Client Controller", func() { Context("in a happy-path scenario", func() { Context("should call create OAuth2 client and", func() { It("create a Secret if it does not exist", func() { tstName, tstClientID, tstSecretName := "test", "testClientID", "my-secret-123" expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}} s := runtime.NewScheme() err := hydrav1alpha1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) err = apiv1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a // channel when it is finished. mgr, err := manager.New(cfg, manager.Options{ Scheme: s, Metrics: server.Options{ BindAddress: ":8080", }, }) Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() mch := &mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", Anything).Return(nil) mch.On("ListOAuth2Client", Anything).Return(nil, nil) mch.On("PostOAuth2Client", AnythingOfType("*hydra.OAuth2ClientJSON")).Return(func(o *hydra.OAuth2ClientJSON) *hydra.OAuth2ClientJSON { return &hydra.OAuth2ClientJSON{ ClientID: &tstClientID, Secret: ptr.To(tstSecret), GrantTypes: o.GrantTypes, ResponseTypes: o.ResponseTypes, RedirectURIs: o.RedirectURIs, Scope: o.Scope, Audience: o.Audience, Owner: o.Owner, } }, func(o *hydra.OAuth2ClientJSON) error { return nil }) recFn, requests := SetupTestReconcile(getAPIReconciler(mgr, mch)) Expect(add(mgr, recFn)).To(Succeed()) //Start the manager and the controller stopMgr := StartTestManager(mgr) instance := testInstance(tstName, tstSecretName) err = c.Create(context.TODO(), instance) // The instance object may not be a valid object because it might be missing some required fields. // Please modify the instance object by adding required fields and then remove the following if statement. if apierrors.IsInvalid(err) { Fail(fmt.Sprintf("failed to create object, got an invalid object error: %v", err)) return } Expect(err).NotTo(HaveOccurred()) Eventually(requests, timeout).Should(Receive(Equal(*expectedRequest))) //Verify the created CR instance status var retrieved hydrav1alpha1.OAuth2Client ok := client.ObjectKey{Name: tstName, Namespace: tstNamespace} err = c.Get(context.TODO(), ok, &retrieved) Expect(err).NotTo(HaveOccurred()) Expect(retrieved.Status.ReconciliationError.Code).To(BeEmpty()) Expect(retrieved.Status.ReconciliationError.Description).To(BeEmpty()) //Verify the created Secret var createdSecret apiv1.Secret ok = client.ObjectKey{Name: tstSecretName, Namespace: tstNamespace} err = k8sClient.Get(context.TODO(), ok, &createdSecret) Expect(err).NotTo(HaveOccurred()) Expect(createdSecret.Data[controllers.ClientIDKey]).To(Equal([]byte(tstClientID))) Expect(createdSecret.Data[controllers.ClientSecretKey]).To(Equal([]byte(tstSecret))) Expect(createdSecret.OwnerReferences).To(Equal(getOwnerReferenceTo(retrieved))) //delete instance c.Delete(context.TODO(), instance) //Ensure manager is stopped properly stopMgr.Done() }) It("update object status if the call failed", func() { tstName, tstSecretName := "test2", "my-secret-456" expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}} s := runtime.NewScheme() err := hydrav1alpha1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) err = apiv1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a // channel when it is finished. mgr, err := manager.New(cfg, manager.Options{Scheme: s, Metrics: server.Options{ BindAddress: ":8081", }, }) Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() mch := &mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("PostOAuth2Client", Anything).Return(nil, errors.New("error")) mch.On("DeleteOAuth2Client", Anything).Return(nil) mch.On("ListOAuth2Client", Anything).Return(nil, nil) recFn, requests := SetupTestReconcile(getAPIReconciler(mgr, mch)) Expect(add(mgr, recFn)).To(Succeed()) //Start the manager and the controller stopMgr := StartTestManager(mgr) instance := testInstance(tstName, tstSecretName) err = c.Create(context.TODO(), instance) // The instance object may not be a valid object because it might be missing some required fields. // Please modify the instance object by adding required fields and then remove the following if statement. if apierrors.IsInvalid(err) { Fail(fmt.Sprintf("failed to create object, got an invalid object error: %v", err)) return } Expect(err).NotTo(HaveOccurred()) Eventually(requests, timeout).Should(Receive(Equal(*expectedRequest))) //Verify the created CR instance status var retrieved hydrav1alpha1.OAuth2Client ok := client.ObjectKey{Name: tstName, Namespace: tstNamespace} err = c.Get(context.TODO(), ok, &retrieved) Expect(err).NotTo(HaveOccurred()) Expect(retrieved.Status.ReconciliationError).NotTo(BeNil()) Expect(retrieved.Status.ReconciliationError.Code).To(Equal(hydrav1alpha1.StatusRegistrationFailed)) Expect(retrieved.Status.ReconciliationError.Description).To(Equal("error")) //Verify no secret has been created var createdSecret apiv1.Secret ok = client.ObjectKey{Name: tstSecretName, Namespace: tstNamespace} err = k8sClient.Get(context.TODO(), ok, &createdSecret) Expect(err).To(HaveOccurred()) Expect(apierrors.IsNotFound(err)).To(BeTrue()) //delete instance c.Delete(context.TODO(), instance) //Ensure manager is stopped properly stopMgr.Done() }) It("use provided Secret if it exists", func() { tstName, tstClientID, tstSecretName := "test3", "testClientID-3", "my-secret-789" var postedClient *hydra.OAuth2ClientJSON expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}} s := runtime.NewScheme() err := hydrav1alpha1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) err = apiv1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a // channel when it is finished. mgr, err := manager.New(cfg, manager.Options{Scheme: s, Metrics: server.Options{ BindAddress: ":8082", }}) Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() mch := mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", Anything).Return(nil) mch.On("ListOAuth2Client", Anything).Return(nil, nil) mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("PostOAuth2Client", AnythingOfType("*hydra.OAuth2ClientJSON")).Return(func(o *hydra.OAuth2ClientJSON) *hydra.OAuth2ClientJSON { postedClient = &hydra.OAuth2ClientJSON{ ClientID: o.ClientID, Secret: o.Secret, GrantTypes: o.GrantTypes, ResponseTypes: o.ResponseTypes, RedirectURIs: o.RedirectURIs, Audience: o.Audience, Scope: o.Scope, Owner: o.Owner, } return postedClient }, func(o *hydra.OAuth2ClientJSON) error { return nil }) recFn, requests := SetupTestReconcile(getAPIReconciler(mgr, &mch)) Expect(add(mgr, recFn)).To(Succeed()) //Start the manager and the controller stopMgr := StartTestManager(mgr) //ensure secret exists secret := apiv1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: tstSecretName, Namespace: tstNamespace, }, Data: map[string][]byte{ controllers.ClientIDKey: []byte(tstClientID), controllers.ClientSecretKey: []byte(tstSecret), }, } err = c.Create(context.TODO(), &secret) Expect(err).NotTo(HaveOccurred()) instance := testInstance(tstName, tstSecretName) err = c.Create(context.TODO(), instance) // The instance object may not be a valid object because it might be missing some required fields. // Please modify the instance object by adding required fields and then remove the following if statement. if apierrors.IsInvalid(err) { Fail(fmt.Sprintf("failed to create object, got an invalid object error: %v", err)) return } Expect(err).NotTo(HaveOccurred()) Eventually(requests, timeout).Should(Receive(Equal(*expectedRequest))) //Verify the created CR instance status var retrieved hydrav1alpha1.OAuth2Client ok := client.ObjectKey{Name: tstName, Namespace: tstNamespace} err = c.Get(context.TODO(), ok, &retrieved) Expect(err).NotTo(HaveOccurred()) Expect(retrieved.Status.ReconciliationError.Code).To(BeEmpty()) Expect(retrieved.Status.ReconciliationError.Description).To(BeEmpty()) // Ensure that secret doesn't have OwnerReference set ok = client.ObjectKey{Name: tstSecretName, Namespace: tstNamespace} err = k8sClient.Get(context.TODO(), ok, &secret) Expect(err).To(BeNil()) Expect(len(secret.OwnerReferences)).To(Equal(0)) //delete instance c.Delete(context.TODO(), instance) //Ensure manager is stopped properly stopMgr.Done() }) It("update object status if provided Secret is invalid", func() { tstName, tstClientID, tstSecretName := "test4", "testClientID-4", "my-secret-000" expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}} s := runtime.NewScheme() err := hydrav1alpha1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) err = apiv1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a // channel when it is finished. mgr, err := manager.New(cfg, manager.Options{Scheme: s, Metrics: server.Options{ BindAddress: ":8083", }}) Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() mch := mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", Anything).Return(nil) mch.On("ListOAuth2Client", Anything).Return(nil, nil) recFn, requests := SetupTestReconcile(getAPIReconciler(mgr, &mch)) Expect(add(mgr, recFn)).To(Succeed()) //Start the manager and the controller stopMgr := StartTestManager(mgr) //ensure invalid secret exists secret := apiv1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: tstSecretName, Namespace: tstNamespace, }, Data: map[string][]byte{ controllers.ClientIDKey: []byte(tstClientID), //missing client secret key }, } err = c.Create(context.TODO(), &secret) Expect(err).NotTo(HaveOccurred()) instance := testInstance(tstName, tstSecretName) err = c.Create(context.TODO(), instance) // The instance object may not be a valid object because it might be missing some required fields. // Please modify the instance object by adding required fields and then remove the following if statement. if apierrors.IsInvalid(err) { Fail(fmt.Sprintf("failed to create object, got an invalid object error: %v", err)) return } Expect(err).NotTo(HaveOccurred()) Eventually(requests, timeout).Should(Receive(Equal(*expectedRequest))) //Verify the created CR instance status var retrieved hydrav1alpha1.OAuth2Client ok := client.ObjectKey{Name: tstName, Namespace: tstNamespace} err = c.Get(context.TODO(), ok, &retrieved) Expect(err).NotTo(HaveOccurred()) Expect(retrieved.Status.ReconciliationError).NotTo(BeNil()) Expect(retrieved.Status.ReconciliationError.Code).To(Equal(hydrav1alpha1.StatusInvalidSecret)) Expect(retrieved.Status.ReconciliationError.Description).To(Equal("CLIENT_SECRET property missing")) //delete instance c.Delete(context.TODO(), instance) //Ensure manager is stopped properly stopMgr.Done() }) It("tolerate nil client_secret if tokenEndpointAuthMethod is none", func() { tstName, tstClientID, tstSecretName := "test5", "testClientID-5", "my-secret-without-client-secret" expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}} s := runtime.NewScheme() err := hydrav1alpha1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) err = apiv1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a // channel when it is finished. mgr, err := manager.New(cfg, manager.Options{Scheme: s, Metrics: server.Options{ BindAddress: ":8085", }}) Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() mch := &mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", Anything).Return(nil) mch.On("ListOAuth2Client", Anything).Return(nil, nil) mch.On("PostOAuth2Client", AnythingOfType("*hydra.OAuth2ClientJSON")).Return(func(o *hydra.OAuth2ClientJSON) *hydra.OAuth2ClientJSON { return &hydra.OAuth2ClientJSON{ ClientID: &tstClientID, Secret: nil, GrantTypes: o.GrantTypes, ResponseTypes: o.ResponseTypes, RedirectURIs: o.RedirectURIs, Scope: o.Scope, Audience: o.Audience, Owner: o.Owner, } }, func(o *hydra.OAuth2ClientJSON) error { return nil }) recFn, requests := SetupTestReconcile(getAPIReconciler(mgr, mch)) Expect(add(mgr, recFn)).To(Succeed()) //Start the manager and the controller stopMgr := StartTestManager(mgr) instance := testInstance(tstName, tstSecretName) instance.Spec.TokenEndpointAuthMethod = "none" err = c.Create(context.TODO(), instance) // The instance object may not be a valid object because it might be missing some required fields. // Please modify the instance object by adding required fields and then remove the following if statement. if apierrors.IsInvalid(err) { Fail(fmt.Sprintf("failed to create object, got an invalid object error: %v", err)) return } Expect(err).NotTo(HaveOccurred()) Eventually(requests, timeout).Should(Receive(Equal(*expectedRequest))) //Verify the created CR instance status var retrieved hydrav1alpha1.OAuth2Client ok := client.ObjectKey{Name: tstName, Namespace: tstNamespace} err = c.Get(context.TODO(), ok, &retrieved) Expect(err).NotTo(HaveOccurred()) Expect(retrieved.Status.ReconciliationError.Code).To(BeEmpty()) Expect(retrieved.Status.ReconciliationError.Description).To(BeEmpty()) //Verify the created Secret var createdSecret apiv1.Secret ok = client.ObjectKey{Name: tstSecretName, Namespace: tstNamespace} err = k8sClient.Get(context.TODO(), ok, &createdSecret) Expect(err).NotTo(HaveOccurred()) Expect(createdSecret.Data[controllers.ClientIDKey]).To(Equal([]byte(tstClientID))) Expect(createdSecret.Data[controllers.ClientSecretKey]).To(BeNil()) Expect(createdSecret.OwnerReferences).To(Equal(getOwnerReferenceTo(retrieved))) //delete instance c.Delete(context.TODO(), instance) //Ensure manager is stopped properly stopMgr.Done() }) It("not delete OAuth2 clients with Orphan deletion policy", func() { tstName, tstClientID, tstSecretName := "test-orphan", "testClientID-orphan", "my-secret-orphan" expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}} s := runtime.NewScheme() err := hydrav1alpha1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) err = apiv1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a // channel when it is finished. mgr, err := manager.New(cfg, manager.Options{ Scheme: s, Metrics: server.Options{ BindAddress: ":8086", }, }) Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() deleteHasHappened := false mch := &mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", Anything).Return(func(id string) error { deleteHasHappened = true return nil }) mch.On("ListOAuth2Client", Anything).Return(func() []*hydra.OAuth2ClientJSON { return []*hydra.OAuth2ClientJSON{ { ClientID: &tstClientID, Secret: ptr.To(tstSecret), Owner: fmt.Sprintf("%s/%s", tstName, tstNamespace), }, } }, nil) mch.On("PostOAuth2Client", AnythingOfType("*hydra.OAuth2ClientJSON")).Return(func(o *hydra.OAuth2ClientJSON) *hydra.OAuth2ClientJSON { return &hydra.OAuth2ClientJSON{ ClientID: &tstClientID, Secret: ptr.To(tstSecret), GrantTypes: o.GrantTypes, ResponseTypes: o.ResponseTypes, RedirectURIs: o.RedirectURIs, Scope: o.Scope, Audience: o.Audience, Owner: o.Owner, } }, func(o *hydra.OAuth2ClientJSON) error { return nil }) recFn, requests := SetupTestReconcile(getAPIReconciler(mgr, mch)) Expect(add(mgr, recFn)).To(Succeed()) //Start the manager and the controller stopMgr := StartTestManager(mgr) // Create OAuth2 client with 'Orphan' policy instance := testInstance(tstName, tstSecretName) instance.Spec.DeletionPolicy = hydrav1alpha1.OAuth2ClientDeletionPolicyOrphan // Call creation API, to actually create the CRD. err = c.Create(context.TODO(), instance) if apierrors.IsInvalid(err) { Fail(fmt.Sprintf("failed to create object, got an invalid object error: %v", err)) return } Expect(err).NotTo(HaveOccurred()) Eventually(requests, timeout).Should(Receive(Equal(*expectedRequest))) // Call deletion API, which should not really delete the CRD because we are in orphan mode. err = c.Delete(context.TODO(), instance) if apierrors.IsInvalid(err) { Fail(fmt.Sprintf("failed to delete object, got an invalid object error: %v", err)) return } Expect(err).NotTo(HaveOccurred()) Eventually(requests, timeout).Should(Receive(Equal(*expectedRequest))) Expect(deleteHasHappened).To(BeFalse()) //Ensure manager is stopped properly stopMgr.Done() }) It("delete OAuth2 clients with Delete deletion policy", func() { tstName, tstClientID, tstSecretName := "test-delete", "testClientID-delete", "my-secret-delete" expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}} s := runtime.NewScheme() err := hydrav1alpha1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) err = apiv1.AddToScheme(s) Expect(err).NotTo(HaveOccurred()) // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a // channel when it is finished. mgr, err := manager.New(cfg, manager.Options{ Scheme: s, Metrics: server.Options{ BindAddress: ":8087", }, }) Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() deleteHasHappened := false mch := &mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", AnythingOfType("string")).Return(func(id string) error { deleteHasHappened = true return nil }) mch.On("ListOAuth2Client", Anything).Return(func() []*hydra.OAuth2ClientJSON { return []*hydra.OAuth2ClientJSON{ { ClientID: &tstClientID, Secret: ptr.To(tstSecret), Owner: fmt.Sprintf("%s/%s", tstName, tstNamespace), }, } }, nil) mch.On("PostOAuth2Client", AnythingOfType("*hydra.OAuth2ClientJSON")).Return(func(o *hydra.OAuth2ClientJSON) *hydra.OAuth2ClientJSON { return &hydra.OAuth2ClientJSON{ ClientID: &tstClientID, Secret: ptr.To(tstSecret), GrantTypes: o.GrantTypes, ResponseTypes: o.ResponseTypes, RedirectURIs: o.RedirectURIs, Scope: o.Scope, Audience: o.Audience, Owner: o.Owner, } }, func(o *hydra.OAuth2ClientJSON) error { return nil }) recFn, requests := SetupTestReconcile(getAPIReconciler(mgr, mch)) Expect(add(mgr, recFn)).To(Succeed()) //Start the manager and the controller stopMgr := StartTestManager(mgr) // Create OAuth2 client with 'Delete' policy instance := testInstance(tstName, tstSecretName) instance.Spec.DeletionPolicy = hydrav1alpha1.OAuth2ClientDeletionPolicyDelete // Call creation API, to actually create the CRD. err = c.Create(context.TODO(), instance) if apierrors.IsInvalid(err) { Fail(fmt.Sprintf("failed to create object, got an invalid object error: %v", err)) return } Expect(err).NotTo(HaveOccurred()) Eventually(requests, timeout).Should(Receive(Equal(*expectedRequest))) // Call deletion API, which should really delete the CRD because we are in orphan mode. err = c.Delete(context.TODO(), instance) if apierrors.IsInvalid(err) { Fail(fmt.Sprintf("failed to delete object, got an invalid object error: %v", err)) return } Expect(err).NotTo(HaveOccurred()) Eventually(requests, timeout).Should(Receive(Equal(*expectedRequest))) Expect(deleteHasHappened).To(BeTrue()) // Ensure manager is stopped properly. stopMgr.Done() }) }) }) }) func getOwnerReferenceTo(c hydrav1alpha1.OAuth2Client) []metav1.OwnerReference { return []metav1.OwnerReference{{ APIVersion: c.APIVersion, Kind: c.Kind, Name: c.Name, UID: c.UID, }} } // add adds a new Controller to mgr with r as the reconcile.Reconciler func add(mgr manager.Manager, r reconcile.Reconciler) error { // Create a new controller c, err := controller.New("api-gateway-controller", mgr, controller.Options{Reconciler: r}) if err != nil { return err } // Watch for changes to Api err = c.Watch(source.Kind(mgr.GetCache(), &hydrav1alpha1.OAuth2Client{}, &handler.TypedEnqueueRequestForObject[*hydrav1alpha1.OAuth2Client]{})) if err != nil { return err } return nil } func getAPIReconciler(mgr ctrl.Manager, mock hydra.Client) reconcile.Reconciler { clientMocker := func(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkipVerify bool) (hydra.Client, error) { return mock, nil } return controllers.New( mgr.GetClient(), mock, ctrl.Log.WithName("controllers").WithName("OAuth2Client"), controllers.WithClientFactory(clientMocker), ) } func testInstance(name, secretName string) *hydrav1alpha1.OAuth2Client { return &hydrav1alpha1.OAuth2Client{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: tstNamespace, }, Spec: hydrav1alpha1.OAuth2ClientSpec{ GrantTypes: []hydrav1alpha1.GrantType{"client_credentials"}, ResponseTypes: []hydrav1alpha1.ResponseType{"token"}, Scope: "a b c", RedirectURIs: []hydrav1alpha1.RedirectURI{"https://example.com"}, PostLogoutRedirectURIs: []hydrav1alpha1.RedirectURI{"https://example.com/logout"}, Audience: []string{"audience-a"}, SecretName: secretName, HydraAdmin: hydrav1alpha1.HydraAdmin{ URL: "http://hydra-admin", Port: 4445, Endpoint: "/client", ForwardedProto: "https", }, }} }