github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/application/config_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  package application_test
     4  
     5  import (
     6  	"bytes"
     7  	"io/ioutil"
     8  	"os"
     9  	"strings"
    10  	"unicode/utf8"
    11  
    12  	"github.com/juju/cmd"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils"
    15  	gc "gopkg.in/check.v1"
    16  	goyaml "gopkg.in/yaml.v2"
    17  
    18  	"github.com/juju/juju/apiserver/common"
    19  	"github.com/juju/juju/cmd/juju/application"
    20  	coretesting "github.com/juju/juju/testing"
    21  )
    22  
    23  type configCommandSuite struct {
    24  	coretesting.FakeJujuXDGDataHomeSuite
    25  	dir  string
    26  	fake *fakeApplicationAPI
    27  }
    28  
    29  var (
    30  	_ = gc.Suite(&configCommandSuite{})
    31  
    32  	validSetTestValue   = "a value with spaces\nand newline\nand UTF-8 characters: \U0001F604 / \U0001F44D"
    33  	invalidSetTestValue = "a value with an invalid UTF-8 sequence: " + string([]byte{0xFF, 0xFF})
    34  	yamlConfigValue     = "dummy-application:\n  skill-level: 9000\n  username: admin001\n\n"
    35  )
    36  
    37  var getTests = []struct {
    38  	application string
    39  	expected    map[string]interface{}
    40  }{
    41  	{
    42  		"dummy-application",
    43  		map[string]interface{}{
    44  			"application": "dummy-application",
    45  			"charm":       "dummy",
    46  			"settings": map[string]interface{}{
    47  				"title": map[string]interface{}{
    48  					"description": "Specifies title",
    49  					"type":        "string",
    50  					"value":       "Nearly There",
    51  				},
    52  				"skill-level": map[string]interface{}{
    53  					"description": "Specifies skill-level",
    54  					"value":       100,
    55  					"type":        "int",
    56  				},
    57  				"username": map[string]interface{}{
    58  					"description": "Specifies username",
    59  					"type":        "string",
    60  					"value":       "admin001",
    61  				},
    62  				"outlook": map[string]interface{}{
    63  					"description": "Specifies outlook",
    64  					"type":        "string",
    65  					"value":       "true",
    66  				},
    67  			},
    68  		},
    69  	},
    70  }
    71  
    72  func (s *configCommandSuite) SetUpTest(c *gc.C) {
    73  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    74  	s.fake = &fakeApplicationAPI{name: "dummy-application", charmName: "dummy",
    75  		values: map[string]interface{}{
    76  			"title":       "Nearly There",
    77  			"skill-level": 100,
    78  			"username":    "admin001",
    79  			"outlook":     "true",
    80  		}}
    81  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    82  
    83  	s.dir = c.MkDir()
    84  	c.Assert(utf8.ValidString(validSetTestValue), jc.IsTrue)
    85  	c.Assert(utf8.ValidString(invalidSetTestValue), jc.IsFalse)
    86  	setupValueFile(c, s.dir, "valid.txt", validSetTestValue)
    87  	setupValueFile(c, s.dir, "invalid.txt", invalidSetTestValue)
    88  	setupBigFile(c, s.dir)
    89  	setupConfigFile(c, s.dir)
    90  }
    91  
    92  func (s *configCommandSuite) TestGetCommandInit(c *gc.C) {
    93  	// missing args
    94  	err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{})
    95  	c.Assert(err, gc.ErrorMatches, "no application name specified")
    96  }
    97  
    98  func (s *configCommandSuite) TestGetCommandInitWithApplication(c *gc.C) {
    99  	err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"app"})
   100  	// everything ok
   101  	c.Assert(err, jc.ErrorIsNil)
   102  }
   103  
   104  func (s *configCommandSuite) TestGetCommandInitWithKey(c *gc.C) {
   105  	err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"app", "key"})
   106  	// everything ok
   107  	c.Assert(err, jc.ErrorIsNil)
   108  }
   109  
   110  func (s *configCommandSuite) TestGetConfig(c *gc.C) {
   111  	for _, t := range getTests {
   112  		ctx := coretesting.Context(c)
   113  		code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{t.application})
   114  		c.Check(code, gc.Equals, 0)
   115  		c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "")
   116  		// round trip via goyaml to avoid being sucked into a quagmire of
   117  		// map[interface{}]interface{} vs map[string]interface{}. This is
   118  		// also required if we add json support to this command.
   119  		buf, err := goyaml.Marshal(t.expected)
   120  		c.Assert(err, jc.ErrorIsNil)
   121  		expected := make(map[string]interface{})
   122  		err = goyaml.Unmarshal(buf, &expected)
   123  		c.Assert(err, jc.ErrorIsNil)
   124  
   125  		actual := make(map[string]interface{})
   126  		err = goyaml.Unmarshal(ctx.Stdout.(*bytes.Buffer).Bytes(), &actual)
   127  		c.Assert(err, jc.ErrorIsNil)
   128  		c.Assert(actual, gc.DeepEquals, expected)
   129  	}
   130  }
   131  
   132  func (s *configCommandSuite) TestGetConfigKey(c *gc.C) {
   133  	ctx := coretesting.Context(c)
   134  	code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{"dummy-application", "title"})
   135  	c.Check(code, gc.Equals, 0)
   136  	c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "")
   137  	c.Assert(ctx.Stdout.(*bytes.Buffer).String(), gc.Equals, "Nearly There\n")
   138  }
   139  
   140  func (s *configCommandSuite) TestGetConfigKeyNotFound(c *gc.C) {
   141  	ctx := coretesting.Context(c)
   142  	code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{"dummy-application", "invalid"})
   143  	c.Check(code, gc.Equals, 1)
   144  	c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "error: key \"invalid\" not found in \"dummy-application\" application settings.\n")
   145  	c.Assert(ctx.Stdout.(*bytes.Buffer).String(), gc.Equals, "")
   146  }
   147  
   148  func (s *configCommandSuite) TestSetCommandInit(c *gc.C) {
   149  	// missing args
   150  	err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{})
   151  	c.Assert(err, gc.ErrorMatches, "no application name specified")
   152  
   153  	// missing application name
   154  	err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"name=foo"})
   155  	c.Assert(err, gc.ErrorMatches, "no application name specified")
   156  
   157  	// --file path, but no application
   158  	err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"--file", "testconfig.yaml"})
   159  	c.Assert(err, gc.ErrorMatches, "no application name specified")
   160  
   161  	// --file and options specified
   162  	err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--file", "testconfig.yaml", "bees="})
   163  	c.Assert(err, gc.ErrorMatches, "cannot specify --file and key=value arguments simultaneously")
   164  
   165  	// --reset and no config name provided
   166  	err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--reset"})
   167  	c.Assert(err, gc.ErrorMatches, "flag needs an argument: --reset")
   168  
   169  	// cannot set and retrieve simultaneously
   170  	err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "get", "set=value"})
   171  	c.Assert(err, gc.ErrorMatches, "cannot set and retrieve values simultaneously")
   172  
   173  	// cannot reset and get simultaneously
   174  	err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--reset", "reset", "get"})
   175  	c.Assert(err, gc.ErrorMatches, "cannot reset and retrieve values simultaneously")
   176  
   177  	// invalid reset keys
   178  	err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--reset", "reset,bad=key"})
   179  	c.Assert(err, gc.ErrorMatches, `--reset accepts a comma delimited set of keys "a,b,c", received: "bad=key"`)
   180  
   181  	// init too many args fails
   182  	err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "key", "another"})
   183  	c.Assert(err, gc.ErrorMatches, "can only retrieve a single value, or all values")
   184  
   185  }
   186  
   187  func (s *configCommandSuite) TestSetOptionSuccess(c *gc.C) {
   188  	s.assertSetSuccess(c, s.dir, []string{
   189  		"username=hello",
   190  		"outlook=hello@world.tld",
   191  	}, map[string]interface{}{
   192  		"username": "hello",
   193  		"outlook":  "hello@world.tld",
   194  	})
   195  	s.assertSetSuccess(c, s.dir, []string{
   196  		"username=hello=foo",
   197  	}, map[string]interface{}{
   198  		"username": "hello=foo",
   199  		"outlook":  "hello@world.tld",
   200  	})
   201  	s.assertSetSuccess(c, s.dir, []string{
   202  		"username=@valid.txt",
   203  	}, map[string]interface{}{
   204  		"username": validSetTestValue,
   205  		"outlook":  "hello@world.tld",
   206  	})
   207  	s.assertSetSuccess(c, s.dir, []string{
   208  		"username=",
   209  	}, map[string]interface{}{
   210  		"username": "",
   211  		"outlook":  "hello@world.tld",
   212  	})
   213  }
   214  
   215  func (s *configCommandSuite) TestSetSameValue(c *gc.C) {
   216  	s.assertSetSuccess(c, s.dir, []string{
   217  		"username=hello",
   218  		"outlook=hello@world.tld",
   219  	}, map[string]interface{}{
   220  		"username": "hello",
   221  		"outlook":  "hello@world.tld",
   222  	})
   223  	s.assertSetWarning(c, s.dir, []string{
   224  		"username=hello",
   225  	}, "the configuration setting \"username\" already has the value \"hello\"")
   226  	s.assertSetWarning(c, s.dir, []string{
   227  		"outlook=hello@world.tld",
   228  	}, "the configuration setting \"outlook\" already has the value \"hello@world.tld\"")
   229  
   230  }
   231  
   232  func (s *configCommandSuite) TestSetOptionFail(c *gc.C) {
   233  	s.assertSetFail(c, s.dir, []string{"foo", "bar"},
   234  		"error: can only retrieve a single value, or all values\n")
   235  	s.assertSetFail(c, s.dir, []string{"=bar"}, "error: expected \"key=value\", got \"=bar\"\n")
   236  	s.assertSetFail(c, s.dir, []string{
   237  		"username=@missing.txt",
   238  	}, "error: cannot read option from file \"missing.txt\": .* "+utils.NoSuchFileErrRegexp+"\n")
   239  	s.assertSetFail(c, s.dir, []string{
   240  		"username=@big.txt",
   241  	}, "error: size of option file is larger than 5M\n")
   242  	s.assertSetFail(c, s.dir, []string{
   243  		"username=@invalid.txt",
   244  	}, "error: value for option \"username\" contains non-UTF-8 sequences\n")
   245  }
   246  
   247  func (s *configCommandSuite) TestSetConfig(c *gc.C) {
   248  	s.assertSetFail(c, s.dir, []string{
   249  		"--file",
   250  		"missing.yaml",
   251  	}, "error.* "+utils.NoSuchFileErrRegexp+"\n")
   252  
   253  	ctx := coretesting.ContextForDir(c, s.dir)
   254  	code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{
   255  		"dummy-application",
   256  		"--file",
   257  		"testconfig.yaml"})
   258  
   259  	c.Check(code, gc.Equals, 0)
   260  	c.Check(s.fake.config, gc.Equals, yamlConfigValue)
   261  }
   262  
   263  func (s *configCommandSuite) TestSetFromStdin(c *gc.C) {
   264  	s.fake = &fakeApplicationAPI{name: "dummy-application"}
   265  	ctx := coretesting.Context(c)
   266  	ctx.Stdin = strings.NewReader("settings:\n  username:\n  value: world\n")
   267  	code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{
   268  		"dummy-application",
   269  		"--file",
   270  		"-"})
   271  
   272  	c.Check(code, gc.Equals, 0)
   273  	c.Check(s.fake.config, jc.DeepEquals, "settings:\n  username:\n  value: world\n")
   274  }
   275  
   276  func (s *configCommandSuite) TestResetConfigToDefault(c *gc.C) {
   277  	s.fake = &fakeApplicationAPI{name: "dummy-application", values: map[string]interface{}{
   278  		"username": "hello",
   279  	}}
   280  	s.assertSetSuccess(c, s.dir, []string{
   281  		"--reset",
   282  		"username",
   283  	}, make(map[string]interface{}))
   284  }
   285  
   286  func (s *configCommandSuite) TestBlockSetConfig(c *gc.C) {
   287  	// Block operation
   288  	s.fake.err = common.OperationBlockedError("TestBlockSetConfig")
   289  	ctx := coretesting.ContextForDir(c, s.dir)
   290  	code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{
   291  		"dummy-application",
   292  		"--file",
   293  		"testconfig.yaml"})
   294  	c.Check(code, gc.Equals, 1)
   295  	// msg is logged
   296  	stripped := strings.Replace(c.GetTestLog(), "\n", "", -1)
   297  	c.Check(stripped, gc.Matches, ".*TestBlockSetConfig.*")
   298  }
   299  
   300  // assertSetSuccess sets configuration options and checks the expected settings.
   301  func (s *configCommandSuite) assertSetSuccess(c *gc.C, dir string, args []string, expect map[string]interface{}) {
   302  	ctx := coretesting.ContextForDir(c, dir)
   303  	code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, append([]string{"dummy-application"}, args...))
   304  	c.Assert(code, gc.Equals, 0)
   305  }
   306  
   307  // assertSetFail sets configuration options and checks the expected error.
   308  func (s *configCommandSuite) assertSetFail(c *gc.C, dir string, args []string, err string) {
   309  	ctx := coretesting.ContextForDir(c, dir)
   310  	code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, append([]string{"dummy-application"}, args...))
   311  	c.Check(code, gc.Not(gc.Equals), 0)
   312  	c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Matches, err)
   313  }
   314  
   315  func (s *configCommandSuite) assertSetWarning(c *gc.C, dir string, args []string, w string) {
   316  	ctx := coretesting.ContextForDir(c, dir)
   317  	code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, append([]string{"dummy-application"}, args...))
   318  	c.Check(code, gc.Equals, 0)
   319  
   320  	c.Assert(strings.Replace(c.GetTestLog(), "\n", " ", -1), gc.Matches, ".*WARNING.*"+w+".*")
   321  }
   322  
   323  // setupValueFile creates a file containing one value for testing
   324  // set with name=@filename.
   325  func setupValueFile(c *gc.C, dir, filename, value string) string {
   326  	ctx := coretesting.ContextForDir(c, dir)
   327  	path := ctx.AbsPath(filename)
   328  	content := []byte(value)
   329  	err := ioutil.WriteFile(path, content, 0666)
   330  	c.Assert(err, jc.ErrorIsNil)
   331  	return path
   332  }
   333  
   334  // setupBigFile creates a too big file for testing
   335  // set with name=@filename.
   336  func setupBigFile(c *gc.C, dir string) string {
   337  	ctx := coretesting.ContextForDir(c, dir)
   338  	path := ctx.AbsPath("big.txt")
   339  	file, err := os.Create(path)
   340  	c.Assert(err, jc.ErrorIsNil)
   341  	defer file.Close()
   342  	chunk := make([]byte, 1024)
   343  	for i := 0; i < cap(chunk); i++ {
   344  		chunk[i] = byte(i % 256)
   345  	}
   346  	for i := 0; i < 6000; i++ {
   347  		_, err = file.Write(chunk)
   348  		c.Assert(err, jc.ErrorIsNil)
   349  	}
   350  	return path
   351  }
   352  
   353  // setupConfigFile creates a configuration file for testing set
   354  // with the --file argument specifying a configuration file.
   355  func setupConfigFile(c *gc.C, dir string) string {
   356  	ctx := coretesting.ContextForDir(c, dir)
   357  	path := ctx.AbsPath("testconfig.yaml")
   358  	content := []byte(yamlConfigValue)
   359  	err := ioutil.WriteFile(path, content, 0666)
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	return path
   362  }