github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/plugin/plugin_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package plugin 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "reflect" 27 "strings" 28 "testing" 29 30 "k8s.io/cli-runtime/pkg/genericiooptions" 31 ) 32 33 func TestPluginPathsAreUnaltered(t *testing.T) { 34 tempDir1, err := os.MkdirTemp(os.TempDir(), "test-cmd-plugins1") 35 if err != nil { 36 t.Fatalf("unexpected error: %v", err) 37 } 38 tempDir2, err := os.MkdirTemp(os.TempDir(), "test-cmd-plugins2") 39 if err != nil { 40 t.Fatalf("unexpected error: %v", err) 41 } 42 // cleanup 43 defer func() { 44 if err := os.RemoveAll(tempDir1); err != nil { 45 panic(fmt.Errorf("unexpected cleanup error: %v", err)) 46 } 47 if err := os.RemoveAll(tempDir2); err != nil { 48 panic(fmt.Errorf("unexpected cleanup error: %v", err)) 49 } 50 }() 51 52 ioStreams, _, _, errOut := genericiooptions.NewTestIOStreams() 53 verifier := newFakePluginPathVerifier() 54 pluginPaths := []string{tempDir1, tempDir2} 55 o := &PluginListOptions{ 56 Verifier: verifier, 57 IOStreams: ioStreams, 58 59 PluginPaths: pluginPaths, 60 } 61 62 // write at least one valid plugin file 63 if _, err := os.CreateTemp(tempDir1, "kbcli-"); err != nil { 64 t.Fatalf("unexpected error %v", err) 65 } 66 if _, err := os.CreateTemp(tempDir2, "kubectl-"); err != nil { 67 t.Fatalf("unexpected error %v", err) 68 } 69 70 if err := o.Run(); err != nil { 71 t.Fatalf("unexpected error %v - %v", err, errOut.String()) 72 } 73 74 // ensure original paths remain unaltered 75 if len(verifier.seenUnsorted) != len(pluginPaths) { 76 t.Fatalf("saw unexpected plugin paths. Expecting %v, got %v", pluginPaths, verifier.seenUnsorted) 77 } 78 for actual := range verifier.seenUnsorted { 79 if !strings.HasPrefix(verifier.seenUnsorted[actual], pluginPaths[actual]) { 80 t.Fatalf("expected PATH slice to be unaltered. Expecting %v, but got %v", pluginPaths[actual], verifier.seenUnsorted[actual]) 81 } 82 } 83 } 84 85 func TestPluginPathsAreValid(t *testing.T) { 86 tempDir, err := os.MkdirTemp(os.TempDir(), "test-cmd-plugins") 87 if err != nil { 88 t.Fatalf("unexpected error: %v", err) 89 } 90 // cleanup 91 defer func() { 92 if err := os.RemoveAll(tempDir); err != nil { 93 panic(fmt.Errorf("unexpected cleanup error: %v", err)) 94 } 95 }() 96 97 tc := []struct { 98 name string 99 pluginPaths []string 100 pluginFile func() (*os.File, error) 101 verifier *fakePluginPathVerifier 102 expectVerifyErrors []error 103 expectErr string 104 expectErrOut string 105 expectOut string 106 }{ 107 { 108 name: "ensure no plugins found if no files begin with kubectl- prefix", 109 pluginPaths: []string{tempDir}, 110 verifier: newFakePluginPathVerifier(), 111 pluginFile: func() (*os.File, error) { 112 return os.CreateTemp(tempDir, "notkbcli-") 113 }, 114 expectErr: "error: unable to find any kbcli or kubectl plugins in your PATH\n", 115 expectOut: "NAME", 116 }, 117 { 118 name: "ensure de-duplicated plugin-paths slice", 119 pluginPaths: []string{tempDir, tempDir}, 120 verifier: newFakePluginPathVerifier(), 121 pluginFile: func() (*os.File, error) { 122 return os.CreateTemp(tempDir, "kbcli-") 123 }, 124 expectOut: "NAME", 125 }, 126 { 127 name: "ensure no errors when empty string or blank path are specified", 128 pluginPaths: []string{tempDir, "", " "}, 129 verifier: newFakePluginPathVerifier(), 130 pluginFile: func() (*os.File, error) { 131 return os.CreateTemp(tempDir, "kbcli-") 132 }, 133 expectOut: "NAME", 134 }, 135 } 136 137 for _, test := range tc { 138 t.Run(test.name, func(t *testing.T) { 139 ioStreams, _, out, errOut := genericiooptions.NewTestIOStreams() 140 o := &PluginListOptions{ 141 Verifier: test.verifier, 142 IOStreams: ioStreams, 143 144 PluginPaths: test.pluginPaths, 145 } 146 147 // create files 148 if test.pluginFile != nil { 149 if _, err := test.pluginFile(); err != nil { 150 t.Fatalf("unexpected error creating plugin file: %v", err) 151 } 152 } 153 154 for _, expected := range test.expectVerifyErrors { 155 for _, actual := range test.verifier.errors { 156 if expected != actual { 157 t.Fatalf("unexpected error: expected %v, but got %v", expected, actual) 158 } 159 } 160 } 161 162 err := o.Run() 163 switch { 164 case err == nil && len(test.expectErr) > 0: 165 t.Fatalf("unexpected non-error: expected %v, but got nothing", test.expectErr) 166 case err != nil && len(test.expectErr) == 0: 167 t.Fatalf("unexpected error: expected nothing, but got %v", err.Error()) 168 case err != nil && err.Error() != test.expectErr: 169 t.Fatalf("unexpected error: expected %v, but got %v", test.expectErr, err.Error()) 170 } 171 172 if len(test.expectErrOut) == 0 && errOut.Len() > 0 { 173 t.Fatalf("unexpected error output: expected nothing, but got %v", errOut.String()) 174 } else if len(test.expectErrOut) > 0 && !strings.Contains(errOut.String(), test.expectErrOut) { 175 t.Fatalf("unexpected error output: expected to contain %v, but got %v", test.expectErrOut, errOut.String()) 176 } 177 178 if len(test.expectOut) > 0 && !strings.Contains(out.String(), test.expectOut) { 179 t.Fatalf("unexpected output: expected to contain %v, but got %v", test.expectOut, out.String()) 180 } 181 }) 182 } 183 } 184 185 func TestListPlugins(t *testing.T) { 186 pluginPath, _ := filepath.Abs("./testdata") 187 expectPlugins := []string{ 188 filepath.Join(pluginPath, "kbcli-foo"), 189 filepath.Join(pluginPath, "kbcli-version"), 190 filepath.Join(pluginPath, "kubectl-foo"), 191 filepath.Join(pluginPath, "kubectl-version"), 192 } 193 194 verifier := newFakePluginPathVerifier() 195 ioStreams, _, _, _ := genericiooptions.NewTestIOStreams() 196 pluginPaths := []string{pluginPath} 197 198 o := &PluginListOptions{ 199 Verifier: verifier, 200 IOStreams: ioStreams, 201 202 PluginPaths: pluginPaths, 203 } 204 205 plugins, errs := o.ListPlugins() 206 if len(errs) > 0 { 207 t.Fatalf("unexpected errors: %v", errs) 208 } 209 210 if !reflect.DeepEqual(expectPlugins, plugins) { 211 t.Fatalf("saw unexpected plugins. Expecting %v, got %v", expectPlugins, plugins) 212 } 213 } 214 215 type duplicatePathError struct { 216 path string 217 } 218 219 func (d *duplicatePathError) Error() string { 220 return fmt.Sprintf("path %q already visited", d.path) 221 } 222 223 type fakePluginPathVerifier struct { 224 errors []error 225 seen map[string]bool 226 seenUnsorted []string 227 } 228 229 func (f *fakePluginPathVerifier) Verify(path string) []error { 230 if f.seen[path] { 231 err := &duplicatePathError{path} 232 f.errors = append(f.errors, err) 233 return []error{err} 234 } 235 f.seen[path] = true 236 f.seenUnsorted = append(f.seenUnsorted, path) 237 return nil 238 } 239 240 func newFakePluginPathVerifier() *fakePluginPathVerifier { 241 return &fakePluginPathVerifier{seen: make(map[string]bool)} 242 }