*
* Copyright (c) 2025 Bocloud Technologies Co., Ltd.
* installer is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* <http://license.coscl.org.cn/MulanPSL2>
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*
*/
package cmd
import (
"net"
"os"
"runtime"
"testing"
"github.com/agiledragon/gomonkey/v2"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"gopkg.openfuyao.cn/bkeadm/pkg/registry"
)
const (
registrySubcommandCount = 9
testRegistryPort = "40443"
firstArgIndex = 0
)
var testRegistryIP = net.IPv4(
testIPv4SegmentA,
testIPv4SegmentB,
testIPv4SegmentC,
testIPv4SegmentD,
)
func TestRegistryCmdInitialization(t *testing.T) {
tests := []struct {
name string
cmd *cobra.Command
expectedUse string
expectedShort string
hasSubcommands bool
}{
{
name: "registry command properties",
cmd: registryCmd,
expectedUse: "registry",
expectedShort: "Synchronize images between two mirror repositories",
hasSubcommands: true,
},
{
name: "sync command properties",
cmd: syncDep,
expectedUse: "sync",
expectedShort: "In the two mirror repositories, mirrors are synchronized by copying data blocks from one repository to another.",
hasSubcommands: false,
},
{
name: "transfer command properties",
cmd: transferDep,
expectedUse: "transfer",
expectedShort: "Transfer images in docker pull / docker push mode",
hasSubcommands: false,
},
{
name: "list-tags command properties",
cmd: listTagsDep,
expectedUse: "list-tags",
expectedShort: "Lists all tags the mirror repository",
hasSubcommands: false,
},
{
name: "inspect command properties",
cmd: inspectDep,
expectedUse: "inspect",
expectedShort: "List information about images in the mirror repository",
hasSubcommands: false,
},
{
name: "manifests command properties",
cmd: manifestsDep,
expectedUse: "manifests",
expectedShort: "Make a multi-architecture wake image",
hasSubcommands: false,
},
{
name: "delete command properties",
cmd: deleteDep,
expectedUse: "delete",
expectedShort: "Delete a specified mirror",
hasSubcommands: false,
},
{
name: "view command properties",
cmd: viewDep,
expectedUse: "view",
expectedShort: "View warehouse view",
hasSubcommands: false,
},
{
name: "patch command properties",
cmd: patchDep,
expectedUse: "patch",
expectedShort: "Specially customized incremental packet mirror synchronization",
hasSubcommands: false,
},
{
name: "download command properties",
cmd: downloadDep,
expectedUse: "download",
expectedShort: "Download the specified file in the image",
hasSubcommands: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expectedUse, tt.cmd.Use)
assert.Equal(t, tt.expectedShort, tt.cmd.Short)
})
}
}
func TestRegisterRegistryCommand(t *testing.T) {
var foundRegistryCmd bool
for _, cmd := range rootCmd.Commands() {
if cmd.Use == "registry" {
foundRegistryCmd = true
assert.Len(t, cmd.Commands(), registrySubcommandCount)
var foundSubcommands []string
for _, subCmd := range cmd.Commands() {
foundSubcommands = append(foundSubcommands, subCmd.Use)
}
expectedSubcommands := []string{"sync", "transfer", "list-tags", "inspect", "manifests", "delete", "view", "patch", "download"}
for _, expected := range expectedSubcommands {
assert.Contains(t, foundSubcommands, expected)
}
break
}
}
assert.True(t, foundRegistryCmd, "registry command should be registered in root command")
}
func TestSyncCmdArgs(t *testing.T) {
tests := []struct {
name string
sourceValue string
targetValue string
multiArchValue bool
archValue string
expectError bool
errorContains string
}{
{
name: "valid inputs",
sourceValue: "docker.io/library/busybox:1.35",
targetValue: "0.0.0.0:40443/library/busybox:1.35",
multiArchValue: false,
archValue: "amd64",
expectError: false,
},
{
name: "missing source should return error",
sourceValue: "",
targetValue: "0.0.0.0:40443/library/busybox:1.35",
multiArchValue: false,
archValue: "amd64",
expectError: true,
errorContains: "The `source` parameter is required",
},
{
name: "missing target should return error",
sourceValue: "docker.io/library/busybox:1.35",
targetValue: "",
multiArchValue: false,
archValue: "amd64",
expectError: true,
errorContains: "The `target` parameter is required",
},
{
name: "multi-arch with arch should return error",
sourceValue: "docker.io/library/busybox:1.35",
targetValue: "0.0.0.0:40443/library/busybox:1.35",
multiArchValue: true,
archValue: "amd64",
expectError: true,
errorContains: "The `arch` parameter is not allowed when `multi-arch` is true",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originalSource := syncOption.Source
originalTarget := syncOption.Target
originalMultiArch := syncOption.MultiArch
originalArch := syncOption.Arch
defer func() {
syncOption.Source = originalSource
syncOption.Target = originalTarget
syncOption.MultiArch = originalMultiArch
syncOption.Arch = originalArch
}()
syncOption.Source = tt.sourceValue
syncOption.Target = tt.targetValue
syncOption.MultiArch = tt.multiArchValue
syncOption.Arch = tt.archValue
err := syncDep.Args(nil, nil)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
}
})
}
}
func TestTransferCmdArgs(t *testing.T) {
tests := []struct {
name string
sourceValue string
targetValue string
imageValue string
fileValue string
archValue string
expectError bool
errorContains string
}{
{
name: "valid inputs with image",
sourceValue: "docker.io/library/",
targetValue: "registry.cloud.com/k8s",
imageValue: "busybox:1.28",
fileValue: "",
archValue: "amd64",
expectError: false,
},
{
name: "valid inputs with file",
sourceValue: "docker.io/library/",
targetValue: "registry.cloud.com/k8s",
imageValue: "",
fileValue: "/path/to/file.txt",
archValue: "amd64",
expectError: false,
},
{
name: "missing source should return error",
sourceValue: "",
targetValue: "registry.cloud.com/k8s",
imageValue: "busybox:1.28",
fileValue: "",
archValue: "amd64",
expectError: true,
errorContains: "The `source` parameter is required",
},
{
name: "missing target should return error",
sourceValue: "docker.io/library/",
targetValue: "",
imageValue: "busybox:1.28",
fileValue: "",
archValue: "amd64",
expectError: true,
errorContains: "The `target` parameter is required",
},
{
name: "missing both image and file should return error",
sourceValue: "docker.io/library/",
targetValue: "registry.cloud.com/k8s",
imageValue: "",
fileValue: "",
archValue: "amd64",
expectError: true,
errorContains: "There must be one of the parameters `image` and `file`",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originalSource := transferOption.Source
originalTarget := transferOption.Target
originalImage := transferOption.Image
originalFile := transferOption.File
originalArch := transferOption.Arch
defer func() {
transferOption.Source = originalSource
transferOption.Target = originalTarget
transferOption.Image = originalImage
transferOption.File = originalFile
transferOption.Arch = originalArch
}()
transferOption.Source = tt.sourceValue
transferOption.Target = tt.targetValue
transferOption.Image = tt.imageValue
transferOption.File = tt.fileValue
transferOption.Arch = tt.archValue
err := transferDep.Args(nil, nil)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
if tt.archValue == "" {
assert.Equal(t, runtime.GOARCH, transferOption.Arch)
}
}
})
}
}
func TestListTagsCmdArgs(t *testing.T) {
tests := []struct {
name string
args []string
expectError bool
errorContains string
}{
{
name: "valid input with image arg",
args: []string{"registry.example.com/image:tag"},
expectError: false,
},
{
name: "missing image arg should return error",
args: []string{},
expectError: true,
errorContains: "The `image` is required",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originalImage := listTagsOption.Image
defer func() {
listTagsOption.Image = originalImage
}()
err := listTagsDep.Args(nil, tt.args)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
if len(tt.args) > 0 {
assert.Equal(t, tt.args[firstArgIndex], listTagsOption.Image)
}
}
})
}
}
func TestInspectCmdArgs(t *testing.T) {
tests := []struct {
name string
args []string
expectError bool
errorContains string
}{
{
name: "valid input with image arg",
args: []string{"registry.example.com/image:tag"},
expectError: false,
},
{
name: "missing image arg should return error",
args: []string{},
expectError: true,
errorContains: "The `image` is required",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originalImage := inspectOption.Image
defer func() {
inspectOption.Image = originalImage
}()
err := inspectDep.Args(nil, tt.args)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
if len(tt.args) > 0 {
assert.Equal(t, tt.args[firstArgIndex], inspectOption.Image)
}
}
})
}
}
func TestManifestsCmdArgs(t *testing.T) {
tests := []struct {
name string
imageValue string
args []string
expectError bool
errorContains string
}{
{
name: "valid input with image and sufficient args",
imageValue: "registry.example.com/image:tag",
args: []string{"arg1", "arg2"},
expectError: false,
},
{
name: "missing image should return error",
imageValue: "",
args: []string{"arg1", "arg2"},
expectError: true,
errorContains: "The `image` is required",
},
{
name: "insufficient args should return error",
imageValue: "registry.example.com/image:tag",
args: []string{"arg1"},
expectError: true,
errorContains: "There are at least two schema images",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originalImage := manifestsOption.Image
defer func() {
manifestsOption.Image = originalImage
}()
manifestsOption.Image = tt.imageValue
err := manifestsDep.Args(nil, tt.args)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
}
})
}
}
func TestDeleteCmdArgs(t *testing.T) {
tests := []struct {
name string
args []string
expectError bool
errorContains string
}{
{
name: "valid input with sufficient args",
args: []string{"arg1", "arg2"},
expectError: false,
},
{
name: "insufficient args should return error",
args: []string{"arg1"},
expectError: true,
errorContains: "There are at least two schema images",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := deleteDep.Args(nil, tt.args)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
}
})
}
}
func TestViewCmdArgs(t *testing.T) {
tests := []struct {
name string
args []string
expectError bool
errorContains string
}{
{
name: "valid input with registry address",
args: []string{testRegistryIP.String() + ":" + testRegistryPort},
expectError: false,
},
{
name: "missing registry address should return error",
args: []string{},
expectError: true,
errorContains: "The `registry address` is required",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := viewDep.Args(nil, tt.args)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
}
})
}
}
func TestDownloadCmdArgs(t *testing.T) {
tests := []struct {
name string
imageValue string
fileValue string
dirValue string
mockGetwd func() (string, error)
expectError bool
errorContains string
}{
{
name: "valid inputs",
imageValue: "repository/kubectl:v1.23.17",
fileValue: "kubectl",
dirValue: "/opt",
expectError: false,
},
{
name: "missing image should return error",
imageValue: "",
fileValue: "kubectl",
dirValue: "/opt",
expectError: true,
errorContains: "The `image` parameter is required",
},
{
name: "missing file should return error",
imageValue: "repository/kubectl:v1.23.17",
fileValue: "",
dirValue: "/opt",
expectError: true,
errorContains: "The `file` parameter is required",
},
{
name: "empty dir gets current working directory",
imageValue: "repository/kubectl:v1.23.17",
fileValue: "kubectl",
dirValue: "",
mockGetwd: func() (string, error) {
return "/tmp/test", nil
},
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originalImage := downloadOption.Image
originalFile := downloadOption.DownloadInImageFile
originalDir := downloadOption.DownloadToDir
defer func() {
downloadOption.Image = originalImage
downloadOption.DownloadInImageFile = originalFile
downloadOption.DownloadToDir = originalDir
}()
downloadOption.Image = tt.imageValue
downloadOption.DownloadInImageFile = tt.fileValue
downloadOption.DownloadToDir = tt.dirValue
if tt.mockGetwd != nil {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(os.Getwd, tt.mockGetwd)
}
err := downloadDep.Args(nil, nil)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
if tt.dirValue == "" && tt.mockGetwd != nil {
assert.Equal(t, "/tmp/test", downloadOption.DownloadToDir)
}
}
})
}
}
func TestSyncCmdRun(t *testing.T) {
originalArgs := syncOption.Args
originalOptions := syncOption.Options
defer func() {
syncOption.Args = originalArgs
syncOption.Options = originalOptions
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc((*registry.Options).Sync, func(o *registry.Options) {
})
cmd := &cobra.Command{}
args := []string{"arg1", "arg2"}
syncDep.Run(cmd, args)
assert.Equal(t, args, syncOption.Args)
assert.Equal(t, options, syncOption.Options)
}
func TestTransferCmdRun(t *testing.T) {
originalArgs := transferOption.Args
originalOptions := transferOption.Options
defer func() {
transferOption.Args = originalArgs
transferOption.Options = originalOptions
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc((*registry.Options).MigrateImage, func(o *registry.Options) {
})
cmd := &cobra.Command{}
args := []string{"arg1", "arg2"}
transferDep.Run(cmd, args)
assert.Equal(t, args, transferOption.Args)
assert.Equal(t, options, transferOption.Options)
}
func TestInspectCmdRun(t *testing.T) {
originalArgs := inspectOption.Args
originalOptions := inspectOption.Options
defer func() {
inspectOption.Args = originalArgs
inspectOption.Options = originalOptions
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc((*registry.Options).Inspect, func(o *registry.Options) {
})
cmd := &cobra.Command{}
args := []string{"arg1", "arg2"}
inspectDep.Run(cmd, args)
assert.Equal(t, args, inspectOption.Args)
assert.Equal(t, options, inspectOption.Options)
}
func TestManifestsCmdRun(t *testing.T) {
originalArgs := manifestsOption.Args
originalOptions := manifestsOption.Options
defer func() {
manifestsOption.Args = originalArgs
manifestsOption.Options = originalOptions
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc((*registry.Options).Manifests, func(o *registry.Options) {
})
cmd := &cobra.Command{}
args := []string{"arg1", "arg2"}
manifestsDep.Run(cmd, args)
assert.Equal(t, args, manifestsOption.Args)
assert.Equal(t, options, manifestsOption.Options)
}
func TestDeleteCmdRun(t *testing.T) {
originalArgs := deleteOption.Args
originalOptions := deleteOption.Options
defer func() {
deleteOption.Args = originalArgs
deleteOption.Options = originalOptions
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc((*registry.Options).Delete, func(o *registry.Options) {
})
cmd := &cobra.Command{}
args := []string{"arg1", "arg2"}
deleteDep.Run(cmd, args)
assert.Equal(t, args, deleteOption.Args)
assert.Equal(t, options, deleteOption.Options)
}
func TestViewCmdRun(t *testing.T) {
originalArgs := viewOption.Args
originalOptions := viewOption.Options
defer func() {
viewOption.Args = originalArgs
viewOption.Options = originalOptions
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc((*registry.Options).View, func(o *registry.Options) {
})
cmd := &cobra.Command{}
args := []string{"arg1", "arg2"}
viewDep.Run(cmd, args)
assert.Equal(t, args, viewOption.Args)
assert.Equal(t, options, viewOption.Options)
}
func TestDownloadCmdRun(t *testing.T) {
originalArgs := downloadOption.Args
originalOptions := downloadOption.Options
defer func() {
downloadOption.Args = originalArgs
downloadOption.Options = originalOptions
}()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc((*registry.OptionsDownload).Download, func(o *registry.OptionsDownload) error {
return nil
})
cmd := &cobra.Command{}
args := []string{"arg1", "arg2"}
downloadDep.Run(cmd, args)
assert.NotEqual(t, args, downloadOption.Args)
assert.Equal(t, options, downloadOption.Options)
}