github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/modelmanager/modelinfo_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package modelmanager_test 5 6 import ( 7 "strings" 8 "time" 9 10 "github.com/juju/errors" 11 gitjujutesting "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils/series" 14 gc "gopkg.in/check.v1" 15 "gopkg.in/juju/names.v2" 16 17 "github.com/juju/juju/apiserver/common" 18 "github.com/juju/juju/apiserver/metricsender" 19 "github.com/juju/juju/apiserver/modelmanager" 20 "github.com/juju/juju/apiserver/params" 21 apiservertesting "github.com/juju/juju/apiserver/testing" 22 "github.com/juju/juju/cloud" 23 "github.com/juju/juju/controller" 24 "github.com/juju/juju/core/description" 25 "github.com/juju/juju/environs" 26 "github.com/juju/juju/environs/config" 27 "github.com/juju/juju/instance" 28 "github.com/juju/juju/permission" 29 "github.com/juju/juju/state" 30 "github.com/juju/juju/status" 31 coretesting "github.com/juju/juju/testing" 32 ) 33 34 type modelInfoSuite struct { 35 coretesting.BaseSuite 36 authorizer apiservertesting.FakeAuthorizer 37 st *mockState 38 modelmanager *modelmanager.ModelManagerAPI 39 } 40 41 func pUint64(v uint64) *uint64 { 42 return &v 43 } 44 45 var _ = gc.Suite(&modelInfoSuite{}) 46 47 func (s *modelInfoSuite) SetUpTest(c *gc.C) { 48 s.BaseSuite.SetUpTest(c) 49 s.authorizer = apiservertesting.FakeAuthorizer{ 50 Tag: names.NewUserTag("admin@local"), 51 } 52 s.st = &mockState{ 53 modelUUID: coretesting.ModelTag.Id(), 54 controllerUUID: coretesting.ControllerTag.Id(), 55 cloud: cloud.Cloud{ 56 Type: "dummy", 57 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType}, 58 }, 59 cfgDefaults: config.ModelDefaultAttributes{ 60 "attr": config.AttributeDefaultValues{ 61 Default: "", 62 Controller: "val", 63 Regions: []config.RegionDefaultValue{{ 64 Name: "dummy", 65 Value: "val++"}}}, 66 "attr2": config.AttributeDefaultValues{ 67 Controller: "val3", 68 Default: "val2", 69 Regions: []config.RegionDefaultValue{{ 70 Name: "left", 71 Value: "spam"}}}, 72 }, 73 } 74 s.st.controllerModel = &mockModel{ 75 owner: names.NewUserTag("admin@local"), 76 life: state.Alive, 77 cfg: coretesting.ModelConfig(c), 78 status: status.StatusInfo{ 79 Status: status.Available, 80 Since: &time.Time{}, 81 }, 82 users: []*mockModelUser{{ 83 userName: "admin", 84 access: permission.AdminAccess, 85 }, { 86 userName: "otheruser", 87 access: permission.AdminAccess, 88 }}, 89 } 90 91 s.st.model = &mockModel{ 92 owner: names.NewUserTag("bob@local"), 93 cfg: coretesting.ModelConfig(c), 94 life: state.Dying, 95 status: status.StatusInfo{ 96 Status: status.Destroying, 97 Since: &time.Time{}, 98 }, 99 100 users: []*mockModelUser{{ 101 userName: "admin", 102 access: permission.AdminAccess, 103 }, { 104 userName: "bob@local", 105 displayName: "Bob", 106 access: permission.ReadAccess, 107 }, { 108 userName: "charlotte@local", 109 displayName: "Charlotte", 110 access: permission.ReadAccess, 111 }, { 112 userName: "mary@local", 113 displayName: "Mary", 114 access: permission.WriteAccess, 115 }}, 116 } 117 s.st.machines = []common.Machine{ 118 &mockMachine{ 119 id: "1", 120 containerType: "none", 121 life: state.Alive, 122 hw: &instance.HardwareCharacteristics{CpuCores: pUint64(1)}, 123 }, 124 &mockMachine{ 125 id: "2", 126 life: state.Alive, 127 containerType: "lxc", 128 }, 129 &mockMachine{ 130 id: "3", 131 life: state.Dead, 132 }, 133 } 134 135 var err error 136 s.modelmanager, err = modelmanager.NewModelManagerAPI(s.st, nil, &s.authorizer) 137 c.Assert(err, jc.ErrorIsNil) 138 } 139 140 func (s *modelInfoSuite) setAPIUser(c *gc.C, user names.UserTag) { 141 s.authorizer.Tag = user 142 var err error 143 s.modelmanager, err = modelmanager.NewModelManagerAPI(s.st, nil, s.authorizer) 144 c.Assert(err, jc.ErrorIsNil) 145 } 146 147 func (s *modelInfoSuite) TestModelInfo(c *gc.C) { 148 info := s.getModelInfo(c) 149 c.Assert(info, jc.DeepEquals, params.ModelInfo{ 150 Name: "testenv", 151 UUID: s.st.model.cfg.UUID(), 152 ControllerUUID: "deadbeef-1bad-500d-9000-4b1d0d06f00d", 153 OwnerTag: "user-bob@local", 154 ProviderType: "someprovider", 155 CloudTag: "cloud-some-cloud", 156 CloudRegion: "some-region", 157 CloudCredentialTag: "cloudcred-some-cloud_bob@local_some-credential", 158 DefaultSeries: series.LatestLts(), 159 Life: params.Dying, 160 Status: params.EntityStatus{ 161 Status: status.Destroying, 162 Since: &time.Time{}, 163 }, 164 Users: []params.ModelUserInfo{{ 165 UserName: "admin", 166 LastConnection: &time.Time{}, 167 Access: params.ModelAdminAccess, 168 }, { 169 UserName: "bob@local", 170 DisplayName: "Bob", 171 LastConnection: &time.Time{}, 172 Access: params.ModelReadAccess, 173 }, { 174 UserName: "charlotte@local", 175 DisplayName: "Charlotte", 176 LastConnection: &time.Time{}, 177 Access: params.ModelReadAccess, 178 }, { 179 UserName: "mary@local", 180 DisplayName: "Mary", 181 LastConnection: &time.Time{}, 182 Access: params.ModelWriteAccess, 183 }}, 184 Machines: []params.ModelMachineInfo{{ 185 Id: "1", 186 Hardware: ¶ms.MachineHardware{Cores: pUint64(1)}, 187 }, { 188 Id: "2", 189 }}, 190 }) 191 s.st.CheckCalls(c, []gitjujutesting.StubCall{ 192 {"ControllerTag", nil}, 193 {"ModelUUID", nil}, 194 {"ForModel", []interface{}{names.NewModelTag(s.st.model.cfg.UUID())}}, 195 {"Model", nil}, 196 {"ControllerConfig", nil}, 197 {"LastModelConnection", []interface{}{names.NewUserTag("admin")}}, 198 {"LastModelConnection", []interface{}{names.NewLocalUserTag("bob")}}, 199 {"LastModelConnection", []interface{}{names.NewLocalUserTag("charlotte")}}, 200 {"LastModelConnection", []interface{}{names.NewLocalUserTag("mary")}}, 201 {"AllMachines", nil}, 202 {"Close", nil}, 203 }) 204 s.st.model.CheckCalls(c, []gitjujutesting.StubCall{ 205 {"Config", nil}, 206 {"Users", nil}, 207 {"ModelTag", nil}, 208 {"ModelTag", nil}, 209 {"ModelTag", nil}, 210 {"ModelTag", nil}, 211 {"Status", nil}, 212 {"Owner", nil}, 213 {"Life", nil}, 214 {"Cloud", nil}, 215 {"CloudRegion", nil}, 216 {"CloudCredential", nil}, 217 }) 218 } 219 220 func (s *modelInfoSuite) TestModelInfoOwner(c *gc.C) { 221 s.setAPIUser(c, names.NewUserTag("bob@local")) 222 info := s.getModelInfo(c) 223 c.Assert(info.Users, gc.HasLen, 4) 224 c.Assert(info.Machines, gc.HasLen, 2) 225 } 226 227 func (s *modelInfoSuite) TestModelInfoWriteAccess(c *gc.C) { 228 mary := names.NewUserTag("mary@local") 229 s.authorizer.HasWriteTag = mary 230 s.setAPIUser(c, mary) 231 info := s.getModelInfo(c) 232 c.Assert(info.Users, gc.HasLen, 1) 233 c.Assert(info.Users[0].UserName, gc.Equals, "mary@local") 234 c.Assert(info.Machines, gc.HasLen, 2) 235 } 236 237 func (s *modelInfoSuite) TestModelInfoNonOwner(c *gc.C) { 238 s.setAPIUser(c, names.NewUserTag("charlotte@local")) 239 info := s.getModelInfo(c) 240 c.Assert(info.Users, gc.HasLen, 1) 241 c.Assert(info.Users[0].UserName, gc.Equals, "charlotte@local") 242 c.Assert(info.Machines, gc.HasLen, 0) 243 } 244 245 func (s *modelInfoSuite) getModelInfo(c *gc.C) params.ModelInfo { 246 results, err := s.modelmanager.ModelInfo(params.Entities{ 247 Entities: []params.Entity{{ 248 names.NewModelTag(s.st.model.cfg.UUID()).String(), 249 }}, 250 }) 251 c.Assert(err, jc.ErrorIsNil) 252 c.Assert(results.Results, gc.HasLen, 1) 253 c.Assert(results.Results[0].Result, gc.NotNil) 254 c.Assert(results.Results[0].Error, gc.IsNil) 255 return *results.Results[0].Result 256 } 257 258 func (s *modelInfoSuite) TestModelInfoErrorInvalidTag(c *gc.C) { 259 s.testModelInfoError(c, "user-bob", `"user-bob" is not a valid model tag`) 260 } 261 262 func (s *modelInfoSuite) TestModelInfoErrorGetModelNotFound(c *gc.C) { 263 s.st.SetErrors(errors.NotFoundf("model")) 264 s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`) 265 } 266 267 func (s *modelInfoSuite) TestModelInfoErrorModelConfig(c *gc.C) { 268 s.st.model.SetErrors(errors.Errorf("no config for you")) 269 s.testModelInfoError(c, coretesting.ModelTag.String(), `no config for you`) 270 } 271 272 func (s *modelInfoSuite) TestModelInfoErrorModelUsers(c *gc.C) { 273 s.st.model.SetErrors(errors.Errorf("no users for you")) 274 s.testModelInfoError(c, coretesting.ModelTag.String(), `no users for you`) 275 } 276 277 func (s *modelInfoSuite) TestModelInfoErrorNoModelUsers(c *gc.C) { 278 s.st.model.users = nil 279 s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`) 280 } 281 282 func (s *modelInfoSuite) TestModelInfoErrorNoAccess(c *gc.C) { 283 s.setAPIUser(c, names.NewUserTag("nemo@local")) 284 s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`) 285 } 286 287 func (s *modelInfoSuite) testModelInfoError(c *gc.C, modelTag, expectedErr string) { 288 results, err := s.modelmanager.ModelInfo(params.Entities{ 289 Entities: []params.Entity{{modelTag}}, 290 }) 291 c.Assert(err, jc.ErrorIsNil) 292 c.Assert(results.Results, gc.HasLen, 1) 293 c.Assert(results.Results[0].Result, gc.IsNil) 294 c.Assert(results.Results[0].Error, gc.ErrorMatches, expectedErr) 295 } 296 297 type unitRetriever interface { 298 Unit(name string) (*state.Unit, error) 299 } 300 301 type mockState struct { 302 gitjujutesting.Stub 303 304 environs.EnvironConfigGetter 305 common.APIHostPortsGetter 306 common.ToolsStorageGetter 307 common.BlockGetter 308 metricsender.MetricsSenderBackend 309 unitRetriever 310 311 modelUUID string 312 controllerUUID string 313 cloud cloud.Cloud 314 clouds map[names.CloudTag]cloud.Cloud 315 model *mockModel 316 controllerModel *mockModel 317 users []permission.UserAccess 318 cred cloud.Credential 319 machines []common.Machine 320 cfgDefaults config.ModelDefaultAttributes 321 blockMsg string 322 block state.BlockType 323 } 324 325 type fakeModelDescription struct { 326 description.Model `yaml:"-"` 327 328 UUID string `yaml:"model-uuid"` 329 } 330 331 func (st *mockState) Export() (description.Model, error) { 332 return &fakeModelDescription{UUID: st.modelUUID}, nil 333 } 334 335 func (st *mockState) ModelUUID() string { 336 st.MethodCall(st, "ModelUUID") 337 return st.modelUUID 338 } 339 340 func (st *mockState) ModelsForUser(user names.UserTag) ([]*state.UserModel, error) { 341 st.MethodCall(st, "ModelsForUser", user) 342 return nil, st.NextErr() 343 } 344 345 func (st *mockState) IsControllerAdmin(user names.UserTag) (bool, error) { 346 st.MethodCall(st, "IsControllerAdmin", user) 347 if st.controllerModel == nil { 348 return user.Canonical() == "admin@local", st.NextErr() 349 } 350 if st.controllerModel.users == nil { 351 return user.Canonical() == "admin@local", st.NextErr() 352 } 353 354 for _, u := range st.controllerModel.users { 355 if user.Name() == u.userName && u.access == permission.AdminAccess { 356 nextErr := st.NextErr() 357 if user.Name() != "admin" { 358 panic(user.Name()) 359 } 360 return true, nextErr 361 } 362 } 363 return false, st.NextErr() 364 } 365 366 func (st *mockState) NewModel(args state.ModelArgs) (common.Model, common.ModelManagerBackend, error) { 367 st.MethodCall(st, "NewModel", args) 368 st.model.tag = names.NewModelTag(args.Config.UUID()) 369 return st.model, st, st.NextErr() 370 } 371 372 func (st *mockState) ControllerModel() (common.Model, error) { 373 st.MethodCall(st, "ControllerModel") 374 return st.controllerModel, st.NextErr() 375 } 376 377 func (st *mockState) ControllerTag() names.ControllerTag { 378 st.MethodCall(st, "ControllerTag") 379 return names.NewControllerTag(st.controllerUUID) 380 } 381 382 func (st *mockState) ComposeNewModelConfig(modelAttr map[string]interface{}, regionSpec *environs.RegionSpec) (map[string]interface{}, error) { 383 st.MethodCall(st, "ComposeNewModelConfig") 384 attr := make(map[string]interface{}) 385 for attrName, val := range modelAttr { 386 attr[attrName] = val 387 } 388 attr["something"] = "value" 389 return attr, st.NextErr() 390 } 391 392 func (st *mockState) ControllerUUID() string { 393 st.MethodCall(st, "ControllerUUID") 394 return st.controllerUUID 395 } 396 397 func (st *mockState) ControllerConfig() (controller.Config, error) { 398 st.MethodCall(st, "ControllerConfig") 399 return controller.Config{ 400 controller.ControllerUUIDKey: "deadbeef-1bad-500d-9000-4b1d0d06f00d", 401 }, st.NextErr() 402 } 403 404 func (st *mockState) ForModel(tag names.ModelTag) (common.ModelManagerBackend, error) { 405 st.MethodCall(st, "ForModel", tag) 406 return st, st.NextErr() 407 } 408 409 func (st *mockState) GetModel(tag names.ModelTag) (common.Model, error) { 410 st.MethodCall(st, "GetModel", tag) 411 return st.model, st.NextErr() 412 } 413 414 func (st *mockState) Model() (common.Model, error) { 415 st.MethodCall(st, "Model") 416 return st.model, st.NextErr() 417 } 418 419 func (st *mockState) ModelTag() names.ModelTag { 420 st.MethodCall(st, "ModelTag") 421 return st.model.ModelTag() 422 } 423 424 func (st *mockState) AllModels() ([]common.Model, error) { 425 st.MethodCall(st, "AllModels") 426 return []common.Model{st.model}, st.NextErr() 427 } 428 429 func (st *mockState) AllMachines() ([]common.Machine, error) { 430 st.MethodCall(st, "AllMachines") 431 return st.machines, st.NextErr() 432 } 433 434 func (st *mockState) Clouds() (map[names.CloudTag]cloud.Cloud, error) { 435 st.MethodCall(st, "Clouds") 436 return st.clouds, st.NextErr() 437 } 438 439 func (st *mockState) Cloud(name string) (cloud.Cloud, error) { 440 st.MethodCall(st, "Cloud", name) 441 return st.cloud, st.NextErr() 442 } 443 444 func (st *mockState) CloudCredential(tag names.CloudCredentialTag) (cloud.Credential, error) { 445 st.MethodCall(st, "CloudCredential", tag) 446 return st.cred, st.NextErr() 447 } 448 449 func (st *mockState) Close() error { 450 st.MethodCall(st, "Close") 451 return st.NextErr() 452 } 453 454 func (st *mockState) AddModelUser(modelUUID string, spec state.UserAccessSpec) (permission.UserAccess, error) { 455 st.MethodCall(st, "AddModelUser", modelUUID, spec) 456 return permission.UserAccess{}, st.NextErr() 457 } 458 459 func (st *mockState) AddControllerUser(spec state.UserAccessSpec) (permission.UserAccess, error) { 460 st.MethodCall(st, "AddControllerUser", spec) 461 return permission.UserAccess{}, st.NextErr() 462 } 463 464 func (st *mockState) RemoveModelUser(tag names.UserTag) error { 465 st.MethodCall(st, "RemoveModelUser", tag) 466 return st.NextErr() 467 } 468 469 func (st *mockState) UserAccess(tag names.UserTag, target names.Tag) (permission.UserAccess, error) { 470 st.MethodCall(st, "ModelUser", tag, target) 471 return permission.UserAccess{}, st.NextErr() 472 } 473 474 func (st *mockState) LastModelConnection(user names.UserTag) (time.Time, error) { 475 st.MethodCall(st, "LastModelConnection", user) 476 return time.Time{}, st.NextErr() 477 } 478 479 func (st *mockState) RemoveUserAccess(subject names.UserTag, target names.Tag) error { 480 st.MethodCall(st, "RemoveUserAccess", subject, target) 481 return st.NextErr() 482 } 483 484 func (st *mockState) SetUserAccess(subject names.UserTag, target names.Tag, access permission.Access) (permission.UserAccess, error) { 485 st.MethodCall(st, "SetUserAccess", subject, target, access) 486 return permission.UserAccess{}, st.NextErr() 487 } 488 489 func (st *mockState) ModelConfigDefaultValues() (config.ModelDefaultAttributes, error) { 490 st.MethodCall(st, "ModelConfigDefaultValues") 491 return st.cfgDefaults, nil 492 } 493 494 func (st *mockState) UpdateModelConfigDefaultValues(update map[string]interface{}, remove []string, rspec *environs.RegionSpec) error { 495 st.MethodCall(st, "UpdateModelConfigDefaultValues", update, remove, rspec) 496 for k, v := range update { 497 if rspec != nil { 498 adv := st.cfgDefaults[k] 499 adv.Regions = append(adv.Regions, config.RegionDefaultValue{ 500 Name: rspec.Region, 501 Value: v}) 502 503 } else { 504 st.cfgDefaults[k] = config.AttributeDefaultValues{Controller: v} 505 } 506 } 507 for _, n := range remove { 508 if rspec != nil { 509 for i, r := range st.cfgDefaults[n].Regions { 510 if r.Name == rspec.Region { 511 adv := st.cfgDefaults[n] 512 adv.Regions = append(adv.Regions[:i], adv.Regions[i+1:]...) 513 st.cfgDefaults[n] = adv 514 } 515 } 516 } else { 517 if len(st.cfgDefaults[n].Regions) == 0 { 518 delete(st.cfgDefaults, n) 519 } else { 520 521 st.cfgDefaults[n] = config.AttributeDefaultValues{ 522 Regions: st.cfgDefaults[n].Regions} 523 } 524 } 525 } 526 return nil 527 } 528 529 func (st *mockState) GetBlockForType(t state.BlockType) (state.Block, bool, error) { 530 st.MethodCall(st, "GetBlockForType", t) 531 if st.block == t { 532 return &mockBlock{t: t, m: st.blockMsg}, true, nil 533 } else { 534 return nil, false, nil 535 } 536 } 537 538 func (st *mockState) DumpAll() (map[string]interface{}, error) { 539 st.MethodCall(st, "DumpAll") 540 return map[string]interface{}{ 541 "models": "lots of data", 542 }, st.NextErr() 543 } 544 545 type mockBlock struct { 546 state.Block 547 t state.BlockType 548 m string 549 } 550 551 func (m mockBlock) Id() string { return "" } 552 553 func (m mockBlock) Tag() (names.Tag, error) { return names.NewModelTag("mocktesting"), nil } 554 555 func (m mockBlock) Type() state.BlockType { return m.t } 556 557 func (m mockBlock) Message() string { return m.m } 558 559 func (m mockBlock) ModelUUID() string { return "" } 560 561 type mockMachine struct { 562 common.Machine 563 id string 564 life state.Life 565 containerType instance.ContainerType 566 hw *instance.HardwareCharacteristics 567 } 568 569 func (m *mockMachine) Id() string { 570 return m.id 571 } 572 573 func (m *mockMachine) Life() state.Life { 574 return m.life 575 } 576 577 func (m *mockMachine) ContainerType() instance.ContainerType { 578 return m.containerType 579 } 580 581 func (m *mockMachine) HardwareCharacteristics() (*instance.HardwareCharacteristics, error) { 582 return m.hw, nil 583 } 584 585 func (m *mockMachine) AgentPresence() (bool, error) { 586 return true, nil 587 } 588 589 func (m *mockMachine) InstanceId() (instance.Id, error) { 590 return "", nil 591 } 592 593 func (m *mockMachine) WantsVote() bool { 594 return false 595 } 596 597 func (m *mockMachine) HasVote() bool { 598 return false 599 } 600 601 func (m *mockMachine) Status() (status.StatusInfo, error) { 602 return status.StatusInfo{}, nil 603 } 604 605 type mockModel struct { 606 gitjujutesting.Stub 607 owner names.UserTag 608 life state.Life 609 tag names.ModelTag 610 status status.StatusInfo 611 cfg *config.Config 612 users []*mockModelUser 613 } 614 615 func (m *mockModel) Config() (*config.Config, error) { 616 m.MethodCall(m, "Config") 617 return m.cfg, m.NextErr() 618 } 619 620 func (m *mockModel) Owner() names.UserTag { 621 m.MethodCall(m, "Owner") 622 m.PopNoErr() 623 return m.owner 624 } 625 626 func (m *mockModel) ModelTag() names.ModelTag { 627 m.MethodCall(m, "ModelTag") 628 m.PopNoErr() 629 return m.tag 630 } 631 632 func (m *mockModel) Life() state.Life { 633 m.MethodCall(m, "Life") 634 m.PopNoErr() 635 return m.life 636 } 637 638 func (m *mockModel) Status() (status.StatusInfo, error) { 639 m.MethodCall(m, "Status") 640 return m.status, m.NextErr() 641 } 642 643 func (m *mockModel) Cloud() string { 644 m.MethodCall(m, "Cloud") 645 m.PopNoErr() 646 return "some-cloud" 647 } 648 649 func (m *mockModel) CloudRegion() string { 650 m.MethodCall(m, "CloudRegion") 651 m.PopNoErr() 652 return "some-region" 653 } 654 655 func (m *mockModel) CloudCredential() (names.CloudCredentialTag, bool) { 656 m.MethodCall(m, "CloudCredential") 657 m.PopNoErr() 658 return names.NewCloudCredentialTag("some-cloud/bob@local/some-credential"), true 659 } 660 661 func (m *mockModel) Users() ([]permission.UserAccess, error) { 662 m.MethodCall(m, "Users") 663 if err := m.NextErr(); err != nil { 664 return nil, err 665 } 666 users := make([]permission.UserAccess, len(m.users)) 667 for i, user := range m.users { 668 users[i] = permission.UserAccess{ 669 UserID: strings.ToLower(user.userName), 670 UserTag: names.NewUserTag(user.userName), 671 Object: m.ModelTag(), 672 Access: user.access, 673 DisplayName: user.displayName, 674 UserName: user.userName, 675 } 676 } 677 return users, nil 678 } 679 680 func (m *mockModel) Destroy() error { 681 m.MethodCall(m, "Destroy") 682 return m.NextErr() 683 } 684 685 func (m *mockModel) DestroyIncludingHosted() error { 686 m.MethodCall(m, "DestroyIncludingHosted") 687 return m.NextErr() 688 } 689 690 type mockModelUser struct { 691 gitjujutesting.Stub 692 userName string 693 displayName string 694 lastConnection time.Time 695 access permission.Access 696 }