/*
Copyright 2019 The Jetstack cert-manager contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package webhookbootstrap

import (
	"context"
	"crypto"
	"crypto/x509"
	"github.com/jetstack/cert-manager/pkg/util/pki"
	"github.com/jetstack/cert-manager/test/unit/gen"
	"testing"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	coretesting "k8s.io/client-go/testing"

	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
)

const (
	defaultWebhookNamespace   = "testns"
	defaultWebhookCAName      = "ca-secret"
	defaultWebhookServingName = "serving-secret"
)

var (
	defaultWebhookDNSNames = []string{"testdomain.com"}
)

type testT struct {
	builder                 *testpkg.Builder
	generatePrivateKeyBytes generatePrivateKeyBytesFn
	signCertificate         signCertificateFunc
	key                     string
	expectedErr             bool
}

func runTest(t *testing.T, test testT) {
	test.builder.T = t
	c := &controller{}
	test.builder.Init()
	defer test.builder.Stop()

	test.builder.Context.WebhookBootstrapOptions.Namespace = defaultWebhookNamespace
	test.builder.Context.WebhookBootstrapOptions.DNSNames = defaultWebhookDNSNames
	test.builder.Context.WebhookBootstrapOptions.ServingSecretName = defaultWebhookServingName
	test.builder.Context.WebhookBootstrapOptions.CASecretName = defaultWebhookCAName
	_, waitSync, runFn, err := c.Register(test.builder.Context)
	if err != nil {
		t.Errorf("failed to setup controller: %v", err)
		t.FailNow()
	}
	test.builder.RegisterAdditionalSyncFuncs(waitSync...)
	test.builder.Start(runFn...)

	if test.generatePrivateKeyBytes != nil {
		c.generatePrivateKeyBytes = test.generatePrivateKeyBytes
	}
	if test.signCertificate != nil {
		c.signCertificate = test.signCertificate
	}

	test.builder.Sync()

	err = c.ProcessItem(context.Background(), test.key)
	if err != nil && !test.expectedErr {
		t.Errorf("expected to not get an error, but got: %v", err)
	}
	if err == nil && test.expectedErr {
		t.Errorf("expected to get an error but did not get one")
	}

	test.builder.CheckAndFinish(err)
}

func TestProcessItem(t *testing.T) {
	exampleBundle := mustCreateCryptoBundle(t, gen.Certificate(defaultWebhookCAName,
		gen.SetCertificateDNSNames(defaultWebhookDNSNames...),
		gen.SetCertificateOrganization("cert-manager.system"),
	))
	exampleBadDNSNameBundle := mustCreateCryptoBundle(t, gen.Certificate(defaultWebhookCAName,
		gen.SetCertificateDNSNames("nottherightdomain.com"),
		gen.SetCertificateOrganization("cert-manager.system"),
	))
	exampleBundleCA := mustCreateCryptoBundle(t, gen.Certificate(defaultWebhookCAName,
		gen.SetCertificateCommonName("cert-manager.webhook.ca"),
		gen.SetCertificateIsCA(true),
		gen.SetCertificateOrganization("cert-manager.system"),
	))

	caSecret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      defaultWebhookCAName,
			Namespace: defaultWebhookNamespace,
		},
		Type: corev1.SecretTypeTLS,
	}
	caSecretKey := caSecret.Namespace + "/" + caSecret.Name

	servingSecret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      defaultWebhookServingName,
			Namespace: defaultWebhookNamespace,
		},
		Type: corev1.SecretTypeTLS,
	}
	servingSecretKey := servingSecret.Namespace + "/" + servingSecret.Name

	tests := map[string]testT{
		"do nothing if the secret's namespace does not match the webhook namespace": {
			key: "notmyns/" + defaultWebhookCAName,
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookCAName,
							Namespace: "notmyns",
						},
					},
				},
				ExpectedActions: []testpkg.Action{},
				ExpectedEvents:  []string{},
			},
		},
		"generate a new private key for the CA secret if none exists": {
			key:                     caSecretKey,
			generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					caSecret,
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						caSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: caSecret.Namespace,
								Name:      caSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       nil,
								corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
								cmmeta.TLSCAKey:         nil,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"generate a new private key for the CA secret if existing private key is garbage": {
			key:                     caSecretKey,
			generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookCAName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: []byte("garbage"),
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						caSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: caSecret.Namespace,
								Name:      caSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       nil,
								corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
								cmmeta.TLSCAKey:         nil,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"return an error for the serving secret if the ca secret is empty": {
			key:                     servingSecretKey,
			generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					caSecret,
					servingSecret,
				},
				ExpectedActions: []testpkg.Action{},
				ExpectedEvents:  []string{},
			},
			expectedErr: true,
		},
		"return an error for the serving secret if the ca certificate data is empty": {
			key:                     servingSecretKey,
			generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookCAName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
					servingSecret,
				},
				ExpectedActions: []testpkg.Action{},
				ExpectedEvents:  []string{},
			},
			expectedErr: true,
		},
		"generate a new private key for the serving secret if none exists": {
			key:                     servingSecretKey,
			generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookCAName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
							corev1.TLSCertKey:       exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
					servingSecret,
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						servingSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: servingSecret.Namespace,
								Name:      servingSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       nil,
								corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
								cmmeta.TLSCAKey:         nil,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"generate a new private key for the serving secret if existing private key is garbage": {
			key:                     servingSecretKey,
			generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookCAName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
							corev1.TLSCertKey:       exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookServingName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: []byte("garbage"),
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						servingSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: servingSecret.Namespace,
								Name:      servingSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       nil,
								corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
								cmmeta.TLSCAKey:         nil,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"sign a new CA certificate if none currently exists": {
			key:             caSecretKey,
			signCertificate: testSignCertificateFn(exampleBundleCA.certBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookCAName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						caSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: caSecret.Namespace,
								Name:      caSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       exampleBundleCA.certBytes,
								corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
								cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"sign a new CA certificate if existing one is garbage": {
			key:             caSecretKey,
			signCertificate: testSignCertificateFn(exampleBundleCA.certBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookCAName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
							corev1.TLSCertKey:       []byte("garbage"),
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						caSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: caSecret.Namespace,
								Name:      caSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       exampleBundleCA.certBytes,
								corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
								cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"sign a new serving certificate if none currently exists": {
			key:             servingSecretKey,
			signCertificate: testSignCertificateFn(exampleBundle.certBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Namespace: caSecret.Namespace,
							Name:      caSecret.Name,
							Annotations: map[string]string{
								cmapi.AllowsInjectionFromSecretAnnotation: "true",
							},
						},
						Data: map[string][]byte{
							corev1.TLSCertKey:       exampleBundleCA.certBytes,
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
							cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookServingName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						servingSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: servingSecret.Namespace,
								Name:      servingSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       exampleBundle.certBytes,
								corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
								cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"sign a new serving certificate if existing one is garbage": {
			key:             servingSecretKey,
			signCertificate: testSignCertificateFn(exampleBundle.certBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Namespace: caSecret.Namespace,
							Name:      caSecret.Name,
							Annotations: map[string]string{
								cmapi.AllowsInjectionFromSecretAnnotation: "true",
							},
						},
						Data: map[string][]byte{
							corev1.TLSCertKey:       exampleBundleCA.certBytes,
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
							cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookServingName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						servingSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: servingSecret.Namespace,
								Name:      servingSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       exampleBundle.certBytes,
								corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
								cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"sign a new serving certificate if existing one contains mismatching private/cert pair": {
			key:             servingSecretKey,
			signCertificate: testSignCertificateFn(exampleBundle.certBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Namespace: caSecret.Namespace,
							Name:      caSecret.Name,
							Annotations: map[string]string{
								cmapi.AllowsInjectionFromSecretAnnotation: "true",
							},
						},
						Data: map[string][]byte{
							corev1.TLSCertKey:       exampleBundleCA.certBytes,
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
							cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookServingName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSCertKey:       exampleBundleCA.certBytes,
							corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
							cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						servingSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: servingSecret.Namespace,
								Name:      servingSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       exampleBundle.certBytes,
								corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
								cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"sign a new serving certificate if existing one contains wrong dnsNames": {
			key:             servingSecretKey,
			signCertificate: testSignCertificateFn(exampleBundle.certBytes),
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Namespace: caSecret.Namespace,
							Name:      caSecret.Name,
						},
						Data: map[string][]byte{
							corev1.TLSCertKey:       exampleBundleCA.certBytes,
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
							cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookServingName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSCertKey:       exampleBadDNSNameBundle.certBytes,
							corev1.TLSPrivateKeyKey: exampleBadDNSNameBundle.privateKeyBytes,
							cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{
					testpkg.NewAction(coretesting.NewUpdateAction(
						corev1.SchemeGroupVersion.WithResource("secrets"),
						servingSecret.Namespace,
						&corev1.Secret{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: servingSecret.Namespace,
								Name:      servingSecret.Name,
								Annotations: map[string]string{
									cmapi.AllowsInjectionFromSecretAnnotation: "true",
								},
							},
							Data: map[string][]byte{
								corev1.TLSCertKey:       exampleBundle.certBytes,
								corev1.TLSPrivateKeyKey: exampleBadDNSNameBundle.privateKeyBytes,
								cmmeta.TLSCAKey:         exampleBundleCA.certBytes,
							},
							Type: corev1.SecretTypeTLS,
						},
					)),
				},
				ExpectedEvents: []string{},
			},
		},
		"do nothing if the existing CA secret is up to date": {
			key: caSecretKey,
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookCAName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
							corev1.TLSCertKey:       exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{},
				ExpectedEvents:  []string{},
			},
		},
		"do nothing if the existing serving secret is up to date": {
			key: servingSecretKey,
			builder: &testpkg.Builder{
				KubeObjects: []runtime.Object{
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookCAName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
							corev1.TLSCertKey:       exampleBundleCA.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
					&corev1.Secret{
						ObjectMeta: metav1.ObjectMeta{
							Name:      defaultWebhookServingName,
							Namespace: defaultWebhookNamespace,
						},
						Data: map[string][]byte{
							corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
							corev1.TLSCertKey:       exampleBundle.certBytes,
						},
						Type: corev1.SecretTypeTLS,
					},
				},
				ExpectedActions: []testpkg.Action{},
				ExpectedEvents:  []string{},
			},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			runTest(t, test)
		})
	}
}

type cryptoBundle struct {
	// certificate is the Certificate resource used to create this bundle
	certificate *cmapi.Certificate

	// privateKey is the private key used as the complement to the certificates
	// in this bundle
	privateKey      crypto.Signer
	privateKeyBytes []byte

	// cert is a signed certificate
	cert      *x509.Certificate
	certBytes []byte
}

func mustCreateCryptoBundle(t *testing.T, crt *cmapi.Certificate) cryptoBundle {
	c, err := createCryptoBundle(crt)
	if err != nil {
		t.Fatalf("error generating crypto bundle: %v", err)
	}
	return *c
}

func createCryptoBundle(crt *cmapi.Certificate) (*cryptoBundle, error) {
	privateKey, err := pki.GeneratePrivateKeyForCertificate(crt)
	if err != nil {
		return nil, err
	}

	privateKeyBytes, err := pki.EncodePrivateKey(privateKey, crt.Spec.KeyEncoding)
	if err != nil {
		return nil, err
	}

	unsignedCert, err := pki.GenerateTemplate(crt)
	if err != nil {
		return nil, err
	}

	certBytes, cert, err := pki.SignCertificate(unsignedCert, unsignedCert, privateKey.Public(), privateKey)
	if err != nil {
		return nil, err
	}

	return &cryptoBundle{
		certificate:     crt,
		privateKey:      privateKey,
		privateKeyBytes: privateKeyBytes,
		cert:            cert,
		certBytes:       certBytes,
	}, nil
}

func testGeneratePrivateKeyBytesFn(b []byte) generatePrivateKeyBytesFn {
	return func(*cmapi.Certificate) ([]byte, error) {
		return b, nil
	}
}

func testSignCertificateFn(b []byte) signCertificateFunc {
	return func(_ *cmapi.Certificate, _, _ crypto.Signer, _ *x509.Certificate) ([]byte, error) {
		return b, nil
	}
}
