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: ¶ms.Action{Tag: validActionTagString}}, 190 {Action: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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 }