Skip to content

Commit f66ca42

Browse files
authored
test: refactor import model tests (#1826)
* test: refactor import model tests * docs: regenerate plug doc for import model * refactor: remove jimm errors pkg * refactor: use correct verb for error * test: remove flag setting
1 parent 4ef8903 commit f66ca42

File tree

4 files changed

+113
-128
lines changed

4 files changed

+113
-128
lines changed

cmd/jaas/cmd/export_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,6 @@ func NewSetControllerDeprecatedCommandForTesting(store jujuclient.ClientStore, l
8888
return modelcmd.WrapBase(cmd)
8989
}
9090

91-
func NewImportModelCommandForTesting(store jujuclient.ClientStore, lp jujuapi.LoginProvider) cmd.Command {
92-
cmd := &importModelCommand{
93-
dialOpts: cmdtest.TestDialOpts(lp),
94-
}
95-
cmd.SetClientStore(store)
96-
97-
return modelcmd.WrapBase(cmd)
98-
}
99-
10091
func NewUpdateMigratedModelCommandForTesting(store jujuclient.ClientStore, lp jujuapi.LoginProvider) cmd.Command {
10192
cmd := &updateMigratedModelCommand{
10293
dialOpts: cmdtest.TestDialOpts(lp),

cmd/jaas/cmd/importmodel.go

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
package cmd
44

55
import (
6+
"errors"
67
"fmt"
78

89
"github.com/juju/cmd/v3"
910
"github.com/juju/gnuflag"
10-
jujuapi "github.com/juju/juju/api"
1111
jujucmd "github.com/juju/juju/cmd"
1212
"github.com/juju/juju/cmd/modelcmd"
1313
"github.com/juju/juju/jujuclient"
@@ -37,24 +37,26 @@ local user and it will switch the model owner to the desired external user.
3737
func NewImportModelCommand() cmd.Command {
3838
cmd := &importModelCommand{}
3939
cmd.SetClientStore(jujuclient.NewFileClientStore())
40+
cmd.jimmAPIFunc = cmd.newClient
4041

4142
return modelcmd.WrapBase(cmd)
4243
}
4344

4445
// importModelCommand imports a model.
4546
type importModelCommand struct {
4647
modelcmd.ControllerCommandBase
47-
dialOpts *jujuapi.DialOpts
4848

4949
req apiparams.ImportModelRequest
50+
51+
jimmAPIFunc func() (JIMMAPI, error)
5052
}
5153

5254
// Info implements the cmd.Command interface.
5355
func (c *importModelCommand) Info() *cmd.Info {
5456
return jujucmd.Info(&cmd.Info{
5557
Name: "import-model",
5658
Args: "<controller name> <model uuid>",
57-
Purpose: "Import a model to jimm",
59+
Purpose: "Import a model to jimm.",
5860
Doc: importModelCommandDoc,
5961
Examples: importModelCommandExample,
6062
Aliases: []string{"register-model"},
@@ -81,27 +83,40 @@ func (c *importModelCommand) Init(args []string) error {
8183

8284
c.req.Controller = args[0]
8385
if !names.IsValidModel(args[1]) {
84-
return fmt.Errorf("invalid model uuid")
86+
return errors.New("invalid model uuid")
8587
}
8688
c.req.ModelTag = names.NewModelTag(args[1]).String()
8789
return nil
8890
}
8991

9092
// Run implements Command.Run.
9193
func (c *importModelCommand) Run(ctxt *cmd.Context) error {
92-
currentController, err := c.ClientStore().CurrentController()
93-
if err != nil {
94-
return fmt.Errorf("could not determine controller: %w", err)
94+
if c.jimmAPIFunc == nil {
95+
c.jimmAPIFunc = c.newClient
9596
}
9697

97-
apiCaller, err := c.NewAPIRootWithDialOpts(c.ClientStore(), currentController, "", c.dialOpts)
98+
jimmAPI, err := c.jimmAPIFunc()
9899
if err != nil {
99-
return err
100+
return fmt.Errorf("could not create JIMM API client: %w", err)
100101
}
102+
defer jimmAPI.Close()
101103

102-
client := api.NewClient(apiCaller)
103-
if err := client.ImportModel(&c.req); err != nil {
104-
return err
104+
if err := jimmAPI.ImportModel(&c.req); err != nil {
105+
return fmt.Errorf("could not import model: %w", err)
105106
}
106107
return nil
107108
}
109+
110+
func (c *importModelCommand) newClient() (JIMMAPI, error) {
111+
currentController, err := c.ClientStore().CurrentController()
112+
if err != nil {
113+
return nil, fmt.Errorf("could not determine controller: %w", err)
114+
}
115+
116+
apiCaller, err := c.NewAPIRootWithDialOpts(c.ClientStore(), currentController, "", nil)
117+
if err != nil {
118+
return nil, err
119+
}
120+
121+
return api.NewClient(apiCaller), nil
122+
}

cmd/jaas/cmd/importmodel_test.go

Lines changed: 85 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,113 @@
11
// Copyright 2025 Canonical.
22

3-
package cmd_test
3+
package cmd
44

55
import (
6-
"context"
6+
"testing"
77

8+
qt "github.com/frankban/quicktest"
89
"github.com/juju/cmd/v3/cmdtesting"
9-
jjcloud "github.com/juju/juju/cloud"
10-
jujuparams "github.com/juju/juju/rpc/params"
11-
"github.com/juju/juju/testing/factory"
12-
"github.com/juju/names/v5"
13-
gc "gopkg.in/check.v1"
14-
15-
"github.com/canonical/jimm/v3/cmd/jaas/cmd"
16-
"github.com/canonical/jimm/v3/internal/dbmodel"
17-
"github.com/canonical/jimm/v3/internal/testutils/cmdtest"
18-
"github.com/canonical/jimm/v3/internal/testutils/jimmtest"
10+
"github.com/juju/gnuflag"
11+
"go.uber.org/mock/gomock"
12+
13+
apiparams "github.com/canonical/jimm/v3/pkg/api/params"
1914
)
2015

21-
type importModelSuite struct {
22-
cmdtest.JimmCmdSuite
16+
func TestImportModelInit_ArgValidation(t *testing.T) {
17+
c := qt.New(t)
18+
19+
validModelUUID := "ac30d6ae-0bed-4398-bba7-75d49e39f189"
20+
21+
cases := []struct {
22+
about string
23+
args []string
24+
errMatch string
25+
}{
26+
{about: "0 args", args: nil, errMatch: "controller not specified"},
27+
{about: "1 arg", args: []string{"controller-1"}, errMatch: "model uuid not specified"},
28+
{about: "too many args", args: []string{"controller-1", validModelUUID, "extra"}, errMatch: "too many args"},
29+
{about: "invalid uuid", args: []string{"controller-1", "not-a-uuid"}, errMatch: "invalid model uuid"},
30+
{about: "valid uuid", args: []string{"controller-1", validModelUUID}, errMatch: ""},
31+
}
32+
33+
for _, tc := range cases {
34+
c.Run(tc.about, func(c *qt.C) {
35+
cmd := &importModelCommand{}
36+
err := cmd.Init(tc.args)
37+
if tc.errMatch != "" {
38+
c.Assert(err, qt.ErrorMatches, tc.errMatch)
39+
return
40+
}
41+
c.Assert(err, qt.IsNil)
42+
c.Assert(cmd.req.Controller, qt.Equals, "controller-1")
43+
c.Assert(cmd.req.ModelTag, qt.Equals, "model-"+validModelUUID)
44+
})
45+
}
2346
}
2447

25-
var _ = gc.Suite(&importModelSuite{})
48+
func TestImportModelRun_PassesRequestToAPI(t *testing.T) {
49+
c := qt.New(t)
2650

27-
func (s *importModelSuite) TestImportModelSuperuser(c *gc.C) {
28-
s.AddController(c, "controller-1", s.APIInfo(c))
51+
cmdMocks := setupCmdMocks(c)
2952

30-
cct := names.NewCloudCredentialTag(jimmtest.TestCloudName + "/charlie@canonical.com/cred")
31-
s.UpdateCloudCredential(c, cct, jujuparams.CloudCredential{AuthType: "empty", Attributes: map[string]string{"key": "value"}})
53+
modelUUID := "ac30d6ae-0bed-4398-bba7-75d49e39f189"
3254

33-
err := s.BackingState.UpdateCloudCredential(cct, jjcloud.NewCredential(jjcloud.EmptyAuthType, map[string]string{"key": "value"}))
34-
c.Assert(err, gc.Equals, nil)
55+
cmdMocks.client.EXPECT().
56+
ImportModel(gomock.Any()).
57+
DoAndReturn(func(req *apiparams.ImportModelRequest) error {
58+
c.Assert(req.Controller, qt.Equals, "controller-1")
59+
c.Assert(req.ModelTag, qt.Equals, "model-"+modelUUID)
60+
c.Assert(req.Owner, qt.Equals, "alice@canonical.com")
61+
return nil
62+
}).
63+
Times(1)
3564

36-
m := s.Factory.MakeModel(c, &factory.ModelParams{
37-
Name: "model-2",
38-
Owner: names.NewUserTag("charlie@canonical.com"),
39-
CloudName: jimmtest.TestCloudName,
40-
CloudRegion: jimmtest.TestCloudRegionName,
41-
CloudCredential: cct,
42-
})
43-
defer m.Close()
65+
cmdMocks.client.EXPECT().Close().Times(1)
4466

45-
// alice is superuser
46-
bClient := s.SetupCLIAccess(c, "alice")
47-
_, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", m.ModelUUID())
48-
c.Assert(err, gc.IsNil)
67+
command := &importModelCommand{
68+
jimmAPIFunc: func() (JIMMAPI, error) {
69+
return cmdMocks.client, nil
70+
},
71+
}
4972

50-
var model2 dbmodel.Model
51-
model2.SetTag(names.NewModelTag(m.ModelUUID()))
52-
err = s.JIMM.Database.GetModel(context.Background(), &model2)
53-
c.Assert(err, gc.Equals, nil)
54-
c.Check(model2.OwnerIdentityName, gc.Equals, "charlie@canonical.com")
55-
}
73+
err := cmdtesting.InitCommand(command, []string{"controller-1", modelUUID, "--owner", "alice@canonical.com"})
74+
c.Assert(err, qt.IsNil)
5675

57-
func (s *importModelSuite) TestImportModelFromLocalUser(c *gc.C) {
58-
s.AddController(c, "controller-1", s.APIInfo(c))
59-
s.AddAdminUser(c, "charlie@canonical.com")
60-
cct := names.NewCloudCredentialTag(jimmtest.TestCloudName + "/charlie@canonical.com/cred")
61-
s.UpdateCloudCredential(c, cct, jujuparams.CloudCredential{AuthType: "empty"})
62-
// Add credentials for Alice on the test cloud, they are needed for the Alice user to become the new model owner
63-
cctAlice := names.NewCloudCredentialTag(jimmtest.TestCloudName + "/alice@canonical.com/cred")
64-
s.UpdateCloudCredential(c, cctAlice, jujuparams.CloudCredential{AuthType: "empty"})
65-
mt := s.AddModel(c, names.NewUserTag("charlie@canonical.com"), "model-2", names.NewCloudTag(jimmtest.TestCloudName), jimmtest.TestCloudRegionName, cct)
66-
var model dbmodel.Model
67-
model.SetTag(mt)
68-
err := s.JIMM.Database.GetModel(context.Background(), &model)
69-
c.Assert(err, gc.Equals, nil)
70-
err = s.JIMM.OpenFGAClient.RemoveControllerModel(context.Background(), model.Controller.ResourceTag(), model.ResourceTag())
71-
c.Assert(err, gc.Equals, nil)
72-
err = s.JIMM.Database.DeleteModel(context.Background(), &model)
73-
c.Assert(err, gc.Equals, nil)
74-
75-
// alice is superuser
76-
bClient := s.SetupCLIAccess(c, "alice")
77-
_, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", mt.Id(), "--owner", "alice@canonical.com")
78-
c.Assert(err, gc.IsNil)
79-
80-
var model2 dbmodel.Model
81-
model2.SetTag(mt)
82-
err = s.JIMM.Database.GetModel(context.Background(), &model2)
83-
c.Assert(err, gc.Equals, nil)
84-
c.Check(model2.CreatedAt.After(model.CreatedAt), gc.Equals, true)
85-
c.Check(model2.OwnerIdentityName, gc.Equals, "alice@canonical.com")
76+
err = command.Run(newTestContext(c))
77+
c.Assert(err, qt.IsNil)
8678
}
8779

88-
func (s *importModelSuite) TestImportModelUnauthorized(c *gc.C) {
89-
s.AddController(c, "controller-1", s.APIInfo(c))
80+
func TestImportModelRun_WithoutOwnerFlag(t *testing.T) {
81+
c := qt.New(t)
9082

91-
cct := names.NewCloudCredentialTag(jimmtest.TestCloudName + "/charlie@canonical.com/cred")
92-
s.UpdateCloudCredential(c, cct, jujuparams.CloudCredential{AuthType: "empty"})
83+
cmdMocks := setupCmdMocks(c)
9384

94-
err := s.BackingState.UpdateCloudCredential(cct, jjcloud.NewCredential(jjcloud.EmptyAuthType, map[string]string{"key": "value"}))
95-
c.Assert(err, gc.Equals, nil)
85+
modelUUID := "ac30d6ae-0bed-4398-bba7-75d49e39f189"
9686

97-
m := s.Factory.MakeModel(c, &factory.ModelParams{
98-
Name: "model-2",
99-
Owner: names.NewUserTag("charlie@canonical.com"),
100-
CloudName: jimmtest.TestCloudName,
101-
CloudRegion: jimmtest.TestCloudRegionName,
102-
CloudCredential: cct,
103-
})
104-
defer m.Close()
87+
cmdMocks.client.EXPECT().
88+
ImportModel(gomock.Any()).
89+
DoAndReturn(func(req *apiparams.ImportModelRequest) error {
90+
c.Assert(req.Controller, qt.Equals, "controller-1")
91+
c.Assert(req.ModelTag, qt.Equals, "model-"+modelUUID)
92+
c.Assert(req.Owner, qt.Equals, "")
93+
return nil
94+
}).
95+
Times(1)
10596

106-
// bob is not superuser
107-
bClient := s.SetupCLIAccess(c, "bob")
108-
_, err = cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-1", m.ModelUUID())
109-
c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`)
110-
}
97+
cmdMocks.client.EXPECT().Close().Times(1)
11198

112-
func (s *importModelSuite) TestImportModelNoController(c *gc.C) {
113-
bClient := s.SetupCLIAccess(c, "bob")
114-
_, err := cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient))
115-
c.Assert(err, gc.ErrorMatches, `controller not specified`)
116-
}
99+
command := &importModelCommand{
100+
jimmAPIFunc: func() (JIMMAPI, error) {
101+
return cmdMocks.client, nil
102+
},
103+
}
117104

118-
func (s *importModelSuite) TestImportModelNoModelUUID(c *gc.C) {
119-
bClient := s.SetupCLIAccess(c, "bob")
120-
_, err := cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-id")
121-
c.Assert(err, gc.ErrorMatches, `model uuid not specified`)
122-
}
105+
fs := gnuflag.NewFlagSet("test", gnuflag.ContinueOnError)
106+
command.SetFlags(fs)
123107

124-
func (s *importModelSuite) TestImportModelInvalidModelUUID(c *gc.C) {
125-
bClient := s.SetupCLIAccess(c, "bob")
126-
_, err := cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-id", "not-a-uuid")
127-
c.Assert(err, gc.ErrorMatches, `invalid model uuid`)
128-
}
108+
err := cmdtesting.InitCommand(command, []string{"controller-1", modelUUID})
109+
c.Assert(err, qt.IsNil)
129110

130-
func (s *importModelSuite) TestImportModelTooManyArgs(c *gc.C) {
131-
bClient := s.SetupCLIAccess(c, "bob")
132-
_, err := cmdtesting.RunCommand(c, cmd.NewImportModelCommandForTesting(s.ClientStore(), bClient), "controller-id", "not-a-uuid", "spare-argument")
133-
c.Assert(err, gc.ErrorMatches, `too many args`)
111+
err = command.Run(newTestContext(c))
112+
c.Assert(err, qt.IsNil)
134113
}

docs/reference/list-of-jaas-plugin-commands/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ See also: topics
498498
**Aliases:** register-model
499499

500500
## Summary
501-
Import a model to jimm
501+
Import a model to jimm.
502502

503503
## Usage
504504
```jaas import-model [options] <controller name> <model uuid>```

0 commit comments

Comments
 (0)