github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/action/run_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.v2" 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 RunSuite struct { 41 BaseActionSuite 42 dir string 43 } 44 45 var _ = gc.Suite(&RunSuite{}) 46 47 func (s *RunSuite) SetUpTest(c *gc.C) { 48 s.BaseActionSuite.SetUpTest(c) 49 s.dir = c.MkDir() 50 c.Assert(utf8.ValidString(validParamsYaml), jc.IsTrue) 51 c.Assert(utf8.ValidString(invalidParamsYaml), jc.IsTrue) 52 c.Assert(utf8.ValidString(invalidUTFYaml), jc.IsFalse) 53 setupValueFile(c, s.dir, "validParams.yml", validParamsYaml) 54 setupValueFile(c, s.dir, "invalidParams.yml", invalidParamsYaml) 55 setupValueFile(c, s.dir, "invalidUTF.yml", invalidUTFYaml) 56 } 57 58 func (s *RunSuite) TestInit(c *gc.C) { 59 tests := []struct { 60 should string 61 args []string 62 expectUnit names.UnitTag 63 expectAction string 64 expectParamsYamlPath string 65 expectParseStrings bool 66 expectKVArgs [][]string 67 expectOutput string 68 expectError string 69 }{{ 70 should: "fail with missing args", 71 args: []string{}, 72 expectError: "no unit specified", 73 }, { 74 should: "fail with no action specified", 75 args: []string{validUnitId}, 76 expectError: "no action specified", 77 }, { 78 should: "fail with invalid unit tag", 79 args: []string{invalidUnitId, "valid-action-name"}, 80 expectError: "invalid unit name \"something-strange-\"", 81 }, { 82 should: "fail with invalid action name", 83 args: []string{validUnitId, "BadName"}, 84 expectError: "invalid action name \"BadName\"", 85 }, { 86 should: "fail with wrong formatting of k-v args", 87 args: []string{validUnitId, "valid-action-name", "uh"}, 88 expectError: "argument \"uh\" must be of the form key...=value", 89 }, { 90 should: "fail with wrong formatting of k-v args", 91 args: []string{validUnitId, "valid-action-name", "foo.Baz=3"}, 92 expectError: "key \"Baz\" must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens", 93 }, { 94 should: "fail with wrong formatting of k-v args", 95 args: []string{validUnitId, "valid-action-name", "no-go?od=3"}, 96 expectError: "key \"no-go\\?od\" must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens", 97 }, { 98 should: "work with empty values", 99 args: []string{validUnitId, "valid-action-name", "ok="}, 100 expectUnit: names.NewUnitTag(validUnitId), 101 expectAction: "valid-action-name", 102 expectKVArgs: [][]string{{"ok", ""}}, 103 }, { 104 should: "handle --parse-strings", 105 args: []string{validUnitId, "valid-action-name", "--string-args"}, 106 expectUnit: names.NewUnitTag(validUnitId), 107 expectAction: "valid-action-name", 108 expectParseStrings: true, 109 }, { 110 // cf. worker/uniter/runner/jujuc/action-set_test.go per @fwereade 111 should: "work with multiple '=' signs", 112 args: []string{validUnitId, "valid-action-name", "ok=this=is=weird="}, 113 expectUnit: names.NewUnitTag(validUnitId), 114 expectAction: "valid-action-name", 115 expectKVArgs: [][]string{{"ok", "this=is=weird="}}, 116 }, { 117 should: "init properly with no params", 118 args: []string{validUnitId, "valid-action-name"}, 119 expectUnit: names.NewUnitTag(validUnitId), 120 expectAction: "valid-action-name", 121 }, { 122 should: "handle --params properly", 123 args: []string{validUnitId, "valid-action-name", "--params=foo.yml"}, 124 expectUnit: names.NewUnitTag(validUnitId), 125 expectAction: "valid-action-name", 126 expectParamsYamlPath: "foo.yml", 127 }, { 128 should: "handle --params and key-value args", 129 args: []string{ 130 validUnitId, 131 "valid-action-name", 132 "--params=foo.yml", 133 "foo.bar=2", 134 "foo.baz.bo=3", 135 "bar.foo=hello", 136 }, 137 expectUnit: names.NewUnitTag(validUnitId), 138 expectAction: "valid-action-name", 139 expectParamsYamlPath: "foo.yml", 140 expectKVArgs: [][]string{ 141 {"foo", "bar", "2"}, 142 {"foo", "baz", "bo", "3"}, 143 {"bar", "foo", "hello"}, 144 }, 145 }, { 146 should: "handle key-value args with no --params", 147 args: []string{ 148 validUnitId, 149 "valid-action-name", 150 "foo.bar=2", 151 "foo.baz.bo=y", 152 "bar.foo=hello", 153 }, 154 expectUnit: names.NewUnitTag(validUnitId), 155 expectAction: "valid-action-name", 156 expectKVArgs: [][]string{ 157 {"foo", "bar", "2"}, 158 {"foo", "baz", "bo", "y"}, 159 {"bar", "foo", "hello"}, 160 }, 161 }} 162 163 for i, t := range tests { 164 for _, modelFlag := range s.modelFlags { 165 wrappedCommand, command := action.NewRunCommandForTest(s.store) 166 c.Logf("test %d: should %s:\n$ juju run-action %s\n", i, 167 t.should, strings.Join(t.args, " ")) 168 args := append([]string{modelFlag, "admin"}, t.args...) 169 err := testing.InitCommand(wrappedCommand, args) 170 if t.expectError == "" { 171 c.Check(command.UnitTag(), gc.Equals, t.expectUnit) 172 c.Check(command.ActionName(), gc.Equals, t.expectAction) 173 c.Check(command.ParamsYAML().Path, gc.Equals, t.expectParamsYamlPath) 174 c.Check(command.Args(), jc.DeepEquals, t.expectKVArgs) 175 c.Check(command.ParseStrings(), gc.Equals, t.expectParseStrings) 176 } else { 177 c.Check(err, gc.ErrorMatches, t.expectError) 178 } 179 } 180 } 181 } 182 183 func (s *RunSuite) TestRun(c *gc.C) { 184 tests := []struct { 185 should string 186 withArgs []string 187 withAPIErr string 188 withActionResults []params.ActionResult 189 expectedActionEnqueued params.Action 190 expectedErr string 191 }{{ 192 should: "fail with multiple results", 193 withArgs: []string{validUnitId, "some-action"}, 194 withActionResults: []params.ActionResult{ 195 {Action: ¶ms.Action{Tag: validActionTagString}}, 196 {Action: ¶ms.Action{Tag: validActionTagString}}, 197 }, 198 expectedErr: "illegal number of results returned", 199 }, { 200 should: "fail with API error", 201 withArgs: []string{validUnitId, "some-action"}, 202 withActionResults: []params.ActionResult{{ 203 Action: ¶ms.Action{Tag: validActionTagString}}, 204 }, 205 withAPIErr: "something wrong in API", 206 expectedErr: "something wrong in API", 207 }, { 208 should: "fail with error in result", 209 withArgs: []string{validUnitId, "some-action"}, 210 withActionResults: []params.ActionResult{{ 211 Action: ¶ms.Action{Tag: validActionTagString}, 212 Error: common.ServerError(errors.New("database error")), 213 }}, 214 expectedErr: "database error", 215 }, { 216 should: "fail with invalid tag in result", 217 withArgs: []string{validUnitId, "some-action"}, 218 withActionResults: []params.ActionResult{{ 219 Action: ¶ms.Action{Tag: invalidActionTagString}, 220 }}, 221 expectedErr: "\"" + invalidActionTagString + "\" is not a valid action tag", 222 }, { 223 should: "fail with missing file passed", 224 withArgs: []string{validUnitId, "some-action", 225 "--params", s.dir + "/" + "missing.yml", 226 }, 227 expectedErr: "open .*missing.yml: " + utils.NoSuchFileErrRegexp, 228 }, { 229 should: "fail with invalid yaml in file", 230 withArgs: []string{validUnitId, "some-action", 231 "--params", s.dir + "/" + "invalidParams.yml", 232 }, 233 expectedErr: "yaml: 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 expectedErr: "yaml: invalid leading UTF-8 octet", 240 }, { 241 should: "fail with invalid YAML passed as arg and no --string-args", 242 withArgs: []string{validUnitId, "some-action", "foo.bar=\""}, 243 expectedErr: "yaml: found unexpected end of stream", 244 }, { 245 should: "enqueue a basic action with no params", 246 withArgs: []string{validUnitId, "some-action"}, 247 withActionResults: []params.ActionResult{{ 248 Action: ¶ms.Action{Tag: validActionTagString}, 249 }}, 250 expectedActionEnqueued: params.Action{ 251 Name: "some-action", 252 Parameters: map[string]interface{}{}, 253 Receiver: names.NewUnitTag(validUnitId).String(), 254 }, 255 }, { 256 should: "enqueue an action with some explicit params", 257 withArgs: []string{validUnitId, "some-action", 258 "out.name=bar", 259 "out.kind=tmpfs", 260 "out.num=3", 261 "out.boolval=y", 262 }, 263 withActionResults: []params.ActionResult{{ 264 Action: ¶ms.Action{Tag: validActionTagString}, 265 }}, 266 expectedActionEnqueued: params.Action{ 267 Name: "some-action", 268 Receiver: names.NewUnitTag(validUnitId).String(), 269 Parameters: map[string]interface{}{ 270 "out": map[string]interface{}{ 271 "name": "bar", 272 "kind": "tmpfs", 273 "num": 3, 274 "boolval": true, 275 }, 276 }, 277 }, 278 }, { 279 should: "enqueue an action with some raw string params", 280 withArgs: []string{validUnitId, "some-action", "--string-args", 281 "out.name=bar", 282 "out.kind=tmpfs", 283 "out.num=3", 284 "out.boolval=y", 285 }, 286 withActionResults: []params.ActionResult{{ 287 Action: ¶ms.Action{Tag: validActionTagString}, 288 }}, 289 expectedActionEnqueued: params.Action{ 290 Name: "some-action", 291 Receiver: names.NewUnitTag(validUnitId).String(), 292 Parameters: map[string]interface{}{ 293 "out": map[string]interface{}{ 294 "name": "bar", 295 "kind": "tmpfs", 296 "num": "3", 297 "boolval": "y", 298 }, 299 }, 300 }, 301 }, { 302 should: "enqueue an action with file params plus CLI args", 303 withArgs: []string{validUnitId, "some-action", 304 "--params", s.dir + "/" + "validParams.yml", 305 "compression.kind=gz", 306 "compression.fast=true", 307 }, 308 withActionResults: []params.ActionResult{{ 309 Action: ¶ms.Action{Tag: validActionTagString}, 310 }}, 311 expectedActionEnqueued: params.Action{ 312 Name: "some-action", 313 Receiver: names.NewUnitTag(validUnitId).String(), 314 Parameters: map[string]interface{}{ 315 "out": "name", 316 "compression": map[string]interface{}{ 317 "kind": "gz", 318 "quality": "high", 319 "fast": true, 320 }, 321 }, 322 }, 323 }, { 324 should: "enqueue an action with file params and explicit params", 325 withArgs: []string{validUnitId, "some-action", 326 "out.name=bar", 327 "out.kind=tmpfs", 328 "compression.quality.speed=high", 329 "compression.quality.size=small", 330 "--params", s.dir + "/" + "validParams.yml", 331 }, 332 withActionResults: []params.ActionResult{{ 333 Action: ¶ms.Action{Tag: validActionTagString}, 334 }}, 335 expectedActionEnqueued: params.Action{ 336 Name: "some-action", 337 Receiver: names.NewUnitTag(validUnitId).String(), 338 Parameters: map[string]interface{}{ 339 "out": map[string]interface{}{ 340 "name": "bar", 341 "kind": "tmpfs", 342 }, 343 "compression": map[string]interface{}{ 344 "kind": "xz", 345 "quality": map[string]interface{}{ 346 "speed": "high", 347 "size": "small", 348 }, 349 }, 350 }, 351 }, 352 }} 353 354 for i, t := range tests { 355 for _, modelFlag := range s.modelFlags { 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 wrappedCommand, _ := action.NewRunCommandForTest(s.store) 369 args := append([]string{modelFlag, "admin"}, t.withArgs...) 370 ctx, err := testing.RunCommand(c, wrappedCommand, args...) 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 } 401 }