Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions cmd/virtwork/cleanupe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// Copyright 2026 Red Hat
// SPDX-License-Identifier: Apache-2.0

package main

import (
"bytes"
"os"
"path/filepath"
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubevirtv1 "kubevirt.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/opdev/virtwork/internal/cluster"
"github.com/opdev/virtwork/internal/constants"
)

func fakeConnectEmpty(_ string) (client.Client, string, error) {
scheme := cluster.NewScheme()
c := fake.NewClientBuilder().WithScheme(scheme).Build()
return c, "fake-context", nil
}

func fakeConnectWithResources(_ string) (client.Client, string, error) {
scheme := cluster.NewScheme()
vm := &kubevirtv1.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "virtwork-cpu-0",
Namespace: constants.DefaultNamespace,
Labels: map[string]string{
constants.LabelManagedBy: constants.ManagedByValue,
constants.LabelRunID: "test-run-001",
},
},
}
c := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(vm).
Build()
return c, "fake-context", nil
}

var _ = Describe("cleanupE", func() {
Context("error paths", func() {
It("returns error for nonexistent config file", func() {
rootCmd := newRootCmd()
rootCmd.SetArgs([]string{"cleanup", "--config", "/nonexistent/config.yaml"})
err := rootCmd.Execute()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("config"))
})

It("returns error when initAuditor fails", func() {
rootCmd := newRootCmd()
rootCmd.SetArgs([]string{
"cleanup",
"--audit-db", "/nonexistent/deeply/nested/path/db.sqlite",
})
err := rootCmd.Execute()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("auditor"))
})

It("returns error for malformed kubeconfig", func() {
tmpDir := GinkgoT().TempDir()
badKubeconfig := filepath.Join(tmpDir, "kubeconfig")
Expect(os.WriteFile(badKubeconfig, []byte(`not valid yaml{{{`), 0o600)).To(Succeed())

rootCmd := newRootCmd()
rootCmd.SetArgs([]string{
"cleanup",
"--kubeconfig", badKubeconfig,
"--no-audit",
})
err := rootCmd.Execute()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("connecting to cluster"))
})
})

Context("with empty fake client", func() {
var origConnect func(string) (client.Client, string, error)

BeforeEach(func() {
origConnect = clusterConnect
clusterConnect = fakeConnectEmpty
})

AfterEach(func() {
clusterConnect = origConnect
})

It("reports nothing to clean up when no managed resources exist", func() {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetArgs([]string{"cleanup", "--no-audit", "--yes"})
Expect(rootCmd.Execute()).To(Succeed())

output := buf.String()
Expect(output).To(ContainSubstring("nothing to clean up"))
})

It("reports nothing in dry-run mode", func() {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetArgs([]string{"cleanup", "--dry-run", "--no-audit"})
Expect(rootCmd.Execute()).To(Succeed())

output := buf.String()
Expect(output).To(ContainSubstring("nothing to clean up"))
})

It("reports nothing with --run-id filter", func() {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetArgs([]string{
"cleanup", "--run-id", "nonexistent-run", "--no-audit",
})
Expect(rootCmd.Execute()).To(Succeed())

output := buf.String()
Expect(output).To(ContainSubstring("nothing to clean up"))
})

It("covers all three cleanup mode branches", func() {
for _, tc := range []struct {
args []string
}{
{[]string{"cleanup", "--no-audit", "--yes"}},
{[]string{"cleanup", "--dry-run", "--no-audit"}},
{[]string{"cleanup", "--run-id", "x", "--no-audit"}},
} {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetArgs(tc.args)
Expect(rootCmd.Execute()).To(Succeed())
}
})
})

Context("with pre-populated fake client", func() {
var origConnect func(string) (client.Client, string, error)

BeforeEach(func() {
origConnect = clusterConnect
clusterConnect = fakeConnectWithResources
})

AfterEach(func() {
clusterConnect = origConnect
})

It("shows preview and skips deletion in dry-run mode", func() {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetArgs([]string{"cleanup", "--dry-run", "--no-audit"})
Expect(rootCmd.Execute()).To(Succeed())

output := buf.String()
Expect(output).To(ContainSubstring("dry-run mode"))
})

It("deletes resources when --yes is set", func() {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetArgs([]string{"cleanup", "--yes", "--no-audit"})
Expect(rootCmd.Execute()).To(Succeed())

output := buf.String()
Expect(output).To(ContainSubstring("cleanup complete"))
})

It("deletes resources when --run-id matches (skips prompt)", func() {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetArgs([]string{
"cleanup", "--run-id", "test-run-001", "--no-audit",
})
Expect(rootCmd.Execute()).To(Succeed())

output := buf.String()
Expect(output).To(ContainSubstring("cleanup complete"))
})

It("aborts when user declines confirmation prompt", func() {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetIn(strings.NewReader("no\n"))
rootCmd.SetArgs([]string{"cleanup", "--no-audit"})
Expect(rootCmd.Execute()).To(Succeed())

output := buf.String()
Expect(output).To(ContainSubstring("cleanup aborted"))
})

It("proceeds when user confirms at prompt", func() {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetIn(strings.NewReader("yes\n"))
rootCmd.SetArgs([]string{"cleanup", "--no-audit"})
Expect(rootCmd.Execute()).To(Succeed())

output := buf.String()
Expect(output).To(ContainSubstring("cleanup complete"))
})

It("deletes resources with --delete-namespace", func() {
rootCmd := newRootCmd()
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetArgs([]string{
"cleanup", "--yes", "--delete-namespace", "--no-audit",
})
Expect(rootCmd.Execute()).To(Succeed())

output := buf.String()
Expect(output).To(ContainSubstring("cleanup complete"))
})
})
})
96 changes: 96 additions & 0 deletions cmd/virtwork/cmdstructure_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2026 Red Hat
// SPDX-License-Identifier: Apache-2.0

package main

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

var _ = Describe("newRootCmd", func() {
It("creates a command named virtwork", func() {
cmd := newRootCmd()
Expect(cmd.Use).To(Equal("virtwork"))
})

It("has run, cleanup, and version subcommands", func() {
cmd := newRootCmd()
names := make([]string, 0)
for _, sub := range cmd.Commands() {
names = append(names, sub.Name())
}
Expect(names).To(ContainElements("run", "cleanup", "version"))
})

It("has persistent flags for namespace, kubeconfig, config, verbose, audit", func() {
cmd := newRootCmd()
Expect(cmd.PersistentFlags().Lookup("namespace")).NotTo(BeNil())
Expect(cmd.PersistentFlags().Lookup("kubeconfig")).NotTo(BeNil())
Expect(cmd.PersistentFlags().Lookup("config")).NotTo(BeNil())
Expect(cmd.PersistentFlags().Lookup("verbose")).NotTo(BeNil())
Expect(cmd.PersistentFlags().Lookup("audit")).NotTo(BeNil())
Expect(cmd.PersistentFlags().Lookup("no-audit")).NotTo(BeNil())
Expect(cmd.PersistentFlags().Lookup("audit-db")).NotTo(BeNil())
})

It("silences usage on error", func() {
cmd := newRootCmd()
Expect(cmd.SilenceUsage).To(BeTrue())
})
})

var _ = Describe("newRunCmd", func() {
It("creates a command named run", func() {
cmd := newRunCmd()
Expect(cmd.Use).To(Equal("run"))
})

It("has expected flags", func() {
cmd := newRunCmd()
Expect(cmd.Flags().Lookup("workloads")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("vm-count")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("cpu-cores")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("memory")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("dry-run")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("no-wait")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("timeout")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("ssh-user")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("ssh-password")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("ssh-key")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("ssh-key-file")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("vm-concurrency")).NotTo(BeNil())
})

It("has RunE set to production runE function", func() {
cmd := newRunCmd()
Expect(cmd.RunE).NotTo(BeNil())
})
})

var _ = Describe("newCleanupCmd", func() {
It("creates a command named cleanup", func() {
cmd := newCleanupCmd()
Expect(cmd.Use).To(Equal("cleanup"))
})

It("has expected flags", func() {
cmd := newCleanupCmd()
Expect(cmd.Flags().Lookup("delete-namespace")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("run-id")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("dry-run")).NotTo(BeNil())
Expect(cmd.Flags().Lookup("yes")).NotTo(BeNil())
})

It("has -y shorthand for --yes", func() {
cmd := newCleanupCmd()
flag := cmd.Flags().Lookup("yes")
Expect(flag).NotTo(BeNil())
Expect(flag.Shorthand).To(Equal("y"))
})

It("has RunE set to production cleanupE function", func() {
cmd := newCleanupCmd()
Expect(cmd.RunE).NotTo(BeNil())
})
})
Loading
Loading