Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ (go/v4): Add tests for the webhooks and controllers which are created so that suite_test are no longer ignored #3631

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2023 The Kubernetes authors.

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 v1

import (
. "github.com/onsi/ginkgo/v2"
)

var _ = Describe("CronJob Webhook", func() {

Context("When creating CronJob under Defaulting Webhook", func() {
It("Should fill in the default value if a required field is empty", func() {

// TODO(user): Add your logic here

})
})

Context("When creating CronJob under Validating Webhook", func() {
It("Should deny if a required field is empty", func() {

// TODO(user): Add your logic here

})

It("Should admit if all required fields are provided", func() {

// TODO(user): Add your logic here

})
})

})
1 change: 1 addition & 0 deletions pkg/plugins/common/kustomize/v2/scaffolds/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package scaffolds

import (
"fmt"

pluginutil "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"

log "github.com/sirupsen/logrus"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ func (f *ControllerTest) SetTemplateDefaults() error {
f.PackageName = "controllers"
}

f.IfExistsAction = machinery.OverwriteFile

log.Println("creating import for %", f.Resource.Path)
f.TemplateBody = controllerTestTemplate

Expand Down
1 change: 1 addition & 0 deletions pkg/plugins/golang/v4/scaffolds/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func (s *apiScaffolder) Scaffold() error {
if err := scaffold.Execute(
&controllers.SuiteTest{Force: s.force, K8SVersion: EnvtestK8SVersion},
&controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force},
&controllers.ControllerTest{Force: s.force, DoAPI: doAPI},
); err != nil {
return fmt.Errorf("error scaffolding controller: %v", err)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
Copyright 2022 The Kubernetes Authors.

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 api

import (
"fmt"
"path/filepath"
"strings"

log "github.com/sirupsen/logrus"

"sigs.k8s.io/kubebuilder/v3/pkg/machinery"
)

var _ machinery.Template = &WebhookTest{}

// WebhookTest scaffolds the file that sets up the webhook unit tests
type WebhookTest struct { // nolint:maligned
machinery.TemplateMixin
machinery.MultiGroupMixin
machinery.BoilerplateMixin
machinery.ResourceMixin

Force bool
}

// SetTemplateDefaults implements file.Template
func (f *WebhookTest) SetTemplateDefaults() error {
if f.Path == "" {
if f.MultiGroup && f.Resource.Group != "" {
f.Path = filepath.Join("api", "%[group]", "%[version]", "%[kind]_webhook_test.go")
} else {
f.Path = filepath.Join("api", "%[version]", "%[kind]_webhook_test.go")
}
}
f.Path = f.Resource.Replacer().Replace(f.Path)
log.Println(f.Path)

webhookTestTemplate := webhookTestTemplate
templates := make([]string, 0)
if f.Resource.HasDefaultingWebhook() {
templates = append(templates, defaultWebhookTestTemplate)
}
if f.Resource.HasValidationWebhook() {
templates = append(templates, validateWebhookTestTemplate)
}
if f.Resource.HasConversionWebhook() {
templates = append(templates, conversionWebhookTestTemplate)
}
f.TemplateBody = fmt.Sprintf(webhookTestTemplate, strings.Join(templates, "\n"))

if f.Force {
f.IfExistsAction = machinery.OverwriteFile
}

return nil
}

const webhookTestTemplate = `{{ .Boilerplate }}

package {{ .Resource.Version }}

import (
. "github.com/onsi/ginkgo/v2"
)

var _ = Describe("{{ .Resource.Kind }} Webhook", func() {
%s
})
`

const conversionWebhookTestTemplate = `
Context("When creating {{ .Resource.Kind }} under Conversion Webhook", func() {
It("Should get the converted version of {{ .Resource.Kind }}" , func() {

// TODO(user): Add your logic here

})
})
`

const validateWebhookTestTemplate = `
Context("When creating {{ .Resource.Kind }} under Validating Webhook", func() {
It("Should deny if a required field is empty", func() {

// TODO(user): Add your logic here

})

It("Should admit if all required fields are provided", func() {

// TODO(user): Add your logic here

})
})
`

const defaultWebhookTestTemplate = `
Context("When creating {{ .Resource.Kind }} under Defaulting Webhook", func() {
It("Should fill in the default value if a required field is empty", func() {

// TODO(user): Add your logic here

})
})
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
Copyright 2022 The Kubernetes Authors.

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 controllers

import (
"path/filepath"

log "github.com/sirupsen/logrus"

"sigs.k8s.io/kubebuilder/v3/pkg/machinery"
)

var _ machinery.Template = &ControllerTest{}

// ControllerTest scaffolds the file that sets up the controller unit tests
// nolint:maligned
type ControllerTest struct {
machinery.TemplateMixin
machinery.MultiGroupMixin
machinery.BoilerplateMixin
machinery.ResourceMixin

Force bool

DoAPI bool
}

// SetTemplateDefaults implements file.Template
func (f *ControllerTest) SetTemplateDefaults() error {
if f.Path == "" {
if f.MultiGroup && f.Resource.Group != "" {
f.Path = filepath.Join("internal", "controller", "%[group]", "%[kind]_controller_test.go")
} else {
f.Path = filepath.Join("internal", "controller", "%[kind]_controller_test.go")
}
}

f.Path = f.Resource.Replacer().Replace(f.Path)
log.Println(f.Path)

f.TemplateBody = controllerTestTemplate

if f.Force {
f.IfExistsAction = machinery.OverwriteFile
}

return nil
}

const controllerTestTemplate = `{{ .Boilerplate }}

{{if and .MultiGroup .Resource.Group }}
package {{ .Resource.PackageName }}
{{else}}
package controller
{{end}}

import (
{{ if .DoAPI -}}
"context"
{{- end }}
. "github.com/onsi/ginkgo/v2"
{{ if .DoAPI -}}

. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
{{ if not (isEmptyStr .Resource.Path) -}}
{{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
{{- end }}
{{- end }}
)

var _ = Describe("{{ .Resource.Kind }} Controller", func() {
Context("When reconciling a resource", func() {
{{ if .DoAPI -}}
const resourceName = "test-resource"

ctx := context.Background()

typeNamespacedName := types.NamespacedName{
Name: resourceName,
Namespace: "default", // TODO(user):Modify as needed
}
{{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}

BeforeEach(func() {
By("creating the custom resource for the Kind {{ .Resource.Kind }}")
err := k8sClient.Get(ctx, typeNamespacedName, {{ lower .Resource.Kind }})
if err != nil && errors.IsNotFound(err) {
resource := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{
ObjectMeta: metav1.ObjectMeta{
Name: resourceName,
Namespace: "default",
},
// TODO(user): Specify other spec details if needed.
}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
}
})

AfterEach(func() {
// TODO(user): Cleanup logic after each test, like removing the resource instance.
resource := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err).NotTo(HaveOccurred())

By("Cleanup the specific resource instance {{ .Resource.Kind }}")
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
})
{{- end }}
It("should successfully reconcile the resource", func() {
{{ if .DoAPI -}}
By("Reconciling the created resource")
controllerReconciler := &{{ .Resource.Kind }}Reconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
}

_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
{{- end }}
// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
// Example: If you expect a certain status condition after reconciliation, verify it here.
})
})
})
`
1 change: 1 addition & 0 deletions pkg/plugins/golang/v4/scaffolds/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func (s *webhookScaffolder) Scaffold() error {
if err := scaffold.Execute(
&api.Webhook{Force: s.force},
&templates.MainUpdater{WireWebhook: true},
&api.WebhookTest{Force: s.force},
); err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2023 The Kubernetes authors.

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 v1

import (
. "github.com/onsi/ginkgo/v2"
)

var _ = Describe("Captain Webhook", func() {

Context("When creating Captain under Defaulting Webhook", func() {
It("Should fill in the default value if a required field is empty", func() {

// TODO(user): Add your logic here

})
})

Context("When creating Captain under Validating Webhook", func() {
It("Should deny if a required field is empty", func() {

// TODO(user): Add your logic here

})

It("Should admit if all required fields are provided", func() {

// TODO(user): Add your logic here

})
})

})