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