github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/user/change_password_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package user_test 5 6 import ( 7 "path/filepath" 8 9 "github.com/juju/cmd" 10 "github.com/juju/errors" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 14 "github.com/juju/juju/cmd/envcmd" 15 "github.com/juju/juju/cmd/juju/user" 16 "github.com/juju/juju/environs/configstore" 17 "github.com/juju/juju/testing" 18 ) 19 20 type ChangePasswordCommandSuite struct { 21 BaseSuite 22 mockAPI *mockChangePasswordAPI 23 mockEnvironInfo *mockEnvironInfo 24 } 25 26 var _ = gc.Suite(&ChangePasswordCommandSuite{}) 27 28 func (s *ChangePasswordCommandSuite) SetUpTest(c *gc.C) { 29 s.BaseSuite.SetUpTest(c) 30 s.mockAPI = &mockChangePasswordAPI{} 31 s.mockEnvironInfo = &mockEnvironInfo{ 32 creds: configstore.APICredentials{"user-name", "password"}, 33 } 34 s.PatchValue(user.GetChangePasswordAPI, func(c *user.ChangePasswordCommand) (user.ChangePasswordAPI, error) { 35 return s.mockAPI, nil 36 }) 37 s.PatchValue(user.GetEnvironInfoWriter, func(c *user.ChangePasswordCommand) (user.EnvironInfoCredsWriter, error) { 38 return s.mockEnvironInfo, nil 39 }) 40 s.PatchValue(user.GetConnectionCredentials, func(c *user.ChangePasswordCommand) (configstore.APICredentials, error) { 41 return s.mockEnvironInfo.creds, nil 42 }) 43 } 44 45 func newUserChangePassword() cmd.Command { 46 return envcmd.Wrap(&user.ChangePasswordCommand{}) 47 } 48 49 func (s *ChangePasswordCommandSuite) TestInit(c *gc.C) { 50 for i, test := range []struct { 51 args []string 52 user string 53 outPath string 54 generate bool 55 errorString string 56 }{ 57 { 58 // no args is fine 59 }, { 60 args: []string{"--generate"}, 61 generate: true, 62 }, { 63 args: []string{"--foobar"}, 64 errorString: "flag provided but not defined: --foobar", 65 }, { 66 args: []string{"foobar"}, 67 user: "foobar", 68 }, { 69 args: []string{"foobar", "extra"}, 70 errorString: `unrecognized args: \["extra"\]`, 71 }, { 72 args: []string{"--output", "somefile"}, 73 errorString: "output is only a valid option when changing another user's password", 74 }, { 75 args: []string{"-o", "somefile"}, 76 errorString: "output is only a valid option when changing another user's password", 77 }, { 78 args: []string{"foobar", "--generate"}, 79 user: "foobar", 80 generate: true, 81 }, { 82 args: []string{"foobar", "--output", "somefile"}, 83 user: "foobar", 84 outPath: "somefile", 85 }, { 86 args: []string{"foobar", "-o", "somefile"}, 87 user: "foobar", 88 outPath: "somefile", 89 }, 90 } { 91 c.Logf("test %d", i) 92 command := &user.ChangePasswordCommand{} 93 err := testing.InitCommand(command, test.args) 94 if test.errorString == "" { 95 c.Check(command.User, gc.Equals, test.user) 96 c.Check(command.OutPath, gc.Equals, test.outPath) 97 c.Check(command.Generate, gc.Equals, test.generate) 98 } else { 99 c.Check(err, gc.ErrorMatches, test.errorString) 100 } 101 } 102 } 103 104 func (s *ChangePasswordCommandSuite) TestFailedToReadInfo(c *gc.C) { 105 s.PatchValue(user.GetEnvironInfoWriter, func(c *user.ChangePasswordCommand) (user.EnvironInfoCredsWriter, error) { 106 return s.mockEnvironInfo, errors.New("something failed") 107 }) 108 _, err := testing.RunCommand(c, newUserChangePassword(), "--generate") 109 c.Assert(err, gc.ErrorMatches, "something failed") 110 } 111 112 func (s *ChangePasswordCommandSuite) TestChangePassword(c *gc.C) { 113 context, err := testing.RunCommand(c, newUserChangePassword()) 114 c.Assert(err, jc.ErrorIsNil) 115 c.Assert(s.mockAPI.username, gc.Equals, "user-name") 116 c.Assert(s.mockAPI.password, gc.Equals, "sekrit") 117 expected := ` 118 password: 119 type password again: 120 `[1:] 121 c.Assert(testing.Stdout(context), gc.Equals, expected) 122 c.Assert(testing.Stderr(context), gc.Equals, "Your password has been updated.\n") 123 } 124 125 func (s *ChangePasswordCommandSuite) TestChangePasswordGenerate(c *gc.C) { 126 context, err := testing.RunCommand(c, newUserChangePassword(), "--generate") 127 c.Assert(err, jc.ErrorIsNil) 128 c.Assert(s.mockAPI.username, gc.Equals, "user-name") 129 c.Assert(s.mockAPI.password, gc.Not(gc.Equals), "sekrit") 130 c.Assert(s.mockAPI.password, gc.HasLen, 24) 131 c.Assert(testing.Stderr(context), gc.Equals, "Your password has been updated.\n") 132 } 133 134 func (s *ChangePasswordCommandSuite) TestChangePasswordFail(c *gc.C) { 135 s.mockAPI.failMessage = "failed to do something" 136 s.mockAPI.failOps = []bool{true, false} 137 _, err := testing.RunCommand(c, newUserChangePassword(), "--generate") 138 c.Assert(err, gc.ErrorMatches, "failed to do something") 139 c.Assert(s.mockAPI.username, gc.Equals, "") 140 } 141 142 // The first write fails, so we try to revert the password which succeeds 143 func (s *ChangePasswordCommandSuite) TestRevertPasswordAfterFailedWrite(c *gc.C) { 144 // Fail to Write the new jenv file 145 s.mockEnvironInfo.failMessage = "failed to write" 146 _, err := testing.RunCommand(c, newUserChangePassword(), "--generate") 147 c.Assert(err, gc.ErrorMatches, "failed to write new password to environments file: failed to write") 148 // Last api call was to set the password back to the original. 149 c.Assert(s.mockAPI.password, gc.Equals, "password") 150 } 151 152 // SetPassword api works the first time, but the write fails, our second call to set password fails 153 func (s *ChangePasswordCommandSuite) TestChangePasswordRevertApiFails(c *gc.C) { 154 s.mockAPI.failMessage = "failed to do something" 155 s.mockEnvironInfo.failMessage = "failed to write" 156 s.mockAPI.failOps = []bool{false, true} 157 _, err := testing.RunCommand(c, newUserChangePassword(), "--generate") 158 c.Assert(err, gc.ErrorMatches, "failed to set password back: failed to do something") 159 } 160 161 func (s *ChangePasswordCommandSuite) TestChangeOthersPassword(c *gc.C) { 162 // The checks for user existence and admin rights are tested 163 // at the apiserver level. 164 context, err := testing.RunCommand(c, newUserChangePassword(), "other") 165 c.Assert(err, jc.ErrorIsNil) 166 c.Assert(s.mockAPI.username, gc.Equals, "other") 167 c.Assert(s.mockAPI.password, gc.Equals, "sekrit") 168 filename := context.AbsPath("other.jenv") 169 expected := ` 170 password: 171 type password again: 172 environment file written to `[1:] + filename + "\n" 173 c.Assert(testing.Stdout(context), gc.Equals, expected) 174 c.Assert(testing.Stderr(context), gc.Equals, "") 175 assertJENVContents(c, context.AbsPath("other.jenv"), "other", "sekrit") 176 177 } 178 179 func (s *ChangePasswordCommandSuite) TestChangeOthersPasswordWithFile(c *gc.C) { 180 // The checks for user existence and admin rights are tested 181 // at the apiserver level. 182 filename := filepath.Join(c.MkDir(), "test.jenv") 183 context, err := testing.RunCommand(c, newUserChangePassword(), "other", "-o", filename) 184 c.Assert(err, jc.ErrorIsNil) 185 c.Assert(s.mockAPI.username, gc.Equals, "other") 186 c.Assert(s.mockAPI.password, gc.Equals, "sekrit") 187 expected := ` 188 password: 189 type password again: 190 environment file written to `[1:] + filename + "\n" 191 c.Assert(testing.Stdout(context), gc.Equals, expected) 192 c.Assert(testing.Stderr(context), gc.Equals, "") 193 assertJENVContents(c, filename, "other", "sekrit") 194 } 195 196 type mockEnvironInfo struct { 197 failMessage string 198 creds configstore.APICredentials 199 } 200 201 func (m *mockEnvironInfo) Write() error { 202 if m.failMessage != "" { 203 return errors.New(m.failMessage) 204 } 205 return nil 206 } 207 208 func (m *mockEnvironInfo) SetAPICredentials(creds configstore.APICredentials) { 209 m.creds = creds 210 } 211 212 func (m *mockEnvironInfo) APICredentials() configstore.APICredentials { 213 return m.creds 214 } 215 216 func (m *mockEnvironInfo) Location() string { 217 return "location" 218 } 219 220 type mockChangePasswordAPI struct { 221 failMessage string 222 currentOp int 223 failOps []bool // Can be used to make the call pass/ fail in a known order 224 username string 225 password string 226 } 227 228 func (m *mockChangePasswordAPI) SetPassword(username, password string) error { 229 if len(m.failOps) > 0 && m.failOps[m.currentOp] { 230 m.currentOp++ 231 return errors.New(m.failMessage) 232 } 233 m.currentOp++ 234 m.username = username 235 m.password = password 236 return nil 237 } 238 239 func (*mockChangePasswordAPI) Close() error { 240 return nil 241 }