diff --git a/go.mod b/go.mod index d2a3e720e6c..5ae7340f6f0 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,8 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/h2non/gock v1.2.0 // indirect + github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 11ca6910ea6..68039420477 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,10 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= +github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -27,6 +31,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= diff --git a/pkg/cli/alpha/command_test.go b/pkg/cli/alpha/command_test.go new file mode 100644 index 00000000000..f9f5c65ade5 --- /dev/null +++ b/pkg/cli/alpha/command_test.go @@ -0,0 +1,44 @@ +/* +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 alpha + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// Figuring out ways to test run these tests similar to existing. +// Currently unable to run without this on VSCode. Will remove once done +func TestCommand(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "alpha") +} + +var _ = Describe("NewScaffoldCommand", func() { + + When("NewScaffoldCommand", func() { + It("Testing the NewScaffoldCommand", func() { + cmd := NewScaffoldCommand() + Expect(cmd).NotTo(BeNil()) + Expect(cmd.Use).NotTo(Equal("")) + Expect(cmd.Use).To(ContainSubstring("generate")) + Expect(cmd.Short).NotTo(Equal("")) + Expect(cmd.Short).To(ContainSubstring("Re-scaffold a Kubebuilder project from its PROJECT file")) + Expect(cmd.Example).NotTo(Equal("")) + Expect(cmd.Example).To(ContainSubstring("kubebuilder alpha generate")) + }) + }) +}) diff --git a/pkg/cli/alpha/internal/update/prepare.go b/pkg/cli/alpha/internal/update/prepare.go index 577db3c43ed..faf4a24fcb9 100644 --- a/pkg/cli/alpha/internal/update/prepare.go +++ b/pkg/cli/alpha/internal/update/prepare.go @@ -75,7 +75,7 @@ func (opts *Update) defineFromVersion(config store.Store) (string, error) { func (opts *Update) defineToVersion() string { if len(opts.ToVersion) != 0 { - if !strings.HasPrefix(opts.FromVersion, "v") { + if !strings.HasPrefix(opts.ToVersion, "v") { return "v" + opts.ToVersion } return opts.ToVersion diff --git a/pkg/cli/alpha/internal/update/prepare_test.go b/pkg/cli/alpha/internal/update/prepare_test.go new file mode 100644 index 00000000000..b704b4aaf32 --- /dev/null +++ b/pkg/cli/alpha/internal/update/prepare_test.go @@ -0,0 +1,186 @@ +/* +Copyright 2025 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 update + +import ( + "os" + "path/filepath" + "testing" + + "github.com/h2non/gock" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "sigs.k8s.io/kubebuilder/v4/pkg/cli/alpha/internal/common" + "sigs.k8s.io/kubebuilder/v4/pkg/config" + "sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml" + v3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3" +) + +func TestCommand(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "update") +} + +var _ = Describe("Prepare for internal update", func() { + var ( + tmpDir string + workDir string + projectFile string + ) + + BeforeEach(func() { + tmpDir, _ = os.MkdirTemp("", "kubebuilder-prepare-test") + workDir, _ = os.Getwd() + os.Chdir(tmpDir) + projectFile = filepath.Join(tmpDir, yaml.DefaultPath) + + config.Register(config.Version{Number: 3}, func() config.Config { + return &v3.Cfg{Version: config.Version{Number: 3}, CliVersion: "1.0.0"} + }) + + gock.New("https://api.github.com"). + Get("/repos/kubernetes-sigs/kubebuilder/releases/latest"). + Reply(200). + JSON(map[string]string{ + "tag_name": "v1.1.0", + }) + }) + + AfterEach(func() { + os.Chdir(workDir) + os.RemoveAll(tmpDir) + defer gock.Off() + }) + + Context("Prepare", func() { + DescribeTable("should succeed for valid options", + func(options *Update) { + + const version = `version: "3"` + Expect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed()) + + result := options.Prepare() + Expect(result).To(BeNil()) + Expect(options.Prepare()).To(Succeed()) + Expect(options.FromVersion).To(Equal("v1.0.0")) + Expect(options.ToVersion).To(Equal("v1.1.0")) + }, + Entry("options", &Update{FromVersion: "v1.0.0", ToVersion: "v1.1.0", FromBranch: "test"}), + Entry("options", &Update{FromVersion: "1.0.0", ToVersion: "1.1.0", FromBranch: "test"}), + Entry("options", &Update{FromVersion: "v1.0.0", ToVersion: "v1.1.0"}), + Entry("options", &Update{FromVersion: "1.0.0"}), + Entry("options", &Update{ToVersion: "1.1.0"}), + Entry("options", &Update{}), + ) + + DescribeTable("Should fail to prepare if project path is undetermined", + func(options *Update) { + result := options.Prepare() + Expect(result).NotTo(BeNil()) + Expect(result.Error()).Should(ContainSubstring("failed to determine project path")) + }, + Entry("options", &Update{FromVersion: "v1.0.0", ToVersion: "v1.1.0", FromBranch: "test"}), + ) + + DescribeTable("Should fail if PROJECT config could not be loaded", + func(options *Update) { + const version = "" + Expect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed()) + + result := options.Prepare() + Expect(result).NotTo(BeNil()) + Expect(result.Error()).Should(ContainSubstring("failed to load PROJECT config")) + }, + Entry("options", &Update{FromVersion: "v1.0.0", ToVersion: "v1.1.0", FromBranch: "test"}), + ) + + DescribeTable("Should fail if FromVersion cannot be determined", + func(options *Update) { + + config.Register(config.Version{Number: 3}, func() config.Config { + return &v3.Cfg{Version: config.Version{Number: 3}} + }) + + const version = `version: "3"` + Expect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed()) + + // TODO: Update test. err is Nil as default branch in Prepare() is being hardcoded to "main". + // Hence, the defineFromVersion condition will never fail. + // result := options.Prepare() + // Expect(result).NotTo(BeNil()) + // Expect(result.Error()).Should(ContainSubstring("failed to determine the version")) + Expect(options.FromVersion).To(BeEquivalentTo("")) + }, + Entry("options", &Update{}), + ) + }) + + Context("DefineFromVersion", func() { + + DescribeTable("Should succeed when branch or CliVersion in Project config is present", + func(options *Update) { + + const version = `version: "3"` + Expect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed()) + + config, err := common.LoadProjectConfig(tmpDir) + Expect(err).NotTo(HaveOccurred()) + fromVersion, err := options.defineFromVersion(config) + Expect(err).To(BeNil()) + Expect(fromVersion).To(BeEquivalentTo("v1.0.0")) + }, + Entry("options", &Update{FromVersion: ""}), + Entry("options", &Update{FromBranch: "test"}), + Entry("options", &Update{FromVersion: "1.0.0", FromBranch: "test"}), + ) + DescribeTable("Should fail when branch and CliVersion in Project config are absent", + func(options *Update) { + + config.Register(config.Version{Number: 3}, func() config.Config { + return &v3.Cfg{Version: config.Version{Number: 3}} + }) + + const version = `version: "3"` + Expect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed()) + + config, err := common.LoadProjectConfig(tmpDir) + Expect(err).NotTo(HaveOccurred()) + fromVersion, err := options.defineFromVersion(config) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no version specified in PROJECT file")) + Expect(fromVersion).To(Equal("")) + }, + Entry("options", &Update{FromVersion: ""}), + Entry("options", &Update{FromVersion: "v1.0.0"}), + ) + }) + + Context("DefineToVersion", func() { + + DescribeTable("Should succeed.", + func(options *Update) { + toVersion := options.defineToVersion() + Expect(toVersion).To(BeEquivalentTo("v1.1.0")) + }, + Entry("options", &Update{ToVersion: "1.1.0"}), + Entry("options", &Update{ToVersion: "v1.1.0"}), + Entry("options", &Update{}), + ) + + }) + +}) diff --git a/pkg/cli/alpha/update_test.go b/pkg/cli/alpha/update_test.go new file mode 100644 index 00000000000..40fae090185 --- /dev/null +++ b/pkg/cli/alpha/update_test.go @@ -0,0 +1,33 @@ +/* +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 alpha + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("NewUpdateCommand", func() { + + When("NewUpdateCommand", func() { + It("Testing the NewUpdateCommand", func() { + cmd := NewUpdateCommand() + Expect(cmd).NotTo(BeNil()) + Expect(cmd.Use).NotTo(Equal("")) + Expect(cmd.Use).To(ContainSubstring("update")) + Expect(cmd.Short).NotTo(Equal("")) + Expect(cmd.Short).To(ContainSubstring("Update a Kubebuilder project to a newer version")) + }) + }) +})