From 66755d2b1305cb986b250be3021931443ea4e094 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 21 Apr 2026 16:52:40 +0200 Subject: [PATCH 1/7] fix(cli): refresh VNC client config on reconnect Signed-off-by: Daniil Antoshin --- src/cli/internal/cmd/vnc/vnc.go | 26 ++++-- src/cli/internal/cmd/vnc/vnc_test.go | 131 +++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 src/cli/internal/cmd/vnc/vnc_test.go diff --git a/src/cli/internal/cmd/vnc/vnc.go b/src/cli/internal/cmd/vnc/vnc.go index 49bd029ce1..6e5086bfe2 100644 --- a/src/cli/internal/cmd/vnc/vnc.go +++ b/src/cli/internal/cmd/vnc/vnc.go @@ -74,6 +74,11 @@ var ( customPort = 0 ) +var ( + clientAndNamespaceFromContext = clientconfig.ClientAndNamespaceFromContext + connectFunc = connect +) + func NewCommand() *cobra.Command { vnc := &VNC{} cmd := &cobra.Command{ @@ -94,17 +99,10 @@ func NewCommand() *cobra.Command { type VNC struct{} func (o *VNC) Run(cmd *cobra.Command, args []string) error { - client, defaultNamespace, _, err := clientconfig.ClientAndNamespaceFromContext(cmd.Context()) + targetNamespace, vmName, err := templates.ParseTarget(args[0]) if err != nil { return err } - namespace, vmName, err := templates.ParseTarget(args[0]) - if err != nil { - return err - } - if namespace == "" { - namespace = defaultNamespace - } // Format the listening address to account for the port (ex: 127.0.0.0:5900) // Set listenAddress to localhost if proxy-only flag is not set @@ -130,9 +128,19 @@ func (o *VNC) Run(cmd *cobra.Command, args []string) error { case <-cmd.Context().Done(): return nil default: + client, defaultNamespace, _, err := clientAndNamespaceFromContext(cmd.Context()) + if err != nil { + return err + } + + namespace := targetNamespace + if namespace == "" { + namespace = defaultNamespace + } + cmd.Printf("Connecting to %s VNC...\n", vmName) - err := connect(cmd.Context(), ln, client, cmd, namespace, vmName) + err = connectFunc(cmd.Context(), ln, client, cmd, namespace, vmName) if err != nil { if strings.Contains(err.Error(), "not found") { return err diff --git a/src/cli/internal/cmd/vnc/vnc_test.go b/src/cli/internal/cmd/vnc/vnc_test.go new file mode 100644 index 0000000000..d6eabf691d --- /dev/null +++ b/src/cli/internal/cmd/vnc/vnc_test.go @@ -0,0 +1,131 @@ +package vnc + +import ( + "bytes" + "context" + "errors" + "net" + "testing" + + "github.com/spf13/cobra" + "k8s.io/client-go/kubernetes/fake" + + virtualizationv1alpha2 "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/typed/core/v1alpha2" + "github.com/deckhouse/virtualization/api/client/kubeclient" +) + +type fakeKubeclient struct { + *fake.Clientset +} + +func (f *fakeKubeclient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { + return nil +} + +func (f *fakeKubeclient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { + return nil +} + +func (f *fakeKubeclient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { + return nil +} + +func (f *fakeKubeclient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { + return nil +} + +func (f *fakeKubeclient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { + return nil +} + +func TestRunRefreshesClientBeforeReconnect(t *testing.T) { + oldProxyOnly := proxyOnly + oldCustomPort := customPort + oldListenAddress := listenAddress + oldClientAndNamespaceFromContext := clientAndNamespaceFromContext + oldConnectFunc := connectFunc + defer func() { + proxyOnly = oldProxyOnly + customPort = oldCustomPort + listenAddress = oldListenAddress + clientAndNamespaceFromContext = oldClientAndNamespaceFromContext + connectFunc = oldConnectFunc + }() + + proxyOnly = true + customPort = 0 + listenAddress = "127.0.0.1" + + var clientCalls int + clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { + clientCalls++ + return &fakeKubeclient{Clientset: fake.NewSimpleClientset()}, "default", false, nil + } + + var connectCalls int + connectFunc = func(_ context.Context, ln *net.TCPListener, _ kubeclient.Client, _ *cobra.Command, namespace, vmName string) error { + connectCalls++ + if ln == nil { + t.Fatal("listener must not be nil") + } + if namespace != "default" { + t.Fatalf("unexpected namespace: %s", namespace) + } + if vmName != "test-vm" { + t.Fatalf("unexpected vm name: %s", vmName) + } + if connectCalls == 1 { + return errors.New("temporary error") + } + return nil + } + + cmd := &cobra.Command{} + stdout := &bytes.Buffer{} + cmd.SetOut(stdout) + cmd.SetErr(stdout) + cmd.SetContext(context.Background()) + + err := (&VNC{}).Run(cmd, []string{"test-vm"}) + if err != nil { + t.Fatalf("Run returned error: %v", err) + } + if connectCalls != 2 { + t.Fatalf("expected 2 connect attempts, got %d", connectCalls) + } + if clientCalls != 2 { + t.Fatalf("expected client to be refreshed before each reconnect, got %d calls", clientCalls) + } +} From 1b56c934d465a83eaa0fa17d94d0b98bb914fd69 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 21 Apr 2026 17:00:44 +0200 Subject: [PATCH 2/7] fix(cli): refresh console client config on reconnect Signed-off-by: Daniil Antoshin --- src/cli/internal/cmd/console/console.go | 26 ++-- src/cli/internal/cmd/console/console_test.go | 136 +++++++++++++++++++ 2 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 src/cli/internal/cmd/console/console_test.go diff --git a/src/cli/internal/cmd/console/console.go b/src/cli/internal/cmd/console/console.go index 374a391a3f..0dc0de0d77 100644 --- a/src/cli/internal/cmd/console/console.go +++ b/src/cli/internal/cmd/console/console.go @@ -80,18 +80,16 @@ const ( reconnectInterval = 2 * time.Second // Interval between reconnection attempts ) +var ( + clientAndNamespaceFromContext = clientconfig.ClientAndNamespaceFromContext + connectFunc = connect +) + func (c *Console) Run(cmd *cobra.Command, args []string) error { - client, defaultNamespace, _, err := clientconfig.ClientAndNamespaceFromContext(cmd.Context()) - if err != nil { - return err - } - namespace, name, err := templates.ParseTarget(args[0]) + targetNamespace, name, err := templates.ParseTarget(args[0]) if err != nil { return err } - if namespace == "" { - namespace = defaultNamespace - } // Set terminal to raw mode once for all connections if term.IsTerminal(int(os.Stdin.Fd())) { @@ -147,7 +145,17 @@ func (c *Console) Run(cmd *cobra.Command, args []string) error { case <-doneChan: return nil default: - err := connect(cmd.Context(), name, namespace, client, c.timeout, stdinCh, doneChan) + client, defaultNamespace, _, err := clientAndNamespaceFromContext(cmd.Context()) + if err != nil { + return err + } + + namespace := targetNamespace + if namespace == "" { + namespace = defaultNamespace + } + + err = connectFunc(cmd.Context(), name, namespace, client, c.timeout, stdinCh, doneChan) if err == nil { return nil // Normal exit (escape sequence) } diff --git a/src/cli/internal/cmd/console/console_test.go b/src/cli/internal/cmd/console/console_test.go new file mode 100644 index 0000000000..fb1ba82f00 --- /dev/null +++ b/src/cli/internal/cmd/console/console_test.go @@ -0,0 +1,136 @@ +package console + +import ( + "context" + "errors" + "net" + "os" + "testing" + "time" + + "github.com/spf13/cobra" + "k8s.io/client-go/kubernetes/fake" + + virtualizationv1alpha2 "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/typed/core/v1alpha2" + "github.com/deckhouse/virtualization/api/client/kubeclient" +) + +type fakeKubeclient struct { + *fake.Clientset +} + +func (f *fakeKubeclient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { + return nil +} + +func (f *fakeKubeclient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { + return nil +} + +func (f *fakeKubeclient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { + return nil +} + +func (f *fakeKubeclient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { + return nil +} + +func (f *fakeKubeclient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { + return nil +} + +func (f *fakeKubeclient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { + return nil +} + +func TestRunRefreshesClientBeforeReconnect(t *testing.T) { + oldStdin := os.Stdin + oldClientAndNamespaceFromContext := clientAndNamespaceFromContext + oldConnectFunc := connectFunc + defer func() { + os.Stdin = oldStdin + clientAndNamespaceFromContext = oldClientAndNamespaceFromContext + connectFunc = oldConnectFunc + }() + + stdinReader, stdinWriter, err := os.Pipe() + if err != nil { + t.Fatalf("create stdin pipe: %v", err) + } + defer stdinReader.Close() + os.Stdin = stdinReader + defer stdinWriter.Close() + + var clientCalls int + clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { + clientCalls++ + return &fakeKubeclient{Clientset: fake.NewSimpleClientset()}, "default", false, nil + } + + var connectCalls int + connectFunc = func(_ context.Context, name, namespace string, _ kubeclient.Client, _ time.Duration, _ <-chan []byte, _ <-chan struct{}) error { + connectCalls++ + if namespace != "default" { + t.Fatalf("unexpected namespace: %s", namespace) + } + if name != "test-vm" { + t.Fatalf("unexpected vm name: %s", name) + } + if connectCalls == 1 { + return errors.New("temporary error") + } + return nil + } + + cmd := &cobra.Command{} + cmd.SetContext(context.Background()) + + go func() { + _ = stdinWriter.Close() + }() + + err = (&Console{timeout: time.Second}).Run(cmd, []string{"test-vm"}) + if err != nil { + t.Fatalf("Run returned error: %v", err) + } + if connectCalls != 2 { + t.Fatalf("expected 2 connect attempts, got %d", connectCalls) + } + if clientCalls != 2 { + t.Fatalf("expected client to be refreshed before each reconnect, got %d calls", clientCalls) + } +} + +func TestShouldWaitErrForAbnormalClosure(t *testing.T) { + if !ShouldWaitErr(&net.OpError{Err: errors.New("Internal error")}) { + t.Fatal("expected ShouldWaitErr to return true for internal error") + } +} From 67f0a18fdf1cc6689a2a3cda9b14b8cd47a4a1b0 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 21 Apr 2026 18:07:55 +0200 Subject: [PATCH 3/7] chore(cli): add license headers to reconnect tests Signed-off-by: Daniil Antoshin --- src/cli/internal/cmd/console/console_test.go | 16 ++++++++++++++++ src/cli/internal/cmd/vnc/vnc_test.go | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/cli/internal/cmd/console/console_test.go b/src/cli/internal/cmd/console/console_test.go index fb1ba82f00..a5edd7098a 100644 --- a/src/cli/internal/cmd/console/console_test.go +++ b/src/cli/internal/cmd/console/console_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2026 Flant JSC + +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 console import ( diff --git a/src/cli/internal/cmd/vnc/vnc_test.go b/src/cli/internal/cmd/vnc/vnc_test.go index d6eabf691d..3dfa161881 100644 --- a/src/cli/internal/cmd/vnc/vnc_test.go +++ b/src/cli/internal/cmd/vnc/vnc_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2026 Flant JSC + +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 vnc import ( From 5c6fe83bdc4e27668872fbd25c1b7c6c9fa3d5f0 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 22 Apr 2026 11:36:46 +0200 Subject: [PATCH 4/7] test(cli): switch reconnect tests to ginkgo Signed-off-by: Daniil Antoshin --- src/cli/go.mod | 5 + src/cli/go.sum | 6 +- src/cli/internal/cmd/console/console_test.go | 183 +++++++------------ src/cli/internal/cmd/testutil/fake_client.go | 87 +++++++++ src/cli/internal/cmd/vnc/vnc_test.go | 173 +++++++----------- 5 files changed, 226 insertions(+), 228 deletions(-) create mode 100644 src/cli/internal/cmd/testutil/fake_client.go diff --git a/src/cli/go.mod b/src/cli/go.mod index 2da6a46666..96ad87b02f 100644 --- a/src/cli/go.mod +++ b/src/cli/go.mod @@ -6,6 +6,8 @@ require ( github.com/deckhouse/virtualization/api v0.15.0 github.com/fatih/color v1.18.0 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 + github.com/onsi/ginkgo/v2 v2.23.3 + github.com/onsi/gomega v1.37.0 github.com/povsister/scp v0.0.0-20250504051308-e467f71ea63c github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.7 @@ -34,9 +36,11 @@ require ( github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -64,6 +68,7 @@ require ( golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.38.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/src/cli/go.sum b/src/cli/go.sum index 08dc251c8f..9277f421f1 100644 --- a/src/cli/go.sum +++ b/src/cli/go.sum @@ -63,7 +63,6 @@ github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -94,8 +93,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -161,7 +160,6 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= diff --git a/src/cli/internal/cmd/console/console_test.go b/src/cli/internal/cmd/console/console_test.go index a5edd7098a..d469c479bc 100644 --- a/src/cli/internal/cmd/console/console_test.go +++ b/src/cli/internal/cmd/console/console_test.go @@ -24,129 +24,84 @@ import ( "testing" "time" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "github.com/spf13/cobra" - "k8s.io/client-go/kubernetes/fake" - virtualizationv1alpha2 "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/typed/core/v1alpha2" "github.com/deckhouse/virtualization/api/client/kubeclient" + "github.com/deckhouse/virtualization/src/cli/internal/cmd/testutil" ) -type fakeKubeclient struct { - *fake.Clientset +func TestConsole(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Console Command Suite") } -func (f *fakeKubeclient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { - return nil -} - -func (f *fakeKubeclient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { - return nil -} - -func (f *fakeKubeclient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { - return nil -} +var _ = Describe("Console", func() { + var ( + oldStdin *os.File + oldClientAndNamespaceFromContext func(context.Context) (kubeclient.Client, string, bool, error) + oldConnectFunc func(context.Context, string, string, kubeclient.Client, time.Duration, <-chan []byte, <-chan struct{}) error + ) -func (f *fakeKubeclient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { - return nil -} + BeforeEach(func() { + oldStdin = os.Stdin + oldClientAndNamespaceFromContext = clientAndNamespaceFromContext + oldConnectFunc = connectFunc + }) -func (f *fakeKubeclient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { - return nil -} - -func (f *fakeKubeclient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { - return nil -} - -func (f *fakeKubeclient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { - return nil -} - -func TestRunRefreshesClientBeforeReconnect(t *testing.T) { - oldStdin := os.Stdin - oldClientAndNamespaceFromContext := clientAndNamespaceFromContext - oldConnectFunc := connectFunc - defer func() { + AfterEach(func() { os.Stdin = oldStdin clientAndNamespaceFromContext = oldClientAndNamespaceFromContext connectFunc = oldConnectFunc - }() - - stdinReader, stdinWriter, err := os.Pipe() - if err != nil { - t.Fatalf("create stdin pipe: %v", err) - } - defer stdinReader.Close() - os.Stdin = stdinReader - defer stdinWriter.Close() - - var clientCalls int - clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { - clientCalls++ - return &fakeKubeclient{Clientset: fake.NewSimpleClientset()}, "default", false, nil - } - - var connectCalls int - connectFunc = func(_ context.Context, name, namespace string, _ kubeclient.Client, _ time.Duration, _ <-chan []byte, _ <-chan struct{}) error { - connectCalls++ - if namespace != "default" { - t.Fatalf("unexpected namespace: %s", namespace) - } - if name != "test-vm" { - t.Fatalf("unexpected vm name: %s", name) - } - if connectCalls == 1 { - return errors.New("temporary error") - } - return nil - } - - cmd := &cobra.Command{} - cmd.SetContext(context.Background()) - - go func() { - _ = stdinWriter.Close() - }() - - err = (&Console{timeout: time.Second}).Run(cmd, []string{"test-vm"}) - if err != nil { - t.Fatalf("Run returned error: %v", err) - } - if connectCalls != 2 { - t.Fatalf("expected 2 connect attempts, got %d", connectCalls) - } - if clientCalls != 2 { - t.Fatalf("expected client to be refreshed before each reconnect, got %d calls", clientCalls) - } -} - -func TestShouldWaitErrForAbnormalClosure(t *testing.T) { - if !ShouldWaitErr(&net.OpError{Err: errors.New("Internal error")}) { - t.Fatal("expected ShouldWaitErr to return true for internal error") - } -} + }) + + Describe("Run", func() { + It("refreshes client before reconnect", func() { + stdinReader, stdinWriter, err := os.Pipe() + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { + _ = stdinReader.Close() + }) + DeferCleanup(func() { + _ = stdinWriter.Close() + }) + os.Stdin = stdinReader + + var clientCalls int + clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { + clientCalls++ + return testutil.NewFakeClient(), "default", false, nil + } + + var connectCalls int + connectFunc = func(_ context.Context, name, namespace string, _ kubeclient.Client, _ time.Duration, _ <-chan []byte, _ <-chan struct{}) error { + connectCalls++ + Expect(namespace).To(Equal("default")) + Expect(name).To(Equal("test-vm")) + if connectCalls == 1 { + return errors.New("temporary error") + } + return nil + } + + cmd := &cobra.Command{} + cmd.SetContext(context.Background()) + + go func() { + _ = stdinWriter.Close() + }() + + err = (&Console{timeout: time.Second}).Run(cmd, []string{"test-vm"}) + Expect(err).NotTo(HaveOccurred()) + Expect(connectCalls).To(Equal(2)) + Expect(clientCalls).To(Equal(2)) + }) + }) + + Describe("ShouldWaitErr", func() { + It("returns true for abnormal closure errors", func() { + Expect(ShouldWaitErr(&net.OpError{Err: errors.New("Internal error")})).To(BeTrue()) + }) + }) +}) diff --git a/src/cli/internal/cmd/testutil/fake_client.go b/src/cli/internal/cmd/testutil/fake_client.go new file mode 100644 index 0000000000..b68c568b43 --- /dev/null +++ b/src/cli/internal/cmd/testutil/fake_client.go @@ -0,0 +1,87 @@ +/* +Copyright 2026 Flant JSC + +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 testutil + +import ( + virtualizationfake "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/fake" + virtualizationv1alpha2 "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/typed/core/v1alpha2" + k8sfake "k8s.io/client-go/kubernetes/fake" +) + +type FakeClient struct { + *k8sfake.Clientset + virtClient *virtualizationfake.Clientset +} + +func NewFakeClient() *FakeClient { + return &FakeClient{ + Clientset: k8sfake.NewSimpleClientset(), + virtClient: virtualizationfake.NewSimpleClientset(), + } +} + +func (f *FakeClient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { + return f.virtClient.VirtualizationV1alpha2().ClusterVirtualImages() +} + +func (f *FakeClient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachines(namespace) +} + +func (f *FakeClient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualImages(namespace) +} + +func (f *FakeClient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualDisks(namespace) +} + +func (f *FakeClient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineBlockDeviceAttachments(namespace) +} + +func (f *FakeClient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddresses(namespace) +} + +func (f *FakeClient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddressLeases() +} + +func (f *FakeClient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineOperations(namespace) +} + +func (f *FakeClient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineClasses() +} + +func (f *FakeClient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddresses(namespace) +} + +func (f *FakeClient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddressLeases() +} + +func (f *FakeClient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { + return f.virtClient.VirtualizationV1alpha2().NodeUSBDevices() +} + +func (f *FakeClient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { + return f.virtClient.VirtualizationV1alpha2().USBDevices(namespace) +} diff --git a/src/cli/internal/cmd/vnc/vnc_test.go b/src/cli/internal/cmd/vnc/vnc_test.go index 3dfa161881..6989c23bf4 100644 --- a/src/cli/internal/cmd/vnc/vnc_test.go +++ b/src/cli/internal/cmd/vnc/vnc_test.go @@ -23,125 +23,78 @@ import ( "net" "testing" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "github.com/spf13/cobra" - "k8s.io/client-go/kubernetes/fake" - virtualizationv1alpha2 "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/typed/core/v1alpha2" "github.com/deckhouse/virtualization/api/client/kubeclient" + "github.com/deckhouse/virtualization/src/cli/internal/cmd/testutil" ) -type fakeKubeclient struct { - *fake.Clientset +func TestVNC(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "VNC Command Suite") } -func (f *fakeKubeclient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { - return nil -} - -func (f *fakeKubeclient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { - return nil -} - -func (f *fakeKubeclient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { - return nil -} - -func (f *fakeKubeclient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { - return nil -} - -func (f *fakeKubeclient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { - return nil -} - -func (f *fakeKubeclient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { - return nil -} - -func TestRunRefreshesClientBeforeReconnect(t *testing.T) { - oldProxyOnly := proxyOnly - oldCustomPort := customPort - oldListenAddress := listenAddress - oldClientAndNamespaceFromContext := clientAndNamespaceFromContext - oldConnectFunc := connectFunc - defer func() { +var _ = Describe("VNC", func() { + var ( + oldProxyOnly bool + oldCustomPort int + oldListenAddress string + oldClientAndNamespaceFromContext func(context.Context) (kubeclient.Client, string, bool, error) + oldConnectFunc func(context.Context, *net.TCPListener, kubeclient.Client, *cobra.Command, string, string) error + ) + + BeforeEach(func() { + oldProxyOnly = proxyOnly + oldCustomPort = customPort + oldListenAddress = listenAddress + oldClientAndNamespaceFromContext = clientAndNamespaceFromContext + oldConnectFunc = connectFunc + }) + + AfterEach(func() { proxyOnly = oldProxyOnly customPort = oldCustomPort listenAddress = oldListenAddress clientAndNamespaceFromContext = oldClientAndNamespaceFromContext connectFunc = oldConnectFunc - }() - - proxyOnly = true - customPort = 0 - listenAddress = "127.0.0.1" - - var clientCalls int - clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { - clientCalls++ - return &fakeKubeclient{Clientset: fake.NewSimpleClientset()}, "default", false, nil - } - - var connectCalls int - connectFunc = func(_ context.Context, ln *net.TCPListener, _ kubeclient.Client, _ *cobra.Command, namespace, vmName string) error { - connectCalls++ - if ln == nil { - t.Fatal("listener must not be nil") - } - if namespace != "default" { - t.Fatalf("unexpected namespace: %s", namespace) - } - if vmName != "test-vm" { - t.Fatalf("unexpected vm name: %s", vmName) - } - if connectCalls == 1 { - return errors.New("temporary error") - } - return nil - } - - cmd := &cobra.Command{} - stdout := &bytes.Buffer{} - cmd.SetOut(stdout) - cmd.SetErr(stdout) - cmd.SetContext(context.Background()) - - err := (&VNC{}).Run(cmd, []string{"test-vm"}) - if err != nil { - t.Fatalf("Run returned error: %v", err) - } - if connectCalls != 2 { - t.Fatalf("expected 2 connect attempts, got %d", connectCalls) - } - if clientCalls != 2 { - t.Fatalf("expected client to be refreshed before each reconnect, got %d calls", clientCalls) - } -} + }) + + Describe("Run", func() { + It("refreshes client before reconnect", func() { + proxyOnly = true + customPort = 0 + listenAddress = "127.0.0.1" + + var clientCalls int + clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { + clientCalls++ + return testutil.NewFakeClient(), "default", false, nil + } + + var connectCalls int + connectFunc = func(_ context.Context, ln *net.TCPListener, _ kubeclient.Client, _ *cobra.Command, namespace, vmName string) error { + connectCalls++ + Expect(ln).NotTo(BeNil()) + Expect(namespace).To(Equal("default")) + Expect(vmName).To(Equal("test-vm")) + if connectCalls == 1 { + return errors.New("temporary error") + } + return nil + } + + cmd := &cobra.Command{} + stdout := &bytes.Buffer{} + cmd.SetOut(stdout) + cmd.SetErr(stdout) + cmd.SetContext(context.Background()) + + err := (&VNC{}).Run(cmd, []string{"test-vm"}) + Expect(err).NotTo(HaveOccurred()) + Expect(connectCalls).To(Equal(2)) + Expect(clientCalls).To(Equal(2)) + }) + }) +}) From 42c4b646d03d70d17a37521403607127adf6c23b Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 22 Apr 2026 11:51:03 +0200 Subject: [PATCH 5/7] refactor(cli): remove unused fake client helper Signed-off-by: Daniil Antoshin --- src/cli/internal/cmd/console/console_test.go | 3 +- src/cli/internal/cmd/testutil/fake_client.go | 87 -------------------- src/cli/internal/cmd/vnc/vnc_test.go | 3 +- 3 files changed, 2 insertions(+), 91 deletions(-) delete mode 100644 src/cli/internal/cmd/testutil/fake_client.go diff --git a/src/cli/internal/cmd/console/console_test.go b/src/cli/internal/cmd/console/console_test.go index d469c479bc..a7c95e62a8 100644 --- a/src/cli/internal/cmd/console/console_test.go +++ b/src/cli/internal/cmd/console/console_test.go @@ -29,7 +29,6 @@ import ( "github.com/spf13/cobra" "github.com/deckhouse/virtualization/api/client/kubeclient" - "github.com/deckhouse/virtualization/src/cli/internal/cmd/testutil" ) func TestConsole(t *testing.T) { @@ -71,7 +70,7 @@ var _ = Describe("Console", func() { var clientCalls int clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { clientCalls++ - return testutil.NewFakeClient(), "default", false, nil + return nil, "default", false, nil } var connectCalls int diff --git a/src/cli/internal/cmd/testutil/fake_client.go b/src/cli/internal/cmd/testutil/fake_client.go deleted file mode 100644 index b68c568b43..0000000000 --- a/src/cli/internal/cmd/testutil/fake_client.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 2026 Flant JSC - -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 testutil - -import ( - virtualizationfake "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/fake" - virtualizationv1alpha2 "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/typed/core/v1alpha2" - k8sfake "k8s.io/client-go/kubernetes/fake" -) - -type FakeClient struct { - *k8sfake.Clientset - virtClient *virtualizationfake.Clientset -} - -func NewFakeClient() *FakeClient { - return &FakeClient{ - Clientset: k8sfake.NewSimpleClientset(), - virtClient: virtualizationfake.NewSimpleClientset(), - } -} - -func (f *FakeClient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { - return f.virtClient.VirtualizationV1alpha2().ClusterVirtualImages() -} - -func (f *FakeClient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachines(namespace) -} - -func (f *FakeClient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualImages(namespace) -} - -func (f *FakeClient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualDisks(namespace) -} - -func (f *FakeClient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineBlockDeviceAttachments(namespace) -} - -func (f *FakeClient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddresses(namespace) -} - -func (f *FakeClient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddressLeases() -} - -func (f *FakeClient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineOperations(namespace) -} - -func (f *FakeClient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineClasses() -} - -func (f *FakeClient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddresses(namespace) -} - -func (f *FakeClient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddressLeases() -} - -func (f *FakeClient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { - return f.virtClient.VirtualizationV1alpha2().NodeUSBDevices() -} - -func (f *FakeClient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { - return f.virtClient.VirtualizationV1alpha2().USBDevices(namespace) -} diff --git a/src/cli/internal/cmd/vnc/vnc_test.go b/src/cli/internal/cmd/vnc/vnc_test.go index 6989c23bf4..6f76c43738 100644 --- a/src/cli/internal/cmd/vnc/vnc_test.go +++ b/src/cli/internal/cmd/vnc/vnc_test.go @@ -28,7 +28,6 @@ import ( "github.com/spf13/cobra" "github.com/deckhouse/virtualization/api/client/kubeclient" - "github.com/deckhouse/virtualization/src/cli/internal/cmd/testutil" ) func TestVNC(t *testing.T) { @@ -70,7 +69,7 @@ var _ = Describe("VNC", func() { var clientCalls int clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { clientCalls++ - return testutil.NewFakeClient(), "default", false, nil + return nil, "default", false, nil } var connectCalls int From 7737c27721c036cf6a97efc80cfab7af27c6a44c Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 22 Apr 2026 11:55:32 +0200 Subject: [PATCH 6/7] test(cli): use generated fake clientsets in tests Signed-off-by: Daniil Antoshin --- src/cli/internal/cmd/console/console_test.go | 69 +++++++++++++++++++- src/cli/internal/cmd/vnc/vnc_test.go | 69 +++++++++++++++++++- 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/src/cli/internal/cmd/console/console_test.go b/src/cli/internal/cmd/console/console_test.go index a7c95e62a8..cfc486527f 100644 --- a/src/cli/internal/cmd/console/console_test.go +++ b/src/cli/internal/cmd/console/console_test.go @@ -27,10 +27,77 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/spf13/cobra" + k8sfake "k8s.io/client-go/kubernetes/fake" + virtualizationfake "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/fake" + virtualizationv1alpha2 "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/typed/core/v1alpha2" "github.com/deckhouse/virtualization/api/client/kubeclient" ) +type fakeClient struct { + *k8sfake.Clientset + virtClient *virtualizationfake.Clientset +} + +func newFakeClient() *fakeClient { + return &fakeClient{ + Clientset: k8sfake.NewSimpleClientset(), + virtClient: virtualizationfake.NewSimpleClientset(), + } +} + +func (f *fakeClient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { + return f.virtClient.VirtualizationV1alpha2().ClusterVirtualImages() +} + +func (f *fakeClient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachines(namespace) +} + +func (f *fakeClient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualImages(namespace) +} + +func (f *fakeClient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualDisks(namespace) +} + +func (f *fakeClient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineBlockDeviceAttachments(namespace) +} + +func (f *fakeClient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddresses(namespace) +} + +func (f *fakeClient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddressLeases() +} + +func (f *fakeClient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineOperations(namespace) +} + +func (f *fakeClient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineClasses() +} + +func (f *fakeClient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddresses(namespace) +} + +func (f *fakeClient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddressLeases() +} + +func (f *fakeClient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { + return f.virtClient.VirtualizationV1alpha2().NodeUSBDevices() +} + +func (f *fakeClient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { + return f.virtClient.VirtualizationV1alpha2().USBDevices(namespace) +} + func TestConsole(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Console Command Suite") @@ -70,7 +137,7 @@ var _ = Describe("Console", func() { var clientCalls int clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { clientCalls++ - return nil, "default", false, nil + return newFakeClient(), "default", false, nil } var connectCalls int diff --git a/src/cli/internal/cmd/vnc/vnc_test.go b/src/cli/internal/cmd/vnc/vnc_test.go index 6f76c43738..409daa4c35 100644 --- a/src/cli/internal/cmd/vnc/vnc_test.go +++ b/src/cli/internal/cmd/vnc/vnc_test.go @@ -26,10 +26,77 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/spf13/cobra" + k8sfake "k8s.io/client-go/kubernetes/fake" + virtualizationfake "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/fake" + virtualizationv1alpha2 "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/typed/core/v1alpha2" "github.com/deckhouse/virtualization/api/client/kubeclient" ) +type fakeClient struct { + *k8sfake.Clientset + virtClient *virtualizationfake.Clientset +} + +func newFakeClient() *fakeClient { + return &fakeClient{ + Clientset: k8sfake.NewSimpleClientset(), + virtClient: virtualizationfake.NewSimpleClientset(), + } +} + +func (f *fakeClient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { + return f.virtClient.VirtualizationV1alpha2().ClusterVirtualImages() +} + +func (f *fakeClient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachines(namespace) +} + +func (f *fakeClient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualImages(namespace) +} + +func (f *fakeClient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualDisks(namespace) +} + +func (f *fakeClient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineBlockDeviceAttachments(namespace) +} + +func (f *fakeClient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddresses(namespace) +} + +func (f *fakeClient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddressLeases() +} + +func (f *fakeClient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineOperations(namespace) +} + +func (f *fakeClient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineClasses() +} + +func (f *fakeClient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddresses(namespace) +} + +func (f *fakeClient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { + return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddressLeases() +} + +func (f *fakeClient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { + return f.virtClient.VirtualizationV1alpha2().NodeUSBDevices() +} + +func (f *fakeClient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { + return f.virtClient.VirtualizationV1alpha2().USBDevices(namespace) +} + func TestVNC(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "VNC Command Suite") @@ -69,7 +136,7 @@ var _ = Describe("VNC", func() { var clientCalls int clientAndNamespaceFromContext = func(context.Context) (kubeclient.Client, string, bool, error) { clientCalls++ - return nil, "default", false, nil + return newFakeClient(), "default", false, nil } var connectCalls int From 6ece7ea5c3ef142168949875511a13f5ee1a774b Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 22 Apr 2026 12:16:11 +0200 Subject: [PATCH 7/7] refactor(cli): simplify console fake client Signed-off-by: Daniil Antoshin --- src/cli/internal/cmd/console/console_test.go | 58 +------------------- src/cli/internal/cmd/vnc/vnc_test.go | 58 +------------------- 2 files changed, 6 insertions(+), 110 deletions(-) diff --git a/src/cli/internal/cmd/console/console_test.go b/src/cli/internal/cmd/console/console_test.go index cfc486527f..bb9be57672 100644 --- a/src/cli/internal/cmd/console/console_test.go +++ b/src/cli/internal/cmd/console/console_test.go @@ -36,68 +36,16 @@ import ( type fakeClient struct { *k8sfake.Clientset - virtClient *virtualizationfake.Clientset + virtualizationv1alpha2.VirtualizationV1alpha2Interface } func newFakeClient() *fakeClient { return &fakeClient{ - Clientset: k8sfake.NewSimpleClientset(), - virtClient: virtualizationfake.NewSimpleClientset(), + Clientset: k8sfake.NewSimpleClientset(), + VirtualizationV1alpha2Interface: virtualizationfake.NewSimpleClientset().VirtualizationV1alpha2(), } } -func (f *fakeClient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { - return f.virtClient.VirtualizationV1alpha2().ClusterVirtualImages() -} - -func (f *fakeClient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachines(namespace) -} - -func (f *fakeClient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualImages(namespace) -} - -func (f *fakeClient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualDisks(namespace) -} - -func (f *fakeClient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineBlockDeviceAttachments(namespace) -} - -func (f *fakeClient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddresses(namespace) -} - -func (f *fakeClient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddressLeases() -} - -func (f *fakeClient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineOperations(namespace) -} - -func (f *fakeClient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineClasses() -} - -func (f *fakeClient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddresses(namespace) -} - -func (f *fakeClient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddressLeases() -} - -func (f *fakeClient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { - return f.virtClient.VirtualizationV1alpha2().NodeUSBDevices() -} - -func (f *fakeClient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { - return f.virtClient.VirtualizationV1alpha2().USBDevices(namespace) -} - func TestConsole(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Console Command Suite") diff --git a/src/cli/internal/cmd/vnc/vnc_test.go b/src/cli/internal/cmd/vnc/vnc_test.go index 409daa4c35..06c8a3a663 100644 --- a/src/cli/internal/cmd/vnc/vnc_test.go +++ b/src/cli/internal/cmd/vnc/vnc_test.go @@ -35,68 +35,16 @@ import ( type fakeClient struct { *k8sfake.Clientset - virtClient *virtualizationfake.Clientset + virtualizationv1alpha2.VirtualizationV1alpha2Interface } func newFakeClient() *fakeClient { return &fakeClient{ - Clientset: k8sfake.NewSimpleClientset(), - virtClient: virtualizationfake.NewSimpleClientset(), + Clientset: k8sfake.NewSimpleClientset(), + VirtualizationV1alpha2Interface: virtualizationfake.NewSimpleClientset().VirtualizationV1alpha2(), } } -func (f *fakeClient) ClusterVirtualImages() virtualizationv1alpha2.ClusterVirtualImageInterface { - return f.virtClient.VirtualizationV1alpha2().ClusterVirtualImages() -} - -func (f *fakeClient) VirtualMachines(namespace string) virtualizationv1alpha2.VirtualMachineInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachines(namespace) -} - -func (f *fakeClient) VirtualImages(namespace string) virtualizationv1alpha2.VirtualImageInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualImages(namespace) -} - -func (f *fakeClient) VirtualDisks(namespace string) virtualizationv1alpha2.VirtualDiskInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualDisks(namespace) -} - -func (f *fakeClient) VirtualMachineBlockDeviceAttachments(namespace string) virtualizationv1alpha2.VirtualMachineBlockDeviceAttachmentInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineBlockDeviceAttachments(namespace) -} - -func (f *fakeClient) VirtualMachineIPAddresses(namespace string) virtualizationv1alpha2.VirtualMachineIPAddressInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddresses(namespace) -} - -func (f *fakeClient) VirtualMachineIPAddressLeases() virtualizationv1alpha2.VirtualMachineIPAddressLeaseInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineIPAddressLeases() -} - -func (f *fakeClient) VirtualMachineOperations(namespace string) virtualizationv1alpha2.VirtualMachineOperationInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineOperations(namespace) -} - -func (f *fakeClient) VirtualMachineClasses() virtualizationv1alpha2.VirtualMachineClassInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineClasses() -} - -func (f *fakeClient) VirtualMachineMACAddresses(namespace string) virtualizationv1alpha2.VirtualMachineMACAddressInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddresses(namespace) -} - -func (f *fakeClient) VirtualMachineMACAddressLeases() virtualizationv1alpha2.VirtualMachineMACAddressLeaseInterface { - return f.virtClient.VirtualizationV1alpha2().VirtualMachineMACAddressLeases() -} - -func (f *fakeClient) NodeUSBDevices() virtualizationv1alpha2.NodeUSBDeviceInterface { - return f.virtClient.VirtualizationV1alpha2().NodeUSBDevices() -} - -func (f *fakeClient) USBDevices(namespace string) virtualizationv1alpha2.USBDeviceInterface { - return f.virtClient.VirtualizationV1alpha2().USBDevices(namespace) -} - func TestVNC(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "VNC Command Suite")