github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/cloud/addcredential_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cloud_test 5 6 import ( 7 "fmt" 8 "io" 9 "io/ioutil" 10 "path/filepath" 11 "regexp" 12 "strings" 13 14 "github.com/golang/mock/gomock" 15 "github.com/juju/cmd" 16 "github.com/juju/cmd/cmdtesting" 17 "github.com/juju/errors" 18 jc "github.com/juju/testing/checkers" 19 gc "gopkg.in/check.v1" 20 21 jujucloud "github.com/juju/juju/cloud" 22 "github.com/juju/juju/cmd/juju/cloud" 23 "github.com/juju/juju/environs" 24 environsTesting "github.com/juju/juju/environs/testing" 25 "github.com/juju/juju/jujuclient" 26 _ "github.com/juju/juju/provider/all" 27 "github.com/juju/juju/testing" 28 ) 29 30 type addCredentialSuite struct { 31 testing.BaseSuite 32 33 store *jujuclient.MemStore 34 schema map[jujucloud.AuthType]jujucloud.CredentialSchema 35 authTypes []jujucloud.AuthType 36 cloudByNameFunc func(string) (*jujucloud.Cloud, error) 37 } 38 39 var _ = gc.Suite(&addCredentialSuite{ 40 store: jujuclient.NewMemStore(), 41 }) 42 43 func (s *addCredentialSuite) SetUpSuite(c *gc.C) { 44 s.BaseSuite.SetUpSuite(c) 45 unreg := environs.RegisterProvider("mock-addcredential-provider", &mockProvider{credSchemas: &s.schema}) 46 s.AddCleanup(func(_ *gc.C) { 47 unreg() 48 }) 49 s.cloudByNameFunc = func(cloud string) (*jujucloud.Cloud, error) { 50 if cloud != "somecloud" && cloud != "anothercloud" { 51 return nil, errors.NotFoundf("cloud %v", cloud) 52 } 53 return &jujucloud.Cloud{ 54 Type: "mock-addcredential-provider", 55 AuthTypes: s.authTypes, 56 Endpoint: "cloud-endpoint", 57 IdentityEndpoint: "cloud-identity-endpoint", 58 }, nil 59 } 60 } 61 62 func (s *addCredentialSuite) SetUpTest(c *gc.C) { 63 s.BaseSuite.SetUpTest(c) 64 s.store.Credentials = make(map[string]jujucloud.CloudCredential) 65 } 66 67 func (s *addCredentialSuite) run(c *gc.C, stdin io.Reader, args ...string) (*cmd.Context, error) { 68 addCmd := cloud.NewAddCredentialCommandForTest(s.store, s.cloudByNameFunc) 69 err := cmdtesting.InitCommand(addCmd, args) 70 if err != nil { 71 return nil, err 72 } 73 ctx := cmdtesting.Context(c) 74 ctx.Stdin = stdin 75 return ctx, addCmd.Run(ctx) 76 } 77 78 func (s *addCredentialSuite) TestBadArgs(c *gc.C) { 79 _, err := s.run(c, nil) 80 c.Assert(err, gc.ErrorMatches, `Usage: juju add-credential <cloud-name> \[-f <credentials.yaml>\]`) 81 _, err = s.run(c, nil, "somecloud", "-f", "credential.yaml", "extra") 82 c.Assert(err, gc.ErrorMatches, `unrecognized args: \["extra"\]`) 83 } 84 85 func (s *addCredentialSuite) TestBadCloudName(c *gc.C) { 86 _, err := s.run(c, nil, "badcloud") 87 c.Assert(err, gc.ErrorMatches, "cloud badcloud not valid") 88 } 89 90 func (s *addCredentialSuite) TestAddFromFileBadFilename(c *gc.C) { 91 _, err := s.run(c, nil, "somecloud", "-f", "somefile.yaml") 92 c.Assert(err, gc.ErrorMatches, ".*open somefile.yaml: .*") 93 } 94 95 func (s *addCredentialSuite) TestNoCredentialsRequired(c *gc.C) { 96 s.authTypes = nil 97 _, err := s.run(c, nil, "somecloud") 98 c.Assert(err, gc.ErrorMatches, `cloud "somecloud" does not require credentials`) 99 } 100 101 func (s *addCredentialSuite) createTestCredentialData(c *gc.C) string { 102 return s.createTestCredentialDataWithAuthType(c, "access-key") 103 } 104 105 func (s *addCredentialSuite) createTestCredentialDataWithAuthType(c *gc.C, authType string) string { 106 dir := c.MkDir() 107 credsFile := filepath.Join(dir, "cred.yaml") 108 data := fmt.Sprintf(` 109 credentials: 110 somecloud: 111 me: 112 auth-type: %v 113 access-key: <key> 114 secret-key: <secret> 115 `[1:], authType) 116 err := ioutil.WriteFile(credsFile, []byte(data), 0600) 117 c.Assert(err, jc.ErrorIsNil) 118 return credsFile 119 } 120 121 func (s *addCredentialSuite) TestAddFromFileNoCredentialsFound(c *gc.C) { 122 sourceFile := s.createTestCredentialData(c) 123 _, err := s.run(c, nil, "anothercloud", "-f", sourceFile) 124 c.Assert(err, gc.ErrorMatches, `no credentials for cloud anothercloud exist in file.*`) 125 } 126 127 func (s *addCredentialSuite) TestAddFromFileExisting(c *gc.C) { 128 s.store.Credentials = map[string]jujucloud.CloudCredential{ 129 "somecloud": { 130 AuthCredentials: map[string]jujucloud.Credential{"cred": {}}, 131 }, 132 } 133 sourceFile := s.createTestCredentialData(c) 134 _, err := s.run(c, nil, "somecloud", "-f", sourceFile) 135 c.Assert(err, gc.ErrorMatches, `local credentials for cloud "somecloud" already exist; use --replace to overwrite / merge`) 136 } 137 138 func (s *addCredentialSuite) TestAddFromFileExistingReplace(c *gc.C) { 139 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType, jujucloud.AccessKeyAuthType} 140 s.store.Credentials = map[string]jujucloud.CloudCredential{ 141 "somecloud": { 142 AuthCredentials: map[string]jujucloud.Credential{ 143 "cred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil)}, 144 }, 145 } 146 sourceFile := s.createTestCredentialData(c) 147 _, err := s.run(c, nil, "somecloud", "-f", sourceFile, "--replace") 148 c.Assert(err, jc.ErrorIsNil) 149 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 150 "somecloud": { 151 AuthCredentials: map[string]jujucloud.Credential{ 152 "cred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil), 153 "me": jujucloud.NewCredential(jujucloud.AccessKeyAuthType, map[string]string{ 154 "access-key": "<key>", 155 "secret-key": "<secret>", 156 })}, 157 }, 158 }) 159 } 160 161 func (s *addCredentialSuite) TestAddNewFromFile(c *gc.C) { 162 s.authTypes = []jujucloud.AuthType{jujucloud.AccessKeyAuthType} 163 sourceFile := s.createTestCredentialData(c) 164 _, err := s.run(c, nil, "somecloud", "-f", sourceFile) 165 c.Assert(err, jc.ErrorIsNil) 166 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 167 "somecloud": { 168 AuthCredentials: map[string]jujucloud.Credential{ 169 "me": jujucloud.NewCredential(jujucloud.AccessKeyAuthType, map[string]string{ 170 "access-key": "<key>", 171 "secret-key": "<secret>", 172 })}, 173 }, 174 }) 175 } 176 177 func (s *addCredentialSuite) TestAddInvalidAuth(c *gc.C) { 178 s.authTypes = []jujucloud.AuthType{jujucloud.AccessKeyAuthType} 179 sourceFile := s.createTestCredentialDataWithAuthType(c, "invalid auth") 180 _, err := s.run(c, nil, "somecloud", "-f", sourceFile) 181 c.Assert(err, gc.ErrorMatches, 182 regexp.QuoteMeta(`credential "me" contains invalid auth type "invalid auth", valid auth types for cloud "somecloud" are [access-key]`)) 183 } 184 185 func (s *addCredentialSuite) TestAddCloudUnsupportedAuth(c *gc.C) { 186 s.authTypes = []jujucloud.AuthType{jujucloud.AccessKeyAuthType} 187 sourceFile := s.createTestCredentialDataWithAuthType(c, fmt.Sprintf("%v", jujucloud.JSONFileAuthType)) 188 _, err := s.run(c, nil, "somecloud", "-f", sourceFile) 189 c.Assert(err, gc.ErrorMatches, 190 regexp.QuoteMeta(`credential "me" contains invalid auth type "jsonfile", valid auth types for cloud "somecloud" are [access-key]`)) 191 } 192 193 func (s *addCredentialSuite) assertAddUserpassCredential(c *gc.C, input string, expected *jujucloud.Credential, msg string) { 194 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 195 jujucloud.UserPassAuthType: { 196 { 197 "username", jujucloud.CredentialAttr{Optional: false}, 198 }, { 199 "password", jujucloud.CredentialAttr{Hidden: true}, 200 }, 201 }, 202 } 203 stdin := strings.NewReader(input) 204 ctx, err := s.run(c, stdin, "somecloud") 205 c.Assert(err, jc.ErrorIsNil) 206 var cred jujucloud.Credential 207 if expected == nil { 208 cred = jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{ 209 "username": "user", 210 "password": "password", 211 }) 212 } else { 213 cred = *expected 214 } 215 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 216 "somecloud": { 217 AuthCredentials: map[string]jujucloud.Credential{ 218 "fred": cred, 219 }, 220 }, 221 }) 222 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, msg) 223 } 224 225 func (s *addCredentialSuite) TestAddCredentialSingleAuthType(c *gc.C) { 226 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 227 expected := ` 228 Enter credential name: 229 Using auth-type "userpass". 230 231 Enter username: 232 Enter password: 233 Credential "fred" added locally for cloud "somecloud". 234 235 `[1:] 236 s.assertAddUserpassCredential(c, "fred\nuser\npassword\n", nil, expected) 237 } 238 239 func (s *addCredentialSuite) TestAddCredentialRetryOnMissingMandatoryAttribute(c *gc.C) { 240 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 241 expected := ` 242 Enter credential name: 243 Using auth-type "userpass". 244 245 Enter username: 246 Enter username: 247 Enter password: 248 Credential "fred" added locally for cloud "somecloud". 249 250 `[1:] 251 s.assertAddUserpassCredential(c, "fred\n\nuser\npassword\n", nil, expected) 252 } 253 254 func (s *addCredentialSuite) TestAddCredentialMultipleAuthType(c *gc.C) { 255 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType, jujucloud.AccessKeyAuthType} 256 expected := ` 257 Enter credential name: 258 Auth Types 259 userpass 260 access-key 261 262 Select auth type [userpass]: 263 Enter username: 264 Enter password: 265 Credential "fred" added locally for cloud "somecloud". 266 267 `[1:] 268 s.assertAddUserpassCredential(c, "fred\nuserpass\nuser\npassword\n", nil, expected) 269 } 270 271 func (s *addCredentialSuite) TestAddCredentialInteractive(c *gc.C) { 272 s.authTypes = []jujucloud.AuthType{"interactive"} 273 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 274 "interactive": {{"username", jujucloud.CredentialAttr{}}}, 275 } 276 277 stdin := strings.NewReader("bobscreds\nbob\n") 278 ctx, err := s.run(c, stdin, "somecloud") 279 c.Assert(err, jc.ErrorIsNil) 280 281 // there's an extra line return after Using auth-type because the rest get a 282 // second line return from the user hitting return when they enter a value 283 // (which is not shown here), but that one does not. 284 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, ` 285 Enter credential name: 286 Using auth-type "interactive". 287 288 Enter username: 289 Credential "bobscreds" added locally for cloud "somecloud". 290 291 `[1:]) 292 293 // FinalizeCredential should have generated a userpass credential 294 // based on the input from the interactive credential. 295 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 296 "somecloud": { 297 AuthCredentials: map[string]jujucloud.Credential{ 298 "bobscreds": jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{ 299 "username": "bob", 300 "password": "cloud-endpoint", 301 "application-password": "cloud-identity-endpoint", 302 }), 303 }, 304 }, 305 }) 306 } 307 308 func (s *addCredentialSuite) TestAddCredentialCredSchemaInteractive(c *gc.C) { 309 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 310 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 311 "interactive": {{"username", jujucloud.CredentialAttr{}}}, 312 jujucloud.UserPassAuthType: { 313 { 314 "username", jujucloud.CredentialAttr{Optional: false}, 315 }, { 316 "password", jujucloud.CredentialAttr{Hidden: true}, 317 }, 318 }, 319 } 320 321 stdin := strings.NewReader("bobscreds\n\nbob\n") 322 ctx, err := s.run(c, stdin, "somecloud") 323 c.Assert(err, jc.ErrorIsNil) 324 325 // there's an extra line return after Using auth-type because the rest get a 326 // second line return from the user hitting return when they enter a value 327 // (which is not shown here), but that one does not. 328 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, ` 329 Enter credential name: 330 Auth Types 331 userpass 332 interactive 333 334 Select auth type [interactive]: 335 Enter username: 336 Credential "bobscreds" added locally for cloud "somecloud". 337 338 `[1:]) 339 340 // FinalizeCredential should have generated a userpass credential 341 // based on the input from the interactive credential. 342 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 343 "somecloud": { 344 AuthCredentials: map[string]jujucloud.Credential{ 345 "bobscreds": jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{ 346 "username": "bob", 347 "password": "cloud-endpoint", 348 "application-password": "cloud-identity-endpoint", 349 }), 350 }, 351 }, 352 }) 353 } 354 355 func (s *addCredentialSuite) TestAddCredentialReplace(c *gc.C) { 356 s.store.Credentials = map[string]jujucloud.CloudCredential{ 357 "somecloud": { 358 AuthCredentials: map[string]jujucloud.Credential{ 359 "fred": jujucloud.NewCredential(jujucloud.UserPassAuthType, nil)}, 360 }, 361 } 362 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 363 expected := ` 364 Enter credential name: 365 A credential "fred" already exists locally on this client. 366 Replace local credential? (y/N): 367 Using auth-type "userpass". 368 369 Enter username: 370 Enter password: 371 Credential "fred" updated locally for cloud "somecloud". 372 373 `[1:] 374 s.assertAddUserpassCredential(c, "fred\ny\nuser\npassword\n", nil, expected) 375 } 376 377 func (s *addCredentialSuite) TestAddCredentialReplaceDecline(c *gc.C) { 378 cred := jujucloud.NewCredential(jujucloud.UserPassAuthType, nil) 379 s.store.Credentials = map[string]jujucloud.CloudCredential{ 380 "somecloud": { 381 AuthCredentials: map[string]jujucloud.Credential{ 382 "fred": cred}, 383 }, 384 } 385 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 386 expected := ` 387 Enter credential name: 388 A credential "fred" already exists locally on this client. 389 Replace local credential? (y/N): 390 `[1:] 391 s.assertAddUserpassCredential(c, "fred\nn\n", &cred, expected) 392 } 393 394 func (s *addCredentialSuite) assertAddFileCredential(c *gc.C, input, fileKey string) { 395 dir := c.MkDir() 396 filename := filepath.Join(dir, "jsonfile") 397 err := ioutil.WriteFile(filename, []byte{}, 0600) 398 c.Assert(err, jc.ErrorIsNil) 399 400 stdin := strings.NewReader(fmt.Sprintf(input, filename)) 401 addCmd := cloud.NewAddCredentialCommandForTest(s.store, s.cloudByNameFunc) 402 err = cmdtesting.InitCommand(addCmd, []string{"somecloud"}) 403 c.Assert(err, jc.ErrorIsNil) 404 ctx := cmdtesting.ContextForDir(c, dir) 405 ctx.Stdin = stdin 406 err = addCmd.Run(ctx) 407 c.Assert(err, jc.ErrorIsNil) 408 409 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 410 "somecloud": { 411 AuthCredentials: map[string]jujucloud.Credential{ 412 "fred": jujucloud.NewCredential(s.authTypes[0], map[string]string{ 413 fileKey: filename, 414 }), 415 }, 416 }, 417 }) 418 } 419 420 func (s *addCredentialSuite) TestAddJsonFileCredential(c *gc.C) { 421 s.authTypes = []jujucloud.AuthType{jujucloud.JSONFileAuthType} 422 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 423 jujucloud.JSONFileAuthType: { 424 { 425 "file", 426 jujucloud.CredentialAttr{ 427 Optional: false, 428 FilePath: true, 429 }, 430 }, 431 }, 432 } 433 // Input includes invalid file info. 434 s.assertAddFileCredential(c, "fred\nbadfile\n.\n%s\n", "file") 435 } 436 437 func (s *addCredentialSuite) TestAddCredentialWithFileAttr(c *gc.C) { 438 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 439 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 440 jujucloud.UserPassAuthType: { 441 { 442 "key", 443 jujucloud.CredentialAttr{ 444 FileAttr: "key-file", 445 }, 446 }, 447 }, 448 } 449 // Input includes invalid file info. 450 s.assertAddFileCredential(c, "fred\nbadfile\n.\n%s\n", "key-file") 451 } 452 453 func (s *addCredentialSuite) assertAddCredentialWithOptions(c *gc.C, input string) { 454 s.authTypes = []jujucloud.AuthType{jujucloud.UserPassAuthType} 455 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 456 jujucloud.UserPassAuthType: { 457 { 458 "username", jujucloud.CredentialAttr{Optional: false}, 459 }, { 460 "algorithm", jujucloud.CredentialAttr{Options: []interface{}{"optionA", "optionB"}}, 461 }, 462 }, 463 } 464 // Input includes a bad option 465 stdin := strings.NewReader(input) 466 _, err := s.run(c, stdin, "somecloud") 467 c.Assert(err, jc.ErrorIsNil) 468 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 469 "somecloud": { 470 AuthCredentials: map[string]jujucloud.Credential{ 471 "fred": jujucloud.NewCredential(jujucloud.UserPassAuthType, map[string]string{ 472 "username": "user", 473 "algorithm": "optionA", 474 }), 475 }, 476 }, 477 }) 478 } 479 480 func (s *addCredentialSuite) TestAddCredentialWithOptions(c *gc.C) { 481 s.assertAddCredentialWithOptions(c, "fred\nuser\nbadoption\noptionA\n") 482 } 483 484 func (s *addCredentialSuite) TestAddCredentialWithOptionsAutofill(c *gc.C) { 485 s.assertAddCredentialWithOptions(c, "fred\nuser\n\n") 486 } 487 488 func (s *addCredentialSuite) TestAddMAASCredential(c *gc.C) { 489 s.authTypes = []jujucloud.AuthType{jujucloud.OAuth1AuthType} 490 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 491 jujucloud.OAuth1AuthType: { 492 { 493 "maas-oauth", jujucloud.CredentialAttr{}, 494 }, 495 }, 496 } 497 stdin := strings.NewReader("fred\nauth:token\n") 498 _, err := s.run(c, stdin, "somecloud") 499 c.Assert(err, jc.ErrorIsNil) 500 c.Assert(s.store.Credentials, jc.DeepEquals, map[string]jujucloud.CloudCredential{ 501 "somecloud": { 502 AuthCredentials: map[string]jujucloud.Credential{ 503 "fred": jujucloud.NewCredential(jujucloud.OAuth1AuthType, map[string]string{ 504 "maas-oauth": "auth:token", 505 }), 506 }, 507 }, 508 }) 509 } 510 511 func (s *addCredentialSuite) TestAddGCEFileCredentials(c *gc.C) { 512 s.authTypes = []jujucloud.AuthType{jujucloud.JSONFileAuthType} 513 s.schema = map[jujucloud.AuthType]jujucloud.CredentialSchema{ 514 jujucloud.JSONFileAuthType: { 515 { 516 "file", 517 jujucloud.CredentialAttr{ 518 Description: "path to the credential file", 519 Optional: false, 520 FilePath: true, 521 }, 522 }, 523 }, 524 } 525 sourceFile := s.createTestCredentialDataWithAuthType(c, fmt.Sprintf("%v", jujucloud.JSONFileAuthType)) 526 stdin := strings.NewReader(fmt.Sprintf("blah\n%s\n", sourceFile)) 527 ctx, err := s.run(c, stdin, "somecloud") 528 c.Assert(err, jc.ErrorIsNil) 529 expected := ` 530 Enter credential name: 531 Using auth-type "jsonfile". 532 533 Enter path to the credential file: 534 Credential "blah" added locally for cloud "somecloud". 535 536 `[1:] 537 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, expected) 538 } 539 540 func (s *addCredentialSuite) TestShouldFinalizeCredentialWithEnvironProvider(c *gc.C) { 541 ctrl := gomock.NewController(c) 542 defer ctrl.Finish() 543 544 provider := environsTesting.NewMockEnvironProvider(ctrl) 545 cred := jujucloud.Credential{} 546 got := cloud.ShouldFinalizeCredential(provider, cred) 547 c.Assert(got, jc.IsFalse) 548 } 549 550 func (s *addCredentialSuite) TestShouldFinalizeCredentialSuccess(c *gc.C) { 551 ctrl := gomock.NewController(c) 552 defer ctrl.Finish() 553 554 provider := struct { 555 environs.EnvironProvider 556 *environsTesting.MockRequestFinalizeCredential 557 }{ 558 EnvironProvider: environsTesting.NewMockEnvironProvider(ctrl), 559 MockRequestFinalizeCredential: environsTesting.NewMockRequestFinalizeCredential(ctrl), 560 } 561 562 cred := jujucloud.Credential{} 563 provider.MockRequestFinalizeCredential.EXPECT().ShouldFinalizeCredential(cred).Return(true) 564 565 got := cloud.ShouldFinalizeCredential(provider, cred) 566 c.Assert(got, jc.IsTrue) 567 } 568 569 func (s *addCredentialSuite) TestShouldFinalizeCredentialFailure(c *gc.C) { 570 ctrl := gomock.NewController(c) 571 defer ctrl.Finish() 572 573 provider := struct { 574 environs.EnvironProvider 575 *environsTesting.MockRequestFinalizeCredential 576 }{ 577 EnvironProvider: environsTesting.NewMockEnvironProvider(ctrl), 578 MockRequestFinalizeCredential: environsTesting.NewMockRequestFinalizeCredential(ctrl), 579 } 580 581 cred := jujucloud.Credential{} 582 provider.MockRequestFinalizeCredential.EXPECT().ShouldFinalizeCredential(cred).Return(false) 583 584 got := cloud.ShouldFinalizeCredential(provider, cred) 585 c.Assert(got, jc.IsFalse) 586 }