github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/runner/jujuc/relation-set_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Copyright 2014 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package jujuc_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"path/filepath"
    12  
    13  	"github.com/juju/cmd"
    14  	"github.com/juju/cmd/cmdtesting"
    15  	jc "github.com/juju/testing/checkers"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    19  	"github.com/juju/juju/worker/uniter/runner/jujuc/jujuctesting"
    20  )
    21  
    22  type RelationSetSuite struct {
    23  	relationSuite
    24  }
    25  
    26  var _ = gc.Suite(&RelationSetSuite{})
    27  
    28  var helpTests = []struct {
    29  	relid  int
    30  	expect string
    31  }{{-1, ""}, {0, "peer0:0"}}
    32  
    33  func (s *RelationSetSuite) TestHelp(c *gc.C) {
    34  	for i, t := range helpTests {
    35  		c.Logf("test %d", i)
    36  		hctx, _ := s.newHookContext(t.relid, "")
    37  		com, err := jujuc.NewCommand(hctx, cmdString("relation-set"))
    38  		c.Assert(err, jc.ErrorIsNil)
    39  		ctx := cmdtesting.Context(c)
    40  		code := cmd.Main(jujuc.NewJujucCommandWrappedForTest(com), ctx, []string{"--help"})
    41  		c.Assert(code, gc.Equals, 0)
    42  		c.Assert(bufferString(ctx.Stdout), gc.Equals, fmt.Sprintf(`
    43  Usage: relation-set [options] key=value [key=value ...]
    44  
    45  Summary:
    46  set relation settings
    47  
    48  Options:
    49  --file  (= )
    50      file containing key-value pairs
    51  --format (= "")
    52      deprecated format flag
    53  -r, --relation  (= %s)
    54      specify a relation by id
    55  
    56  Details:
    57  "relation-set" writes the local unit's settings for some relation.
    58  If no relation is specified then the current relation is used. The
    59  setting values are not inspected and are stored as strings. Setting
    60  an empty string causes the setting to be removed. Duplicate settings
    61  are not allowed.
    62  
    63  The --file option should be used when one or more key-value pairs are
    64  too long to fit within the command length limit of the shell or
    65  operating system. The file will contain a YAML map containing the
    66  settings.  Settings in the file will be overridden by any duplicate
    67  key-value arguments. A value of "-" for the filename means <stdin>.
    68  `[1:], t.expect))
    69  		c.Assert(bufferString(ctx.Stderr), gc.Equals, "")
    70  	}
    71  }
    72  
    73  type relationSetInitTest struct {
    74  	summary  string
    75  	ctxrelid int
    76  	args     []string
    77  	content  string
    78  	err      string
    79  	relid    int
    80  	settings map[string]string
    81  }
    82  
    83  func (t relationSetInitTest) log(c *gc.C, i int) {
    84  	var summary string
    85  	if t.summary != "" {
    86  		summary = " - " + t.summary
    87  	}
    88  	c.Logf("test %d%s", i, summary)
    89  }
    90  
    91  func (t relationSetInitTest) filename() (string, int) {
    92  	for i, arg := range t.args {
    93  		next := i + 1
    94  		if arg == "--file" && next < len(t.args) {
    95  			return t.args[next], next
    96  		}
    97  	}
    98  	return "", -1
    99  }
   100  
   101  func (t relationSetInitTest) init(c *gc.C, s *RelationSetSuite) (cmd.Command, []string, *cmd.Context) {
   102  	args := make([]string, len(t.args))
   103  	copy(args, t.args)
   104  
   105  	hctx, _ := s.newHookContext(t.ctxrelid, "")
   106  	com, err := jujuc.NewCommand(hctx, cmdString("relation-set"))
   107  	c.Assert(err, jc.ErrorIsNil)
   108  
   109  	ctx := cmdtesting.Context(c)
   110  
   111  	// Adjust the args and context for the filename.
   112  	filename, i := t.filename()
   113  	if filename == "-" {
   114  		ctx.Stdin = bytes.NewBufferString(t.content)
   115  	} else if filename != "" {
   116  		filename = filepath.Join(c.MkDir(), filename)
   117  		args[i] = filename
   118  		err := ioutil.WriteFile(filename, []byte(t.content), 0644)
   119  		c.Assert(err, jc.ErrorIsNil)
   120  	}
   121  
   122  	return com, args, ctx
   123  }
   124  
   125  func (t relationSetInitTest) check(c *gc.C, com cmd.Command, err error) {
   126  	if t.err == "" {
   127  		if !c.Check(err, jc.ErrorIsNil) {
   128  			return
   129  		}
   130  
   131  		rset := com.(*jujuc.RelationSetCommand)
   132  		c.Check(rset.RelationId, gc.Equals, t.relid)
   133  
   134  		settings := t.settings
   135  		if settings == nil {
   136  			settings = map[string]string{}
   137  		}
   138  		c.Check(rset.Settings, jc.DeepEquals, settings)
   139  	} else {
   140  		c.Logf("%#v", com.(*jujuc.RelationSetCommand).Settings)
   141  		c.Check(err, gc.ErrorMatches, t.err)
   142  	}
   143  }
   144  
   145  var relationSetInitTests = []relationSetInitTest{
   146  	{
   147  		// compatibility: 0 args is valid.
   148  	}, {
   149  		ctxrelid: -1,
   150  		err:      `no relation id specified`,
   151  	}, {
   152  		ctxrelid: -1,
   153  		args:     []string{"-r", "one"},
   154  		err:      `invalid value "one" for option -r: invalid relation id`,
   155  	}, {
   156  		ctxrelid: 1,
   157  		args:     []string{"-r", "one"},
   158  		err:      `invalid value "one" for option -r: invalid relation id`,
   159  	}, {
   160  		ctxrelid: -1,
   161  		args:     []string{"-r", "ignored:one"},
   162  		err:      `invalid value "ignored:one" for option -r: invalid relation id`,
   163  	}, {
   164  		ctxrelid: 1,
   165  		args:     []string{"-r", "ignored:one"},
   166  		err:      `invalid value "ignored:one" for option -r: invalid relation id`,
   167  	}, {
   168  		ctxrelid: -1,
   169  		args:     []string{"-r", "2"},
   170  		err:      `invalid value "2" for option -r: relation not found`,
   171  	}, {
   172  		ctxrelid: 1,
   173  		args:     []string{"-r", "ignored:2"},
   174  		err:      `invalid value "ignored:2" for option -r: relation not found`,
   175  	}, {
   176  		ctxrelid: -1,
   177  		err:      `no relation id specified`,
   178  	}, {
   179  		ctxrelid: 1,
   180  		args:     []string{"-r", "ignored:0"},
   181  		relid:    0,
   182  	}, {
   183  		ctxrelid: 1,
   184  		args:     []string{"-r", "0"},
   185  		relid:    0,
   186  	}, {
   187  		ctxrelid: -1,
   188  		args:     []string{"-r", "1"},
   189  		relid:    1,
   190  	}, {
   191  		ctxrelid: 0,
   192  		args:     []string{"-r", "1"},
   193  		relid:    1,
   194  	}, {
   195  		ctxrelid: 1,
   196  		args:     []string{"haha"},
   197  		err:      `expected "key=value", got "haha"`,
   198  	}, {
   199  		ctxrelid: 1,
   200  		args:     []string{"=haha"},
   201  		err:      `expected "key=value", got "=haha"`,
   202  	}, {
   203  		ctxrelid: 1,
   204  		args:     []string{"foo="},
   205  		relid:    1,
   206  		settings: map[string]string{"foo": ""},
   207  	}, {
   208  		ctxrelid: 1,
   209  		args:     []string{"foo='"},
   210  		relid:    1,
   211  		settings: map[string]string{"foo": "'"},
   212  	}, {
   213  		ctxrelid: 1,
   214  		args:     []string{"foo=bar"},
   215  		relid:    1,
   216  		settings: map[string]string{"foo": "bar"},
   217  	}, {
   218  		ctxrelid: 1,
   219  		args:     []string{"foo=bar=baz=qux"},
   220  		relid:    1,
   221  		settings: map[string]string{"foo": "bar=baz=qux"},
   222  	}, {
   223  		ctxrelid: 1,
   224  		args:     []string{"foo=foo: bar"},
   225  		relid:    1,
   226  		settings: map[string]string{"foo": "foo: bar"},
   227  	}, {
   228  		ctxrelid: 0,
   229  		args:     []string{"-r", "1", "foo=bar"},
   230  		relid:    1,
   231  		settings: map[string]string{"foo": "bar"},
   232  	}, {
   233  		ctxrelid: 1,
   234  		args:     []string{"foo=123", "bar=true", "baz=4.5", "qux="},
   235  		relid:    1,
   236  		settings: map[string]string{"foo": "123", "bar": "true", "baz": "4.5", "qux": ""},
   237  	}, {
   238  		summary:  "file with a valid setting",
   239  		args:     []string{"--file", "spam"},
   240  		content:  "{foo: bar}",
   241  		settings: map[string]string{"foo": "bar"},
   242  	}, {
   243  		summary:  "file with multiple settings on a line",
   244  		args:     []string{"--file", "spam"},
   245  		content:  "{foo: bar, spam: eggs}",
   246  		settings: map[string]string{"foo": "bar", "spam": "eggs"},
   247  	}, {
   248  		summary:  "file with multiple lines",
   249  		args:     []string{"--file", "spam"},
   250  		content:  "{\n  foo: bar,\n  spam: eggs\n}",
   251  		settings: map[string]string{"foo": "bar", "spam": "eggs"},
   252  	}, {
   253  		summary:  "an empty file",
   254  		args:     []string{"--file", "spam"},
   255  		content:  "",
   256  		settings: map[string]string{},
   257  	}, {
   258  		summary:  "an empty map",
   259  		args:     []string{"--file", "spam"},
   260  		content:  "{}",
   261  		settings: map[string]string{},
   262  	}, {
   263  		summary: "accidental same format as command-line",
   264  		args:    []string{"--file", "spam"},
   265  		content: "foo=bar ham=eggs good=bad",
   266  		err:     "yaml: unmarshal errors:\n  line 1: cannot unmarshal !!str `foo=bar...` into map.*",
   267  	}, {
   268  		summary: "scalar instead of map",
   269  		args:    []string{"--file", "spam"},
   270  		content: "haha",
   271  		err:     "yaml: unmarshal errors:\n  line 1: cannot unmarshal !!str `haha` into map.*",
   272  	}, {
   273  		summary: "sequence instead of map",
   274  		args:    []string{"--file", "spam"},
   275  		content: "[haha]",
   276  		err:     "yaml: unmarshal errors:\n  line 1: cannot unmarshal !!seq into map.*",
   277  	}, {
   278  		summary:  "multiple maps",
   279  		args:     []string{"--file", "spam"},
   280  		content:  "{a: b}\n{c: d}",
   281  		settings: map[string]string{"a": "b"},
   282  	}, {
   283  		summary:  "value with a space",
   284  		args:     []string{"--file", "spam"},
   285  		content:  "{foo: 'bar baz'}",
   286  		settings: map[string]string{"foo": "bar baz"},
   287  	}, {
   288  		summary:  "value with an equal sign",
   289  		args:     []string{"--file", "spam"},
   290  		content:  "{foo: foo=bar, base64: YmFzZTY0IGV4YW1wbGU=}",
   291  		settings: map[string]string{"foo": "foo=bar", "base64": "YmFzZTY0IGV4YW1wbGU="},
   292  	}, {
   293  		summary:  "values with brackets",
   294  		args:     []string{"--file", "spam"},
   295  		content:  "{foo: '[x]', bar: '{y}'}",
   296  		settings: map[string]string{"foo": "[x]", "bar": "{y}"},
   297  	}, {
   298  		summary:  "a messy file",
   299  		args:     []string{"--file", "spam"},
   300  		content:  "\n {  \n # a comment \n\n  \nfoo: bar,  \nham: eggs,\n\n  good: bad,\nup: down, left: right\n}\n",
   301  		settings: map[string]string{"foo": "bar", "ham": "eggs", "good": "bad", "up": "down", "left": "right"},
   302  	}, {
   303  		summary:  "file + settings",
   304  		args:     []string{"--file", "spam", "foo=bar"},
   305  		content:  "{ham: eggs}",
   306  		settings: map[string]string{"ham": "eggs", "foo": "bar"},
   307  	}, {
   308  		summary:  "file overridden by settings",
   309  		args:     []string{"--file", "spam", "foo=bar"},
   310  		content:  "{foo: baz}",
   311  		settings: map[string]string{"foo": "bar"},
   312  	}, {
   313  		summary:  "read from stdin",
   314  		args:     []string{"--file", "-"},
   315  		content:  "{foo: bar}",
   316  		settings: map[string]string{"foo": "bar"},
   317  	},
   318  }
   319  
   320  func (s *RelationSetSuite) TestInit(c *gc.C) {
   321  	for i, t := range relationSetInitTests {
   322  		t.log(c, i)
   323  		com, args, ctx := t.init(c, s)
   324  
   325  		err := cmdtesting.InitCommand(com, args)
   326  		if err == nil {
   327  			err = jujuc.HandleSettingsFile(com.(*jujuc.RelationSetCommand), ctx)
   328  		}
   329  		t.check(c, com, err)
   330  	}
   331  }
   332  
   333  // Tests start with a relation with the settings {"base": "value"}
   334  var relationSetRunTests = []struct {
   335  	change map[string]string
   336  	expect jujuctesting.Settings
   337  }{
   338  	{
   339  		map[string]string{"base": ""},
   340  		jujuctesting.Settings{},
   341  	}, {
   342  		map[string]string{"foo": "bar"},
   343  		jujuctesting.Settings{"base": "value", "foo": "bar"},
   344  	}, {
   345  		map[string]string{"base": "changed"},
   346  		jujuctesting.Settings{"base": "changed"},
   347  	},
   348  }
   349  
   350  func (s *RelationSetSuite) TestRun(c *gc.C) {
   351  	hctx, info := s.newHookContext(0, "")
   352  	for i, t := range relationSetRunTests {
   353  		c.Logf("test %d", i)
   354  
   355  		pristine := jujuctesting.Settings{"pristine": "untouched"}
   356  		info.rels[0].Units["u/0"] = pristine
   357  		basic := jujuctesting.Settings{"base": "value"}
   358  		info.rels[1].Units["u/0"] = basic
   359  
   360  		// Run the command.
   361  		com, err := jujuc.NewCommand(hctx, cmdString("relation-set"))
   362  		c.Assert(err, jc.ErrorIsNil)
   363  		rset := com.(*jujuc.RelationSetCommand)
   364  		rset.RelationId = 1
   365  		rset.Settings = t.change
   366  		ctx := cmdtesting.Context(c)
   367  		err = com.Run(ctx)
   368  		c.Assert(err, jc.ErrorIsNil)
   369  
   370  		// Check changes.
   371  		c.Assert(info.rels[0].Units["u/0"], gc.DeepEquals, pristine)
   372  		c.Assert(info.rels[1].Units["u/0"], gc.DeepEquals, t.expect)
   373  	}
   374  }
   375  
   376  func (s *RelationSetSuite) TestRunDeprecationWarning(c *gc.C) {
   377  	hctx, _ := s.newHookContext(0, "")
   378  	com, _ := jujuc.NewCommand(hctx, cmdString("relation-set"))
   379  	com = jujuc.NewJujucCommandWrappedForTest(com)
   380  	// The rel= is needed to make this a valid command.
   381  	ctx, err := cmdtesting.RunCommand(c, com, "--format", "foo", "rel=")
   382  
   383  	c.Assert(err, jc.ErrorIsNil)
   384  	c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "")
   385  	c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "--format flag deprecated for command \"relation-set\"")
   386  }