github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	randomPassword  string
    25  	serverFilename  string
    26  }
    27  
    28  var _ = gc.Suite(&ChangePasswordCommandSuite{})
    29  
    30  func (s *ChangePasswordCommandSuite) SetUpTest(c *gc.C) {
    31  	s.BaseSuite.SetUpTest(c)
    32  	s.mockAPI = &mockChangePasswordAPI{}
    33  	s.mockEnvironInfo = &mockEnvironInfo{
    34  		creds: configstore.APICredentials{"user-name", "password"},
    35  	}
    36  	s.randomPassword = ""
    37  	s.serverFilename = ""
    38  	s.PatchValue(user.RandomPasswordNotify, func(pwd string) {
    39  		s.randomPassword = pwd
    40  	})
    41  	s.PatchValue(user.ServerFileNotify, func(filename string) {
    42  		s.serverFilename = filename
    43  	})
    44  }
    45  
    46  func (s *ChangePasswordCommandSuite) run(c *gc.C, args ...string) (*cmd.Context, error) {
    47  	changePasswordCommand := envcmd.WrapSystem(user.NewChangePasswordCommand(s.mockAPI, s.mockEnvironInfo))
    48  	return testing.RunCommand(c, changePasswordCommand, args...)
    49  }
    50  
    51  func (s *ChangePasswordCommandSuite) TestInit(c *gc.C) {
    52  	for i, test := range []struct {
    53  		args        []string
    54  		user        string
    55  		outPath     string
    56  		generate    bool
    57  		errorString string
    58  	}{
    59  		{
    60  		// no args is fine
    61  		}, {
    62  			args:     []string{"--generate"},
    63  			generate: true,
    64  		}, {
    65  			args:     []string{"foobar"},
    66  			user:     "foobar",
    67  			generate: true,
    68  			outPath:  "foobar.server",
    69  		}, {
    70  			args:     []string{"foobar", "--generate"},
    71  			user:     "foobar",
    72  			generate: true,
    73  			outPath:  "foobar.server",
    74  		}, {
    75  			args:     []string{"foobar", "--output", "somefile"},
    76  			user:     "foobar",
    77  			generate: true,
    78  			outPath:  "somefile",
    79  		}, {
    80  			args:        []string{"--foobar"},
    81  			errorString: "flag provided but not defined: --foobar",
    82  		}, {
    83  			args:        []string{"foobar", "extra"},
    84  			errorString: `unrecognized args: \["extra"\]`,
    85  		}, {
    86  			args:        []string{"--output", "somefile"},
    87  			errorString: "output is only a valid option when changing another user's password",
    88  		},
    89  	} {
    90  		c.Logf("test %d", i)
    91  		command := &user.ChangePasswordCommand{}
    92  		err := testing.InitCommand(command, test.args)
    93  		if test.errorString == "" {
    94  			c.Check(command.User, gc.Equals, test.user)
    95  			c.Check(command.OutPath, gc.Equals, test.outPath)
    96  			c.Check(command.Generate, gc.Equals, test.generate)
    97  		} else {
    98  			c.Check(err, gc.ErrorMatches, test.errorString)
    99  		}
   100  	}
   101  }
   102  
   103  func (s *ChangePasswordCommandSuite) assertRandomPassword(c *gc.C) {
   104  	c.Assert(s.mockAPI.password, gc.Equals, s.randomPassword)
   105  	c.Assert(s.mockAPI.password, gc.HasLen, 24)
   106  }
   107  
   108  func (s *ChangePasswordCommandSuite) assertPasswordFromReadPass(c *gc.C) {
   109  	c.Assert(s.mockAPI.password, gc.Equals, "sekrit")
   110  }
   111  
   112  func (s *ChangePasswordCommandSuite) TestChangePassword(c *gc.C) {
   113  	context, err := s.run(c)
   114  	c.Assert(err, jc.ErrorIsNil)
   115  	c.Assert(s.mockAPI.username, gc.Equals, "user-name")
   116  	s.assertPasswordFromReadPass(c)
   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 := s.run(c, "--generate")
   127  	c.Assert(err, jc.ErrorIsNil)
   128  	c.Assert(s.mockAPI.username, gc.Equals, "user-name")
   129  	s.assertRandomPassword(c)
   130  	c.Assert(testing.Stderr(context), gc.Equals, "Your password has been updated.\n")
   131  }
   132  
   133  func (s *ChangePasswordCommandSuite) TestChangePasswordFail(c *gc.C) {
   134  	s.mockAPI.failMessage = "failed to do something"
   135  	s.mockAPI.failOps = []bool{true, false}
   136  	_, err := s.run(c, "--generate")
   137  	c.Assert(err, gc.ErrorMatches, "failed to do something")
   138  	c.Assert(s.mockAPI.username, gc.Equals, "")
   139  }
   140  
   141  // The first write fails, so we try to revert the password which succeeds
   142  func (s *ChangePasswordCommandSuite) TestRevertPasswordAfterFailedWrite(c *gc.C) {
   143  	// Fail to Write the new jenv file
   144  	s.mockEnvironInfo.failMessage = "failed to write"
   145  	_, err := s.run(c, "--generate")
   146  	c.Assert(err, gc.ErrorMatches, "failed to write new password to environments file: failed to write")
   147  	// Last api call was to set the password back to the original.
   148  	c.Assert(s.mockAPI.password, gc.Equals, "password")
   149  }
   150  
   151  // SetPassword api works the first time, but the write fails, our second call to set password fails
   152  func (s *ChangePasswordCommandSuite) TestChangePasswordRevertApiFails(c *gc.C) {
   153  	s.mockAPI.failMessage = "failed to do something"
   154  	s.mockEnvironInfo.failMessage = "failed to write"
   155  	s.mockAPI.failOps = []bool{false, true}
   156  	_, err := s.run(c, "--generate")
   157  	c.Assert(err, gc.ErrorMatches, "failed to set password back: failed to do something")
   158  }
   159  
   160  func (s *ChangePasswordCommandSuite) TestChangeOthersPassword(c *gc.C) {
   161  	// The checks for user existence and admin rights are tested
   162  	// at the apiserver level.
   163  	context, err := s.run(c, "other")
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	c.Assert(s.mockAPI.username, gc.Equals, "other")
   166  	s.assertRandomPassword(c)
   167  	s.assertServerFileMatches(c, s.serverFilename, "other", s.randomPassword)
   168  	expected := `
   169  server file written to .*other.server
   170  `[1:]
   171  	c.Assert(testing.Stderr(context), gc.Matches, expected)
   172  }
   173  
   174  func (s *ChangePasswordCommandSuite) TestChangeOthersPasswordWithFile(c *gc.C) {
   175  	// The checks for user existence and admin rights are tested
   176  	// at the apiserver level.
   177  	filename := filepath.Join(c.MkDir(), "test.result")
   178  	_, err := s.run(c, "other", "-o", filename)
   179  	c.Assert(err, jc.ErrorIsNil)
   180  	s.assertRandomPassword(c)
   181  	c.Assert(filepath.Base(s.serverFilename), gc.Equals, "test.result")
   182  	s.assertServerFileMatches(c, s.serverFilename, "other", s.randomPassword)
   183  }
   184  
   185  type mockEnvironInfo struct {
   186  	failMessage string
   187  	creds       configstore.APICredentials
   188  }
   189  
   190  func (m *mockEnvironInfo) Write() error {
   191  	if m.failMessage != "" {
   192  		return errors.New(m.failMessage)
   193  	}
   194  	return nil
   195  }
   196  
   197  func (m *mockEnvironInfo) SetAPICredentials(creds configstore.APICredentials) {
   198  	m.creds = creds
   199  }
   200  
   201  func (m *mockEnvironInfo) APICredentials() configstore.APICredentials {
   202  	return m.creds
   203  }
   204  
   205  type mockChangePasswordAPI struct {
   206  	failMessage string
   207  	currentOp   int
   208  	failOps     []bool // Can be used to make the call pass/ fail in a known order
   209  	username    string
   210  	password    string
   211  }
   212  
   213  func (m *mockChangePasswordAPI) SetPassword(username, password string) error {
   214  	if len(m.failOps) > 0 && m.failOps[m.currentOp] {
   215  		m.currentOp++
   216  		return errors.New(m.failMessage)
   217  	}
   218  	m.currentOp++
   219  	m.username = username
   220  	m.password = password
   221  	return nil
   222  }
   223  
   224  func (*mockChangePasswordAPI) Close() error {
   225  	return nil
   226  }