github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/action/do_test.go (about)

     1  // Copyright 2014, 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package action_test
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"strings"
    10  	"unicode/utf8"
    11  
    12  	"github.com/juju/names"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/yaml.v1"
    17  
    18  	"github.com/juju/juju/apiserver/common"
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/cmd/juju/action"
    21  	"github.com/juju/juju/testing"
    22  )
    23  
    24  var (
    25  	validParamsYaml = `
    26  out: name
    27  compression:
    28    kind: xz
    29    quality: high
    30  `[1:]
    31  	invalidParamsYaml = `
    32  broken-map:
    33    foo:
    34      foo
    35      bar: baz
    36  `[1:]
    37  	invalidUTFYaml = "out: ok" + string([]byte{0xFF, 0xFF})
    38  )
    39  
    40  type DoSuite struct {
    41  	BaseActionSuite
    42  	subcommand *action.DoCommand
    43  	dir        string
    44  }
    45  
    46  var _ = gc.Suite(&DoSuite{})
    47  
    48  func (s *DoSuite) SetUpTest(c *gc.C) {
    49  	s.BaseActionSuite.SetUpTest(c)
    50  	s.dir = c.MkDir()
    51  	c.Assert(utf8.ValidString(validParamsYaml), jc.IsTrue)
    52  	c.Assert(utf8.ValidString(invalidParamsYaml), jc.IsTrue)
    53  	c.Assert(utf8.ValidString(invalidUTFYaml), jc.IsFalse)
    54  	setupValueFile(c, s.dir, "validParams.yml", validParamsYaml)
    55  	setupValueFile(c, s.dir, "invalidParams.yml", invalidParamsYaml)
    56  	setupValueFile(c, s.dir, "invalidUTF.yml", invalidUTFYaml)
    57  }
    58  
    59  func (s *DoSuite) TestHelp(c *gc.C) {
    60  	s.checkHelp(c, s.subcommand)
    61  }
    62  
    63  func (s *DoSuite) TestInit(c *gc.C) {
    64  	tests := []struct {
    65  		should               string
    66  		args                 []string
    67  		expectUnit           names.UnitTag
    68  		expectAction         string
    69  		expectParamsYamlPath string
    70  		expectKVArgs         [][]string
    71  		expectOutput         string
    72  		expectError          string
    73  	}{{
    74  		should:      "fail with missing args",
    75  		args:        []string{},
    76  		expectError: "no unit specified",
    77  	}, {
    78  		should:      "fail with no action specified",
    79  		args:        []string{validUnitId},
    80  		expectError: "no action specified",
    81  	}, {
    82  		should:      "fail with invalid unit tag",
    83  		args:        []string{invalidUnitId, "valid-action-name"},
    84  		expectError: "invalid unit name \"something-strange-\"",
    85  	}, {
    86  		should:      "fail with invalid action name",
    87  		args:        []string{validUnitId, "BadName"},
    88  		expectError: "invalid action name \"BadName\"",
    89  	}, {
    90  		should:      "fail with wrong formatting of k-v args",
    91  		args:        []string{validUnitId, "valid-action-name", "uh"},
    92  		expectError: "argument \"uh\" must be of the form key...=value",
    93  	}, {
    94  		should:      "fail with wrong formatting of k-v args",
    95  		args:        []string{validUnitId, "valid-action-name", "foo.Baz=3"},
    96  		expectError: "key \"Baz\" must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens",
    97  	}, {
    98  		should:      "fail with wrong formatting of k-v args",
    99  		args:        []string{validUnitId, "valid-action-name", "no-go?od=3"},
   100  		expectError: "key \"no-go\\?od\" must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens",
   101  	}, {
   102  		should:       "work with empty values",
   103  		args:         []string{validUnitId, "valid-action-name", "ok="},
   104  		expectUnit:   names.NewUnitTag(validUnitId),
   105  		expectAction: "valid-action-name",
   106  		expectKVArgs: [][]string{{"ok", ""}},
   107  	}, {
   108  		// cf. worker/uniter/runner/jujuc/action-set_test.go per @fwereade
   109  		should:       "work with multiple '=' signs",
   110  		args:         []string{validUnitId, "valid-action-name", "ok=this=is=weird="},
   111  		expectUnit:   names.NewUnitTag(validUnitId),
   112  		expectAction: "valid-action-name",
   113  		expectKVArgs: [][]string{{"ok", "this=is=weird="}},
   114  	}, {
   115  		should:       "init properly with no params",
   116  		args:         []string{validUnitId, "valid-action-name"},
   117  		expectUnit:   names.NewUnitTag(validUnitId),
   118  		expectAction: "valid-action-name",
   119  	}, {
   120  		should:               "handle --params properly",
   121  		args:                 []string{validUnitId, "valid-action-name", "--params=foo.yml"},
   122  		expectUnit:           names.NewUnitTag(validUnitId),
   123  		expectAction:         "valid-action-name",
   124  		expectParamsYamlPath: "foo.yml",
   125  	}, {
   126  		should: "handle --params and key-value args",
   127  		args: []string{
   128  			validUnitId,
   129  			"valid-action-name",
   130  			"--params=foo.yml",
   131  			"foo.bar=2",
   132  			"foo.baz.bo=3",
   133  			"bar.foo=hello",
   134  		},
   135  		expectUnit:           names.NewUnitTag(validUnitId),
   136  		expectAction:         "valid-action-name",
   137  		expectParamsYamlPath: "foo.yml",
   138  		expectKVArgs: [][]string{
   139  			{"foo", "bar", "2"},
   140  			{"foo", "baz", "bo", "3"},
   141  			{"bar", "foo", "hello"},
   142  		},
   143  	}, {
   144  		should: "handle key-value args with no --params",
   145  		args: []string{
   146  			validUnitId,
   147  			"valid-action-name",
   148  			"foo.bar=2",
   149  			"foo.baz.bo=3",
   150  			"bar.foo=hello",
   151  		},
   152  		expectUnit:   names.NewUnitTag(validUnitId),
   153  		expectAction: "valid-action-name",
   154  		expectKVArgs: [][]string{
   155  			{"foo", "bar", "2"},
   156  			{"foo", "baz", "bo", "3"},
   157  			{"bar", "foo", "hello"},
   158  		},
   159  	}}
   160  
   161  	for i, t := range tests {
   162  		s.subcommand = &action.DoCommand{}
   163  		c.Logf("test %d: should %s:\n$ juju actions do %s\n", i,
   164  			t.should, strings.Join(t.args, " "))
   165  		err := testing.InitCommand(s.subcommand, t.args)
   166  		if t.expectError == "" {
   167  			c.Check(s.subcommand.UnitTag(), gc.Equals, t.expectUnit)
   168  			c.Check(s.subcommand.ActionName(), gc.Equals, t.expectAction)
   169  			c.Check(s.subcommand.ParamsYAMLPath(), gc.Equals, t.expectParamsYamlPath)
   170  			c.Check(s.subcommand.KeyValueDoArgs(), jc.DeepEquals, t.expectKVArgs)
   171  		} else {
   172  			c.Check(err, gc.ErrorMatches, t.expectError)
   173  		}
   174  	}
   175  }
   176  
   177  func (s *DoSuite) TestRun(c *gc.C) {
   178  	tests := []struct {
   179  		should                 string
   180  		withArgs               []string
   181  		withAPIErr             string
   182  		withActionResults      []params.ActionResult
   183  		expectedActionEnqueued params.Action
   184  		expectedErr            string
   185  	}{{
   186  		should:   "fail with multiple results",
   187  		withArgs: []string{validUnitId, "some-action"},
   188  		withActionResults: []params.ActionResult{
   189  			{Action: &params.Action{Tag: validActionTagString}},
   190  			{Action: &params.Action{Tag: validActionTagString}},
   191  		},
   192  		expectedErr: "illegal number of results returned",
   193  	}, {
   194  		should:   "fail with API error",
   195  		withArgs: []string{validUnitId, "some-action"},
   196  		withActionResults: []params.ActionResult{{
   197  			Action: &params.Action{Tag: validActionTagString}},
   198  		},
   199  		withAPIErr:  "something wrong in API",
   200  		expectedErr: "something wrong in API",
   201  	}, {
   202  		should:   "fail with error in result",
   203  		withArgs: []string{validUnitId, "some-action"},
   204  		withActionResults: []params.ActionResult{{
   205  			Action: &params.Action{Tag: validActionTagString},
   206  			Error:  common.ServerError(errors.New("database error")),
   207  		}},
   208  		expectedErr: "database error",
   209  	}, {
   210  		should:   "fail with invalid tag in result",
   211  		withArgs: []string{validUnitId, "some-action"},
   212  		withActionResults: []params.ActionResult{{
   213  			Action: &params.Action{Tag: invalidActionTagString},
   214  		}},
   215  		expectedErr: "\"" + invalidActionTagString + "\" is not a valid action tag",
   216  	}, {
   217  		should: "fail with missing file passed",
   218  		withArgs: []string{validUnitId, "some-action",
   219  			"--params", s.dir + "/" + "missing.yml",
   220  		},
   221  		withActionResults: []params.ActionResult{{
   222  			Action: &params.Action{Tag: validActionTagString},
   223  		}},
   224  		expectedErr: "open .*missing.yml: " + utils.NoSuchFileErrRegexp,
   225  	}, {
   226  		should: "fail with invalid yaml in file",
   227  		withArgs: []string{validUnitId, "some-action",
   228  			"--params", s.dir + "/" + "invalidParams.yml",
   229  		},
   230  		withActionResults: []params.ActionResult{{
   231  			Action: &params.Action{Tag: validActionTagString},
   232  		}},
   233  		expectedErr: "YAML error: line 3: mapping values are not allowed in this context",
   234  	}, {
   235  		should: "fail with invalid UTF in file",
   236  		withArgs: []string{validUnitId, "some-action",
   237  			"--params", s.dir + "/" + "invalidUTF.yml",
   238  		},
   239  		withActionResults: []params.ActionResult{{
   240  			Action: &params.Action{Tag: validActionTagString},
   241  		}},
   242  		expectedErr: "YAML error: invalid leading UTF-8 octet",
   243  	}, {
   244  		should:   "enqueue a basic action with no params",
   245  		withArgs: []string{validUnitId, "some-action"},
   246  		withActionResults: []params.ActionResult{{
   247  			Action: &params.Action{Tag: validActionTagString},
   248  		}},
   249  		expectedActionEnqueued: params.Action{
   250  			Name:       "some-action",
   251  			Parameters: map[string]interface{}{},
   252  			Receiver:   names.NewUnitTag(validUnitId).String(),
   253  		},
   254  	}, {
   255  		should: "enqueue an action with some explicit params",
   256  		withArgs: []string{validUnitId, "some-action",
   257  			"out.name=bar",
   258  			"out.kind=tmpfs",
   259  		},
   260  		withActionResults: []params.ActionResult{{
   261  			Action: &params.Action{
   262  				Tag: validActionTagString,
   263  				Parameters: map[string]interface{}{
   264  					"out": map[string]interface{}{
   265  						"name": "bar",
   266  						"kind": "tmpfs",
   267  					},
   268  				},
   269  			},
   270  		}},
   271  		expectedActionEnqueued: params.Action{
   272  			Name:     "some-action",
   273  			Receiver: names.NewUnitTag(validUnitId).String(),
   274  			Parameters: map[string]interface{}{
   275  				"out": map[string]interface{}{
   276  					"name": "bar",
   277  					"kind": "tmpfs",
   278  				},
   279  			},
   280  		},
   281  	}, {
   282  		should: "enqueue an action with file params",
   283  		withArgs: []string{validUnitId, "some-action",
   284  			"--params", s.dir + "/" + "validParams.yml",
   285  		},
   286  		withActionResults: []params.ActionResult{{
   287  			Action: &params.Action{
   288  				Tag: validActionTagString,
   289  				Parameters: map[string]interface{}{
   290  					"out": "name",
   291  					"compression": map[string]interface{}{
   292  						"kind":    "xz",
   293  						"quality": "high",
   294  					},
   295  				},
   296  			},
   297  		}},
   298  		expectedActionEnqueued: params.Action{
   299  			Name:     "some-action",
   300  			Receiver: names.NewUnitTag(validUnitId).String(),
   301  			Parameters: map[string]interface{}{
   302  				"out": "name",
   303  				"compression": map[string]interface{}{
   304  					"kind":    "xz",
   305  					"quality": "high",
   306  				},
   307  			},
   308  		},
   309  	}, {
   310  		should: "enqueue an action with file params and explicit params",
   311  		withArgs: []string{validUnitId, "some-action",
   312  			"out.name=bar",
   313  			"out.kind=tmpfs",
   314  			"compression.quality.speed=high",
   315  			"compression.quality.size=small",
   316  			"--params", s.dir + "/" + "validParams.yml",
   317  		},
   318  		withActionResults: []params.ActionResult{{
   319  			Action: &params.Action{
   320  				Tag: validActionTagString,
   321  				Parameters: map[string]interface{}{
   322  					"out": map[string]interface{}{
   323  						"name": "bar",
   324  						"kind": "tmpfs",
   325  					},
   326  					"compression": map[string]interface{}{
   327  						"kind": "xz",
   328  						"quality": map[string]interface{}{
   329  							"speed": "high",
   330  							"size":  "small",
   331  						},
   332  					},
   333  				},
   334  			},
   335  		}},
   336  		expectedActionEnqueued: params.Action{
   337  			Name:     "some-action",
   338  			Receiver: names.NewUnitTag(validUnitId).String(),
   339  			Parameters: map[string]interface{}{
   340  				"out": map[string]interface{}{
   341  					"name": "bar",
   342  					"kind": "tmpfs",
   343  				},
   344  				"compression": map[string]interface{}{
   345  					"kind": "xz",
   346  					"quality": map[string]interface{}{
   347  						"speed": "high",
   348  						"size":  "small",
   349  					},
   350  				},
   351  			},
   352  		},
   353  	}}
   354  
   355  	for i, t := range tests {
   356  		func() {
   357  			c.Logf("test %d: should %s:\n$ juju actions do %s\n", i,
   358  				t.should, strings.Join(t.withArgs, " "))
   359  			fakeClient := &fakeAPIClient{
   360  				actionResults: t.withActionResults,
   361  			}
   362  			if t.withAPIErr != "" {
   363  				fakeClient.apiErr = errors.New(t.withAPIErr)
   364  			}
   365  			restore := s.patchAPIClient(fakeClient)
   366  			defer restore()
   367  
   368  			s.subcommand = &action.DoCommand{}
   369  			ctx, err := testing.RunCommand(c, s.subcommand, t.withArgs...)
   370  
   371  			if t.expectedErr != "" || t.withAPIErr != "" {
   372  				c.Check(err, gc.ErrorMatches, t.expectedErr)
   373  			} else {
   374  				c.Assert(err, gc.IsNil)
   375  				// Before comparing, double-check to avoid
   376  				// panics in malformed tests.
   377  				c.Assert(len(t.withActionResults), gc.Equals, 1)
   378  				// Make sure the test's expected Action was
   379  				// non-nil and correct.
   380  				c.Assert(t.withActionResults[0].Action, gc.NotNil)
   381  				expectedTag, err := names.ParseActionTag(t.withActionResults[0].Action.Tag)
   382  				c.Assert(err, gc.IsNil)
   383  				// Make sure the CLI responded with the expected tag
   384  				sillyKey := "Action queued with id"
   385  				expectedMap := map[string]string{sillyKey: expectedTag.Id()}
   386  				outputResult := ctx.Stdout.(*bytes.Buffer).Bytes()
   387  				resultMap := make(map[string]string)
   388  				err = yaml.Unmarshal(outputResult, &resultMap)
   389  				c.Assert(err, gc.IsNil)
   390  				c.Check(resultMap, jc.DeepEquals, expectedMap)
   391  				// Make sure the Action sent to the API to be
   392  				// enqueued was indeed the expected map
   393  				enqueued := fakeClient.EnqueuedActions()
   394  				c.Assert(enqueued.Actions, gc.HasLen, 1)
   395  				c.Check(enqueued.Actions[0], jc.DeepEquals, t.expectedActionEnqueued)
   396  			}
   397  		}()
   398  	}
   399  }