github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/extension/command_test.go (about) 1 package extension 2 3 import ( 4 "io" 5 "io/ioutil" 6 "os" 7 "strings" 8 "testing" 9 10 "github.com/MakeNowJust/heredoc" 11 "github.com/cli/cli/internal/config" 12 "github.com/cli/cli/pkg/cmdutil" 13 "github.com/cli/cli/pkg/extensions" 14 "github.com/cli/cli/pkg/iostreams" 15 "github.com/spf13/cobra" 16 "github.com/stretchr/testify/assert" 17 ) 18 19 func TestNewCmdExtension(t *testing.T) { 20 tempDir := t.TempDir() 21 oldWd, _ := os.Getwd() 22 assert.NoError(t, os.Chdir(tempDir)) 23 t.Cleanup(func() { _ = os.Chdir(oldWd) }) 24 25 tests := []struct { 26 name string 27 args []string 28 managerStubs func(em *extensions.ExtensionManagerMock) func(*testing.T) 29 isTTY bool 30 wantErr bool 31 errMsg string 32 wantStdout string 33 wantStderr string 34 }{ 35 { 36 name: "install an extension", 37 args: []string{"install", "owner/gh-some-ext"}, 38 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 39 em.ListFunc = func(bool) []extensions.Extension { 40 return []extensions.Extension{} 41 } 42 em.InstallFunc = func(s string, out, errOut io.Writer) error { 43 return nil 44 } 45 return func(t *testing.T) { 46 installCalls := em.InstallCalls() 47 assert.Equal(t, 1, len(installCalls)) 48 assert.Equal(t, "https://github.com/owner/gh-some-ext.git", installCalls[0].URL) 49 listCalls := em.ListCalls() 50 assert.Equal(t, 1, len(listCalls)) 51 } 52 }, 53 }, 54 { 55 name: "install an extension with same name as existing extension", 56 args: []string{"install", "owner/gh-existing-ext"}, 57 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 58 em.ListFunc = func(bool) []extensions.Extension { 59 e := &Extension{path: "owner2/gh-existing-ext"} 60 return []extensions.Extension{e} 61 } 62 return func(t *testing.T) { 63 calls := em.ListCalls() 64 assert.Equal(t, 1, len(calls)) 65 } 66 }, 67 wantErr: true, 68 errMsg: "there is already an installed extension that provides the \"existing-ext\" command", 69 }, 70 { 71 name: "install local extension", 72 args: []string{"install", "."}, 73 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 74 em.InstallLocalFunc = func(dir string) error { 75 return nil 76 } 77 return func(t *testing.T) { 78 calls := em.InstallLocalCalls() 79 assert.Equal(t, 1, len(calls)) 80 assert.Equal(t, tempDir, normalizeDir(calls[0].Dir)) 81 } 82 }, 83 }, 84 { 85 name: "upgrade error", 86 args: []string{"upgrade"}, 87 wantErr: true, 88 errMsg: "must specify an extension to upgrade", 89 }, 90 { 91 name: "upgrade an extension", 92 args: []string{"upgrade", "hello"}, 93 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 94 em.UpgradeFunc = func(name string, force bool, out, errOut io.Writer) error { 95 return nil 96 } 97 return func(t *testing.T) { 98 calls := em.UpgradeCalls() 99 assert.Equal(t, 1, len(calls)) 100 assert.Equal(t, "hello", calls[0].Name) 101 } 102 }, 103 }, 104 { 105 name: "upgrade an extension gh-prefix", 106 args: []string{"upgrade", "gh-hello"}, 107 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 108 em.UpgradeFunc = func(name string, force bool, out, errOut io.Writer) error { 109 return nil 110 } 111 return func(t *testing.T) { 112 calls := em.UpgradeCalls() 113 assert.Equal(t, 1, len(calls)) 114 assert.Equal(t, "hello", calls[0].Name) 115 } 116 }, 117 }, 118 { 119 name: "upgrade an extension full name", 120 args: []string{"upgrade", "monalisa/gh-hello"}, 121 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 122 em.UpgradeFunc = func(name string, force bool, out, errOut io.Writer) error { 123 return nil 124 } 125 return func(t *testing.T) { 126 calls := em.UpgradeCalls() 127 assert.Equal(t, 1, len(calls)) 128 assert.Equal(t, "hello", calls[0].Name) 129 } 130 }, 131 }, 132 { 133 name: "upgrade all", 134 args: []string{"upgrade", "--all"}, 135 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 136 em.UpgradeFunc = func(name string, force bool, out, errOut io.Writer) error { 137 return nil 138 } 139 return func(t *testing.T) { 140 calls := em.UpgradeCalls() 141 assert.Equal(t, 1, len(calls)) 142 assert.Equal(t, "", calls[0].Name) 143 } 144 }, 145 }, 146 { 147 name: "remove extension tty", 148 args: []string{"remove", "hello"}, 149 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 150 em.RemoveFunc = func(name string) error { 151 return nil 152 } 153 return func(t *testing.T) { 154 calls := em.RemoveCalls() 155 assert.Equal(t, 1, len(calls)) 156 assert.Equal(t, "hello", calls[0].Name) 157 } 158 }, 159 isTTY: true, 160 wantStdout: "✓ Removed extension hello\n", 161 }, 162 { 163 name: "remove extension nontty", 164 args: []string{"remove", "hello"}, 165 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 166 em.RemoveFunc = func(name string) error { 167 return nil 168 } 169 return func(t *testing.T) { 170 calls := em.RemoveCalls() 171 assert.Equal(t, 1, len(calls)) 172 assert.Equal(t, "hello", calls[0].Name) 173 } 174 }, 175 isTTY: false, 176 wantStdout: "", 177 }, 178 { 179 name: "remove extension gh-prefix", 180 args: []string{"remove", "gh-hello"}, 181 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 182 em.RemoveFunc = func(name string) error { 183 return nil 184 } 185 return func(t *testing.T) { 186 calls := em.RemoveCalls() 187 assert.Equal(t, 1, len(calls)) 188 assert.Equal(t, "hello", calls[0].Name) 189 } 190 }, 191 isTTY: false, 192 wantStdout: "", 193 }, 194 { 195 name: "remove extension full name", 196 args: []string{"remove", "monalisa/gh-hello"}, 197 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 198 em.RemoveFunc = func(name string) error { 199 return nil 200 } 201 return func(t *testing.T) { 202 calls := em.RemoveCalls() 203 assert.Equal(t, 1, len(calls)) 204 assert.Equal(t, "hello", calls[0].Name) 205 } 206 }, 207 isTTY: false, 208 wantStdout: "", 209 }, 210 { 211 name: "list extensions", 212 args: []string{"list"}, 213 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 214 em.ListFunc = func(bool) []extensions.Extension { 215 ex1 := &Extension{path: "cli/gh-test", url: "https://github.com/cli/gh-test", updateAvailable: false} 216 ex2 := &Extension{path: "cli/gh-test2", url: "https://github.com/cli/gh-test2", updateAvailable: true} 217 return []extensions.Extension{ex1, ex2} 218 } 219 return func(t *testing.T) { 220 assert.Equal(t, 1, len(em.ListCalls())) 221 } 222 }, 223 wantStdout: "gh test\tcli/gh-test\t\ngh test2\tcli/gh-test2\tUpgrade available\n", 224 }, 225 { 226 name: "create extension tty", 227 args: []string{"create", "test"}, 228 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 229 em.CreateFunc = func(name string) error { 230 return nil 231 } 232 return func(t *testing.T) { 233 calls := em.CreateCalls() 234 assert.Equal(t, 1, len(calls)) 235 assert.Equal(t, "gh-test", calls[0].Name) 236 } 237 }, 238 isTTY: true, 239 wantStdout: heredoc.Doc(` 240 ✓ Created directory gh-test 241 ✓ Initialized git repository 242 ✓ Set up extension scaffolding 243 244 gh-test is ready for development 245 246 Install locally with: cd gh-test && gh extension install . 247 248 Publish to GitHub with: gh repo create gh-test 249 250 For more information on writing extensions: 251 https://docs.github.com/github-cli/github-cli/creating-github-cli-extensions 252 `), 253 }, 254 { 255 name: "create extension notty", 256 args: []string{"create", "gh-test"}, 257 managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { 258 em.CreateFunc = func(name string) error { 259 return nil 260 } 261 return func(t *testing.T) { 262 calls := em.CreateCalls() 263 assert.Equal(t, 1, len(calls)) 264 assert.Equal(t, "gh-test", calls[0].Name) 265 } 266 }, 267 isTTY: false, 268 wantStdout: "", 269 }, 270 } 271 272 for _, tt := range tests { 273 t.Run(tt.name, func(t *testing.T) { 274 ios, _, stdout, stderr := iostreams.Test() 275 ios.SetStdoutTTY(tt.isTTY) 276 ios.SetStderrTTY(tt.isTTY) 277 278 var assertFunc func(*testing.T) 279 em := &extensions.ExtensionManagerMock{} 280 if tt.managerStubs != nil { 281 assertFunc = tt.managerStubs(em) 282 } 283 284 f := cmdutil.Factory{ 285 Config: func() (config.Config, error) { 286 return config.NewBlankConfig(), nil 287 }, 288 IOStreams: ios, 289 ExtensionManager: em, 290 } 291 292 cmd := NewCmdExtension(&f) 293 cmd.SetArgs(tt.args) 294 cmd.SetOut(ioutil.Discard) 295 cmd.SetErr(ioutil.Discard) 296 297 _, err := cmd.ExecuteC() 298 if tt.wantErr { 299 assert.EqualError(t, err, tt.errMsg) 300 } else { 301 assert.NoError(t, err) 302 } 303 304 if assertFunc != nil { 305 assertFunc(t) 306 } 307 308 assert.Equal(t, tt.wantStdout, stdout.String()) 309 assert.Equal(t, tt.wantStderr, stderr.String()) 310 }) 311 } 312 } 313 314 func normalizeDir(d string) string { 315 return strings.TrimPrefix(d, "/private") 316 } 317 318 func Test_checkValidExtension(t *testing.T) { 319 rootCmd := &cobra.Command{} 320 rootCmd.AddCommand(&cobra.Command{Use: "help"}) 321 rootCmd.AddCommand(&cobra.Command{Use: "auth"}) 322 323 m := &extensions.ExtensionManagerMock{ 324 ListFunc: func(bool) []extensions.Extension { 325 return []extensions.Extension{ 326 &extensions.ExtensionMock{ 327 NameFunc: func() string { return "screensaver" }, 328 }, 329 &extensions.ExtensionMock{ 330 NameFunc: func() string { return "triage" }, 331 }, 332 } 333 }, 334 } 335 336 type args struct { 337 rootCmd *cobra.Command 338 manager extensions.ExtensionManager 339 extName string 340 } 341 tests := []struct { 342 name string 343 args args 344 wantError string 345 }{ 346 { 347 name: "valid extension", 348 args: args{ 349 rootCmd: rootCmd, 350 manager: m, 351 extName: "gh-hello", 352 }, 353 }, 354 { 355 name: "invalid extension name", 356 args: args{ 357 rootCmd: rootCmd, 358 manager: m, 359 extName: "gherkins", 360 }, 361 wantError: "extension repository name must start with `gh-`", 362 }, 363 { 364 name: "clashes with built-in command", 365 args: args{ 366 rootCmd: rootCmd, 367 manager: m, 368 extName: "gh-auth", 369 }, 370 wantError: "\"auth\" matches the name of a built-in command", 371 }, 372 { 373 name: "clashes with an installed extension", 374 args: args{ 375 rootCmd: rootCmd, 376 manager: m, 377 extName: "gh-triage", 378 }, 379 wantError: "there is already an installed extension that provides the \"triage\" command", 380 }, 381 } 382 for _, tt := range tests { 383 t.Run(tt.name, func(t *testing.T) { 384 err := checkValidExtension(tt.args.rootCmd, tt.args.manager, tt.args.extName) 385 if tt.wantError == "" { 386 assert.NoError(t, err) 387 } else { 388 assert.EqualError(t, err, tt.wantError) 389 } 390 }) 391 } 392 }